Merge branch 'dev' into staging

This commit is contained in:
Maxime Lalo 2024-04-09 16:03:52 +02:00
commit 9d46400dfb
12 changed files with 431 additions and 5420 deletions

5314
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 5005",
"build": "next build",
"start": "next start",
"lint": "next lint",

View File

@ -14,10 +14,14 @@
.underline {
width: 100%;
height: 3px;
background-color: $black;
background-color: $white;
position: absolute;
bottom: 0;
left: 0;
&[data-active="true"] {
background-color: $black;
}
}
&.desactivated {

View File

@ -1,54 +1,55 @@
import classNames from "classnames";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import React, { useEffect } from "react";
import Typography, { ITypo } from "../../Typography";
import classes from "./classes.module.scss";
import useHoverable from "@Front/Hooks/useHoverable";
type IProps = {
export type IHeaderLinkProps = {
text: string | JSX.Element;
path: string;
isActive?: boolean;
routesActive?: string[];
};
type IPropsClass = IProps;
export default function HeaderLink(props: IHeaderLinkProps) {
const router = useRouter();
const { pathname } = router;
const [isActive, setIsActive] = React.useState(props.path === pathname);
const { handleMouseLeave, handleMouseEnter, isHovered } = useHoverable();
type IStateClass = {};
useEffect(() => {
setIsActive(false);
if (props.path === pathname) setIsActive(true);
if (props.routesActive) {
for (const routeActive of props.routesActive) {
if (isActive) break;
if (pathname.includes(routeActive)) setIsActive(true);
}
}
}, [isActive, pathname, props.path, props.routesActive]);
class HeaderLinkClass extends React.Component<IPropsClass, IStateClass> {
public override render(): JSX.Element {
if (this.props.path !== "" && this.props.path !== undefined) {
if (props.path !== "" && props.path !== undefined) {
return (
<Link href={this.props.path} className={classNames(classes["root"], this.props.isActive && classes["active"])}>
<Link
href={props.path}
className={classNames(classes["root"], isActive && classes["active"])}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}>
<div className={classes["content"]}>
<Typography typo={this.props.isActive ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{this.props.text}</Typography>
<Typography typo={isActive || isHovered ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{props.text}</Typography>
</div>
{this.props.isActive && <div className={classes["underline"]} />}
<div className={classes["underline"]} data-active={(isActive || isHovered).toString()} />
</Link>
);
} else {
return (
<div className={classNames(classes["root"], classes["desactivated"])}>
<div className={classes["content"]}>
<Typography typo={ITypo.NAV_HEADER_18}>{this.props.text}</Typography>
<Typography typo={ITypo.NAV_HEADER_18}>{props.text}</Typography>
</div>
</div>
);
}
}
}
export default function HeaderLink(props: IProps) {
const router = useRouter();
const { pathname } = router;
let isActive = props.path === pathname;
if(props.routesActive){
for (const routeActive of props.routesActive) {
if (isActive) break;
isActive = pathname.includes(routeActive);
}
}
return <HeaderLinkClass {...props} isActive={isActive} />;
}

View File

@ -0,0 +1,35 @@
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect } from "react";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import useHoverable from "@Front/Hooks/useHoverable";
export type IHeaderLinkProps = {
text: string | JSX.Element;
path: string;
routesActive?: string[];
};
export default function HeaderSubmenuLink(props: IHeaderLinkProps) {
const router = useRouter();
const { pathname } = router;
const [isActive, setIsActive] = React.useState(props.path === pathname);
const { handleMouseLeave, handleMouseEnter, isHovered } = useHoverable();
useEffect(() => {
if (props.path === pathname) setIsActive(true);
if (props.routesActive) {
for (const routeActive of props.routesActive) {
if (isActive) break;
if (pathname.includes(routeActive)) setIsActive(true);
}
}
}, [isActive, pathname, props.path, props.routesActive]);
return (
<Link href={props.path} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<Typography typo={isActive || isHovered ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{props.text}</Typography>
</Link>
);
}

View File

@ -0,0 +1,44 @@
@import "@Themes/constants.scss";
.root {
display: flex;
position: relative;
width: fit-content;
margin: auto;
height: 83px;
padding: 10px 16px;
.content {
margin: auto;
}
.underline {
width: 100%;
height: 3px;
background-color: $white;
position: absolute;
bottom: 0;
left: 0;
&[data-active="true"] {
background-color: $black;
}
}
&.desactivated {
cursor: not-allowed;
}
.sub-menu {
box-shadow: 0px 8px 10px 0px #00000012;
padding: 24px;
text-align: center;
gap: 24px;
left: 0;
transform: translateX(-25%);
width: 300px;
top: 112px;
display: flex;
flex-direction: column;
background: white;
position: absolute;
}
}

View File

@ -0,0 +1,48 @@
import classNames from "classnames";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { IHeaderLinkProps } from "../HeaderLink";
import Typography, { ITypo } from "../../Typography";
import classes from "./classes.module.scss";
import useHoverable from "@Front/Hooks/useHoverable";
import HeaderSubmenuLink from "./HeaderSubmenuLink";
import { IAppRule } from "@Front/Api/Entities/rule";
type IProps = {
text: string | JSX.Element;
links: (IHeaderLinkProps & {
rules?: IAppRule[];
})[];
};
export default function HeaderSubmenu(props: IProps) {
const router = useRouter();
const { pathname } = router;
const [isActive, setIsActive] = useState(false);
const { handleMouseLeave, handleMouseEnter, isHovered } = useHoverable(100);
useEffect(() => {
setIsActive(false);
if (props.links.some((link) => link.path === pathname)) setIsActive(true);
if (props.links.some((link) => link.routesActive?.some((routeActive) => pathname.includes(routeActive)))) setIsActive(true);
}, [isActive, pathname, props.links]);
return (
<div className={classes["container"]} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<div className={classNames(classes["root"], (isActive || isHovered) && classes["active"])}>
<div className={classes["content"]}>
<Typography typo={isActive || isHovered ? ITypo.P_SB_18 : ITypo.NAV_HEADER_18}>{props.text}</Typography>
</div>
<div className={classes["underline"]} data-active={(isActive || isHovered).toString()} />
{isHovered && (
<div className={classes["sub-menu"]}>
{props.links.map((link) => (
<HeaderSubmenuLink key={link.path} {...link} />
))}
</div>
)}
</div>
</div>
);
}

View File

@ -9,6 +9,7 @@ import { usePathname } from "next/navigation";
import Notifications from "@Front/Api/LeCoffreApi/Notary/Notifications/Notifications";
import Toasts from "@Front/Stores/Toasts";
import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors";
import HeaderSubmenu from "../HeaderSubmenu";
export default function Navigation() {
const pathname = usePathname();
@ -83,25 +84,125 @@ export default function Navigation() {
name: AppRuleNames.officeRoles,
},
]}>
<HeaderLink
text={"Collaborateurs"}
path={Module.getInstance().get().modules.pages.Collaborators.props.path}
routesActive={[Module.getInstance().get().modules.pages.Collaborators.props.path]}
/>
</Rules>
<HeaderLink
text={"Abonnement"}
path={Module.getInstance().get().modules.pages.Subscription.pages.Manage.props.path}
routesActive={[
Module.getInstance().get().modules.pages.Subscription.pages.Manage.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Invite.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Success.props.path,
<HeaderSubmenu
text={"Espace office"}
links={[
{
text: "Collaborateurs",
path: Module.getInstance().get().modules.pages.Collaborators.props.path,
routesActive: [
Module.getInstance().get().modules.pages.Collaborators.pages.CollaboratorInformations.props.path,
Module.getInstance().get().modules.pages.Collaborators.props.path,
],
rules: [
{
action: AppRuleActions.read,
name: AppRuleNames.users,
},
],
},
{
text: "Gestion des rôles",
path: Module.getInstance().get().modules.pages.Roles.props.path,
routesActive: [
Module.getInstance().get().modules.pages.Roles.pages.Create.props.path,
Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path,
Module.getInstance().get().modules.pages.Roles.props.path,
],
rules: [
{
action: AppRuleActions.update,
name: AppRuleNames.officeRoles,
},
],
},
{
text: "Paramétrage types d'actes",
path: Module.getInstance().get().modules.pages.DeedTypes.props.path,
routesActive: [
Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DeedTypes.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path,
],
rules: [
{
action: AppRuleActions.update,
name: AppRuleNames.deedTypes,
},
],
},
{
text: "RIB Office",
path: Module.getInstance().get().modules.pages.OfficesRib.props.path,
rules: [
{
action: AppRuleActions.update,
name: AppRuleNames.rib,
},
],
},
{
text: "Abonnement",
path: Module.getInstance().get().modules.pages.Subscription.pages.Manage.props.path,
routesActive: [
Module.getInstance().get().modules.pages.Subscription.pages.Error.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Success.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Invite.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Manage.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.ManageCollaborators.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.New.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Subscribe.pages.Standard.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Subscribe.pages.Illimity.props.path,
Module.getInstance().get().modules.pages.Subscription.pages.Subscribe.props.path,
],
},
]}
/>
</Rules>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.officeRoles,
},
]}>
<HeaderSubmenu
text={"Espace super admin"}
links={[
{
text: "Gestion des utilisateurs",
path: Module.getInstance().get().modules.pages.Users.props.path,
routesActive: [
Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path,
Module.getInstance().get().modules.pages.Users.props.path,
],
rules: [
{
action: AppRuleActions.update,
name: AppRuleNames.offices,
},
],
},
{
text: "Gestion des offices",
path: Module.getInstance().get().modules.pages.Offices.props.path,
routesActive: [
Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path,
Module.getInstance().get().modules.pages.Offices.props.path,
],
rules: [
{
action: AppRuleActions.update,
name: AppRuleNames.offices,
},
],
},
]}
/>
</Rules>
</div>
);
}

View File

@ -4,8 +4,6 @@ import React from "react";
import NavigationLink from "../../NavigationLink";
import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
type IProps = {
isOpen: boolean;
@ -22,94 +20,6 @@ export default class ProfileModal extends React.Component<IProps, IState> {
<div className={classes["background"]} onClick={this.props.closeModal} />
<div className={classes["root"]}>
<NavigationLink path={Module.getInstance().get().modules.pages.MyAccount.props.path} text="Mon compte" />
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.officeRoles,
},
]}>
<NavigationLink
path={Module.getInstance().get().modules.pages.Roles.props.path}
text="Gestion des rôles"
routesActive={[
Module.getInstance().get().modules.pages.Roles.props.path,
Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path,
]}
/>
</Rules>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.deedTypes,
},
]}>
<NavigationLink
path={Module.getInstance().get().modules.pages.DeedTypes.props.path}
text="Paramétrage des listes de pièces"
routesActive={[
Module.getInstance().get().modules.pages.DeedTypes.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.DeedTypesInformations.props.path,
Module.getInstance().get().modules.pages.DeedTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Edit.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.Create.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path,
Module.getInstance().get().modules.pages.DocumentTypes.props.path,
]}
/>
</Rules>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.offices,
},
]}>
<NavigationLink
path={Module.getInstance().get().modules.pages.Users.props.path}
text="Gestion des utilisateurs"
routesActive={[
Module.getInstance().get().modules.pages.Users.props.path,
Module.getInstance().get().modules.pages.Users.pages.UsersInformations.props.path,
]}
/>
</Rules>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.offices,
},
]}>
<NavigationLink
path={Module.getInstance().get().modules.pages.Offices.props.path}
text="Gestion des offices"
routesActive={[
Module.getInstance().get().modules.pages.Offices.props.path,
Module.getInstance().get().modules.pages.Offices.pages.OfficesInformations.props.path,
]}
/>
</Rules>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.update,
name: AppRuleNames.rib,
},
]}>
<NavigationLink
path={Module.getInstance().get().modules.pages.OfficesRib.props.path}
text="Gestion du RIB"
routesActive={[Module.getInstance().get().modules.pages.OfficesRib.props.path]}
/>
</Rules>
<NavigationLink target="_blank" path="/CGU_LeCoffre_io.pdf" text="CGU" />
<div className={classes["separator"]} />
<LogOutButton />

