✨ feat(analytics): add PrivacyPolicy page
This commit is contained in:
+13
-4
@@ -4,10 +4,11 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'
|
|||||||
import ErrorPage from 'components/pages/ErrorPage/index.js'
|
import ErrorPage from 'components/pages/ErrorPage/index.js'
|
||||||
import RootLayout from 'components/templates/RouteTracker/index.js'
|
import RootLayout from 'components/templates/RouteTracker/index.js'
|
||||||
|
|
||||||
const MainPage = React.lazy(() => import('components/pages/MainPage'))
|
const MainPage = React.lazy(() => import('components/pages/MainPage/index.js'))
|
||||||
const ProjectsPage = React.lazy(() => import('components/pages/ProjectsPage'))
|
const ProjectsPage = React.lazy(() => import('components/pages/ProjectsPage/index.js'))
|
||||||
const CurriculumPage = React.lazy(() => import('components/pages/CurriculumPage'))
|
const CurriculumPage = React.lazy(() => import('components/pages/CurriculumPage/index.js'))
|
||||||
const ContactPage = React.lazy(() => import('components/pages/ContactPage'))
|
const ContactPage = React.lazy(() => import('components/pages/ContactPage/index.js'))
|
||||||
|
const PrivacyPage = React.lazy(() => import('components/pages/PrivacyPage/index.js'))
|
||||||
|
|
||||||
const Loading = () => (
|
const Loading = () => (
|
||||||
<Backdrop open={true} aria-label="loading-screen">
|
<Backdrop open={true} aria-label="loading-screen">
|
||||||
@@ -53,6 +54,14 @@ const router = createBrowserRouter([
|
|||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'privacy',
|
||||||
|
element: (
|
||||||
|
<React.Suspense fallback={<Loading />}>
|
||||||
|
<PrivacyPage />
|
||||||
|
</React.Suspense>
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ListItem, Typography, ListItemProps } from '@mui/material'
|
||||||
|
|
||||||
|
interface ListItemTextProps extends ListItemProps {
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
const ListItemText: React.FC<ListItemTextProps> = ({ title, children, ...props }) => (
|
||||||
|
<ListItem component={Typography} color="text.secondary" {...props}>
|
||||||
|
{title && (
|
||||||
|
<Typography component="b" fontWeight={700} color="text.primary">
|
||||||
|
{title}{' '}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ListItemText
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { screen, fireEvent } from '@testing-library/react'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import { useAnalytics } from 'use-analytics'
|
||||||
|
|
||||||
|
import { renderWithRouter as render } from 'components/utils/tests/index.js'
|
||||||
|
|
||||||
|
import CookiePreferences from './index.js'
|
||||||
|
|
||||||
|
// Mock the analytics module
|
||||||
|
const analytics = { plugins: { enable: vi.fn(), disable: vi.fn() } }
|
||||||
|
vi.mock('use-analytics', () => ({
|
||||||
|
useAnalytics: () => analytics,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const localStorageMock = (() => {
|
||||||
|
let store: Record<string, string> = {}
|
||||||
|
return {
|
||||||
|
getItem: vi.fn((key: string) => store[key] || null),
|
||||||
|
setItem: vi.fn((key: string, value: string) => {
|
||||||
|
store[key] = value
|
||||||
|
}),
|
||||||
|
clear: vi.fn(() => {
|
||||||
|
store = {}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||||
|
|
||||||
|
describe('CookiePreferences', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
localStorageMock.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts all cookies when clicking Accept All', () => {
|
||||||
|
render(<CookiePreferences />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Cookie Preferences')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Accept All'))
|
||||||
|
fireEvent.click(screen.getByText('Save Preferences'))
|
||||||
|
|
||||||
|
expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ necessary: true, analytics: true }))
|
||||||
|
expect(analytics.plugins.disable).not.toHaveBeenCalled()
|
||||||
|
expect(analytics.plugins.enable).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects analytics cookies when clicking Reject All', () => {
|
||||||
|
render(<CookiePreferences />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Cookie Preferences')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Reject All'))
|
||||||
|
fireEvent.click(screen.getByText('Save Preferences'))
|
||||||
|
|
||||||
|
expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ necessary: true, analytics: false }))
|
||||||
|
expect(analytics.plugins.enable).not.toHaveBeenCalled()
|
||||||
|
expect(analytics.plugins.disable).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toggles analytics cookies when clicking the Analytics button', () => {
|
||||||
|
render(<CookiePreferences />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Cookie Preferences')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText('analytics-cookies-toggle'))
|
||||||
|
fireEvent.click(screen.getByText('Save Preferences'))
|
||||||
|
|
||||||
|
expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ necessary: true, analytics: true }))
|
||||||
|
expect(analytics.plugins.disable).not.toHaveBeenCalled()
|
||||||
|
expect(analytics.plugins.enable).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the cookie consent in the right state when consent in local storage', () => {
|
||||||
|
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify({ necessary: true, analytics: true }))
|
||||||
|
render(<CookiePreferences />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Cookie Preferences')).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText('analytics-cookies-toggle'))
|
||||||
|
fireEvent.click(screen.getByText('Save Preferences'))
|
||||||
|
|
||||||
|
expect(localStorageMock.setItem).toHaveBeenCalledWith('m-tartari-cookie-consent', JSON.stringify({ necessary: true, analytics: false }))
|
||||||
|
expect(analytics.plugins.enable).not.toHaveBeenCalled()
|
||||||
|
expect(analytics.plugins.disable).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { Box, Button, Link, Stack, Switch, Typography, SnackbarCloseReason, Snackbar, Alert } from '@mui/material'
|
||||||
|
import { useAnalytics } from 'use-analytics'
|
||||||
|
|
||||||
|
import { ANALYTICS_PLUGINS, COOKIE_CONSENT_KEY } from 'components/utils/analytics/index.js'
|
||||||
|
|
||||||
|
interface CookieConsentState {
|
||||||
|
necessary: boolean // Always true, but included for completeness
|
||||||
|
analytics: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CookieConsentProps = {
|
||||||
|
consentDisplayDefaultOpen?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CookieConsent: React.FC<CookieConsentProps> = ({ consentDisplayDefaultOpen = true }) => {
|
||||||
|
const analytics = useAnalytics()
|
||||||
|
const [consent, setConsent] = useState<CookieConsentState>({
|
||||||
|
necessary: true,
|
||||||
|
analytics: false,
|
||||||
|
})
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
|
const handleClose = (_event: React.SyntheticEvent | Event, reason?: SnackbarCloseReason) => {
|
||||||
|
if (reason === 'clickaway') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if user has already given consent
|
||||||
|
const savedConsent = localStorage.getItem(COOKIE_CONSENT_KEY)
|
||||||
|
|
||||||
|
if (savedConsent) {
|
||||||
|
const parsedConsent = JSON.parse(savedConsent) as CookieConsentState
|
||||||
|
setConsent(parsedConsent)
|
||||||
|
}
|
||||||
|
}, [analytics.plugins, consentDisplayDefaultOpen])
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
localStorage.setItem(COOKIE_CONSENT_KEY, JSON.stringify(consent))
|
||||||
|
|
||||||
|
// Initialize GA if analytics consent was given
|
||||||
|
if (consent.analytics) {
|
||||||
|
analytics.plugins.enable(ANALYTICS_PLUGINS)
|
||||||
|
} else {
|
||||||
|
analytics.plugins.disable(ANALYTICS_PLUGINS)
|
||||||
|
}
|
||||||
|
setOpen(true)
|
||||||
|
}, [consent, analytics.plugins])
|
||||||
|
|
||||||
|
const handleChange = useCallback((type: keyof CookieConsentState) => {
|
||||||
|
if (type === 'necessary') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setConsent(prev => ({
|
||||||
|
...prev,
|
||||||
|
[type]: !prev[type],
|
||||||
|
}))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box id="cookie-preferences" aria-labelledby="cookie-preferences-title" aria-describedby="cookie-preferences-description">
|
||||||
|
<Typography id="cookie-preferences-title" aria-label="cookie-preferences" variant="h3" component="h2" gutterBottom>
|
||||||
|
Cookie Preferences
|
||||||
|
</Typography>
|
||||||
|
<Typography id="cookie-preferences-description" color="textSecondary" flexGrow={1}>
|
||||||
|
Please set your preferences below. You can change these settings at any time in the future.
|
||||||
|
<br />
|
||||||
|
For more details, please see our{' '}
|
||||||
|
<Link href="/privacy" color="textPrimary">
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={theme => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
my: theme.spacing(2),
|
||||||
|
p: theme.spacing(4),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
})}>
|
||||||
|
<Stack direction="row" alignSelf="flex-end" mb={-2}>
|
||||||
|
<Button onClick={() => setConsent({ necessary: true, analytics: false })} color="inherit" variant="text" sx={{ textTransform: 'none' }}>
|
||||||
|
Reject All
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setConsent({ necessary: true, analytics: true })} color="inherit" variant="text" sx={{ textTransform: 'none' }}>
|
||||||
|
Accept All
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Stack direction="row" alignItems="center" justifyContent="space-between" alignContent="center">
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
Necessary Cookies
|
||||||
|
</Typography>
|
||||||
|
<Switch slotProps={{ input: { 'aria-label': 'necessary-cookies-toggle' } }} color="default" disabled checked />
|
||||||
|
</Stack>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
These cookies are essential for the website to function properly and cannot be disabled.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Stack direction="row" alignItems="center" justifyContent="space-between" alignContent="center">
|
||||||
|
<Typography variant="body1" fontWeight="bold">
|
||||||
|
Analytics Cookies
|
||||||
|
</Typography>
|
||||||
|
<Switch
|
||||||
|
checked={consent.analytics}
|
||||||
|
slotProps={{ input: { 'aria-label': 'analytics-cookies-toggle' } }}
|
||||||
|
onChange={() => handleChange('analytics')}
|
||||||
|
color="default"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
These cookies help us understand how visitors interact with our website, helping us improve our services.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button onClick={handleSave} variant="outlined" sx={{ alignSelf: 'flex-end' }}>
|
||||||
|
Save Preferences
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>
|
||||||
|
<Alert onClose={handleClose} sx={{ backgroundColor: 'background.paper', width: '100%' }}>
|
||||||
|
Preferences saved!
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CookieConsent
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
|
||||||
|
import PrivacyPolicy from './index.js'
|
||||||
|
|
||||||
|
describe('Privacy Policy', () => {
|
||||||
|
it('renders the Privacy Policy page', async () => {
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<PrivacyPolicy />
|
||||||
|
</BrowserRouter>
|
||||||
|
)
|
||||||
|
expect(screen.getByText('Privacy Policy')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Google Analytics')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Your Rights')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Contact Us')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Typography, Link, List as MuiList, styled, Box } from '@mui/material'
|
||||||
|
|
||||||
|
import ListItemText from 'components/atoms/ListItemText/index.js'
|
||||||
|
|
||||||
|
const List = styled(MuiList)(({ theme }) => ({
|
||||||
|
listStyleType: 'disc',
|
||||||
|
paddingLeft: theme.spacing(4),
|
||||||
|
'& .MuiListItem-root': {
|
||||||
|
display: 'list-item',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const PrivacyPolicy: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Box aria-labelledby="privacy-policy">
|
||||||
|
<Typography id="privacy-policy" aria-label="privacy-policy" variant="h3" component="h2" gutterBottom>
|
||||||
|
Privacy Policy
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" color="textSecondary" component="p" pb={4}>
|
||||||
|
Last updated: May 27, 2025
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom>
|
||||||
|
Introduction
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
m-tartari ("we", "our", or "us") respects your privacy and is committed to protecting your personal data. This privacy policy will inform you
|
||||||
|
about how we look after your personal data when you visit our website and tell you about your privacy rights and how the law protects you.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom sx={{ mt: 4 }}>
|
||||||
|
The Data We Collect About You
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
We collect and process the following information when you visit our website:
|
||||||
|
</Typography>
|
||||||
|
<List dense>
|
||||||
|
<ListItemText title="Technical Data">
|
||||||
|
includes internet protocol (IP) address, browser type and version, time zone setting and location, browser plug-in types and versions,
|
||||||
|
operating system and platform, and other technology on the devices you use to access this website.
|
||||||
|
</ListItemText>
|
||||||
|
<ListItemText title="Usage Data"> includes information about how you use our website, products, and services.</ListItemText>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom sx={{ mt: 4 }}>
|
||||||
|
Cookies
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
We use cookies to distinguish you from other users of our website. This helps us to provide you with a good experience when you browse our
|
||||||
|
website and also allows us to improve our site.
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
We use the following types of cookies:
|
||||||
|
</Typography>
|
||||||
|
<List dense>
|
||||||
|
<ListItemText title="Necessary Cookies:">
|
||||||
|
These are essential to enable you to use the website and its features. Without these cookies, services you have asked for cannot be
|
||||||
|
provided.
|
||||||
|
</ListItemText>
|
||||||
|
<ListItemText title="Analytics Cookies:">
|
||||||
|
We use Google Analytics to help us understand how visitors interact with our website. These cookies collect information about your visit to
|
||||||
|
our website, the content you viewed, the links you followed, and information about your browser, device, and IP address. We do this to
|
||||||
|
improve how our website works.
|
||||||
|
</ListItemText>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom sx={{ mt: 4 }}>
|
||||||
|
Google Analytics
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
This website uses Google Analytics 4, a web analytics service provided by Google, Inc. ("Google"). Google Analytics uses cookies to help the
|
||||||
|
website analyze how users use the site. The information generated by the cookie about your use of the website will be transmitted to and
|
||||||
|
stored by Google on servers in the United States.
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
We have implemented the following measures to be GDPR compliant:
|
||||||
|
</Typography>
|
||||||
|
<List dense>
|
||||||
|
<ListItemText>We have obtained your consent before processing your data through cookies</ListItemText>
|
||||||
|
<ListItemText>We have implemented IP address anonymization</ListItemText>
|
||||||
|
<ListItemText>We have disabled data sharing with Google</ListItemText>
|
||||||
|
<ListItemText>We have set the data retention period to the minimum required for our analysis</ListItemText>
|
||||||
|
</List>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
You can prevent the collection of your data by Google Analytics by declining analytics cookies in our cookie consent banner or by using the
|
||||||
|
Google Analytics Opt-out Browser Add-on available at{' '}
|
||||||
|
<Link color="textPrimary" href="https://tools.google.com/dlpage/gaoptout" target="_blank" rel="noopener noreferrer">
|
||||||
|
https://tools.google.com/dlpage/gaoptout
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom sx={{ mt: 4 }}>
|
||||||
|
Your Rights
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
Under the General Data Protection Regulation (GDPR), you have various rights in relation to your personal data, including:
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<List dense>
|
||||||
|
<ListItemText>The right to access your personal data</ListItemText>
|
||||||
|
<ListItemText>The right to rectification of your personal data</ListItemText>
|
||||||
|
<ListItemText>The right to erasure of your personal data</ListItemText>
|
||||||
|
<ListItemText>The right to restrict processing of your personal data</ListItemText>
|
||||||
|
<ListItemText>The right to data portability</ListItemText>
|
||||||
|
<ListItemText>The right to object to processing of your personal data</ListItemText>
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Typography variant="h5" component="h2" gutterBottom sx={{ mt: 4 }}>
|
||||||
|
Contact Us
|
||||||
|
</Typography>
|
||||||
|
<Typography component="p" color="textSecondary">
|
||||||
|
If you have any questions about this privacy policy or our privacy practices, please contact us at{' '}
|
||||||
|
<Link color="textPrimary" href="mailto:privacy@m-tartari.eu" target="_blank" rel="noopener noreferrer">
|
||||||
|
privacy@m-tartari.eu
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrivacyPolicy
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { screen } from '@testing-library/react'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import { useAnalytics } from 'use-analytics'
|
||||||
|
|
||||||
|
import { renderWithRouter as render } from 'components/utils/tests/index.js'
|
||||||
|
|
||||||
|
import PrivacyPage from './index.js'
|
||||||
|
|
||||||
|
// Mock the analytics module
|
||||||
|
vi.mock('use-analytics', () => ({
|
||||||
|
useAnalytics: () => ({ plugins: { enable: vi.fn(), disable: vi.fn() } }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('PrivacyPage', () => {
|
||||||
|
it('renders the Privacy Policy page', () => {
|
||||||
|
render(<PrivacyPage />)
|
||||||
|
expect(screen.getAllByText('Privacy Policy')).toHaveLength(3) // title, body (cookie preferences section) and footer
|
||||||
|
expect(screen.getByText('Google Analytics')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Your Rights')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('Contact Us')).toBeInTheDocument()
|
||||||
|
expect(screen.getAllByText('Cookie Preferences')).toHaveLength(2) // body and footer
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Page from 'components/templates/Page/index.js'
|
||||||
|
import CookiePreferences from 'components/molecules/CookiePreferences/index.js'
|
||||||
|
import PrivacyPolicy from 'components/molecules/PrivacyPolicy/index.js'
|
||||||
|
import { Divider } from '@mui/material'
|
||||||
|
|
||||||
|
const PrivacyPage: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Page title="Privacy" showCookieConsent={false}>
|
||||||
|
<PrivacyPolicy />
|
||||||
|
<Divider sx={{ my: 4 }} />
|
||||||
|
<CookiePreferences />
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrivacyPage
|
||||||
Reference in New Issue
Block a user