Merge branch 'dev' into staging

This commit is contained in:
Vins 2024-04-04 10:33:55 +02:00
commit eda95803ec
40 changed files with 2230 additions and 437 deletions

940
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@heroicons/react": "^2.1.3",
"@mui/material": "^5.11.13",
"@types/node": "18.15.1",
"@types/react": "18.0.28",
@ -24,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.119",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.126",
"next": "13.2.4",
"prettier": "^2.8.7",
"react": "18.2.0",

View File

@ -0,0 +1,40 @@
import { EType } from "le-coffre-resources/dist/Admin/Subscription";
import BaseAdmin from "../BaseAdmin";
export interface IPostStripeParams {
type: EType;
nb_seats: number;
}
export type IPostStripeResponse = {
url: string;
};
export default class Stripe extends BaseAdmin {
private static instance: Stripe;
private readonly baseURl = this.namespaceUrl.concat("/stripe");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Stripe();
} else {
return this.instance;
}
}
public async post(body: IPostStripeParams) {
const url = new URL(this.baseURl);
try {
console.log(body);
return await this.postRequest<IPostStripeResponse>(url, body as any);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -96,6 +96,10 @@
flex: 1;
}
&[fullwidthattr="false"] {
width: fit-content;
}
&[touppercase="false"] {
text-transform: inherit;
}
@ -110,4 +114,4 @@
line-height: 22px;
text-decoration-line: underline;
}
}
}

View File

@ -8,8 +8,13 @@
&.H1-60 {
font-style: normal;
font-weight: 500;
font-size: 50px;
line-height: 61px;
font-size: 56px;
line-height: 67.2px;
@media (max-width: $screen-m) {
font-size: 48px;
line-height: 56.7px;
}
}
&.H1-bis-40 {
@ -115,6 +120,14 @@
letter-spacing: 0.5px;
}
&.Caption_14-semibold {
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 22px;
letter-spacing: 0.5px;
}
&.re-hover {
color: $re-hover;
}

View File

@ -29,6 +29,7 @@ export enum ITypo {
P_ERR_16 = "Paragraphe-16-error",
CAPTION_14 = "Caption_14",
CAPTION_14_SB = "Caption_14-semibold",
}
export enum ITypoColor {
@ -45,7 +46,7 @@ export enum ITypoColor {
export default class Typography extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return (
<div
<span
className={classNames(
classes["root"],
classes[this.props.typo],
@ -54,7 +55,7 @@ export default class Typography extends React.Component<IProps, IState> {
)}
title={this.props.title}>
{this.props.children}
</div>
</span>
);
}
}

View File

@ -0,0 +1,31 @@
.root {
display: flex;
gap: 24px;
padding: 16px;
svg {
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
}
&.info {
border: 1px solid #005176;
background: #c3eae64d;
}
&.warning {
background-color: var(--Warning-100);
}
&.success {
border: 1px solid var(--green-flash);
background: #12bf4d0d;
}
&.error {
border: 1px solid var(--red-soft);
background: #f087711a;
}
}

View File

@ -0,0 +1,37 @@
import classes from "./classes.module.scss";
import classNames from "classnames";
import { InformationCircleIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
export type IProps = {
type: "info" | "warning" | "success" | "error";
children?: React.ReactNode;
className?: string;
};
export default function MessageBox(props: IProps) {
const { className, type, children } = props;
return (
<div className={classNames(className, classes["root"], classes[type])}>
{getIcon(type)}
<div className={classes["content"]}>
<Typography className={classes["text"]} typo={ITypo.CAPTION_14}>
{children}
</Typography>
</div>
</div>
);
function getIcon(type: IProps["type"]) {
switch (type) {
case "info":
return <InformationCircleIcon />;
case "warning":
return <ExclamationTriangleIcon />;
case "success":
return <InformationCircleIcon />;
case "error":
return <InformationCircleIcon />;
}
}
}

View File

@ -0,0 +1,25 @@
.root {
nav {
display: flex;
gap: 32px;
.link {
cursor: pointer;
padding: 16px;
text-decoration: none;
border-bottom: 1px solid transparent;
&:hover {
border-bottom: 1px solid black;
}
&.active {
color: black;
border-bottom: 1px solid black;
}
}
}
.content {
margin-top: 24px;
}
}

View File

@ -0,0 +1,43 @@
import classNames from "classnames";
import classes from "./classes.module.scss";
import Link from "next/link";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import { useRouter } from "next/router";
type ITabItem = {
label: string;
path: string;
activePaths?: string[];
};
type IProps = {
items: ITabItem[];
};
export default function NavTab(props: IProps) {
const router = useRouter();
return (
<div className={classes["root"]}>
<nav>
{props.items.map((item, index) => {
let isMatch = false;
if (item.activePaths) {
isMatch = item.activePaths.some((path) => router.pathname.includes(path));
} else {
isMatch = router.pathname.includes(item.path) ? true : false;
}
return (
<Link
key={item.path.toString()}
href={item.path}
className={classNames(classes["link"], isMatch && classes["active"])}>
<Typography key={index} typo={isMatch ? ITypo.P_SB_18 : ITypo.P_18}>
{item.label}
</Typography>
</Link>
);
})}
</nav>
</div>
);
}

View File

@ -0,0 +1,2 @@
.root {
}

View File

@ -0,0 +1,47 @@
import { useState } from "react";
import classes from "./classes.module.scss";
type IProps = {
defaultValue: number;
onChange: (value: number) => void;
min?: number;
max?: number;
disabled?: boolean;
};
export default function NumberPicker(props: IProps) {
const { defaultValue, onChange, min, max, disabled } = props;
const [value, setValue] = useState(defaultValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = parseInt(e.target.value);
if (min && value < min) {
value = min;
}
if (max && value > max) {
value = max;
}
setValue(value);
onChange(value);
};
const handleMinus = () => {
handleChange({ target: { value: value - 1 } } as any);
};
const handlePlus = () => {
handleChange({ target: { value: value + 1 } } as any);
};
return (
<div className={classes["root"]}>
<button onClick={handleMinus} disabled={min && value <= min ? true : false}>
-
</button>
<input type="number" value={value} onChange={handleChange} disabled={disabled} />
<button onClick={handlePlus} disabled={max && value >= max ? true : false}>
+
</button>
</div>
);
}

View File

@ -4,7 +4,6 @@
margin: var(--root-margin);
max-width: var(--root-max-width);
min-width: 100%;
min-height: calc(100vh - 83px);
&.padding {
padding: var(--root-padding);
@ -17,4 +16,4 @@
padding: 0 16px;
}
}
}
}