View File

@ -22,14 +22,34 @@ export default function SubscriptionFacturation() {
const router = useRouter();
const [subscription, setSubscription] = useState<Subscription | null>(null);
const { close: closeCancelSubscription, isOpen: isCancelSubscriptionOpen, open: openCancelSubscription } = useOpenable();
const { close: closeConfirmation, isOpen: isConfirmationOpen, open: openConfirmation } = useOpenable();
const { close: closeCancelSubscription, isOpen: isCancelSubscriptionOpen } = useOpenable();
const { close: closeConfirmation, isOpen: isConfirmationOpen } = useOpenable();
const cancelSubscription = useCallback(() => {
closeCancelSubscription();
openConfirmation();
return;
}, [closeCancelSubscription, openConfirmation]);
// const cancelSubscription = useCallback(() => {
// closeCancelSubscription();
// openConfirmation();
// return;
// }, [closeCancelSubscription, openConfirmation]);
const manageSubscription = 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 + "/update");
} catch (error) {}
};
const cancelSubscription = 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");
} catch (error) {}
};
const manageBilling = async () => {
try {
@ -106,23 +126,23 @@ export default function SubscriptionFacturation() {
</div>
<div className={classes["button-container"]}>
{subscription.type === "UNLIMITED" && (
<Link
href={Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Standard.props.path}>
<Button fullwidth variant={EButtonVariant.GHOST}>
// <Link
// href={Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Standard.props.path}>
<Button onClick={manageSubscription} fullwidth variant={EButtonVariant.GHOST}>
Rétrograder mon abonnement
</Button>
</Link>
// </Link>
)}
{subscription.type === "STANDARD" && (
<>
<Link
{/* <Link
href={
Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Standard.props.path
}>
<Button fullwidth variant={EButtonVariant.PRIMARY}>
}> */}
<Button onClick={manageBilling} fullwidth variant={EButtonVariant.PRIMARY}>
Gérer mon abonnement
</Button>
</Link>
{/* </Link> */}
<Link
href={
Module.getInstance().get().modules.pages.Subscription.pages.ManageCollaborators.props.path
@ -175,18 +195,18 @@ export default function SubscriptionFacturation() {
</Button>
)}
{subscription.type === "STANDARD" && (
<Link
href={Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Illimity.props.path}>
<Button fullwidth variant={EButtonVariant.GHOST}>
// <Link
// href={Module.getInstance().get().modules.pages.Subscription.pages.Manage.pages.Illimity.props.path}>
<Button onClick={manageSubscription} fullwidth variant={EButtonVariant.GHOST}>
Améliorer mon abonnement
</Button>
</Link>
// </Link>
)}
</div>
</div>
</div>
<div className={classes["actions-container"]}>
<Button variant={EButtonVariant.LINE} onClick={openCancelSubscription}>
<Button variant={EButtonVariant.LINE} onClick={cancelSubscription}>
<Typography typo={ITypo.P_18} color={ITypoColor.RED_FLASH}>
Arrêter l'abonnement
</Typography>

View File

@ -0,0 +1,26 @@
import { useState } from "react";
export default function useHoverable(delay: number = 0) {
const [isHovered, setIsHovered] = useState(false);
const [stateTimeout, setStateTimeout] = useState<NodeJS.Timeout | null>(null);
const handleMouseEnter = () => {
if (stateTimeout) clearTimeout(stateTimeout);
setIsHovered(true);
};
const handleMouseLeave = () => {
setStateTimeout(
setTimeout(() => {
setIsHovered(false);
}, delay),
);
};
return {
isHovered,
handleMouseEnter,
handleMouseLeave,
};
}