Merge branch 'dev' into staging
This commit is contained in:
commit
86bea91396
@ -1,106 +0,0 @@
|
||||
.container {
|
||||
.root {
|
||||
padding: 24px;
|
||||
background-color: var(--color-generic-white);
|
||||
border: 1px dashed #e7e7e7;
|
||||
|
||||
height: fit-content;
|
||||
|
||||
&[data-drag-over="true"] {
|
||||
border: 1px dashed var(--color-neutral-500);
|
||||
}
|
||||
|
||||
&.validated {
|
||||
border: 1px dashed var(--color-success-600);
|
||||
}
|
||||
|
||||
.top-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.left {
|
||||
margin-right: 28px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
background-color: #939393;
|
||||
width: 1px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.right {
|
||||
margin-left: 18px;
|
||||
|
||||
.validated {
|
||||
color: var(--color-success-600);
|
||||
}
|
||||
|
||||
.refused-button {
|
||||
font-size: 14px;
|
||||
color: var(--color-error-800);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.documents-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
|
||||
.file-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.left-part {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.loader {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.cross {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
margin-top: 16px;
|
||||
|
||||
.add-button {
|
||||
.add-document {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--color-error-600);
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
@ -1,452 +0,0 @@
|
||||
import DepositDocumentIcon from "@Assets/Icons/deposit-document.svg";
|
||||
import PlusIcon from "@Assets/Icons/plus.svg";
|
||||
import CrossIcon from "@Assets/Icons/cross.svg";
|
||||
import DocumentCheckIcon from "@Assets/Icons/document-check.svg";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "../Button";
|
||||
import Tooltip from "../ToolTip";
|
||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||
import classes from "./classes.module.scss";
|
||||
import { Document, DocumentHistory, File as FileCustomer } from "le-coffre-resources/dist/Customer";
|
||||
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
||||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||
import classNames from "classnames";
|
||||
import Confirm from "../OldModal/Confirm";
|
||||
import Alert from "../OldModal/Alert";
|
||||
import GreenCheckIcon from "@Assets/Icons/green-check.svg";
|
||||
import Loader from "../Loader";
|
||||
import TextAreaField from "../Form/TextareaField";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
type IProps = {
|
||||
defaultFiles?: FileCustomer[];
|
||||
onChange?: (files: File[]) => void;
|
||||
document: Document;
|
||||
};
|
||||
|
||||
type IFile = {
|
||||
index: number;
|
||||
file: File;
|
||||
uid: string;
|
||||
archived: Date | null;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
files: IFile[];
|
||||
isDragOver: boolean;
|
||||
currentFiles?: FileCustomer[];
|
||||
refusedReason?: string;
|
||||
isShowRefusedReasonModalVisible: boolean;
|
||||
showFailedUploaded: string | null;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
type fileAccepted = {
|
||||
extension: string;
|
||||
size: number;
|
||||
};
|
||||
|
||||
const filesAccepted: { [key: string]: fileAccepted } = {
|
||||
"application/pdf": {
|
||||
extension: "pdf",
|
||||
size: 41943040,
|
||||
},
|
||||
"image/jpeg": {
|
||||
extension: "jpeg",
|
||||
size: 41943040,
|
||||
},
|
||||
"image/png": {
|
||||
extension: "png",
|
||||
size: 41943040,
|
||||
},
|
||||
"image/jpg": {
|
||||
extension: "jpg",
|
||||
size: 41943040,
|
||||
},
|
||||
};
|
||||
|
||||
export default class DepositDocument extends React.Component<IProps, IState> {
|
||||
private inputRef = React.createRef<HTMLInputElement>();
|
||||
private index = 0;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
files: [],
|
||||
isDragOver: false,
|
||||
currentFiles: this.props.defaultFiles,
|
||||
refusedReason: "",
|
||||
isShowRefusedReasonModalVisible: false,
|
||||
showFailedUploaded: null,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
this.addDocument = this.addDocument.bind(this);
|
||||
this.onFileChange = this.onFileChange.bind(this);
|
||||
this.addFile = this.addFile.bind(this);
|
||||
this.removeFile = this.removeFile.bind(this);
|
||||
this.onDragOver = this.onDragOver.bind(this);
|
||||
this.onDragDrop = this.onDragDrop.bind(this);
|
||||
this.onDragLeave = this.onDragLeave.bind(this);
|
||||
this.onCloseModalShowRefusedReason = this.onCloseModalShowRefusedReason.bind(this);
|
||||
this.onOpenModalShowRefusedReason = this.onOpenModalShowRefusedReason.bind(this);
|
||||
this.showRefusedReason = this.showRefusedReason.bind(this);
|
||||
this.onCloseAlertUpload = this.onCloseAlertUpload.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["container"]}>
|
||||
<div
|
||||
className={classNames(
|
||||
classes["root"],
|
||||
this.props.document.document_status === EDocumentStatus.VALIDATED && classes["validated"],
|
||||
)}
|
||||
onDragOver={this.onDragOver}
|
||||
onDrop={this.onDragDrop}
|
||||
onDragLeave={this.onDragLeave}
|
||||
data-drag-over={this.state.isDragOver.toString()}>
|
||||
<input
|
||||
type="file"
|
||||
ref={this.inputRef}
|
||||
hidden
|
||||
onChange={this.onFileChange}
|
||||
accept={Object.keys(filesAccepted).join(",")}
|
||||
/>
|
||||
<div className={classes["top-container"]}>
|
||||
<div className={classes["left"]}>
|
||||
<Image src={DepositDocumentIcon} alt="Deposit document" />
|
||||
</div>
|
||||
<div className={classes["separator"]} />
|
||||
<div className={classes["right"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["title"]}>
|
||||
<div
|
||||
className={
|
||||
this.props.document.document_status === EDocumentStatus.VALIDATED ? classes["validated"] : ""
|
||||
}>
|
||||
{this.props.document.document_type?.name}
|
||||
</div>
|
||||
{this.props.document.document_type?.public_description !== " " &&
|
||||
this.props.document.document_type?.public_description !== "" &&
|
||||
this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
||||
<Tooltip text={this.props.document.document_type?.public_description || ""} />
|
||||
)}
|
||||
{this.props.document.document_status === EDocumentStatus.VALIDATED && (
|
||||
<Image src={GreenCheckIcon} alt="Document check" />
|
||||
)}
|
||||
</Typography>
|
||||
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
||||
Sélectionnez des documents .jpg, .pdf ou .png
|
||||
</Typography>
|
||||
)}
|
||||
{this.props.document.document_history?.map((history) => (
|
||||
<div key={history.uid}>{this.renderDocumentHistory(history)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes["documents-container"]}>
|
||||
{this.state.files.map((file) => {
|
||||
const fileObj = file.file;
|
||||
if (file.archived) return;
|
||||
return (
|
||||
<div className={classes["file-container"]} key={fileObj.name + file.index}>
|
||||
<div className={classes["left-part"]}>
|
||||
<Image src={DocumentCheckIcon} alt="Document check" />
|
||||
<Typography
|
||||
typo={ETypo.TEXT_MD_REGULAR}
|
||||
color={ETypoColor.COLOR_NEUTRAL_500}
|
||||
title={file.fileName ?? fileObj.name}>
|
||||
{this.shortName(file.fileName || fileObj.name)}
|
||||
</Typography>
|
||||
</div>
|
||||
<Image
|
||||
src={CrossIcon}
|
||||
alt="Cross icon"
|
||||
className={classes["cross"]}
|
||||
onClick={this.removeFile}
|
||||
data-file={file.index}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{this.state.loading && (
|
||||
<div className={classes["file-container"]}>
|
||||
<div className={classes["left-part"]}>
|
||||
<div className={classes["loader"]}>
|
||||
<Loader />
|
||||
</div>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
||||
Chargement...
|
||||
</Typography>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
|
||||
<div className={classes["bottom-container"]}>
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.TEXT}
|
||||
className={classes["add-button"]}
|
||||
onClick={this.addDocument}>
|
||||
<Typography
|
||||
typo={ETypo.TEXT_MD_SEMIBOLD}
|
||||
color={ETypoColor.COLOR_SECONDARY_500}
|
||||
className={classes["add-document"]}>
|
||||
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
|
||||
</Typography>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Confirm
|
||||
isOpen={this.state.isShowRefusedReasonModalVisible}
|
||||
onClose={this.onCloseModalShowRefusedReason}
|
||||
showCancelButton={false}
|
||||
onAccept={this.onCloseModalShowRefusedReason}
|
||||
closeBtn
|
||||
header={"Motif du refus"}
|
||||
confirmText={"J'ai compris"}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Votre document a été refusé pour la raison suivante :
|
||||
</Typography>
|
||||
<TextAreaField placeholder="Description" defaultValue={this.state.refusedReason} readonly />
|
||||
</div>
|
||||
</Confirm>
|
||||
</div>
|
||||
{this.props.document.document_status === EDocumentStatus.REFUSED && (
|
||||
<Typography typo={ETypo.TEXT_SM_REGULAR} className={classes["error-message"]}>
|
||||
Ce document n'est pas conforme. Veuillez le déposer à nouveau.
|
||||
</Typography>
|
||||
)}
|
||||
{this.state.showFailedUploaded && (
|
||||
<Alert onClose={this.onCloseAlertUpload} header={"Fichier non autorisé"} isOpen={!!this.state.showFailedUploaded}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
{this.state.showFailedUploaded}
|
||||
</Typography>
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidMount(): void {
|
||||
if (this.props.defaultFiles) {
|
||||
this.setState({
|
||||
files: this.props.defaultFiles.map((file) => ({
|
||||
index: this.index++,
|
||||
file: new File([""], file.file_path ?? "", {}),
|
||||
uid: file.uid!,
|
||||
fileName: file.file_name,
|
||||
archived: file.archived_at ? new Date(file.archived_at) : null,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private openSuccessToast() {
|
||||
toast.success("Document envoyé avec succès");
|
||||
}
|
||||
|
||||
private onCloseModalShowRefusedReason() {
|
||||
this.setState({
|
||||
isShowRefusedReasonModalVisible: false,
|
||||
});
|
||||
}
|
||||
|
||||
private onOpenModalShowRefusedReason() {
|
||||
this.setState({
|
||||
isShowRefusedReasonModalVisible: true,
|
||||
});
|
||||
}
|
||||
|
||||
private renderDocumentHistory(history: DocumentHistory): JSX.Element | null {
|
||||
switch (history.document_status) {
|
||||
case EDocumentStatus.ASKED:
|
||||
return (
|
||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
||||
Demandé par votre notaire le {this.formatDate(history.created_at!)}
|
||||
</Typography>
|
||||
);
|
||||
case EDocumentStatus.VALIDATED:
|
||||
return (
|
||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
||||
Validé par votre notaire le {this.formatDate(history.created_at!)}
|
||||
</Typography>
|
||||
);
|
||||
case EDocumentStatus.DEPOSITED:
|
||||
return (
|
||||
<Typography color={ETypoColor.COLOR_NEUTRAL_500} typo={ETypo.TEXT_SM_REGULAR}>
|
||||
Déposé le {this.formatDate(history.created_at!)}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
case EDocumentStatus.REFUSED:
|
||||
return (
|
||||
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_ERROR_800}>
|
||||
Document non conforme
|
||||
{history.refused_reason && history.refused_reason.length > 0 && (
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.TEXT}
|
||||
className={classes["refused-button"]}
|
||||
onClick={() => this.showRefusedReason(history.refused_reason ?? "")}>
|
||||
Voir le motif de refus
|
||||
</Button>
|
||||
)}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private shortName(name: string): string {
|
||||
const maxLength = 20;
|
||||
if (name.length > maxLength) {
|
||||
return name.substring(0, maxLength / 2) + "..." + name.substring(name.length - maxLength / 2, name.length);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private onDragOver(event: React.DragEvent<HTMLDivElement>) {
|
||||
if (!this.state.isDragOver) {
|
||||
this.setState({
|
||||
isDragOver: true,
|
||||
});
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private showRefusedReason(refusedReason: string) {
|
||||
this.setState({
|
||||
refusedReason,
|
||||
});
|
||||
this.onOpenModalShowRefusedReason();
|
||||
}
|
||||
|
||||
private onDragLeave(event: React.DragEvent<HTMLDivElement>) {
|
||||
this.setState({
|
||||
isDragOver: false,
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private async onDragDrop(event: React.DragEvent<HTMLDivElement>) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
isDragOver: false,
|
||||
});
|
||||
const file = event.dataTransfer.files[0];
|
||||
if (file) this.addFile(file);
|
||||
}
|
||||
|
||||
private async addFile(file: File) {
|
||||
const fileAccepted = filesAccepted[file.type];
|
||||
if (!fileAccepted) {
|
||||
alert("Le fichier déposé doit être au format .jpg .pdf .jpeg ou .png");
|
||||
return false;
|
||||
}
|
||||
if (file.size > fileAccepted.size) {
|
||||
alert("Le fichier est trop volumineux et ne doit pas dépasser 32mo");
|
||||
return false;
|
||||
}
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, file.name);
|
||||
const query = JSON.stringify({ document: { uid: this.props.document.uid } });
|
||||
formData.append("q", query);
|
||||
|
||||
let newFile: FileCustomer;
|
||||
try {
|
||||
newFile = await Files.getInstance().post(formData);
|
||||
} catch (e) {
|
||||
this.setState({ showFailedUploaded: "Le fichier ne correspond pas aux critères demandés", loading: false });
|
||||
return false;
|
||||
}
|
||||
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
|
||||
|
||||
const newFileList = [
|
||||
...this.state.files,
|
||||
{
|
||||
index: this.index++,
|
||||
file: file,
|
||||
uid: newFile.uid!,
|
||||
archived: null,
|
||||
fileName: newFile?.file_name ?? "",
|
||||
},
|
||||
];
|
||||
this.openSuccessToast();
|
||||
this.setState(
|
||||
{
|
||||
currentFiles: files,
|
||||
loading: false,
|
||||
files: newFileList,
|
||||
},
|
||||
() => {
|
||||
if (this.props.onChange) this.props.onChange(newFileList.map((file) => file.file));
|
||||
},
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async removeFile(e: any) {
|
||||
const image = e.target as HTMLElement;
|
||||
const indexToRemove = image.getAttribute("data-file");
|
||||
if (!indexToRemove) return;
|
||||
const file = this.state.files.find((file) => file.index === parseInt(indexToRemove));
|
||||
if (!file) return;
|
||||
this.setState({
|
||||
files: this.state.files.filter((file) => file.index !== parseInt(indexToRemove)),
|
||||
});
|
||||
|
||||
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
|
||||
await Files.getInstance().delete(file.uid);
|
||||
}
|
||||
|
||||
private async onFileChange() {
|
||||
if (!this.inputRef.current) return;
|
||||
const files = this.inputRef.current.files;
|
||||
if (!files) {
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
|
||||
try {
|
||||
if (file) {
|
||||
await this.setState({ loading: true }, () => {
|
||||
this.addFile(file);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
private onCloseAlertUpload() {
|
||||
this.setState({ showFailedUploaded: null });
|
||||
}
|
||||
|
||||
private addDocument() {
|
||||
if (!this.inputRef.current) return;
|
||||
this.inputRef.current.value = "";
|
||||
this.inputRef.current.click();
|
||||
}
|
||||
|
||||
private formatDate(date: Date) {
|
||||
const dateToConvert = new Date(date);
|
||||
return dateToConvert.toLocaleDateString("fr-FR");
|
||||
}
|
||||
}
|
@ -7,13 +7,13 @@ import Loader from "../../Loader";
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
isLoading: boolean;
|
||||
file: File | null;
|
||||
onRemove: () => void;
|
||||
isLoading?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export default function DocumentElement(props: IProps) {
|
||||
export default function DocumentFileElement(props: IProps) {
|
||||
const { isLoading, onRemove, file, error } = props;
|
||||
|
||||
return (
|
@ -1,18 +1,33 @@
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button";
|
||||
import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator";
|
||||
import classes from "./classes.module.scss";
|
||||
import DocumentElement from "./DocumentElement";
|
||||
import DocumentFileElement from "./DocumentFileElement";
|
||||
|
||||
/**
|
||||
* @description Drag and drop component to upload files
|
||||
* @param {string} title - Title of the component
|
||||
* @param {string} description - Description of the component
|
||||
* @param {IDocumentFileWithUid[]} defaultFiles - Default files to display
|
||||
* @param {(fileUid: string) => Promise<any>} onDelete - Function to delete a file (must be used with defaultFiles)
|
||||
* @param {(file: File) => Promise<any>} onAddFile - Function to add a file (must be used with defaultFiles)
|
||||
*/
|
||||
type IProps = {
|
||||
name?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
description?: string;
|
||||
defaultFiles?: IDocumentFileWithUid[];
|
||||
onDelete?: (fileUid: string) => Promise<any>;
|
||||
onAddFile?: (file: File) => Promise<any>;
|
||||
} & (
|
||||
| { onDelete: (fileUid: string) => Promise<any>; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] }
|
||||
| { onDelete?: never; onAddFile: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
|
||||
| { onDelete?: (fileUid: string) => Promise<any>; onAddFile?: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
|
||||
| { onDelete?: never; onAddFile?: never; defaultFiles?: never }
|
||||
);
|
||||
|
||||
type IMimeTypes = {
|
||||
extension: string;
|
||||
@ -38,53 +53,87 @@ const mimeTypesAccepted: { [key: string]: IMimeTypes } = {
|
||||
},
|
||||
};
|
||||
|
||||
type IDocument = {
|
||||
type IDocumentFileBase = {
|
||||
id: string;
|
||||
file: File | null;
|
||||
isLoading: boolean;
|
||||
uid?: string;
|
||||
isLoading?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type IDocumentFileWithUid = IDocumentFileBase & {
|
||||
uid: string;
|
||||
};
|
||||
|
||||
type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid;
|
||||
|
||||
export default function DragAndDrop(props: IProps) {
|
||||
const { name, title, description } = props;
|
||||
const { title, description, defaultFiles, onDelete, onAddFile } = props;
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [documents, setDocuments] = useState<IDocument[]>([]);
|
||||
const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);
|
||||
|
||||
const handleFiles = useCallback((files: File[]) => {
|
||||
files.forEach((file) => {
|
||||
setDocuments((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]);
|
||||
setTimeout(() => {
|
||||
if (mimeTypesAccepted[file.type]) {
|
||||
const newDoc: IDocument = { id: file.name, file, isLoading: false };
|
||||
setDocuments((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
|
||||
} else {
|
||||
const errorDoc: IDocument = { id: file.name, file: null, isLoading: false, error: "Type de fichier non accepté" };
|
||||
setDocuments((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc)));
|
||||
useEffect(() => {
|
||||
if (defaultFiles) {
|
||||
setDocumentFiles(defaultFiles);
|
||||
}
|
||||
}, [defaultFiles]);
|
||||
|
||||
const handleAddFiles = useCallback(
|
||||
(files: File[]) => {
|
||||
files.forEach((file) => {
|
||||
setDocumentFiles((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]);
|
||||
try {
|
||||
if (!mimeTypesAccepted[file.type]) {
|
||||
throw new Error("Type de fichier non accepté");
|
||||
}
|
||||
const newDoc: IDocumentFile = { id: file.name, file, isLoading: false };
|
||||
|
||||
if (onAddFile) {
|
||||
// As onAddFile is used along defaultFiles prop we dont need to update the state here but the parent component should update the defaultFiles prop
|
||||
return onAddFile(file);
|
||||
}
|
||||
|
||||
return setTimeout(async () => {
|
||||
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
|
||||
}, 1000);
|
||||
} catch (error: any) {
|
||||
const errorDoc: IDocumentFile = { id: file.name, file: null, isLoading: false, error: error.message };
|
||||
return setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc)));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}, []);
|
||||
});
|
||||
},
|
||||
[onAddFile],
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const files = Array.from(event.dataTransfer.files);
|
||||
handleFiles(files);
|
||||
handleAddFiles(files);
|
||||
},
|
||||
[handleFiles],
|
||||
[handleAddFiles],
|
||||
);
|
||||
|
||||
const handleRemove = useCallback((id: string) => {
|
||||
setDocuments((prevDocs) => prevDocs.filter((doc) => doc.id !== id));
|
||||
}, []);
|
||||
const handleRemove = useCallback(
|
||||
(documentFile: IDocumentFile) => {
|
||||
const loadingDoc = { ...documentFile, isLoading: true };
|
||||
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc)));
|
||||
|
||||
if (documentFile.uid) {
|
||||
return onDelete?.(documentFile.uid);
|
||||
}
|
||||
return setDocumentFiles((prevDocs) => prevDocs.filter((doc) => doc.id !== documentFile.id));
|
||||
},
|
||||
[onDelete],
|
||||
);
|
||||
|
||||
const handleBrowse = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files || []);
|
||||
handleFiles(files);
|
||||
handleAddFiles(files);
|
||||
},
|
||||
[handleFiles],
|
||||
[handleAddFiles],
|
||||
);
|
||||
|
||||
const triggerFileInput = () => {
|
||||
@ -95,7 +144,7 @@ export default function DragAndDrop(props: IProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(classes["root"], documents.length > 0 && classes["filled"])}
|
||||
className={classNames(classes["root"], documentFiles.length > 0 && classes["filled"])}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={(e) => e.preventDefault()}>
|
||||
<div className={classes["content"]}>
|
||||
@ -127,20 +176,22 @@ export default function DragAndDrop(props: IProps) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
||||
{description}
|
||||
</Typography>
|
||||
{description && (
|
||||
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.TEXT_SECONDARY}>
|
||||
{description}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{documents.length > 0 && (
|
||||
{documentFiles.length > 0 && (
|
||||
<div className={classes["documents"]}>
|
||||
{documents.map((doc) => (
|
||||
<DocumentElement
|
||||
key={doc.id}
|
||||
isLoading={doc.isLoading}
|
||||
file={doc.file}
|
||||
onRemove={() => handleRemove(doc.id)}
|
||||
error={doc.error}
|
||||
{documentFiles.map((documentFile) => (
|
||||
<DocumentFileElement
|
||||
key={documentFile.id}
|
||||
isLoading={documentFile.isLoading}
|
||||
file={documentFile.file}
|
||||
onRemove={() => handleRemove(documentFile)}
|
||||
error={documentFile.error}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -152,7 +203,6 @@ export default function DragAndDrop(props: IProps) {
|
||||
return (
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
name={name}
|
||||
type="file"
|
||||
multiple
|
||||
accept={Object.keys(mimeTypesAccepted).join(",")}
|
||||
|
@ -172,6 +172,8 @@ export enum ETypoColor {
|
||||
|
||||
TOASTER_CONTRAST_TITLE = "--toaster-contrast-title",
|
||||
TOASTER_CONTRAST_TEXT = "--toaster-contrast-text",
|
||||
|
||||
TABLE_COLUMN_CONTRAST = "--table-column-contrast",
|
||||
}
|
||||
|
||||
export default function Typography(props: IProps) {
|
||||
|
22
src/front/Components/Elements/ContactBox/classes.module.scss
Normal file
22
src/front/Components/Elements/ContactBox/classes.module.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
padding: var(--spacing-md, 16px);
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 16px);
|
||||
background: var(--primary-weak-higlight, #e5eefa);
|
||||
min-width: 300px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
48
src/front/Components/Elements/ContactBox/index.tsx
Normal file
48
src/front/Components/Elements/ContactBox/index.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { Contact as ContactCustomer, Note } from "le-coffre-resources/dist/Customer";
|
||||
import { Contact as ContactNotary } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {
|
||||
contact: ContactCustomer | ContactNotary;
|
||||
note: Note | null;
|
||||
};
|
||||
|
||||
export default function ContactBox(props: IProps) {
|
||||
const { contact, note } = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<div className={classes["header"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.COLOR_PRIMARY_500}>
|
||||
{contact?.first_name} {contact?.last_name}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||
Numéro de téléphone
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
{contact?.cell_phone_number ?? contact?.phone_number ?? "_"}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||
E-mail
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
{contact?.email ?? "_"}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_700}>
|
||||
Note client
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_LG_REGULAR} color={ETypoColor.COLOR_NEUTRAL_950}>
|
||||
{note?.content ?? "-"}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
import { IBlock } from "@Front/Components/DesignSystem/SearchBlockList/BlockList/Block";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { OfficeFolder } from "le-coffre-resources/dist/Customer";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import DefaultDashboardWithList, { IPropsDashboardWithList } from "../DefaultDashboardWithList";
|
||||
|
||||
type IProps = IPropsDashboardWithList & {};
|
||||
|
||||
export default function DefaultCustomerDashboard(props: IProps) {
|
||||
const router = useRouter();
|
||||
const { folderUid } = router.query;
|
||||
const [folders, setFolders] = useState<OfficeFolder[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
if (!jwt) return;
|
||||
|
||||
Folders.getInstance()
|
||||
.get({
|
||||
q: {
|
||||
where: {
|
||||
customers: {
|
||||
some: {
|
||||
contact: {
|
||||
email: jwt.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{
|
||||
created_at: "desc",
|
||||
},
|
||||
],
|
||||
include: {
|
||||
customers: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((folders) => setFolders(folders));
|
||||
}, []);
|
||||
|
||||
const onSelectedBlock = (block: IBlock) => {
|
||||
const folder = folders.find((folder) => folder.uid === block.id);
|
||||
if (!folder) return;
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.uid ?? ""),
|
||||
);
|
||||
};
|
||||
return <DefaultDashboardWithList {...props} onSelectedBlock={onSelectedBlock} blocks={getBlocks(folders)} headerConnected={false}/>;
|
||||
|
||||
function getBlocks(folders: OfficeFolder[]): IBlock[] {
|
||||
return folders.map((folder) => {
|
||||
return {
|
||||
id: folder.uid!,
|
||||
primaryText: folder.name!,
|
||||
secondaryText: folder.folder_number!,
|
||||
isActive: folderUid === folder.uid,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import DragAndDrop, { IDocumentFileWithUid } from "@Front/Components/DesignSystem/DragAndDrop";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import { Document } from "le-coffre-resources/dist/Customer";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
|
||||
|
||||
type IProps = {
|
||||
document: Document;
|
||||
onChange: () => void;
|
||||
};
|
||||
|
||||
export default function DepositDocumentComponent(props: IProps) {
|
||||
const { document, onChange } = props;
|
||||
|
||||
const defaultFiles: IDocumentFileWithUid[] = useMemo(() => {
|
||||
const filesNotArchived = document.files?.filter((file) => !file.archived_at) ?? [];
|
||||
return filesNotArchived.map((file) => ({
|
||||
id: file.uid!,
|
||||
file: new File([""], file.file_name!, { type: file.mimetype }),
|
||||
uid: file.uid!,
|
||||
}));
|
||||
}, [document.files]);
|
||||
|
||||
const addFile = useCallback(
|
||||
(file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, file.name);
|
||||
const query = JSON.stringify({ document: { uid: document.uid } });
|
||||
formData.append("q", query);
|
||||
return Files.getInstance().post(formData).then(onChange);
|
||||
},
|
||||
[document.uid, onChange],
|
||||
);
|
||||
|
||||
const deleteFile = useCallback(
|
||||
(filedUid: string) => {
|
||||
return Files.getInstance().delete(filedUid).then(onChange);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<div className={classes["title"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.TEXT_PRIMARY}>
|
||||
{document.document_type?.name ?? "_"}
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||
Demandé le: {document.created_at ? new Date(document.created_at).toLocaleDateString() : "_"}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<DragAndDrop
|
||||
title={"Drag and drop ou"}
|
||||
description={document.document_type?.public_description ?? undefined}
|
||||
defaultFiles={defaultFiles}
|
||||
onAddFile={addFile}
|
||||
onDelete={deleteFile}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,120 +1,39 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl, 32px);
|
||||
|
||||
.title-container {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
padding: 64px;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
|
||||
@media (max-width: $screen-m) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.text {
|
||||
margin: 32px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.text {
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.folder-number {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.office-name {
|
||||
margin-top: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 64px 0 32px 0;
|
||||
}
|
||||
|
||||
.contact {
|
||||
.office-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.contact-text {
|
||||
text-align: right;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.contact-button {
|
||||
margin-top: 4%;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 20px;
|
||||
height: 50px; /* Adjust the height as needed */
|
||||
background-color: gray;
|
||||
margin: 0 20px; /* Adjust the margin as needed */
|
||||
}
|
||||
|
||||
.note-box {
|
||||
border: 1px solid #e0e0e0; /* Light grey border */
|
||||
margin-top: 25px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
height: 100px; /* Adjust height as needed */
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md, 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.sub-container {
|
||||
background-color: var(--color-neutral-50);
|
||||
padding: 64px;
|
||||
.content {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg, 24px);
|
||||
align-items: flex-start;
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 32px;
|
||||
margin-bottom: 64px;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.notary {
|
||||
display: flex;
|
||||
width: 300px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-lg, 24px);
|
||||
}
|
||||
|
||||
.component-to-replace {
|
||||
min-width: 124px;
|
||||
height: 98px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.text {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.component-to-replace {
|
||||
background-color: var(--color-neutral-50);
|
||||
height: 98px;
|
||||
.documents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xl, 32px);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,25 @@
|
||||
"use client";
|
||||
import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
|
||||
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||
import Customer, { Document, DocumentType, Note, OfficeFolder } from "le-coffre-resources/dist/Customer";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import Customer, { Document } from "le-coffre-resources/dist/Customer";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import { useRouter } from "next/router";
|
||||
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
|
||||
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
|
||||
import OfficeRib from "@Front/Api/LeCoffreApi/Customer/OfficeRib/OfficeRib";
|
||||
|
||||
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
||||
import DefaultCustomerDashboard from "@Front/Components/LayoutTemplates/DefaultCustomerDashboard";
|
||||
import ContactBox from "@Front/Components/Elements/ContactBox";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||
import DepositDocumentComponent from "./DepositDocumentComponent";
|
||||
|
||||
type IProps = {};
|
||||
|
||||
export default function ClientDashboard(props: IProps) {
|
||||
@ -22,12 +28,11 @@ export default function ClientDashboard(props: IProps) {
|
||||
const [documents, setDocuments] = useState<Document[] | null>(null);
|
||||
|
||||
const [customer, setCustomer] = useState<Customer | null>(null);
|
||||
const [contact, setContact] = useState<Customer["contact"] | null>(null);
|
||||
const [folder, setFolder] = useState<OfficeFolder | null>(null);
|
||||
const [note, setNote] = useState<Note | null>(null);
|
||||
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
|
||||
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
|
||||
|
||||
const getDocuments = useCallback(async () => {
|
||||
const [ribUrl, setRibUrl] = useState<string | null>(null);
|
||||
|
||||
const fetchFolderAndCustomer = useCallback(async () => {
|
||||
let jwt: ICustomerJwtPayload | undefined;
|
||||
if (typeof document !== "undefined") {
|
||||
jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
@ -50,66 +55,73 @@ export default function ClientDashboard(props: IProps) {
|
||||
},
|
||||
},
|
||||
});
|
||||
//Loop through the folder stakeholders, if there is at least one stakeholder that role is "Collaborateur" set contact to this stakeholders.contact, else, take the first stakeholders of the list
|
||||
const contact = folder.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact;
|
||||
setContact(contact ?? folder.stakeholders![0]!.contact);
|
||||
|
||||
const actualCustomer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
|
||||
if (!actualCustomer) throw new Error("Customer not found");
|
||||
const customer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
|
||||
if (!customer) throw new Error("Customer not found");
|
||||
|
||||
let note = folder.notes?.find((note) => note.customer?.uid === actualCustomer.uid);
|
||||
// if (!note) throw new Error("Note not found");
|
||||
if (!note) {
|
||||
note = {
|
||||
content: "Aucune note",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
}
|
||||
setFolder(folder);
|
||||
setCustomer(customer);
|
||||
|
||||
const query: IGetDocumentsparams = {
|
||||
where: { depositor: { uid: actualCustomer.uid }, folder_uid: folderUid as string },
|
||||
include: {
|
||||
files: true,
|
||||
document_history: true,
|
||||
document_type: true,
|
||||
depositor: true,
|
||||
folder: {
|
||||
include: {
|
||||
customers: {
|
||||
include: {
|
||||
contact: true,
|
||||
return { folder, customer };
|
||||
}, [folderUid]);
|
||||
|
||||
const fetchDocuments = useCallback(
|
||||
(customerUid: string | undefined) => {
|
||||
const query: IGetDocumentsparams = {
|
||||
where: { depositor: { uid: customerUid }, folder_uid: folderUid as string },
|
||||
include: {
|
||||
files: true,
|
||||
document_history: true,
|
||||
document_type: true,
|
||||
depositor: true,
|
||||
folder: {
|
||||
include: {
|
||||
customers: {
|
||||
include: {
|
||||
contact: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return Documents.getInstance()
|
||||
.get(query)
|
||||
.then((documents) => setDocuments(documents));
|
||||
},
|
||||
[folderUid],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFolderAndCustomer().then(({ customer }) => fetchDocuments(customer.uid));
|
||||
}, [fetchDocuments, fetchFolderAndCustomer]);
|
||||
|
||||
const notaryContact = useMemo(
|
||||
() =>
|
||||
folder?.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact ??
|
||||
folder?.stakeholders![0]!.contact,
|
||||
[folder],
|
||||
);
|
||||
|
||||
const note = useMemo(
|
||||
() =>
|
||||
folder?.notes?.find((note) => note.customer?.uid === customer?.uid) ?? {
|
||||
content: "Aucune note",
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
},
|
||||
};
|
||||
[customer?.uid, folder?.notes],
|
||||
);
|
||||
|
||||
const documentList = await Documents.getInstance().get(query);
|
||||
|
||||
//const folder = await Folders.getInstance().getByUid(folderUid as string, { q: { office: true, customers: true } });
|
||||
|
||||
setFolder(folder);
|
||||
setDocuments(documentList);
|
||||
setCustomer(actualCustomer);
|
||||
setNote(note);
|
||||
}, [folderUid]);
|
||||
|
||||
const onCloseModalAddDocument = useCallback(() => {
|
||||
setIsAddDocumentModalVisible(false);
|
||||
getDocuments();
|
||||
}, [getDocuments]);
|
||||
|
||||
const onOpenModalAddDocument = useCallback(() => {
|
||||
setIsAddDocumentModalVisible(true);
|
||||
}, []);
|
||||
|
||||
const downloadFile = useCallback(async () => {
|
||||
useEffect(() => {
|
||||
if (!folder?.office?.uid) return;
|
||||
const blob = await OfficeRib.getInstance().getRibStream(folder.office.uid);
|
||||
const ribUrl = URL.createObjectURL(blob);
|
||||
OfficeRib.getInstance()
|
||||
.getRibStream(folder.office.uid)
|
||||
.then((blob) => setRibUrl(URL.createObjectURL(blob)));
|
||||
}, [folder]);
|
||||
|
||||
const downloadRib = useCallback(async () => {
|
||||
if (!ribUrl) return;
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
@ -117,122 +129,64 @@ export default function ClientDashboard(props: IProps) {
|
||||
a.download = "";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
}, [folder]);
|
||||
|
||||
useEffect(() => {
|
||||
getDocuments();
|
||||
}, [folderUid, getDocuments]);
|
||||
|
||||
const renderHeader = useCallback(() => {
|
||||
return (
|
||||
<div className={classes["header"]}>
|
||||
<div className={classes["text-container"]}>
|
||||
{/* TODO Get name from userStore */}
|
||||
<div className={classes["title-container"]}>
|
||||
<Typography typo={ETypo.DISPLAY_LARGE} className={classes["title"]}>
|
||||
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["folder-number"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
||||
Dossier {folder?.folder_number} - {folder?.name}
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["office-name"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
||||
{folder?.office?.name}
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TITLE_H3} className={classes["subtitle"]}>
|
||||
Documents à envoyer
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Votre notaire est dans l'attente de documents pour valider votre dossier. Voici la liste des documents.
|
||||
<br /> Veuillez glisser / déposer chaque document dans la zone prévue à cet effet ou cliquez sur la zone puis
|
||||
sélectionnez le document correspondant. <br /> En déposant un document, celui-ci est automatiquement enregistré et
|
||||
transmis à votre notaire.
|
||||
</Typography>
|
||||
<div className={classes["note-box"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
||||
{note?.content}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes["contact"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["contact-text"]} color={ETypoColor.COLOR_NEUTRAL_500}>
|
||||
<p>
|
||||
{contact?.first_name} {contact?.last_name}
|
||||
</p>
|
||||
<p>{contact?.phone_number ?? contact?.cell_phone_number}</p>
|
||||
<p>{contact?.email}</p>
|
||||
</Typography>
|
||||
<div className="separator"></div>
|
||||
{folder?.office?.rib_name && (
|
||||
//Div to avoid the button to be on the same line as the text
|
||||
<Button className={classes["contact-button"]} onClick={downloadFile}>
|
||||
Télécharger le RIB de votre notaire
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
contact?.cell_phone_number,
|
||||
contact?.email,
|
||||
contact?.first_name,
|
||||
contact?.last_name,
|
||||
contact?.phone_number,
|
||||
customer?.contact?.first_name,
|
||||
customer?.contact?.last_name,
|
||||
downloadFile,
|
||||
folder?.folder_number,
|
||||
folder?.name,
|
||||
folder?.office?.name,
|
||||
folder?.office?.rib_name,
|
||||
note?.content,
|
||||
]);
|
||||
|
||||
const renderBox = useCallback(() => {
|
||||
return (
|
||||
<DepositOtherDocument
|
||||
folder_uid={folderUid!}
|
||||
customer_uid={customer!.uid!}
|
||||
open={isAddDocumentModalVisible}
|
||||
onClose={onCloseModalAddDocument}
|
||||
document={Document.hydrate<Document>({
|
||||
document_type: DocumentType.hydrate<DocumentType>({
|
||||
name: "Autres documents",
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument]);
|
||||
}, [ribUrl]);
|
||||
|
||||
return (
|
||||
<DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}>
|
||||
<DefaultCustomerDashboard>
|
||||
<div className={classes["root"]}>
|
||||
{renderHeader()}
|
||||
<div className={classes["sub-container"]}>
|
||||
<div className={classes["content"]}>
|
||||
<div className={classes["title-container"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||
Dossier {folder?.folder_number} - {folder?.name}
|
||||
</Typography>
|
||||
<Typography typo={ETypo.TITLE_H4} color={ETypoColor.TEXT_PRIMARY}>
|
||||
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
|
||||
</Typography>
|
||||
<Tag color={ETagColor.INFO} label={"todo"} />
|
||||
<div className={classes["office-container"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||
Office
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_ACCENT}>
|
||||
{folder?.office?.name}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes["content"]}>
|
||||
<div className={classes["notary"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
|
||||
Votre Notaire
|
||||
</Typography>
|
||||
{notaryContact && <ContactBox contact={notaryContact} note={note} />}
|
||||
{ribUrl && (
|
||||
<Button
|
||||
fullwidth
|
||||
onClick={downloadRib}
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
size={EButtonSize.LG}
|
||||
styletype={EButtonstyletype.CONTAINED}
|
||||
rightIcon={<ArrowDownTrayIcon />}>
|
||||
Télécharger le RIB
|
||||
</Button>
|
||||
)}
|
||||
<Button fullwidth variant={EButtonVariant.PRIMARY} size={EButtonSize.LG} styletype={EButtonstyletype.OUTLINED}>
|
||||
Voir les documents reçus
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classes["documents"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD} color={ETypoColor.TABLE_COLUMN_CONTRAST}>
|
||||
Documents à envoyer
|
||||
</Typography>
|
||||
{documents?.map((document) => (
|
||||
<DepositDocument document={document} key={document.uid} defaultFiles={document.files ?? []} />
|
||||
<DepositDocumentComponent
|
||||
key={document.uid}
|
||||
document={document}
|
||||
onChange={() => fetchDocuments(customer?.uid)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Vous souhaitez envoyer d'autres documents à votre notaire ?
|
||||
</Typography>
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.OUTLINED}
|
||||
className={classes["button"]}
|
||||
onClick={onOpenModalAddDocument}>
|
||||
Ajouter d'autres documents
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{isAddDocumentModalVisible && renderBox()}
|
||||
</DefaultTemplate>
|
||||
</DefaultCustomerDashboard>
|
||||
);
|
||||
}
|
||||
|
@ -1,140 +0,0 @@
|
||||
//import Customers from "@Front/Api/LeCoffreApi/Customer/Customers/Customers";
|
||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm";
|
||||
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Base from "@Front/Components/Layouts/Base";
|
||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer";
|
||||
import React from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
|
||||
type IProps = {};
|
||||
type IState = {
|
||||
isAddDocumentModalVisible: boolean;
|
||||
documents: Document[];
|
||||
mockedCustomer: Customer | null;
|
||||
};
|
||||
|
||||
export default class ClientDashboard extends Base<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isAddDocumentModalVisible: false,
|
||||
documents: [],
|
||||
mockedCustomer: null,
|
||||
};
|
||||
this.onCloseModalAddDocument = this.onCloseModalAddDocument.bind(this);
|
||||
this.onOpenModalAddDocument = this.onOpenModalAddDocument.bind(this);
|
||||
}
|
||||
|
||||
public override render(): JSX.Element {
|
||||
return (
|
||||
<DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}>
|
||||
<div className={classes["root"]}>
|
||||
{this.renderHeader()}
|
||||
<div className={classes["sub-container"]}>
|
||||
<div className={classes["content"]}>
|
||||
{this.state.documents?.map((document) => (
|
||||
<DepositDocument document={document} key={document.uid} defaultFiles={document.files ?? []} />
|
||||
))}
|
||||
</div>
|
||||
<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Vous souhaitez envoyer d'autres documents à votre notaire ?
|
||||
</Typography>
|
||||
<Button
|
||||
variant={EButtonVariant.PRIMARY}
|
||||
styletype={EButtonstyletype.OUTLINED}
|
||||
className={classes["button"]}
|
||||
onClick={this.onOpenModalAddDocument}>
|
||||
Ajouter d'autres documents
|
||||
</Button>
|
||||
</div>
|
||||
<Confirm
|
||||
isOpen={this.state.isAddDocumentModalVisible}
|
||||
onClose={this.onCloseModalAddDocument}
|
||||
onAccept={this.onOpenModalAddDocument}
|
||||
closeBtn
|
||||
header={"Ajouter un document"}
|
||||
cancelText={"Annuler"}
|
||||
confirmText={"Déposer le document"}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Vous souhaitez envoyer un autre document à votre notaire ?
|
||||
</Typography>
|
||||
<TextField placeholder="Nom du document" />
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Glissez / Déposez votre document dans la zone prévue à cet effet ou cliquez sur la zone puis sélectionnez le
|
||||
document correspondant.
|
||||
</Typography>
|
||||
<DepositDocument
|
||||
document={Document.hydrate<Document>({
|
||||
document_type: DocumentType.hydrate<DocumentType>({
|
||||
name: "Autres documents",
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Confirm>
|
||||
</div>
|
||||
</DefaultTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
private renderHeader(): JSX.Element {
|
||||
return (
|
||||
<div className={classes["header"]}>
|
||||
<div className={classes["text-container"]}>
|
||||
{/* TODO Get name from userStore */}
|
||||
<Typography typo={ETypo.DISPLAY_LARGE} className={classes["title"]}>
|
||||
Bonjour {this.state.mockedCustomer?.contact?.first_name.concat(" ", this.state.mockedCustomer?.contact?.last_name)}
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TITLE_H3} className={classes["subtitle"]}>
|
||||
Documents à envoyer
|
||||
</Typography>
|
||||
|
||||
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
|
||||
Votre notaire est dans l'attente de documents pour valider votre dossier. Voici la liste des documents.Veuillez
|
||||
glisser / déposez chaque document dans la zone prévue à cet effet ou cliquez sur la zone puis sélectionnez le
|
||||
document correspondant. Si un des documents demandés ne vous concernent pas, veuillez contacter votre notaire à
|
||||
l'aide du bouton ci-dessus.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// public override async componentDidMount() {
|
||||
// // TODO Get documents of the current customer according to userStore
|
||||
// // REMOVE this mock
|
||||
|
||||
// const jwt = JwtService.getInstance().decodeJwt();
|
||||
// const mockedCustomers = await Customers.getInstance().get({
|
||||
// where: { contact: { email: jwt?.email } },
|
||||
// });
|
||||
// const mockedCustomer: Customer = mockedCustomers[0]!;
|
||||
|
||||
// const query: IGetDocumentsparams = {
|
||||
// where: { depositor: { uid: mockedCustomer.uid } },
|
||||
// include: {
|
||||
// files: true,
|
||||
// document_history: true,
|
||||
// document_type: true,
|
||||
// },
|
||||
// };
|
||||
// const documents: Document[] = await Documents.getInstance().get(query);
|
||||
// this.setState({ documents, mockedCustomer });
|
||||
// }
|
||||
|
||||
private onCloseModalAddDocument() {
|
||||
this.setState({ isAddDocumentModalVisible: false });
|
||||
}
|
||||
|
||||
private onOpenModalAddDocument() {
|
||||
this.setState({ isAddDocumentModalVisible: true });
|
||||
}
|
||||
}
|
@ -131,7 +131,7 @@ export default function DesignSystem() {
|
||||
Logged Out
|
||||
</Button>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Drag and Drop</Typography>
|
||||
<DragAndDrop name="test" title="Upload de document" description="Description" />
|
||||
<DragAndDrop title="Upload de document" description="Description" />
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Separators</Typography>
|
||||
<div className={classes["rows"]}>
|
||||
<Separator color={ESeperatorColor.DEFAULT} direction={ESeperatorDirection.HORIZONTAL} />
|
||||
|
@ -1,40 +1,24 @@
|
||||
import LogoIcon from "@Assets/logo_small_blue.svg";
|
||||
import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
|
||||
import Module from "@Front/Config/Module";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import { DocumentIcon } from "@heroicons/react/24/outline";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
|
||||
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
|
||||
import { useRouter } from "next/router";
|
||||
import useUser from "@Front/Hooks/useUser";
|
||||
|
||||
export default function Folder() {
|
||||
const [_isArchivedModalOpen, _setIsArchivedModalOpen] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
const [activeUser, setActiveUser] = useState<User | null>();
|
||||
|
||||
useEffect(() => {
|
||||
const decodedJwt = JwtService.getInstance().decodeJwt();
|
||||
if (!decodedJwt) return;
|
||||
Users.getInstance()
|
||||
.getByUid(decodedJwt.userId, {
|
||||
q: {
|
||||
contact: true,
|
||||
},
|
||||
})
|
||||
.then((user) => {
|
||||
setActiveUser(user);
|
||||
});
|
||||
}, []);
|
||||
const { user: activeUser } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
Folders.getInstance()
|
||||
|
@ -12,10 +12,11 @@ import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import Module from "@Front/Config/Module";
|
||||
|
||||
export default function SelectFolder() {
|
||||
const [folders, setFolders] = useState<OfficeFolder[]>([]);
|
||||
const router = useRouter();
|
||||
const [folders, setFolders] = useState<OfficeFolder[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const jwt = JwtService.getInstance().decodeCustomerJwt();
|
||||
@ -48,7 +49,11 @@ export default function SelectFolder() {
|
||||
|
||||
const handleSelectBlock = useCallback(
|
||||
(folder: IBlock) => {
|
||||
router.push("/client-dashboard/" + folder.id);
|
||||
router.push(
|
||||
Module.getInstance()
|
||||
.get()
|
||||
.modules.pages.ClientDashboard.props.path.replace("[folderUid]", folder.id ?? ""),
|
||||
);
|
||||
},
|
||||
[router],
|
||||
);
|
||||
|
@ -31,6 +31,13 @@
|
||||
"labelKey": "customer_login"
|
||||
}
|
||||
},
|
||||
"ClientDashboard": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
"path": "/client-dashboard/[folderUid]",
|
||||
"labelKey": "client-dashboard"
|
||||
}
|
||||
},
|
||||
"Folder": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
|
@ -31,6 +31,13 @@
|
||||
"labelKey": "customer_login"
|
||||
}
|
||||
},
|
||||
"ClientDashboard": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
"path": "/client-dashboard/[folderUid]",
|
||||
"labelKey": "client-dashboard"
|
||||
}
|
||||
},
|
||||
"Folder": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
|
@ -31,6 +31,13 @@
|
||||
"labelKey": "customer_login"
|
||||
}
|
||||
},
|
||||
"ClientDashboard": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
"path": "/client-dashboard/[folderUid]",
|
||||
"labelKey": "client-dashboard"
|
||||
}
|
||||
},
|
||||
"Folder": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
|
@ -31,6 +31,13 @@
|
||||
"labelKey": "customer_login"
|
||||
}
|
||||
},
|
||||
"ClientDashboard": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
"path": "/client-dashboard/[folderUid]",
|
||||
"labelKey": "client-dashboard"
|
||||
}
|
||||
},
|
||||
"Folder": {
|
||||
"enabled": true,
|
||||
"props": {
|
||||
|
26
src/front/Hooks/useUser.ts
Normal file
26
src/front/Hooks/useUser.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
|
||||
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||
import User from "le-coffre-resources/dist/Notary";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function useUser() {
|
||||
const [user, setUser] = useState<User | null>();
|
||||
|
||||
useEffect(() => {
|
||||
const decodedJwt = JwtService.getInstance().decodeJwt();
|
||||
if (!decodedJwt) return;
|
||||
Users.getInstance()
|
||||
.getByUid(decodedJwt.userId, {
|
||||
q: {
|
||||
contact: true,
|
||||
},
|
||||
})
|
||||
.then((user) => {
|
||||
setUser(user);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user