View File

@ -0,0 +1,63 @@
@import "@Themes/constants.scss";
.root {
width: 372px;
@media (max-width: $screen-s) {
width: 100%;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
gap: 32px;
box-shadow: 0px 8px 10px 0px #00000012;
padding: 22px 40px;
border-radius: 16px;
@media (max-width: $screen-s) {
box-shadow: none;
padding: 0px;
}
.forfeit-type {
text-align: center;
}
.container-frequency {
display: flex;
justify-content: space-between;
}
.separator {
width: 100%;
height: 1px;
background-color: gray;
}
.container-line {
display: flex;
flex-direction: column;
gap: 24px;
.line {
display: flex;
justify-content: space-between;
.line-sub-container {
display: flex;
flex-direction: column;
}
}
}
.container-tight {
gap: 8px;
}
.payment-button {
@media (max-width: $screen-s) {
display: none;
}
}
}
}

View File

@ -0,0 +1,196 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import { EForfeitType } from "../../SubscriptionFacturation";
import classes from "./classes.module.scss";
import { useEffect, useState } from "react";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Button from "@Front/Components/DesignSystem/Button";
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";
export const forfeitsPrices: Record<EForfeitType, number> = {
[EForfeitType.standard]: 99,
[EForfeitType.unlimited]: 249,
};
export const collaboratorPrice = 6.99;
type IProps = {
forfeitType: EForfeitType;
numberOfCollaborators: number;
defaultFrequency?: EPaymentFrequency;
};
export enum EPaymentFrequency {
monthly,
yearly,
}
export default function SubscribeCheckoutTicket(props: IProps) {
const router = useRouter();
const { forfeitType, numberOfCollaborators } = props;
const [paymentFrequency, setPaymentFrequency] = useState<EPaymentFrequency>(props.defaultFrequency ?? EPaymentFrequency.monthly);
const [multiplier, setMultiplier] = useState<number>(1);
useEffect(() => {
setMultiplier(paymentFrequency === EPaymentFrequency.monthly ? 1 : 12);
}, [paymentFrequency]);
const handleFrequencyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPaymentFrequency(parseInt(e.target.value) as EPaymentFrequency);
};
const formatFloat = (value: number) => {
return value.toFixed(2).replace(".", ",");
};
const handleSubmitPayment = async () => {
console.log("handleSubmitPayment");
const stripeCheckout = {
type: EType.Standard,
nb_seats: numberOfCollaborators,
};
try {
const newStripeCheckout = await Stripe.getInstance().post(stripeCheckout);
router.push(newStripeCheckout.url);
} catch (error) {}
};
return (
<div className={classes["root"]}>
<div className={classes["container"]}>
<div className={classes["forfeit-type"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
{forfeitType === EForfeitType.standard ? "Forfait standard" : "Forfait illimité"}
</Typography>
</div>
<div className={classes["separator"]} />
<div className={classes["container-frequency"]}>
<RadioBox
name="paymentFrequency"
value={EPaymentFrequency.yearly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.yearly}>
<Typography typo={ITypo.P_ERR_18}>Annuel</Typography>
</RadioBox>
<RadioBox
name="paymentFrequency"
value={EPaymentFrequency.monthly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.monthly}>
<Typography typo={ITypo.P_ERR_18}>Mensuel</Typography>
</RadioBox>
</div>
<div className={classes["separator"]} />
<div className={classes["container-line"]}>
<div className={classes["line"]}>
<div className={classes["line-sub-container"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
{forfeitType === EForfeitType.standard ? "Plan individuel" : "Plan illimité"}
</Typography>
{paymentFrequency === EPaymentFrequency.yearly && (
<Typography typo={ITypo.CAPTION_14_SB} color={ITypoColor.BLACK}>
{forfeitType === EForfeitType.standard
? forfeitsPrices[EForfeitType.standard]
: forfeitsPrices[EForfeitType.unlimited]}
&nbsp; x 12
</Typography>
)}
</div>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{forfeitType === EForfeitType.standard
? forfeitsPrices[EForfeitType.standard] * multiplier
: forfeitsPrices[EForfeitType.unlimited] * multiplier}
</Typography>
</div>
{forfeitType === EForfeitType.standard && (
<div className={classes["line"]}>
<div className={classes["line-sub-container"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
{numberOfCollaborators} collaborateurs
</Typography>
<Typography typo={ITypo.CAPTION_14_SB} color={ITypoColor.BLACK}>
{formatFloat(collaboratorPrice)}&nbsp; x {numberOfCollaborators}
</Typography>
</div>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(collaboratorPrice * numberOfCollaborators * multiplier)}&nbsp;
</Typography>
</div>
)}
</div>
{forfeitType === EForfeitType.standard && (
<>
<div className={classes["separator"]} />
<div className={classnames(classes["container-line"], classes["container-tight"])}>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Total HT
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(
(forfeitsPrices[EForfeitType.standard] + collaboratorPrice * numberOfCollaborators) * multiplier,
)}
&nbsp;
</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
TVA 20%
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(
(forfeitsPrices[EForfeitType.standard] + collaboratorPrice * numberOfCollaborators) *
0.2 *
multiplier,
)}
&nbsp;
</Typography>
</div>
</div>
</>
)}
<div className={classes["separator"]} />
<div className={classnames(classes["container-line"], classes["container-tight"])}>
{forfeitType === EForfeitType.unlimited && (
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
TVA 20%
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(forfeitsPrices[EForfeitType.unlimited] * 0.2 * multiplier)}&nbsp;
</Typography>
</div>
)}
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Total TTC
</Typography>
{forfeitType === EForfeitType.standard && (
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(
(forfeitsPrices[EForfeitType.standard] + collaboratorPrice * numberOfCollaborators) * 1.2 * multiplier,
)}
&nbsp;
</Typography>
)}
{forfeitType === EForfeitType.unlimited && (
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(forfeitsPrices[EForfeitType.unlimited] * 1.2 * multiplier)}
&nbsp;
</Typography>
)}
</div>
</div>
<Button onClick={handleSubmitPayment} fullwidth className={classes["payment-button"]}>
Passer au paiement
</Button>
</div>
</div>
);
}

