diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 01736e706..79d1a56ab 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -43,6 +43,9 @@ jobs: CYPRESS_BASE_URL: ${{ vars.HOST }} CYPRESS_EMAIL: ${{ secrets.CYPRESS_EMAIL }} CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }} + CYPRESS_MAILER_HOST: ${{ vars.CYPRESS_MAILER_HOST }} + CYPRESS_MAILER_USER: ${{ secrets.CYPRESS_MAILER_USER }} + CYPRESS_MAILER_PASSWORD: ${{ secrets.CYPRESS_MAILER_PASSWORD }} - name: Upload screenshots uses: actions/upload-artifact@v4 diff --git a/e2e/.env.example b/e2e/.env.example index b45ba6934..7322f6a03 100644 --- a/e2e/.env.example +++ b/e2e/.env.example @@ -2,3 +2,7 @@ CYPRESS_BASE_URL=https://zerologementvacant-staging.incubateur.net CYPRESS_API=https://api.zerologementvacant-staging.incubateur.net/api CYPRESS_EMAIL=E2E_EMAIL CYPRESS_PASSWORD=E2E_PASSWORD + +CYPRESS_MAILER_HOST=https://maildev.zerologementvacant.beta.gouv.fr +CYPRESS_MAILER_USER= +CYPRESS_MAILER_PASSWORD= diff --git a/e2e/config.ts b/e2e/config.ts index 0450c8512..b813ece11 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -5,6 +5,11 @@ export interface Config { baseURL: string; email: string; password: string; + mailer: { + host: string; + user: string; + password: string; + }; } const config = convict({ @@ -17,12 +22,14 @@ const config = convict({ baseURL: { env: 'CYPRESS_BASE_URL', doc: 'The base URL of the application', + format: String, default: null, nullable: false }, email: { env: 'CYPRESS_EMAIL', doc: 'The email to use for authentication', + format: String, default: null, sensitive: true, nullable: false @@ -30,9 +37,35 @@ const config = convict({ password: { env: 'CYPRESS_PASSWORD', doc: 'The password to use for authentication', + format: String, default: null, sensitive: true, nullable: false + }, + mailer: { + host: { + env: 'CYPRESS_MAILER_HOST', + doc: 'The nodemailer host', + format: String, + default: null, + nullable: false + }, + user: { + env: 'CYPRESS_MAILER_USER', + doc: 'The nodemailer username', + format: String, + default: null, + sensitive: true, + nullable: false + }, + password: { + env: 'CYPRESS_MAILER_PASSWORD', + doc: 'The nodemailer password', + format: String, + default: null, + sensitive: true, + nullable: false + } } }); diff --git a/e2e/cypress/e2e/sign-up.cy.ts b/e2e/cypress/e2e/sign-up.cy.ts new file mode 100644 index 000000000..10587f5d2 --- /dev/null +++ b/e2e/cypress/e2e/sign-up.cy.ts @@ -0,0 +1,155 @@ +import { faker } from '@faker-js/faker/locale/fr'; + +describe('Sign up', () => { + it('should sign up', () => { + cy.visit('/connexion'); + cy.get('a').contains('Créer votre compte').click(); + + const user = faker.internet.email(); + + cy.get('label') + .contains(/Adresse e-mail/i) + .next() + .type(`${user}{enter}`); + + cy.location('pathname').should('eq', '/inscription/activation'); + + // Fetch emails from the Nodemailer API + cy.request({ + method: 'GET', + url: `${Cypress.env('MAILER_HOST')}/email`, + auth: { + username: Cypress.env('MAILER_USER'), + password: Cypress.env('MAILER_PASSWORD') + } + }).then((response) => { + const emails: ReadonlyArray = response.body; + const email: Email = emails + .filter(subject('Activation du compte')) + .filter(to(user)) + .filter(unread()) + .reduce((acc, email) => (acc.date > email.date ? acc : email)); + const link = email.html.substring( + email.html.indexOf('/inscription/mot-de-passe') + ); + cy.visit(link); + }); + + cy.get('label') + .contains(/Définissez votre mot de passe/i) + .next() + .type('123QWEasd'); + cy.get('label') + .contains(/Confirmez votre mot de passe/i) + .next() + .type('123QWEasd{enter}'); + + cy.get('button') + .contains(/Créer mon compte/i) + .click(); + + cy.location('pathname').should('eq', '/parc-de-logements'); + }); + + it('should await access to LOVAC', () => { + cy.visit('/connexion'); + cy.get('a').contains('Créer votre compte').click(); + + const user = 'lovac_ko@beta.gouv.fr'; + + cy.get('label') + .contains(/Adresse e-mail/i) + .next() + .type(`${user}{enter}`); + + cy.location('pathname').should('eq', '/inscription/activation'); + + // Fetch emails from the Nodemailer API + cy.request({ + method: 'GET', + url: `${Cypress.env('MAILER_HOST')}/email`, + auth: { + username: Cypress.env('MAILER_USER'), + password: Cypress.env('MAILER_PASSWORD') + } + }).then((response) => { + const emails: ReadonlyArray = response.body; + const email: Email = emails + .filter(subject('Activation du compte')) + .filter(to(user)) + .filter(unread()) + .reduce((acc, email) => (acc.date > email.date ? acc : email)); + const link = email.html.substring( + email.html.indexOf('/inscription/mot-de-passe') + ); + cy.visit(link); + }); + + cy.location('pathname').should('eq', '/inscription/en-attente'); + }); + + it('should forbid access to unauthorized users', () => { + cy.visit('/connexion'); + cy.get('a').contains('Créer votre compte').click(); + + const user = 'account_ko@beta.gouv.fr'; + + cy.get('label') + .contains(/Adresse e-mail/i) + .next() + .type(`${user}{enter}`); + + cy.location('pathname').should('eq', '/inscription/activation'); + + // Fetch emails from the Nodemailer API + cy.request({ + method: 'GET', + url: `${Cypress.env('MAILER_HOST')}/email`, + auth: { + username: Cypress.env('MAILER_USER'), + password: Cypress.env('MAILER_PASSWORD') + } + }).then((response) => { + const emails: ReadonlyArray = response.body; + const email: Email = emails + .filter(subject('Activation du compte')) + .filter(to(user)) + .filter(unread()) + .reduce((acc, email) => (acc.date > email.date ? acc : email)); + const link = email.html.substring( + email.html.indexOf('/inscription/mot-de-passe') + ); + cy.visit(link); + }); + + cy.location('pathname').should('eq', '/inscription/impossible'); + }); +}); + +interface Email { + id: string; + html: string; + subject: string; + from: ReadonlyArray<{ + address: string; + name: string; + }>; + to: ReadonlyArray<{ + address: string; + name: string; + }>; + date: string; + read: boolean; +} + +function subject(subject: string) { + return (email: Email) => email.subject === subject; +} + +function to(recipient: string) { + return (email: Email) => email.to.some((to) => to.address === recipient); +} + +function unread() { + return (email: Email) => !email.read; +} diff --git a/e2e/package.json b/e2e/package.json index e402ea47e..f8913a8e5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,6 +7,7 @@ }, "devDependencies": { "@dotenvx/dotenvx": "^1.14.2", + "@faker-js/faker": "^8.4.1", "@types/convict": "^6.1.6", "convict": "^6.2.4", "cypress": "^13.15.0", diff --git a/frontend/jest.polyfills.js b/frontend/jest.polyfills.js index 3d68c9628..4b9531e01 100644 --- a/frontend/jest.polyfills.js +++ b/frontend/jest.polyfills.js @@ -30,3 +30,17 @@ Object.defineProperties(globalThis, { Request: { value: Request, configurable: true }, Response: { value: Response, configurable: true } }); + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: (query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn() + }) +}); diff --git a/frontend/package.json b/frontend/package.json index ba61e7b88..4fce27ac7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "@codegouvfr/react-dsfr": "1.9.16", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@hookform/resolvers": "^3.9.1", "@jonkoops/matomo-tracker-react": "^0.7.0", "@lexical/html": "^0.18.0", "@lexical/list": "^0.18.0", @@ -55,6 +56,7 @@ "qs": "^6.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", "react-map-gl": "^7.1.7", "react-redux": "^8.1.3", "react-redux-loading-bar": "^5.0.8", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fe93ae6b6..4899a4754 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -30,6 +30,7 @@ import ForgottenPasswordView from './views/Account/ForgottenPasswordView'; import ResetPasswordView from './views/Account/ResetPasswordView'; import NotFoundView from './views/NotFoundView'; import AnalysisView from './views/Analysis/AnalysisView'; +import { useIsDsfrReady } from './hooks/useIsDsfrReady'; const router = createBrowserRouter( createRoutesFromElements( @@ -92,6 +93,8 @@ function App() { ) ); + useIsDsfrReady(); + useEffect(() => { if (isSomeQueryPending) { dispatch(showLoading()); diff --git a/frontend/src/assets/images/community.svg b/frontend/src/assets/images/community.svg new file mode 100644 index 000000000..7fc4fe951 --- /dev/null +++ b/frontend/src/assets/images/community.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/fifty-hours.svg b/frontend/src/assets/images/fifty-hours.svg new file mode 100644 index 000000000..9008eb6b5 --- /dev/null +++ b/frontend/src/assets/images/fifty-hours.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/thousand-structures.svg b/frontend/src/assets/images/thousand-structures.svg new file mode 100644 index 000000000..5ce441b7e --- /dev/null +++ b/frontend/src/assets/images/thousand-structures.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/Auth/RequireGuest.tsx b/frontend/src/components/Auth/RequireGuest.tsx index 320d5e44c..9f79301d8 100644 --- a/frontend/src/components/Auth/RequireGuest.tsx +++ b/frontend/src/components/Auth/RequireGuest.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren } from 'react'; import { useUser } from '../../hooks/useUser'; -import NotFoundView from '../../views/NotFoundView'; +import { Navigate } from 'react-router-dom'; interface RequireGuestProps {} @@ -12,7 +12,7 @@ function RequireGuest(props: PropsWithChildren) { return props.children; } - return ; + return ; } export default RequireGuest; diff --git a/frontend/src/components/CampaignIntent/CampaignIntent.tsx b/frontend/src/components/CampaignIntent/CampaignIntent.tsx deleted file mode 100644 index 56ca586cd..000000000 --- a/frontend/src/components/CampaignIntent/CampaignIntent.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { MessageType } from '../../hooks/useForm'; -import RadioButtons from '@codegouvfr/react-dsfr/RadioButtons'; - -interface Props { - defaultValue?: string; - disabled?: boolean; - message?: string; - messageType?: MessageType; - onChange(value: string): void; -} - -const CampaignIntent = (props: Props) => { - const values = [ - { label: Dans les 2 prochains mois, value: '0-2' }, - { - label: ( - - Dans 2 à 4 mois - - ), - value: '2-4' - }, - { - label: ( - - Dans plus de 4 mois - - ), - value: '4+' - } - ]; - - const disabled = props.disabled ?? false; - - function defaultChecked(value: string): boolean { - return value === props.defaultValue; - } - - return ( - ({ - label: item.label, - nativeInputProps: { - value: item.value, - defaultChecked: defaultChecked(item.value), - onChange: () => props.onChange(item.value) - } - }))} - state={props.messageType} - stateRelatedMessage={props.message} - /> - ); -}; - -export default CampaignIntent; diff --git a/frontend/src/components/Footer/Footer.tsx b/frontend/src/components/Footer/Footer.tsx index 3f37971c1..4cd5aa9ef 100644 --- a/frontend/src/components/Footer/Footer.tsx +++ b/frontend/src/components/Footer/Footer.tsx @@ -11,12 +11,11 @@ function Footer() { - Ministère - du Logement
+ Ministère du Logement
et de la Rénovation
urbaine @@ -119,7 +118,7 @@ function Footer() { partnersLogos={{ sub: [ { - alt: 'Logo de l\'Agence nationale de l’habitat', + alt: "Logo de l'Agence nationale de l’habitat", imgUrl: anah, linkProps: { to: 'https://www.anah.gouv.fr/', diff --git a/frontend/src/components/Header/Header.test.tsx b/frontend/src/components/Header/Header.test.tsx deleted file mode 100644 index b9faefdf5..000000000 --- a/frontend/src/components/Header/Header.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import Header from './Header'; -import { MemoryRouter as Router } from 'react-router-dom'; -import { Provider } from 'react-redux'; -import { applicationMiddlewares, applicationReducer } from '../../store/store'; -import { configureStore } from '@reduxjs/toolkit'; -import { genAuthUser } from '../../../test/fixtures.test'; - -describe('AppHeader', () => { - test('should not display navbar when no user is logged', () => { - const store = configureStore({ - reducer: applicationReducer, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - serializableCheck: false - }).concat(applicationMiddlewares), - preloadedState: { authentication: { authUser: undefined } } - }); - - render( - - -
- - - ); - - const housingNavItem = screen.queryByTestId( - 'fr-header-nav-item-parc-de-logements' - ); - const campaignNavItem = screen.queryByTestId( - 'fr-header-nav-item-campagnes' - ); - const infosNavItem = screen.queryByTestId( - 'fr-header-nav-item-informations-publiques' - ); - expect(housingNavItem).not.toBeInTheDocument(); - expect(campaignNavItem).not.toBeInTheDocument(); - expect(infosNavItem).not.toBeInTheDocument(); - }); - - test('should display navbar when a user is logged', () => { - const store = configureStore({ - reducer: applicationReducer, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - serializableCheck: false - }).concat(applicationMiddlewares), - preloadedState: { authentication: { authUser: genAuthUser() } } - }); - - render( - - -
- - - ); - - const housingNavItem = screen.queryByTestId( - 'fr-header-nav-item-parc-de-logements' - ); - const campaignNavItem = screen.queryByTestId( - 'fr-header-nav-item-campagnes' - ); - const resourcesNavItem = screen.queryByTestId( - 'fr-header-nav-item-ressources' - ); - expect(housingNavItem).toBeInTheDocument(); - expect(campaignNavItem).toBeInTheDocument(); - expect(resourcesNavItem).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index 3ec861325..a86da9b67 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -1,133 +1,29 @@ import { Header as DSFRHeader } from '@codegouvfr/react-dsfr/Header'; -import { useLocation } from 'react-router-dom'; -import LoadingBar from 'react-redux-loading-bar'; +import logo from '../../assets/images/zlv.svg'; import styles from './header.module.scss'; -import { getUserNavItem, UserNavItems } from '../../models/UserNavItem'; -import { changeEstablishment } from '../../store/actions/authenticationAction'; -import { useUser } from '../../hooks/useUser'; -import { useAppDispatch, useAppSelector } from '../../hooks/useStore'; -import EstablishmentSearchableSelect from '../EstablishmentSearchableSelect/EstablishmentSearchableSelect'; -import AccountSideMenu from '../../views/Account/AccountSideMenu'; -import Collapse from '../Collapse/Collapse'; -import { Container } from '../_dsfr'; function Header() { - const location = useLocation(); - const dispatch = useAppDispatch(); - const { isAdmin, isVisitor, isAuthenticated } = useUser(); - - const { authUser } = useAppSelector((state) => state.authentication); - - function displayName(): string { - return authUser - ? authUser.user.firstName && authUser.user.lastName - ? `${authUser.user.firstName} ${authUser.user.lastName}` - : authUser.user.email - : ''; - } - - const withNavItems = ['/'].includes(location.pathname); - - const getMainNavigationItem = (navItem: UserNavItems) => ({ - linkProps: { - to: getUserNavItem(navItem).url, - 'data-testid': `fr-header-nav-item-${getUserNavItem( - navItem - ).url.substring(1)}` - }, - text: getUserNavItem(navItem).label, - isActive: location.pathname.startsWith(getUserNavItem(navItem).url) - }); - return ( - <> - - Ministère
- du Logement
- et de la Rénovation
- urbaine - - } - homeLinkProps={{ - to: '/', - title: 'Accueil - Zéro Logement Vacant' - }} - serviceTitle="Zéro Logement Vacant" - serviceTagline={ - isAuthenticated ? ( - isAdmin || isVisitor ? ( - { - dispatch(changeEstablishment(id)); - }} - /> - ) : ( - authUser?.establishment.name - ) - ) : ( - '' - ) - } - quickAccessItems={ - isAuthenticated - ? [ - - - - } - /> - ] - : [ - { - iconId: 'fr-icon-user-fill', - linkProps: { - to: '/connexion' - }, - text: 'Connexion' - } - ] - } - navigation={ - isAuthenticated - ? [ - getMainNavigationItem(UserNavItems.HousingList), - getMainNavigationItem(UserNavItems.Analysis), - getMainNavigationItem(UserNavItems.Campaign), - getMainNavigationItem(UserNavItems.Resources) - ] - : withNavItems && [] - } - data-testid="header" - /> - - + + Ministère du Logement
+ et de la Rénovation
+ urbaine + + } + className={styles.shadowless} + homeLinkProps={{ + to: '/', + title: 'Accueil - Zéro Logement Vacant' + }} + operatorLogo={{ + alt: 'Zéro Logement Vacant', + imgUrl: logo, + orientation: 'horizontal' + }} + /> ); } diff --git a/frontend/src/components/Header/header.module.scss b/frontend/src/components/Header/header.module.scss index f1c1affb6..32232c3b2 100644 --- a/frontend/src/components/Header/header.module.scss +++ b/frontend/src/components/Header/header.module.scss @@ -1,11 +1,3 @@ -.loading { - position: fixed; - top: 0; - background-color: var(--blue-france-113); - height: 2px; - z-index: 1000; -} - -.navItemLink { - font-size: 1.125rem; +.shadowless { + filter: none; } diff --git a/frontend/src/components/Image/Image.tsx b/frontend/src/components/Image/Image.tsx index 37f6b3175..19a8be96f 100644 --- a/frontend/src/components/Image/Image.tsx +++ b/frontend/src/components/Image/Image.tsx @@ -1,10 +1,51 @@ +import { fr } from '@codegouvfr/react-dsfr'; +import styled from '@emotion/styled'; +import classNames from 'classnames'; + +const responsiveValues = [ + '1x1', + '2x3', + '3x2', + '3x4', + '4x3', + '16x9', + '32x9' +] as const; +type Responsive = (typeof responsiveValues)[number]; + interface Props { alt: string; + className?: string; + responsive?: boolean | Responsive | 'max-width'; src: string; } function Image(props: Readonly) { - return {props.alt}; + const { className, responsive, ...rest } = props; + + return ( + + ); } -export default Image; +export default styled(Image)` + max-width: ${(props) => (props.responsive === 'max-width' ? '100%' : 'none')}; + height: auto; +`; diff --git a/frontend/src/components/_app/AppLinkAsButton/link-as-button.module.scss b/frontend/src/components/_app/AppLinkAsButton/link-as-button.module.scss index fa8a438e0..68d39c869 100644 --- a/frontend/src/components/_app/AppLinkAsButton/link-as-button.module.scss +++ b/frontend/src/components/_app/AppLinkAsButton/link-as-button.module.scss @@ -1,5 +1,4 @@ .buttonLink { - padding: 0 0 0.2rem 0; background-image: var(--underline-img),var(--underline-img); background-position: var(--underline-x) 100%,var(--underline-x) calc(100% - 0.0625em); background-repeat: no-repeat,no-repeat; @@ -19,3 +18,7 @@ .buttonLinkSm { font-size: 0.875rem; } + +.colorInherit { + color: inherit; +} diff --git a/frontend/src/components/_app/AppTextInput/AppTextInput.tsx b/frontend/src/components/_app/AppTextInput/AppTextInput.tsx index cf7a02f89..d5c38f3a8 100644 --- a/frontend/src/components/_app/AppTextInput/AppTextInput.tsx +++ b/frontend/src/components/_app/AppTextInput/AppTextInput.tsx @@ -1,7 +1,7 @@ import { ComponentPropsWithoutRef, InputHTMLAttributes, - TextareaHTMLAttributes, + TextareaHTMLAttributes } from 'react'; import { ObjectShape } from 'yup/lib/object'; import { useForm } from '../../../hooks/useForm'; @@ -21,6 +21,11 @@ type AppTextInputProps = Partial< dataTestId?: string; }; +/** + * @deprecated See {@link AppTextInputNext} + * @param props + * @constructor + */ function AppTextInput(props: AppTextInputProps) { const { textArea, @@ -43,7 +48,7 @@ function AppTextInput(props: AppTextInputProps) { nativeTextAreaProps={{ ...textInputProps, placeholder, - onBlur: () => inputForm.validateAt(String(inputKey)), + onBlur: () => inputForm.validateAt(String(inputKey)) }} state={state ?? inputForm.messageType(String(inputKey))} stateRelatedMessage={ @@ -58,7 +63,7 @@ function AppTextInput(props: AppTextInputProps) { nativeInputProps={{ ...textInputProps, placeholder, - onBlur: () => inputForm.validateAt(String(inputKey)), + onBlur: () => inputForm.validateAt(String(inputKey)) }} state={state ?? inputForm.messageType(String(inputKey))} stateRelatedMessage={ diff --git a/frontend/src/components/_app/AppTextInput/AppTextInputNext.tsx b/frontend/src/components/_app/AppTextInput/AppTextInputNext.tsx new file mode 100644 index 000000000..5076adddf --- /dev/null +++ b/frontend/src/components/_app/AppTextInput/AppTextInputNext.tsx @@ -0,0 +1,77 @@ +import Input, { InputProps } from '@codegouvfr/react-dsfr/Input'; +import { ReactNode } from 'react'; +import { useController } from 'react-hook-form'; +import { match, Pattern } from 'ts-pattern'; + +export type AppTextInputNextProps = InputProps & { + name: string; +}; + +/** + * A text input to be used with react-hook-form and validated using yup. + */ +function AppTextInputNext(props: AppTextInputNextProps) { + const { field, fieldState } = useController({ + name: props.name, + disabled: props.disabled + }); + + const isTextArea = props.textArea === true; + + return ( + () + .with(undefined, () => fieldState.error?.message) + .otherwise((errors) => + Object.values(errors) + .map((error) => + match(error) + .returnType() + .with(Pattern.string, (value) => value) + .with(Pattern.array(Pattern.string), (values) => + values.join(', ') + ) + .otherwise(() => null) + ) + .join(' ') + ) + : undefined + } + /> + ); +} + +export default AppTextInputNext; diff --git a/frontend/src/components/modals/OnboardingModal/OnboardingModal.tsx b/frontend/src/components/modals/OnboardingModal/OnboardingModal.tsx new file mode 100644 index 000000000..92b01df78 --- /dev/null +++ b/frontend/src/components/modals/OnboardingModal/OnboardingModal.tsx @@ -0,0 +1,62 @@ +import { useLocation } from 'react-router-dom'; +import { useEffect } from 'react'; +import { createModal } from '@codegouvfr/react-dsfr/Modal'; +import Grid from '@mui/material/Unstable_Grid2'; +import Typography from '@mui/material/Typography'; + +import { useIsDsfrReady } from '../../../hooks/useIsDsfrReady'; +import image from '../../../assets/images/community.svg'; + +const id = 'onboarding-modal'; +const modal = createModal({ + id, + isOpenedByDefault: false +}); + +function OnboardingModal() { + const location = useLocation(); + const onboarding: boolean = location.state?.onboarding ?? false; + + const ready = useIsDsfrReady(); + + useEffect(() => { + if (ready && onboarding && modal) { + // Dirty hack to provide a larger modal + document + .getElementById(id) + ?.querySelector('.fr-col-lg-8') + ?.classList?.remove('fr-col-lg-8'); + modal.open(); + } + }, [onboarding, ready]); + + return ( + + + + + + Pour prendre en main rapidement ZLV, inscrivez-vous à une session de + prise en main (1h) afin de découvrir les principales fonctionnalités + de la plateforme. Cette inscription est optionnelle, mais + recommandée. + + + + +