diff --git a/package-lock.json b/package-lock.json index 4554bcda..2c39bf04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "eslint-config-next": "13.2.4", "form-data": "^4.0.0", "jwt-decode": "^3.1.2", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.130", "next": "13.2.4", "prettier": "^2.8.7", "react": "18.2.0", @@ -32,7 +32,6 @@ "react-toastify": "^9.1.3", "sass": "^1.59.2", "sharp": "^0.32.1", - "ts-pattern": "^4.3.0", "typescript": "4.9.5", "uuidv4": "^6.2.13" } @@ -1527,9 +1526,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001609", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz", - "integrity": "sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==", + "version": "1.0.30001610", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", + "integrity": "sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA==", "funding": [ { "type": "opencollective", @@ -3505,7 +3504,7 @@ } }, "node_modules/le-coffre-resources": { - "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#bb3e87116ea3e2682f61ff322b99bae895cc4308", + "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#1ecb4711cfdf1c6efc59e06642ba4dbbeb746e74", "license": "MIT", "dependencies": { "class-transformer": "^0.5.1", @@ -3743,9 +3742,9 @@ } }, "node_modules/node-abi": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz", - "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==", + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.58.0.tgz", + "integrity": "sha512-pXY1jnGf5T7b8UNzWzIqf0EkX4bx/w8N2AvwlGnk2SYYA/kzDVPaH0Dh0UG4EwxBB5eKOIZKPr8VAHSHL1DPGg==", "dependencies": { "semver": "^7.3.5" }, @@ -4911,11 +4910,6 @@ "node": ">=8.0" } }, - "node_modules/ts-pattern": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-4.3.0.tgz", - "integrity": "sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", diff --git a/package.json b/package.json index 2b5e4e6c..353638c6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "PORT=5005 next dev", "build": "next build", "start": "next start", "lint": "next lint", @@ -25,7 +25,7 @@ "eslint-config-next": "13.2.4", "form-data": "^4.0.0", "jwt-decode": "^3.1.2", - "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126", + "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.130", "next": "13.2.4", "prettier": "^2.8.7", "react": "18.2.0", @@ -34,7 +34,6 @@ "react-toastify": "^9.1.3", "sass": "^1.59.2", "sharp": "^0.32.1", - "ts-pattern": "^4.3.0", "typescript": "4.9.5", "uuidv4": "^6.2.13" } diff --git a/src/front/Api/Entities/rule.ts b/src/front/Api/Entities/rule.ts index 764f4720..d71b3856 100644 --- a/src/front/Api/Entities/rule.ts +++ b/src/front/Api/Entities/rule.ts @@ -18,4 +18,6 @@ export enum AppRuleNames { offices = "offices", documents = "documents", rib = "rib", + subscriptions = "subscriptions", + stripe = "stripe", } diff --git a/src/front/Components/DesignSystem/BlockList/index.tsx b/src/front/Components/DesignSystem/BlockList/index.tsx index 6ce419a3..2081f9d1 100644 --- a/src/front/Components/DesignSystem/BlockList/index.tsx +++ b/src/front/Components/DesignSystem/BlockList/index.tsx @@ -9,6 +9,7 @@ export type IBlock = { name: string; id: string; selected: boolean; + rightIcon?: JSX.Element; hasFlag?: boolean; }; @@ -35,6 +36,7 @@ export default function BlockList({ blocks, onSelectedBlock }: IProps) {
{folder.hasFlag && } chevron + {folder.rightIcon && folder.rightIcon}
diff --git a/src/front/Components/DesignSystem/Header/Navigation/index.tsx b/src/front/Components/DesignSystem/Header/Navigation/index.tsx index e926c670..3ac31b2d 100644 --- a/src/front/Components/DesignSystem/Header/Navigation/index.tsx +++ b/src/front/Components/DesignSystem/Header/Navigation/index.tsx @@ -157,6 +157,12 @@ export default function Navigation() { Module.getInstance().get().modules.pages.Subscription.pages.New.props.path, Module.getInstance().get().modules.pages.Subscription.pages.Subscribe.props.path, ], + rules: [ + { + action: AppRuleActions.update, + name: AppRuleNames.subscriptions, + }, + ], }, ]} /> diff --git a/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx b/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx index 6b3c124b..9147e5e7 100644 --- a/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx +++ b/src/front/Components/LayoutTemplates/DefaultCollaboratorDashboard/index.tsx @@ -92,10 +92,18 @@ export default class DefaultCollaboratorDashboard extends React.Component - -
Identifiez-vous
-
+ Connectez-vous en tant que client {/* Pour accéder à votre espace de dépôt des documents, veuillez vous identifier. france-connect
Qu'est ce que FranceConnect ?
Ou */} - Pour accéder à votre espace de dépôt des documents, veuillez vous identifier.
(LoginStep.EMAIL); + const [totpCodeUid, setTotpCodeUid] = useState(""); + const [totpCode, setTotpCode] = useState(""); + const [email, setEmail] = useState(""); + const [partialPhoneNumber, setPartialPhoneNumber] = useState(""); + const [validationErrors, setValidationErrors] = useState([]); + + const openErrorModal = useCallback(() => { + setIsErrorModalOpen(1); + }, []); + + const closeErrorModal = useCallback(() => { + setIsErrorModalOpen(0); + }, []); + + const onEmailFormSubmit = useCallback(async (e: React.FormEvent | null, values: { [key: string]: string }) => { + try { + if (!values["email"]) return; + setEmail(values["email"]); + const res = await Auth.getInstance().mailVerifySms({ email: values["email"] }); + setPartialPhoneNumber(res.partialPhoneNumber); + setTotpCodeUid(res.totpCodeUid); + setStep(LoginStep.TOTP); + setValidationErrors([]); + } catch (error: any) { + setValidationErrors([ + { + property: "email", + constraints: { + [error.http_status]: error.message, + }, + }, + ]); + return; + } + }, []); + + const onSmsCodeSubmit = useCallback( + async (e: React.FormEvent | null, values: { [key: string]: string }) => { + try { + if (!values["totpCode"]) return; + const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email }); + + // If the code is valid setting it in state + if (res.validCode) setTotpCode(values["totpCode"]); + + setValidationErrors([]); + // If it's first connection, show the form for first connection + if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD); + // If it's password forgotten, show the form for password forgotten + else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN); + // Else just login normally + else setStep(LoginStep.PASSWORD); + } catch (error: any) { + setValidationErrors([ + { + property: "totpCode", + constraints: { + [error.http_status]: error.message, + }, + }, + ]); + return; + } + }, + [email, setStep], + ); + + const onNewPasswordSubmit = useCallback( + async (e: React.FormEvent | null, values: { [key: string]: string }) => { + try { + if (!values["password"] || !values["confirm_password"]) return; + if (values["password"] !== values["confirm_password"]) { + setValidationErrors([ + { + property: "confirm_password", + constraints: { + "400": "Les mots de passe ne correspondent pas.", + }, + }, + ]); + return; + } + + const passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/); + if (!passwordRegex.test(values["password"])) { + setValidationErrors([ + { + property: "password", + constraints: { + "400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.", + }, + }, + ]); + return; + } + const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] }); + CustomerStore.instance.connect(token.accessToken, token.refreshToken); + setValidationErrors([]); + router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + // If set password worked, setting the token and redirecting + } catch (error: any) { + setValidationErrors([ + { + property: "password", + constraints: { + [error.http_status]: error.message, + }, + }, + ]); + return; + } + }, + [totpCode, email, router], + ); + + const onPasswordSubmit = useCallback( + async (e: React.FormEvent | null, values: { [key: string]: string }) => { + try { + if (!values["password"]) return; + const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] }); + CustomerStore.instance.connect(token.accessToken, token.refreshToken); + setValidationErrors([]); + router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); + } catch (error: any) { + setValidationErrors([ + { + property: "password", + constraints: { + [error.http_status]: error.message, + }, + }, + ]); + return; + } + }, + [email, router, totpCode], + ); + + const onPasswordForgotClicked = useCallback(async () => { + try { + const res = await Auth.getInstance().askNewPassword({ email }); + setPartialPhoneNumber(res.partialPhoneNumber); + setValidationErrors([]); + setStep(LoginStep.TOTP); + } catch (error: any) { + // If token already exists and is still valid redirect to the connect/register page + if (error.http_status === 425) { + setStep(LoginStep.TOTP); + return; + } + return; + } + }, [email]); + + const onSendAnotherCode = useCallback(async () => { + try { + const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid }); + + setValidationErrors([]); + setPartialPhoneNumber(res.partialPhoneNumber); + setTotpCodeUid(res.totpCodeUid); + } catch (error: any) { + setValidationErrors([ + { + property: "totpCode", + constraints: { + [error.http_status]: error.message, + }, + }, + ]); + return; + } + }, [email, totpCodeUid]); + const redirectUserOnConnection = useCallback(() => { const variables = FrontendVariables.getInstance(); router.push( @@ -28,14 +218,6 @@ export default function Login() { ); }, [router]); - const openErrorModal = useCallback((index: number) => { - setIsErrorModalOpen(index); - }, []); - - const closeErrorModal = useCallback(() => { - setIsErrorModalOpen(0); - }, []); - const closeNoEmailModal = useCallback(() => { setIsErrorModalOpen(0); router.push("https://connexion.idnot.fr/"); @@ -47,25 +229,49 @@ export default function Login() { }; useEffect(() => { - openErrorModal(parseInt(error as string)); + if (error === "1") openErrorModal(); }, [error, openErrorModal]); return (
- coffre - -
Connexion espace professionnel
-
- - -
Vous n'arrivez pas à vous connecter ?
-
- - - + {step === LoginStep.EMAIL && ( +
+ + Connectez vous en tant que notaire + + + + Vous n'arrivez pas à vous connecter ? + + + + +
+
+ )} + {step === LoginStep.EMAIL && } + {step === LoginStep.TOTP && ( + + )} + {step === LoginStep.PASSWORD && ( + + )} + {step === LoginStep.NEW_PASSWORD && } + {step === LoginStep.PASSWORD_FORGOTTEN && ( + + )}
(LoginStep.EMAIL); - const [totpCodeUid, setTotpCodeUid] = useState(""); - const [totpCode, setTotpCode] = useState(""); - const [email, setEmail] = useState(""); - const [partialPhoneNumber, setPartialPhoneNumber] = useState(""); - const [validationErrors, setValidationErrors] = useState([]); - - const openErrorModal = useCallback(() => { - setIsErrorModalOpen(true); - }, []); - - const closeErrorModal = useCallback(() => { - setIsErrorModalOpen(false); - }, []); - - useEffect(() => { - if (error === "1") openErrorModal(); - }, [error, openErrorModal]); - - const onEmailFormSubmit = useCallback(async (e: React.FormEvent | null, values: { [key: string]: string }) => { - try { - if (!values["email"]) return; - setEmail(values["email"]); - const res = await Auth.getInstance().mailVerifySms({ email: values["email"] }); - setPartialPhoneNumber(res.partialPhoneNumber); - setTotpCodeUid(res.totpCodeUid); - setStep(LoginStep.TOTP); - setValidationErrors([]); - } catch (error: any) { - setValidationErrors([ - { - property: "email", - constraints: { - [error.http_status]: error.message, - }, - }, - ]); - return; - } - }, []); - - const onSmsCodeSubmit = useCallback( - async (e: React.FormEvent | null, values: { [key: string]: string }) => { - try { - if (!values["totpCode"]) return; - const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email }); - - // If the code is valid setting it in state - if (res.validCode) setTotpCode(values["totpCode"]); - - setValidationErrors([]); - // If it's first connection, show the form for first connection - if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD); - // If it's password forgotten, show the form for password forgotten - else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN); - // Else just login normally - else setStep(LoginStep.PASSWORD); - } catch (error: any) { - setValidationErrors([ - { - property: "totpCode", - constraints: { - [error.http_status]: error.message, - }, - }, - ]); - return; - } - }, - [email, setStep], - ); - - const onNewPasswordSubmit = useCallback( - async (e: React.FormEvent | null, values: { [key: string]: string }) => { - try { - if (!values["password"] || !values["confirm_password"]) return; - if (values["password"] !== values["confirm_password"]) { - setValidationErrors([ - { - property: "confirm_password", - constraints: { - "400": "Les mots de passe ne correspondent pas.", - }, - }, - ]); - return; - } - - const passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/); - if (!passwordRegex.test(values["password"])) { - setValidationErrors([ - { - property: "password", - constraints: { - "400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.", - }, - }, - ]); - return; - } - const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] }); - CustomerStore.instance.connect(token.accessToken, token.refreshToken); - setValidationErrors([]); - router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); - // If set password worked, setting the token and redirecting - } catch (error: any) { - setValidationErrors([ - { - property: "password", - constraints: { - [error.http_status]: error.message, - }, - }, - ]); - return; - } - }, - [totpCode, email, router], - ); - - const onPasswordSubmit = useCallback( - async (e: React.FormEvent | null, values: { [key: string]: string }) => { - try { - if (!values["password"]) return; - const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] }); - CustomerStore.instance.connect(token.accessToken, token.refreshToken); - setValidationErrors([]); - router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path); - } catch (error: any) { - setValidationErrors([ - { - property: "password", - constraints: { - [error.http_status]: error.message, - }, - }, - ]); - return; - } - }, - [email, router, totpCode], - ); - - const onPasswordForgotClicked = useCallback(async () => { - try { - const res = await Auth.getInstance().askNewPassword({ email }); - setPartialPhoneNumber(res.partialPhoneNumber); - setValidationErrors([]); - setStep(LoginStep.TOTP); - } catch (error: any) { - // If token already exists and is still valid redirect to the connect/register page - if (error.http_status === 425) { - setStep(LoginStep.TOTP); - return; - } - return; - } - }, [email]); - - const onSendAnotherCode = useCallback(async () => { - try { - const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid }); - - setValidationErrors([]); - setPartialPhoneNumber(res.partialPhoneNumber); - setTotpCodeUid(res.totpCodeUid); - } catch (error: any) { - setValidationErrors([ - { - property: "totpCode", - constraints: { - [error.http_status]: error.message, - }, - }, - ]); - return; - } - }, [email, totpCodeUid]); - - return ( - -
- {step === LoginStep.EMAIL && } - {step === LoginStep.TOTP && ( - - )} - {step === LoginStep.PASSWORD && ( - - )} - {step === LoginStep.NEW_PASSWORD && } - {step === LoginStep.PASSWORD_FORGOTTEN && ( - - )} -
- -
- - Une erreur est survenue lors de la connexion. Veuillez réessayer. - -
-
-
- ); -} diff --git a/src/front/Components/Layouts/LoginCustomer/landing-connect.jpeg b/src/front/Components/Layouts/LoginCustomer/landing-connect.jpeg deleted file mode 100644 index 789e0ef3..00000000 Binary files a/src/front/Components/Layouts/LoginCustomer/landing-connect.jpeg and /dev/null differ diff --git a/src/front/Components/Layouts/Subscription/Components/SubscribeCheckoutTicket/index.tsx b/src/front/Components/Layouts/Subscription/Components/SubscribeCheckoutTicket/index.tsx index da20e7bc..76339377 100644 --- a/src/front/Components/Layouts/Subscription/Components/SubscribeCheckoutTicket/index.tsx +++ b/src/front/Components/Layouts/Subscription/Components/SubscribeCheckoutTicket/index.tsx @@ -8,7 +8,6 @@ import classnames from "classnames"; import { EType } from "le-coffre-resources/dist/Admin/Subscription"; import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe"; import { useRouter } from "next/router"; -import * as P from "ts-pattern"; type IProps = { forfeitType: EForfeitType; @@ -37,15 +36,13 @@ export default function SubscribeCheckoutTicket(props: IProps) { useEffect(() => { let multiplierToUse = paymentFrequency === EPaymentFrequency.yearly ? multiplier - 1 : multiplier; - P.match(forfeitType) - .with(EForfeitType.standard, () => { - setTotalPlan(forfeitsPrices[EForfeitType.standard] * multiplierToUse); - setTotalCollaborator(collaboratorPrice * numberOfCollaborators * multiplier); - }) - .with(EForfeitType.unlimited, () => { - setTotalPlan(forfeitsPrices[EForfeitType.unlimited] * multiplierToUse); - setTotalCollaborator(0); - }); + if (forfeitType === EForfeitType.unlimited) { + setTotalPlan(forfeitsPrices[EForfeitType.unlimited] * multiplierToUse); + setTotalCollaborator(0); + } else { + setTotalPlan(forfeitsPrices[EForfeitType.standard] * multiplierToUse); + setTotalCollaborator(collaboratorPrice * numberOfCollaborators * multiplier); + } }, [multiplier, forfeitType, numberOfCollaborators, paymentFrequency]); const handleFrequencyChange = (e: React.ChangeEvent) => { diff --git a/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx b/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx index c0f06dbb..435157bf 100644 --- a/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx +++ b/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx @@ -71,7 +71,7 @@ export default function SubscriptionManageCollaborators() { useEffect(() => { loadSubscription(); loadCollaborators(); - }, [loadSubscription]); + }, [loadCollaborators, loadSubscription]); return ( diff --git a/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx b/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx index 6a11e7f6..d0a225aa 100644 --- a/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx +++ b/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx @@ -148,7 +148,7 @@ export default function SubscriptionFacturation() { Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Standard.props.path }> */} {/* */} diff --git a/src/pages/customer-login.tsx b/src/pages/customer-login.tsx index 785decd1..40f96abe 100644 --- a/src/pages/customer-login.tsx +++ b/src/pages/customer-login.tsx @@ -1,5 +1,5 @@ -import LoginCustomer from "@Front/Components/Layouts/LoginCustomer"; +import Login from "@Front/Components/Layouts/Login"; export default function Route() { - return ; + return ; } diff --git a/src/pages/customers/login.tsx b/src/pages/customers/login.tsx index 785decd1..fe395216 100644 --- a/src/pages/customers/login.tsx +++ b/src/pages/customers/login.tsx @@ -1,4 +1,4 @@ -import LoginCustomer from "@Front/Components/Layouts/LoginCustomer"; +import LoginCustomer from "@Front/Components/Layouts/Login"; export default function Route() { return ; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 40f96abe..3def80ce 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,4 @@ -import Login from "@Front/Components/Layouts/Login"; +import Login from "./login"; export default function Route() { return ;