diff --git a/.github/workflows/ppd.yml b/.github/workflows/ppd.yml index ef5ce7bc..2b001517 100644 --- a/.github/workflows/ppd.yml +++ b/.github/workflows/ppd.yml @@ -14,6 +14,12 @@ env: CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: e975f056-967e-43fe-b237-84bfa8032e64 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreppdmp73pool + + IMAGE_NAME: front CONTAINER_NAME: front @@ -66,6 +72,30 @@ jobs: run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - name: Push the Docker Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + build-and-push-image-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Docker Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Docker Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} deploy-to-scaleway: needs: build-and-push-image runs-on: ubuntu-latest diff --git a/.github/workflows/prd.yml b/.github/workflows/prd.yml index c74cdd41..1f8a1349 100644 --- a/.github/workflows/prd.yml +++ b/.github/workflows/prd.yml @@ -9,6 +9,11 @@ env: NAMESPACE_ID: 17374437-5428-468c-9f41-d89787ffce0e CONTAINER_REGISTRY_ENDPOINT: rg.fr-par.scw.cloud/funcscwlecoffreprdg7h5bbub + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a NAMESPACE_ID_LECOFFRE: 8fbbce9d-31d1-4368-94c4-445e79f10834 CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffreprdjulp9mam @@ -65,10 +70,34 @@ jobs: run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - name: Push the Docker Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + build-and-push-image-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Docker Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Docker Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} deploy-to-scaleway: needs: build-and-push-image runs-on: ubuntu-latest - environment: prod + environment: preprod steps: - name: Install CLI uses: scaleway/action-scw@v0 diff --git a/.github/workflows/stg.yml b/.github/workflows/stg.yml index 2f70c176..28d74955 100644 --- a/.github/workflows/stg.yml +++ b/.github/workflows/stg.yml @@ -14,6 +14,12 @@ env: CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffrestgbqbfhtv6 + + PROJECT_ID_LECOFFRE: 72d08499-37c2-412b-877e-f8af0471654a + NAMESPACE_ID_LECOFFRE: f8137e85-47ad-46a5-9e2e-18af5de829c5 + CONTAINER_REGISTRY_ENDPOINT_LECOFFRE: rg.fr-par.scw.cloud/funcscwlecoffrestgbqbfhtv6 + + IMAGE_NAME: front CONTAINER_NAME: front @@ -66,6 +72,30 @@ jobs: run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} - name: Push the Docker Image to Scaleway Container Registry run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + build-and-push-image-lecoffre: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Copy SSH + run: cp ~/.ssh/id_rsa id_rsa + - name: Login to Scaleway Container Registry + uses: docker/login-action@v3 + with: + username: nologin + password: ${{ secrets.SCW_SECRET_KEY_LECOFFRE }} + registry: ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }} + - name: Build the Docker Image + run: docker build . -t ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} + - name: Push the Docker Image to Scaleway Container Registry + run: docker push ${{ env.CONTAINER_REGISTRY_ENDPOINT_LECOFFRE }}/${{ env.IMAGE_NAME }} deploy-to-scaleway: needs: build-and-push-image runs-on: ubuntu-latest diff --git a/package-lock.json b/package-lock.json index d5f111c7..7988639e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "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" } @@ -4910,6 +4911,11 @@ "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/src/front/Api/LeCoffreApi/Admin/Stripe/Stripe.ts b/src/front/Api/LeCoffreApi/Admin/Stripe/Stripe.ts index b477cf76..bef47a5d 100644 --- a/src/front/Api/LeCoffreApi/Admin/Stripe/Stripe.ts +++ b/src/front/Api/LeCoffreApi/Admin/Stripe/Stripe.ts @@ -12,6 +12,7 @@ export type IPostStripeResponse = { export type IGetClientPortalSessionResponse = { url: string; + cancel_at?: number; }; export interface IGetCustomerBySubscriptionIdParams { @@ -45,7 +46,7 @@ export default class Stripe extends BaseAdmin { } } - public async getClientPortalSession(stripe_subscription_id: string) { + public async getStripeSubscriptionByUid(stripe_subscription_id: string) { const url = new URL(this.baseURl.concat(`/${stripe_subscription_id}`)); try { return await this.getRequest(url); @@ -55,6 +56,16 @@ export default class Stripe extends BaseAdmin { } } + public async getClientPortalSession(stripe_subscription_id: string) { + const url = new URL(this.baseURl.concat(`/${stripe_subscription_id}/client-portal`)); + try { + return await this.getRequest(url); + } catch (err) { + this.onError(err); + return Promise.reject(err); + } + } + public async getCustomerBySubscriptionId(subscriptionId: string) { const url = new URL(this.baseURl.concat(`/${subscriptionId}/customer`)); try { diff --git a/src/front/Api/LeCoffreApi/Notary/OfficeRib/OfficeRib.ts b/src/front/Api/LeCoffreApi/Notary/OfficeRib/OfficeRib.ts index 9b9432eb..895781a9 100644 --- a/src/front/Api/LeCoffreApi/Notary/OfficeRib/OfficeRib.ts +++ b/src/front/Api/LeCoffreApi/Notary/OfficeRib/OfficeRib.ts @@ -15,7 +15,7 @@ export interface IPostFilesParams {} export default class OfficeRib extends BaseNotary { private static instance: OfficeRib; - private readonly baseURl = this.namespaceUrl.concat("/office/rib"); + private readonly baseURl = this.namespaceUrl.concat("/rib"); private constructor() { super(); diff --git a/src/front/Assets/logo.svg b/src/front/Assets/logo.svg index c79b2e66..8269f65f 100644 --- a/src/front/Assets/logo.svg +++ b/src/front/Assets/logo.svg @@ -1,38 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/front/Components/DesignSystem/Header/BurgerMenu/BurgerModal/index.tsx b/src/front/Components/DesignSystem/Header/BurgerMenu/BurgerModal/index.tsx index 42687181..40406928 100644 --- a/src/front/Components/DesignSystem/Header/BurgerMenu/BurgerModal/index.tsx +++ b/src/front/Components/DesignSystem/Header/BurgerMenu/BurgerModal/index.tsx @@ -6,6 +6,7 @@ import NavigationLink from "../../NavigationLink"; import classes from "./classes.module.scss"; import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule"; import BurgerModalSubmenu from "./BurgerModalSubmenu"; +import Rules, { RulesMode } from "@Front/Components/Elements/Rules"; type IProps = { isOpen: boolean; @@ -21,20 +22,31 @@ export default class BurgerModal extends React.Component { <>
- - -
+ + <> + + +
+ + { ], rules: [ { - action: AppRuleActions.read, + action: AppRuleActions.update, name: AppRuleNames.users, }, ], diff --git a/src/front/Components/DesignSystem/Header/Navigation/index.tsx b/src/front/Components/DesignSystem/Header/Navigation/index.tsx index 3ac31b2d..e5cd1b3e 100644 --- a/src/front/Components/DesignSystem/Header/Navigation/index.tsx +++ b/src/front/Components/DesignSystem/Header/Navigation/index.tsx @@ -63,110 +63,113 @@ export default function Navigation() { return (
- - - + <> + + + + + {/* */} (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( @@ -218,6 +28,14 @@ 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/"); @@ -229,49 +47,25 @@ export default function Login() { }; useEffect(() => { - if (error === "1") openErrorModal(); + openErrorModal(parseInt(error as string)); }, [error, openErrorModal]); return (
- {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 && ( - - )} + coffre + +
Connexion espace professionnel
+
+ + +
Vous n'arrivez pas à vous connecter ?
+
+ + +
- Connectez-vous en tant que client + +
Identifiez-vous
+
{/* 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(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 new file mode 100644 index 00000000..789e0ef3 Binary files /dev/null and b/src/front/Components/Layouts/LoginCustomer/landing-connect.jpeg differ diff --git a/src/front/Components/Layouts/LoginHome/classes.module.scss b/src/front/Components/Layouts/LoginHome/classes.module.scss new file mode 100644 index 00000000..6d2dc12b --- /dev/null +++ b/src/front/Components/Layouts/LoginHome/classes.module.scss @@ -0,0 +1,23 @@ +.root { + padding: 64px; + + .content { + display: flex; + gap: 48px; + flex-direction: column; + margin-top: 48px; + + .section { + display: flex; + flex-direction: column; + gap: 24px; + } + .separator { + height: 1px; + background-color: var(--grey-medium); + } + } + .bottom { + margin-top: 48px; + } +} diff --git a/src/front/Components/Layouts/LoginHome/index.tsx b/src/front/Components/Layouts/LoginHome/index.tsx new file mode 100644 index 00000000..1c3cb2cd --- /dev/null +++ b/src/front/Components/Layouts/LoginHome/index.tsx @@ -0,0 +1,47 @@ +import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; +import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage"; + +import classes from "./classes.module.scss"; +import LandingImage from "../Login/landing-connect.jpeg"; +import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; +import Link from "next/link"; +import Module from "@Front/Config/Module"; + +export default function LoginHome() { + return ( + +
+ +
Connectez-vous à votre plateforme LEcoffre.io
+
+
+
+ + Je suis un notaire + + + + +
+
+
+ + Je suis un client + + + + +
+
+
+ +
Vous n'arrivez pas à vous connecter ?
+
+ + + +
+
+ + ); +} diff --git a/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx b/src/front/Components/Layouts/Subscription/Manage/SubscriptionManageCollaborators/index.tsx index 435157bf..c0f06dbb 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(); - }, [loadCollaborators, loadSubscription]); + }, [loadSubscription]); return ( diff --git a/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx b/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx index cb0238c8..47f76ac0 100644 --- a/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx +++ b/src/front/Components/Layouts/Subscription/SubscriptionFacturation/index.tsx @@ -27,10 +27,11 @@ export const forfeitsPrices: Record = { export default function SubscriptionFacturation() { const router = useRouter(); const [subscription, setSubscription] = useState(null); + const [cancelAt, setCancelAt] = useState(null); const { close: closeCancelSubscription, isOpen: isCancelSubscriptionOpen } = useOpenable(); const { close: closeConfirmation, isOpen: isConfirmationOpen } = useOpenable(); - + const { close: closeConfirmationRemoveSeats, isOpen: isConfirmationRemoveSeatsOpen, open: openConfirmationRemoveSeats } = useOpenable(); // const cancelSubscription = useCallback(() => { // closeCancelSubscription(); // openConfirmation(); @@ -47,13 +48,17 @@ export default function SubscriptionFacturation() { } catch (error) {} }; - const cancelSubscription = async () => { + const cancelOrReactivateSubscription = async () => { try { const jwt = JwtService.getInstance().decodeJwt(); const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } }); if (!subscription[0]) return; const stripe_client_portal = await Stripe.getInstance().getClientPortalSession(subscription[0].stripe_subscription_id!); - router.push(stripe_client_portal.url + "/subscriptions/" + subscription[0].stripe_subscription_id + "/cancel"); + if (!cancelAt) { + router.push(stripe_client_portal.url + "/subscriptions/" + subscription[0].stripe_subscription_id + "/cancel"); + } else { + router.push(stripe_client_portal.url + "/subscriptions/" + subscription[0].stripe_subscription_id + "/reactivate"); + } } catch (error) {} }; @@ -73,6 +78,8 @@ export default function SubscriptionFacturation() { if (!subscription[0]) { router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path); } else { + const stripeSubscription = await Stripe.getInstance().getStripeSubscriptionByUid(subscription[0].stripe_subscription_id!); + if (stripeSubscription.cancel_at !== null) setCancelAt(new Date(stripeSubscription.cancel_at! * 1000)); setSubscription(subscription[0]); } }, [router]); @@ -110,13 +117,20 @@ export default function SubscriptionFacturation() { Plan par utilisateur
- {subscription.type === "STANDARD" && ( + {subscription.type === "STANDARD" && !cancelAt && (
Plan actif
)} + {subscription.type === "STANDARD" && cancelAt && ( +
+ + Plan actif jusqu'au {cancelAt.toLocaleDateString()} + +
+ )}
@@ -146,7 +160,7 @@ export default function SubscriptionFacturation() { href={ Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Standard.props.path }> */} - {/* */} @@ -174,13 +188,20 @@ export default function SubscriptionFacturation() { Plan par office
- {subscription.type === "UNLIMITED" && ( + {subscription.type === "UNLIMITED" && !cancelAt && (
Plan actif
)} + {subscription.type === "UNLIMITED" && cancelAt && ( +
+ + Plan actif jusqu'au {cancelAt.toLocaleDateString()} + +
+ )}
@@ -213,10 +234,17 @@ export default function SubscriptionFacturation() {
-
@@ -225,7 +253,7 @@ export default function SubscriptionFacturation() {
+ + +
+ + En cas de suppression de sièges, les derniers comptes ajoutés seront concernés. Vous pouvez réattribuer les sièges + que vous avez dans votre page sur la page "Gérer mes attributions”. + +
+
); } diff --git a/src/front/Components/Layouts/Subscription/SubscriptionInvite/classes.module.scss b/src/front/Components/Layouts/Subscription/SubscriptionInvite/classes.module.scss index 35366fac..33f1c4df 100644 --- a/src/front/Components/Layouts/Subscription/SubscriptionInvite/classes.module.scss +++ b/src/front/Components/Layouts/Subscription/SubscriptionInvite/classes.module.scss @@ -17,7 +17,7 @@ .input-container { display: flex; gap: 16px; - justify-content: flex-start; + justify-content: center; align-items: center; > span { width: 100%; @@ -26,7 +26,7 @@ } .add-line-container { display: flex; - justify-content: flex-start; + justify-content: center; } .button-container { diff --git a/src/front/Components/Layouts/Users/UserInformations/index.tsx b/src/front/Components/Layouts/Users/UserInformations/index.tsx index 50a71fd3..0a08aa29 100644 --- a/src/front/Components/Layouts/Users/UserInformations/index.tsx +++ b/src/front/Components/Layouts/Users/UserInformations/index.tsx @@ -269,7 +269,7 @@ export default function UserInformations(props: IProps) { )} ; + return ; } diff --git a/src/pages/customers/login.tsx b/src/pages/customers/login.tsx index fe395216..785decd1 100644 --- a/src/pages/customers/login.tsx +++ b/src/pages/customers/login.tsx @@ -1,4 +1,4 @@ -import LoginCustomer from "@Front/Components/Layouts/Login"; +import LoginCustomer from "@Front/Components/Layouts/LoginCustomer"; export default function Route() { return ; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3def80ce..790c8a7c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,5 +1,5 @@ -import Login from "./login"; +import LoginHome from "@Front/Components/Layouts/LoginHome"; export default function Route() { - return ; + return ; }