View File

@ -0,0 +1,98 @@
@import "@Themes/constants.scss";
.root {
display: flex;
justify-content: space-between;
gap: 104px;
max-width: 1400px;
margin: auto;
@media (max-width: $screen-m) {
margin-top: 40px;
margin-bottom: 40px;
gap: 72px;
}
@media (max-width: $screen-s) {
margin-bottom: 50px;
}
.left {
display: flex;
gap: 40px;
flex-direction: column;
.infos-container {
display: flex;
flex-direction: column;
gap: 16px;
.line {
svg {
min-width: 24px;
min-height: 24px;
}
display: flex;
gap: 16px;
}
}
}
.right {
width: 372px;
@media (max-width: $screen-m) {
margin-top: 50px;
}
@media (max-width: $screen-s) {
display: none;
}
}
button {
@media (max-width: $screen-s) {
display: none;
}
}
}
.bottom {
display: none;
position: sticky;
bottom: 0px;
flex-direction: column;
justify-content: center;
gap: 32px;
box-shadow: 0px 8px 10px 0px #00000012;
padding: 22px 16px;
border-radius: 16px 16px 0 0;
background: white;
@media (max-width: $screen-s) {
display: flex;
}
box-shadow: 0px 4px 24px 0px #00000026;
.forfeit-type {
text-align: center;
}
.container-frequency {
display: flex;
justify-content: space-between;
}
.separator {
width: 100%;
height: 1px;
background-color: gray;
}
.container-line {
display: flex;
justify-content: space-between;
gap: 24px;
}
.container-tight {
gap: 8px;
}
}

View File

@ -0,0 +1,125 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import NavTab from "@Front/Components/Elements/NavTab";
import SubscribeCheckoutTicket, { EPaymentFrequency, forfeitsPrices } from "../SubscribeCheckoutTicket";
import { EForfeitType } from "../../SubscriptionFacturation";
import { useEffect, useState } from "react";
import Check from "@Front/Components/Elements/Icons/Check";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import useOpenable from "@Front/Hooks/useOpenable";
export default function SubscribeIllimity() {
const { close, isOpen, open } = useOpenable();
const formatFloat = (value: number) => {
return value.toFixed(2).replace(".", ",");
};
const [paymentFrequency, setPaymentFrequency] = useState<EPaymentFrequency>(EPaymentFrequency.monthly);
const [multiplier, setMultiplier] = useState<number>(1);
useEffect(() => {
setMultiplier(paymentFrequency === EPaymentFrequency.monthly ? 1 : 12);
}, [paymentFrequency]);
const handleFrequencyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPaymentFrequency(parseInt(e.target.value) as EPaymentFrequency);
};
return (
<>
<DefaultTemplate title="Nouvelle souscription" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["left"]}>
<NavTab
items={[
{ label: "Forfait standard", path: "/subscription/subscribe/standard" },
{ label: "Forfait illimité", path: "/subscription/subscribe/illimity" },
]}
/>
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
Nombre de collaborateurs illimité
</Typography>
<div className={classes["infos-container"]}>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Accompagnement facilité : profitez d'un onboarding individualisé, nous vous guidons pour une prise en
main optimale de l'application
</Typography>
</div>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Support technique : notre équipe support est disponible pour vous assister en cas dincident
</Typography>
</div>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Mises à jour régulières : bénéficiez de mises à jour fréquentes pour profiter des dernières
fonctionnalités, améliorations de sécurité et performances optimisées
</Typography>
</div>
<div className={classes["line"]}>
<Check color={ITypoColor.BLACK} />
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Sans limite d'utilisateurs
</Typography>
</div>
</div>
</div>
<div className={classes["right"]}>
<SubscribeCheckoutTicket forfeitType={EForfeitType.unlimited} numberOfCollaborators={1} />
</div>
</div>
</DefaultTemplate>
<Confirm isOpen={isOpen} onClose={close} showCancelButton={false} confirmText={"Passer au paiement"} closeBtn onAccept={close}>
<SubscribeCheckoutTicket
forfeitType={EForfeitType.unlimited}
numberOfCollaborators={1}
defaultFrequency={paymentFrequency}
/>
</Confirm>
<div className={classes["bottom"]}>
<div className={classes["container-frequency"]}>
<RadioBox
name="paymentFrequencyInSubscription"
value={EPaymentFrequency.yearly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.yearly}>
<Typography typo={ITypo.P_ERR_18}>Annuel</Typography>
</RadioBox>
<RadioBox
name="paymentFrequencyInSubscription"
value={EPaymentFrequency.monthly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.monthly}>
<Typography typo={ITypo.P_ERR_18}>Mensuel</Typography>
</RadioBox>
</div>
<div className={classes["separator"]} />
<div className={classes["container-line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Total TTC
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(forfeitsPrices[EForfeitType.unlimited] * 1.2 * multiplier)}
&nbsp;
</Typography>
</div>
<div className={classes["voir-recap"]}>
<Button fullwidth variant={EButtonVariant.LINE} onClick={open}>
Voir le récapitulatif plus en détail
</Button>
</div>
<div className={classes["payment-button"]}>
<Button fullwidth>Passer au paiement</Button>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,98 @@
@import "@Themes/constants.scss";
.root {
display: flex;
justify-content: space-between;
gap: 104px;
max-width: 1400px;
margin: auto;
@media (max-width: $screen-m) {
margin-top: 40px;
margin-bottom: 40px;
gap: 72px;
}
@media (max-width: $screen-s) {
margin-bottom: 50px;
}
.left {
display: flex;
gap: 40px;
flex-direction: column;
.infos-container {
display: flex;
flex-direction: column;
gap: 16px;
.line {
svg {
min-width: 24px;
min-height: 24px;
}
display: flex;
gap: 16px;
}
}
}
.right {
width: 372px;
@media (max-width: $screen-m) {
margin-top: 50px;
}
@media (max-width: $screen-s) {
display: none;
}
}
button {
@media (max-width: $screen-s) {
display: none;
}
}
}
.bottom {
display: none;
position: sticky;
bottom: 0px;
flex-direction: column;
justify-content: center;
gap: 32px;
box-shadow: 0px 8px 10px 0px #00000012;
padding: 22px 16px;
border-radius: 16px 16px 0 0;
background: white;
@media (max-width: $screen-s) {
display: flex;
}
box-shadow: 0px 4px 24px 0px #00000026;
.forfeit-type {
text-align: center;
}
.container-frequency {
display: flex;
justify-content: space-between;
}
.separator {
width: 100%;
height: 1px;
background-color: gray;
}
.container-line {
display: flex;
justify-content: space-between;
gap: 24px;
}
.container-tight {
gap: 8px;
}
}

View File

@ -0,0 +1,129 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import NavTab from "@Front/Components/Elements/NavTab";
import NumberPicker from "@Front/Components/Elements/NumberPicker";
import SubscribeCheckoutTicket, { EPaymentFrequency, collaboratorPrice, forfeitsPrices } from "../SubscribeCheckoutTicket";
import { EForfeitType } from "../../SubscriptionFacturation";
import { useEffect, useState } from "react";
import Check from "@Front/Components/Elements/Icons/Check";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import RadioBox from "@Front/Components/DesignSystem/RadioBox";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import useOpenable from "@Front/Hooks/useOpenable";
// import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
// import { EType } from "le-coffre-resources/dist/Admin/Subscription";
export default function SubscribeStandard() {
const [numberOfCollaborators, setNumberOfCollaborators] = useState(1);
const { close, isOpen, open } = useOpenable();
const handleCollaboratorsChange = (value: number) => {
setNumberOfCollaborators(value);
};
const formatFloat = (value: number) => {
return value.toFixed(2).replace(".", ",");
};
const [paymentFrequency, setPaymentFrequency] = useState<EPaymentFrequency>(EPaymentFrequency.monthly);
const [multiplier, setMultiplier] = useState<number>(1);
useEffect(() => {
setMultiplier(paymentFrequency === EPaymentFrequency.monthly ? 1 : 12);
}, [paymentFrequency]);
const handleFrequencyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPaymentFrequency(parseInt(e.target.value) as EPaymentFrequency);
};
return (
<>
<DefaultTemplate title="Nouvelle souscription" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["left"]}>
<NavTab
items={[
{ label: "Forfait standard", path: "/subscription/subscribe/standard" },
{ label: "Forfait illimité", path: "/subscription/subscribe/illimity" },
]}
/>
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
Choisissez le nombre de collaborateurs pour votre abonnement
</Typography>
<NumberPicker defaultValue={1} onChange={handleCollaboratorsChange} min={1} />
<div className={classes["infos-container"]}>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Accompagnement facilité : profitez d'un onboarding individualisé, nous vous guidons pour une prise en
main optimale de l'application
</Typography>
</div>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Support technique : notre équipe support est disponible pour vous assister en cas dincident
</Typography>
</div>
<div className={classes["line"]}>
<Check color={ITypoColor.GREY} />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Mises à jour régulières : bénéficiez de mises à jour fréquentes pour profiter des dernières
fonctionnalités, améliorations de sécurité et performances optimisées
</Typography>
</div>
</div>
</div>
<div className={classes["right"]}>
<SubscribeCheckoutTicket forfeitType={EForfeitType.standard} numberOfCollaborators={numberOfCollaborators} />
</div>
</div>
</DefaultTemplate>
<Confirm isOpen={isOpen} onClose={close} showCancelButton={false} confirmText={"Passer au paiement"} closeBtn onAccept={close}>
<SubscribeCheckoutTicket
forfeitType={EForfeitType.standard}
numberOfCollaborators={numberOfCollaborators}
defaultFrequency={paymentFrequency}
/>
</Confirm>
<div className={classes["bottom"]}>
<div className={classes["container-frequency"]}>
<RadioBox
name="paymentFrequencyInSubscription"
value={EPaymentFrequency.yearly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.yearly}>
<Typography typo={ITypo.P_ERR_18}>Annuel</Typography>
</RadioBox>
<RadioBox
name="paymentFrequencyInSubscription"
value={EPaymentFrequency.monthly.toString()}
onChange={handleFrequencyChange}
defaultChecked={paymentFrequency === EPaymentFrequency.monthly}>
<Typography typo={ITypo.P_ERR_18}>Mensuel</Typography>
</RadioBox>
</div>
<div className={classes["separator"]} />
<div className={classes["container-line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Total TTC
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{formatFloat(
(forfeitsPrices[EForfeitType.standard] + collaboratorPrice * numberOfCollaborators) * 1.2 * multiplier,
)}
&nbsp;
</Typography>
</div>
<div className={classes["voir-recap"]}>
<Button fullwidth variant={EButtonVariant.LINE} onClick={open}>
Voir le récapitulatif plus en détail
</Button>
</div>
<div className={classes["payment-button"]}>
<Button fullwidth>Passer au paiement</Button>
</div>
</div>
</>
);
}

View File

@ -0,0 +1,5 @@
.root {
display: flex;
flex-direction: column;
gap: 32px;
}

View File

@ -0,0 +1,26 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
export default function SubscriptionClientInfos() {
return (
<div className={classes["root"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Informations client
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
john.doe@contact.fr
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Adresse de facturation
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
John Doe <br />
23 rue taitbout,
<br />
75009 Paris
<br />
France
</Typography>
</div>
);
}

View File

@ -0,0 +1,17 @@
.root {
display: flex;
gap: 104px;
justify-content: center;
.left {
display: flex;
flex-direction: column;
gap: 32px;
width: 548px;
}
.separator {
width: 100%;
height: 2px;
background: var(--grey-medium);
}
}

View File

@ -0,0 +1,41 @@
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import classes from "./classes.module.scss";
import SubscriptionTicket from "../SubscriptionTicket";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import MessageBox from "@Front/Components/Elements/MessageBox";
import SubscriptionClientInfos from "../SubscriptionClientInfos";
import Button from "@Front/Components/DesignSystem/Button";
export default function SubscriptionError() {
return (
<DefaultTemplate title="Erreur à la souscription" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["left"]}>
<div className={classes["title"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
Paiement échoué
</Typography>
</div>
<div className={classes["alert"]}>
<MessageBox type={"error"}>
Votre transaction n'a pas pu être complétée.
<br />
<br />
Malheureusement, nous n'avons pas pu traiter votre paiement et votre abonnement n'a pas é activé. Veuillez
vérifier vos informations de paiement et essayer à nouveau.
</MessageBox>
</div>
<div className={classes["separator"]} />
<div className={classes["client-infos"]}>
<SubscriptionClientInfos />
</div>
<div className={classes["separator"]} />
<Button>Réessayer le paiement</Button>
</div>
<div className={classes["right"]}>
<SubscriptionTicket />
</div>
</div>
</DefaultTemplate>
);
}

View File

@ -0,0 +1,83 @@
@import "@Themes/constants.scss";
.root {
display: flex;
justify-content: baseline;
align-items: center;
flex-direction: column;
gap: 64px;
max-width: 1000px;
margin: auto;
.top-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.forfeits-container {
display: flex;
gap: 32px;
width: 100%;
@media (max-width: $screen-s) {
flex-direction: column;
}
.forfeit-block {
flex: 1;
padding: 32px;
border: 1px solid black;
display: flex;
flex-direction: column;
gap: 32px;
&[data-inactive="true"] {
border: 1px solid #e7e7e7;
}
.forfeit-header {
display: flex;
justify-content: space-between;
.left {
display: flex;
flex-direction: column;
}
.active-plan {
@media (max-width: $screen-s) {
display: none;
}
}
}
.separator {
border-bottom: 1px solid black;
}
.price-container {
display: flex;
flex-direction: column;
gap: 8px;
}
}
}
.actions-container {
display: flex;
align-items: center;
gap: 48px;
justify-content: flex-end;
justify-self: flex-end;
align-self: flex-end;
margin-bottom: 64px;
@media (max-width: $screen-s) {
flex-direction: column-reverse;
align-self: center;
justify-self: center;
}
}
}

View File

@ -0,0 +1,185 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { useCallback, useState } from "react";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import useOpenable from "@Front/Hooks/useOpenable";
import MessageBox from "@Front/Components/Elements/MessageBox";
export enum EForfeitType {
"standard",
"unlimited",
}
export default function SubscriptionFacturation() {
const [forfeitType, _setForfeitType] = useState(EForfeitType.standard);
const { close: closeCancelSubscription, isOpen: isCancelSubscriptionOpen, open: openCancelSubscription } = useOpenable();
const { close: closeConfirmation, isOpen: isConfirmationOpen, open: openConfirmation } = useOpenable();
const cancelSubscription = useCallback(() => {
closeCancelSubscription();
openConfirmation();
return;
}, [closeCancelSubscription, openConfirmation]);
return (
<DefaultTemplate title="Nouvelle souscription" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["top-container"]}>
<div className={classes["top-container-title"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
Facturation
</Typography>
</div>
<div className={classes["sub-title"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Nos forfaits sont adaptés à la taille de votre office
</Typography>
</div>
</div>
<div className={classes["forfeits-container"]}>
<div className={classes["forfeit-block"]} data-inactive={forfeitType !== EForfeitType.standard}>
<div className={classes["forfeit-header"]}>
<div className={classes["left"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Forfait standard
</Typography>
<Typography typo={ITypo.P_16} color={ITypoColor.PINK_FLASH}>
Plan par utilisateur
</Typography>
</div>
{forfeitType === EForfeitType.standard && (
<div className={classes["active-plan"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.GREEN_FLASH}>
Plan actif
</Typography>
</div>
)}
</div>
<div className={classes["separator"]} />
<div className={classes["price-container"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
99
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
&nbsp;HT&nbsp;
</Typography>
/ mois
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
+ 6,99 / collaborateur / mois
</Typography>
</div>
<div className={classes["button-container"]}>
{forfeitType !== EForfeitType.standard && (
<Button fullwidth variant={EButtonVariant.GHOST}>
Rétrograder mon abonnement
</Button>
)}
{forfeitType === EForfeitType.standard && (
<Button fullwidth variant={EButtonVariant.PRIMARY}>
Gérer mes collaborateurs
</Button>
)}
</div>
</div>
<div className={classes["forfeit-block"]} data-inactive={forfeitType === EForfeitType.standard}>
<div className={classes["forfeit-header"]}>
<div className={classes["left"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Forfait illimité
</Typography>
<Typography typo={ITypo.P_16} color={ITypoColor.PINK_FLASH}>
Plan par office
</Typography>
</div>
{forfeitType !== EForfeitType.standard && (
<div className={classes["active-plan"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.GREEN_FLASH}>
Plan actif
</Typography>
</div>
)}
</div>
<div className={classes["separator"]} />
<div className={classes["price-container"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
249
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
&nbsp;HT&nbsp;
</Typography>
/ mois
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Sans limite de collaborateurs
</Typography>
</div>
<div className={classes["button-container"]}>
{forfeitType !== EForfeitType.standard && (
<Button fullwidth variant={EButtonVariant.PRIMARY} disabled>
Abonnement Max Activé
</Button>
)}
{forfeitType === EForfeitType.standard && (
<Button fullwidth variant={EButtonVariant.GHOST}>
Améliorer mon abonnement
</Button>
)}
</div>
</div>
</div>
<div className={classes["actions-container"]}>
<Button variant={EButtonVariant.LINE} onClick={openCancelSubscription}>
<Typography typo={ITypo.P_18} color={ITypoColor.RED_FLASH}>
Arrêter l'abonnement
</Typography>
</Button>
<Button>Gérer la facturation</Button>
</div>
</div>
<Confirm
isOpen={isCancelSubscriptionOpen}
onClose={closeCancelSubscription}
onAccept={cancelSubscription}
closeBtn
header={"Êtes-vous sûr de vouloir arrêter votre abonnement ?"}
confirmText={"Confirmer"}
cancelText={"Annuler"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Avant de confirmer, veuillez prendre note des conséquences <br />
suivantes :
<br />
<ul>
<li>
Arrêt des fonctionnalités : Vous n'aurez plus accès aux outils de traitement et de mise à jour en temps
réel.
</li>
<li>
Accès limité : Vous pourrez uniquement télécharger vos documents existants, sans possibilité de les éditer
ou de créer de nouveaux fichiers.
</li>
</ul>
Votre abonnement se terminera le XX/XX/XXXX. Assurez-vous de sauvegarder tout ce dont vous avez besoin avant cette
date.
</Typography>
</div>
</Confirm>
<Confirm
isOpen={isConfirmationOpen}
onClose={closeConfirmation}
onAccept={closeConfirmation}
closeBtn
header={"Abonnement résilié avec succès"}
confirmText={"Retour à la plateforme"}
showCancelButton={false}>
<div className={classes["modal-content"]}>
<MessageBox type="info">
Votre abonnement se terminera le XX/XX/XXXX. Assurez-vous de sauvegarder tout ce dont vous avez besoin avant cette
date.
</MessageBox>
</div>
</Confirm>
</DefaultTemplate>
);
}

View File

@ -0,0 +1,63 @@
@import "@Themes/constants.scss";
.root {
display: flex;
justify-content: baseline;
align-items: center;
flex-direction: column;
gap: 64px;
max-width: 1000px;
margin: auto;
.top-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.forfeits-container {
display: flex;
gap: 32px;
width: 100%;
@media (max-width: $screen-s) {
flex-direction: column;
}
.forfeit-block {
flex: 1;
padding: 32px;
border: 1px solid black;
display: flex;
flex-direction: column;
gap: 32px;
.forfeit-header {
display: flex;
flex-direction: column;
}
.separator {
border-bottom: 1px solid black;
}
.price-container {
display: flex;
flex-direction: column;
gap: 8px;
}
}
}
.infos-container {
display: flex;
flex-direction: column;
gap: 16px;
.line {
display: flex;
gap: 16px;
}
}
}

View File

@ -0,0 +1,108 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import CheckIcon from "@Assets/Icons/check.svg";
import Image from "next/image";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Button from "@Front/Components/DesignSystem/Button";
import Link from "next/link";
export default function SubscriptionNew() {
return (
<DefaultTemplate title="Nouvelle souscription" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["top-container"]}>
<div className={classes["top-container-title"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
Tarifs
</Typography>
</div>
<div className={classes["sub-title"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Nos forfaits sont adaptés à la taille de votre office
</Typography>
</div>
</div>
<div className={classes["forfeits-container"]}>
<div className={classes["forfeit-block"]}>
<div className={classes["forfeit-header"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Forfait standard
</Typography>
<Typography typo={ITypo.P_16} color={ITypoColor.PINK_FLASH}>
Plan par utilisateur
</Typography>
</div>
<div className={classes["separator"]} />
<div className={classes["price-container"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
99
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
&nbsp;HT&nbsp;
</Typography>
/ mois
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
+ 6,99 / collaborateur / mois
</Typography>
</div>
<div className={classes["button-container"]}>
<Link href={"/subscription/subscribe/standard"}>
<Button fullwidth>S'abonner</Button>
</Link>
</div>
</div>
<div className={classes["forfeit-block"]}>
<div className={classes["forfeit-header"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Forfait illimité
</Typography>
<Typography typo={ITypo.P_16} color={ITypoColor.PINK_FLASH}>
Plan par office
</Typography>
</div>
<div className={classes["separator"]} />
<div className={classes["price-container"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
249
<Typography typo={ITypo.H2} color={ITypoColor.BLACK}>
&nbsp;HT&nbsp;
</Typography>
/ mois
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Sans limite de collaborateurs
</Typography>
</div>
<div className={classes["button-container"]}>
<Link href={"/subscription/subscribe/illimity"}>
<Button fullwidth>S'abonner</Button>
</Link>
</div>
</div>
</div>
<div className={classes["infos-container"]}>
<div className={classes["line"]}>
<Image src={CheckIcon} alt="Check icon" />
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Accompagnement facilité : profitez d'un onboarding individualisé, nous vous guidons pour une prise en main
optimale de l'application
</Typography>
</div>
<div className={classes["line"]}>
<Image src={CheckIcon} alt="Check icon" />
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Support technique : notre équipe support est disponible pour vous assister en cas dincident
</Typography>
</div>
<div className={classes["line"]}>
<Image src={CheckIcon} alt="Check icon" />
<Typography typo={ITypo.P_16} color={ITypoColor.BLACK}>
Mises à jour régulières : bénéficiez de mises à jour fréquentes pour profiter des dernières fonctionnalités,
améliorations de sécurité et performances optimisées
</Typography>
</div>
</div>
</div>
</DefaultTemplate>
);
}

View File

@ -0,0 +1,17 @@
.root {
display: flex;
gap: 104px;
justify-content: center;
.left {
display: flex;
flex-direction: column;
gap: 32px;
width: 548px;
}
.separator {
width: 100%;
height: 2px;
background: var(--grey-medium);
}
}

View File

@ -0,0 +1,40 @@
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import classes from "./classes.module.scss";
import SubscriptionTicket from "../SubscriptionTicket";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import MessageBox from "@Front/Components/Elements/MessageBox";
import SubscriptionClientInfos from "../SubscriptionClientInfos";
import Button from "@Front/Components/DesignSystem/Button";
export default function SubscriptionSuccess() {
return (
<DefaultTemplate title="Abonnement réussi" hasHeaderLinks={false}>
<div className={classes["root"]}>
<div className={classes["left"]}>
<div className={classes["title"]}>
<Typography typo={ITypo.H1} color={ITypoColor.BLACK}>
Abonnement réussi !
</Typography>
</div>
<div className={classes["alert"]}>
<MessageBox type={"success"}>
Votre transaction a é effectuée avec succès !
<br />
<br />
Votre abonnement a é pris en compte et est désormais actif.
</MessageBox>
</div>
<div className={classes["separator"]} />
<div className={classes["client-infos"]}>
<SubscriptionClientInfos />
</div>
<div className={classes["separator"]} />
<Button>Inviter vos collaborateurs</Button>
</div>
<div className={classes["right"]}>
<SubscriptionTicket />
</div>
</div>
</DefaultTemplate>
);
}

View File

@ -0,0 +1,31 @@
.root {
width: 372px;
display: flex;
flex-direction: column;
gap: 40px;
box-shadow: 0px 8px 10px 0px #00000012;
padding: 24px;
border-radius: 16px;
.top-category {
display: flex;
justify-content: center;
align-items: center;
}
.category {
display: flex;
flex-direction: column;
gap: 8px;
.line {
display: flex;
justify-content: space-between;
}
}
.separator {
width: 100%;
height: 2px;
background: var(--grey-medium);
}
}

View File

@ -0,0 +1,84 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
type IProps = {};
export default function SubscriptionTicket(props: IProps) {
return (
<div className={classes["root"]}>
<div className={classes["top-category"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Récapitulatif
</Typography>
</div>
<div className={classes["separator"]} />
<div className={classes["top-category"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Forfait standard
</Typography>
</div>
<div className={classes["separator"]} />
<div className={classes["category"]}>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Plan individuel
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
99&nbsp;
</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
2 collaborateurs
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
13,98&nbsp;
</Typography>
</div>
<div className={classes["sub-line"]}>
<Typography typo={ITypo.CAPTION_14_SB} color={ITypoColor.BLACK}>
6,99&nbsp; x 2
</Typography>
</div>
</div>
<div className={classes["separator"]} />
<div className={classes["category"]}>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Sous-total
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
112,98&nbsp;
</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
FR TVA
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
14&nbsp;
</Typography>
</div>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Taxes
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
14&nbsp;
</Typography>
</div>
</div>
<div className={classes["separator"]} />
<div className={classes["category"]}>
<div className={classes["line"]}>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
Total
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
112,98&nbsp;
</Typography>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,26 @@
import { useCallback, useState } from "react";
function useOpenable() {
const [isOpen, setIsOpen] = useState(false);
const open = useCallback(() => {
setIsOpen(true);
}, []);
const close = useCallback(() => {
setIsOpen(false);
}, []);
const toggle = useCallback(() => {
setIsOpen((prev) => !prev);
}, []);
return {
isOpen,
open,
close,
toggle,
};
}
export default useOpenable;

View File

@ -4,6 +4,8 @@ body.body {
// --black: #000000;
--green-flash: #{green-flash};
--black: #{$black};
// --blue-flash: #005176;
// --turquoise-flash: #3fa79e;
// --purple-flash: #320756;

View File

@ -31,5 +31,6 @@
--grey-medium: #{$grey-medium};
--grey-soft: #{$grey-soft};
--black: #{$black};
--white: #{$white};
}

View File

@ -0,0 +1,5 @@
import SubscriptionError from "@Front/Components/Layouts/Subscription/SubscriptionError";
export default function Route() {
return <SubscriptionError />;
}

View File

@ -0,0 +1,5 @@
import SubscriptionFacturation from "@Front/Components/Layouts/Subscription/SubscriptionFacturation";
export default function Route() {
return <SubscriptionFacturation />;
}

View File

@ -0,0 +1,5 @@
import SubscriptionNew from "@Front/Components/Layouts/Subscription/SubscriptionNew";
export default function Route() {
return <SubscriptionNew />;
}

View File

@ -0,0 +1,5 @@
import SubscribeIllimity from "@Front/Components/Layouts/Subscription/Subscribe/SubscribeIllimity";
export default function Route() {
return <SubscribeIllimity />;
}

View File

@ -0,0 +1,5 @@
import SubscribeStandard from "@Front/Components/Layouts/Subscription/Subscribe/SubscribeStandard";
export default function Route() {
return <SubscribeStandard />;
}

View File

@ -0,0 +1,5 @@
import SubscriptionSuccess from "@Front/Components/Layouts/Subscription/SubscriptionSuccess";
export default function Route() {
return <SubscriptionSuccess />;
}