Merge branch 'dev' into staging

This commit is contained in:
Maxime Lalo 2023-09-14 16:53:52 +02:00
commit 0205a42fe3
58 changed files with 5524 additions and 910 deletions

4678
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.68", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.73",
"next": "13.2.4", "next": "13.2.4",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"react": "18.2.0", "react": "18.2.0",
@ -33,5 +33,6 @@
"sass": "^1.59.2", "sass": "^1.59.2",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"typescript": "4.9.5" "typescript": "4.9.5"
} },
"devDependencies": {}
} }

Binary file not shown.

View File

@ -2,7 +2,7 @@ import BaseApiService from "@Front/Api/BaseApiService";
export default class User extends BaseApiService { export default class User extends BaseApiService {
private static instance: User; private static instance: User;
private readonly baseURl = this.getBaseUrl().concat("/idnot/user"); private readonly baseURl = `${this.getBaseUrl()}/idnot/user`;
private constructor() { private constructor() {
super(); super();
@ -17,7 +17,18 @@ export default class User extends BaseApiService {
} }
public async login(uid: string) { public async login(uid: string) {
const url = new URL(this.baseURl.concat("/login/").concat(uid)); const url = new URL(`${this.baseURl}/login/${uid}`);
try {
return await this.postRequest(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async verifyJwt(jwt: string) {
console.log(this.baseURl);
const url = new URL(`${this.baseURl}/verify-token/${jwt}`);
try { try {
return await this.postRequest(url); return await this.postRequest(url);
} catch (err) { } catch (err) {
@ -27,7 +38,7 @@ export default class User extends BaseApiService {
} }
public async refreshToken(refreshToken: string): Promise<{ accessToken: string }> { public async refreshToken(refreshToken: string): Promise<{ accessToken: string }> {
const url = new URL(this.baseURl.concat("/refresh-token")); const url = new URL(`${this.baseURl}/refresh-token`);
try { try {
return await this.postRequest(url, {}, refreshToken); return await this.postRequest(url, {}, refreshToken);
} catch (err) { } catch (err) {

View File

@ -11,13 +11,13 @@ export default abstract class BaseApiService {
protected constructor() { protected constructor() {
BaseApiService.baseUrl ??= BaseApiService.baseUrl ??=
FrontendVariables.getInstance().BACK_API_PROTOCOL + this.variables.BACK_API_PROTOCOL +
FrontendVariables.getInstance().BACK_API_HOST + this.variables.BACK_API_HOST +
FrontendVariables.getInstance().BACK_API_ROOT_URL + this.variables.BACK_API_ROOT_URL +
FrontendVariables.getInstance().BACK_API_VERSION; this.variables.BACK_API_VERSION;
} }
protected getBaseUrl() { protected getBaseUrl(): string {
return BaseApiService.baseUrl; return BaseApiService.baseUrl;
} }

View File

@ -0,0 +1,16 @@
export interface IAppRule {
name: AppRuleNames;
action: AppRuleActions;
}
export enum AppRuleActions {
read = "GET",
create = "POST",
update = "PUT",
delete = "DELETE",
}
export enum AppRuleNames {
users = "users",
officeFolders = "folders",
}

View File

@ -1,4 +1,4 @@
import { DocumentType } from "le-coffre-resources/dist/SuperAdmin"; import { DocumentType } from "le-coffre-resources/dist/Admin";
import BaseAdmin from "../BaseAdmin"; import BaseAdmin from "../BaseAdmin";

View File

@ -0,0 +1,90 @@
import { Contact, Customer } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
import { ECivility } from "le-coffre-resources/dist/Customer/Contact";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetCustomersparams {
where?: {};
include?: {};
}
// TODO Type getbyuid query params
export type IPutCustomersParams = {
uid?: Customer["uid"];
contact?: Customer["contact"];
};
export interface IPostCustomersParams {
first_name: string;
last_name: string;
email: string;
cell_phone_number: string;
civility: ECivility;
address?: Contact["address"];
}
export default class Customers extends BaseNotary {
private static instance: Customers;
private readonly baseURl = this.namespaceUrl.concat("/customers");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
public async get(q: IGetCustomersparams): Promise<Customer[]> {
const url = new URL(this.baseURl);
const query = { q };
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Customer[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Create a Customer
*/
public async post(body: any): Promise<Customer> {
const url = new URL(this.baseURl);
try {
return await this.postRequest<Customer>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<Customer> {
const url = new URL(this.baseURl.concat(`/${uid}`));
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Customer>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async put(uid: string, body: IPutCustomersParams): Promise<Customer> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<Customer>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,84 @@
import { DeedType } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
export type IPutDeedTypesParams = {
uid?: DeedType["uid"];
name?: DeedType["name"];
description?: DeedType["description"];
deed?: DeedType["deed"];
office?: DeedType["office"];
archived_at?: DeedType["archived_at"];
document_types?: DeedType["document_types"];
};
export type IPostDeedTypesParams = {
name?: DeedType["name"];
description?: DeedType["description"];
};
export type IGetDeedTypesParams = {
where?: {};
include?: {};
select?: {};
};
export default class DeedTypes extends BaseNotary {
private static instance: DeedTypes;
private readonly baseURl = this.namespaceUrl.concat("/deed-types");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new DeedTypes();
} else {
return this.instance;
}
}
public async get(q?: IGetDeedTypesParams): Promise<DeedType[]> {
const url = new URL(this.baseURl);
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<DeedType[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<DeedType> {
const url = new URL(this.baseURl.concat(`/${uid}`));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<DeedType>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async put(uid: string, body: IPutDeedTypesParams) {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<DeedType>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async post(body: IPostDeedTypesParams) {
const url = new URL(this.baseURl);
try {
return await this.postRequest<DeedType>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,72 @@
import { Deed, OfficeFolder } from "le-coffre-resources/dist/Notary";
import BaseAdmin from "../BaseNotary";
export type IGetDeedsParams = {
where?: {};
include?: {};
select?: {};
};
export type IPutDeedsParams = {
uid?: OfficeFolder["uid"];
folder_number?: OfficeFolder["folder_number"];
name?: OfficeFolder["name"];
description?: OfficeFolder["description"];
archived_description?: OfficeFolder["archived_description"];
status?: OfficeFolder["status"];
document_types?: Deed["document_types"];
};
export default class Deeds extends BaseAdmin {
private static instance: Deeds;
private readonly baseURl = this.namespaceUrl.concat("/deeds");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new Deeds();
} else {
return this.instance;
}
}
public async get(q: IGetDeedsParams): Promise<Deed[]> {
const url = new URL(this.baseURl);
const query = { q };
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Deed[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<Deed> {
const url = new URL(this.baseURl.concat(`/${uid}`));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Deed>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Update the folder description
*/
public async put(uid: string, body: IPutDeedsParams): Promise<Deed> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<Deed>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,86 @@
import { DocumentType } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetDocumentTypesparams {
where?: {};
include?: {};
}
// TODO Type getbyuid query params
export type IPutDocumentTypesParams = {};
export interface IPostDocumentTypesParams {
name: string;
public_description: string;
private_description: string | null;
office?: {
uid?: string;
};
}
export default class DocumentTypes extends BaseNotary {
private static instance: DocumentTypes;
private readonly baseURl = this.namespaceUrl.concat("/document-types");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
public async get(q: IGetDocumentTypesparams): Promise<DocumentType[]> {
const url = new URL(this.baseURl);
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<DocumentType[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Create a Document
*/
public async post(body: IPostDocumentTypesParams): Promise<DocumentType> {
const url = new URL(this.baseURl);
try {
return await this.postRequest<DocumentType>(url, body as any);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<DocumentType> {
const url = new URL(this.baseURl.concat(`/${uid}`));
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<DocumentType>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async put(uid: string, body: IPutDocumentTypesParams): Promise<DocumentType> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<DocumentType>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,93 @@
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import { Document } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetDocumentsparams {
where?: {};
include?: {};
}
// TODO Type getbyuid query params
export type IPutDocumentsParams = {
document_status?: EDocumentStatus;
refused_reason?: string;
};
export interface IPostDocumentsParams {}
export default class Documents extends BaseNotary {
private static instance: Documents;
private readonly baseURl = this.namespaceUrl.concat("/documents");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
public async get(q: IGetDocumentsparams): Promise<Document[]> {
const url = new URL(this.baseURl);
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Document[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Create a Document
*/
public async post(body: any): Promise<Document> {
const url = new URL(this.baseURl);
try {
return await this.postRequest<Document>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<Document> {
const url = new URL(this.baseURl.concat(`/${uid}`));
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Document>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async put(uid: string, body: IPutDocumentsParams): Promise<Document> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest<Document>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async delete(uid: string): Promise<Document> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.deleteRequest<Document>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,119 @@
import { type OfficeFolder } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetFoldersParams {
q?: {
select?: {};
where?: {};
include?: {};
};
}
export default class Folders extends BaseNotary {
private static instance: Folders;
private readonly baseURl = this.namespaceUrl.concat("/folders");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
/**
* @description : Get all folders
*/
public async get(q: IGetFoldersParams): Promise<OfficeFolder[]> {
const url = new URL(this.baseURl);
Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<OfficeFolder[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Get a folder by uid
*/
public async getByUid(uid: string, q?: any): Promise<OfficeFolder> {
const url = new URL(this.baseURl.concat(`/${uid}`));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<OfficeFolder>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Create a folder
*/
public async post(officeFolder: Partial<OfficeFolder>): Promise<OfficeFolder> {
const url = new URL(this.baseURl);
try {
return await this.postRequest(url, officeFolder);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Update the folder description
*/
public async put(uid: string, body: Partial<OfficeFolder>): Promise<OfficeFolder> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Delete a folder only if the folder don't contains customers
*/
public async delete(uid: string): Promise<OfficeFolder> {
const url = new URL(this.baseURl.concat(`/${uid}`));
const targetedFolder = await this.getByUid(uid);
if (targetedFolder.customers) return Promise.reject(`The folder ${uid} contains customers`);
try {
return await this.deleteRequest(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async archive(uid: string, body: Partial<OfficeFolder>): Promise<OfficeFolder> {
body.status = EFolderStatus.ARCHIVED;
try {
return await this.put(uid, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async restore(uid: string, body: Partial<OfficeFolder>): Promise<OfficeFolder> {
body.status = EFolderStatus.LIVE;
try {
return await this.put(uid, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -1,9 +1,15 @@
import BaseNotary from "../BaseNotary"; import BaseNotary from "../BaseNotary";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Notary";
export type IGetUsersParams = {
where?: {};
include?: {};
select?: {};
};
export default class Users extends BaseNotary { export default class Users extends BaseNotary {
private static instance: Users; private static instance: Users;
private readonly baseURl = this.namespaceUrl.concat("/Users"); private readonly baseURl = this.namespaceUrl.concat("/users");
private constructor() { private constructor() {
super(); super();
@ -17,8 +23,10 @@ export default class Users extends BaseNotary {
} }
} }
public async get(): Promise<User[]> { public async get(q?: IGetUsersParams): Promise<User[]> {
const url = new URL(this.baseURl); const url = new URL(this.baseURl);
const query = { q };
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try { try {
return await this.getRequest<User[]>(url); return await this.getRequest<User[]>(url);
} catch (err) { } catch (err) {
@ -27,23 +35,9 @@ export default class Users extends BaseNotary {
} }
} }
public async getAuthorizationCode(): Promise<any> { public async getByUid(uid: string, q?: any): Promise<User> {
try {
const url = new URL(`https://qual-connexion.idnot.fr/IdPOAuth2/authorize/idnot_idp_v1?
client_id=4501646203F3EF67
&redirect_uri=https://app.stg.lecoffre.smart-chain.fr/
&scope=openid,profile,offline_access
&response_type=code`);
// const url = new URL("https://jsonplaceholder.typicode.com/todos/1");
await this.getRequest(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getByUid(uid: string): Promise<User> {
const url = new URL(this.baseURl.concat("/").concat(uid)); const url = new URL(this.baseURl.concat("/").concat(uid));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try { try {
return await this.getRequest<User>(url); return await this.getRequest<User>(url);
} catch (err) { } catch (err) {
@ -51,14 +45,4 @@ export default class Users extends BaseNotary {
return Promise.reject(err); return Promise.reject(err);
} }
} }
// public async post(params: User): Promise<User> {
// const url = new URL(this.baseURl);
// try {
// return await this.postRequest<User>(url, params);
// } catch (err) {
// this.onError(err);
// return Promise.reject(err);
// }
// }
} }

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 9H11C9.89543 9 9 9.89543 9 11V20C9 21.1046 9.89543 22 11 22H20C21.1046 22 22 21.1046 22 20V11C22 9.89543 21.1046 9 20 9Z" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 15H4C3.46957 15 2.96086 14.7893 2.58579 14.4142C2.21071 14.0391 2 13.5304 2 13V4C2 3.46957 2.21071 2.96086 2.58579 2.58579C2.96086 2.21071 3.46957 2 4 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V5" stroke="#939393" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View File

@ -17,18 +17,18 @@ type IProps = {
export default function BlockList({ blocks, onSelectedBlock }: IProps) { export default function BlockList({ blocks, onSelectedBlock }: IProps) {
const selectBlock = useCallback( const selectBlock = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => { (e: React.MouseEvent<HTMLDivElement>) => {
onSelectedBlock && onSelectedBlock(blocks.find((block) => block.id === e.currentTarget.id)!); onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === e.currentTarget.id)!);
}, },
[blocks, onSelectedBlock], [blocks, onSelectedBlock],
); );
return ( return (
<div> <div>
{blocks.map((block) => { {blocks.map((folder) => {
return ( return (
<div onClick={selectBlock} key={block.id} id={block.id}> <div onClick={selectBlock} key={folder.id} id={folder.id}>
<div className={classes["root"]} data-selected={block.selected.toString()}> <div className={classes["root"]} data-selected={folder.selected.toString()}>
<div className={classes["left-side"]}> <div className={classes["left-side"]}>
<Typography typo={ITypo.P_16}>{block.name}</Typography> <Typography typo={ITypo.P_16}>{folder.name}</Typography>
</div> </div>
<Image alt="chevron" src={ChevronIcon} /> <Image alt="chevron" src={ChevronIcon} />
</div> </div>

View File

@ -41,6 +41,30 @@ type IState = {
loading: boolean; 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> { export default class DepositDocument extends React.Component<IProps, IState> {
private inputRef = React.createRef<HTMLInputElement>(); private inputRef = React.createRef<HTMLInputElement>();
private index = 0; private index = 0;
@ -80,7 +104,13 @@ export default class DepositDocument extends React.Component<IProps, IState> {
onDrop={this.onDragDrop} onDrop={this.onDragDrop}
onDragLeave={this.onDragLeave} onDragLeave={this.onDragLeave}
data-drag-over={this.state.isDragOver.toString()}> data-drag-over={this.state.isDragOver.toString()}>
<input type="file" ref={this.inputRef} hidden onChange={this.onFileChange} /> <input
type="file"
ref={this.inputRef}
hidden
onChange={this.onFileChange}
accept={Object.keys(filesAccepted).join(",")}
/>
<div className={classes["top-container"]}> <div className={classes["top-container"]}>
<div className={classes["left"]}> <div className={classes["left"]}>
<Image src={DepositDocumentIcon} alt="Deposit document" /> <Image src={DepositDocumentIcon} alt="Deposit document" />
@ -121,7 +151,7 @@ export default class DepositDocument extends React.Component<IProps, IState> {
<div className={classes["file-container"]} key={fileObj.name + file.index}> <div className={classes["file-container"]} key={fileObj.name + file.index}>
<div className={classes["left-part"]}> <div className={classes["left-part"]}>
<Image src={DocumentCheckIcon} alt="Document check" /> <Image src={DocumentCheckIcon} alt="Document check" />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}> <Typography typo={ITypo.P_16} color={ITypoColor.GREY} title={file.fileName ?? fileObj.name}>
{this.shortName(file.fileName || fileObj.name)} {this.shortName(file.fileName || fileObj.name)}
</Typography> </Typography>
</div> </div>
@ -281,19 +311,29 @@ export default class DepositDocument extends React.Component<IProps, IState> {
} }
private async onDragDrop(event: React.DragEvent<HTMLDivElement>) { private async onDragDrop(event: React.DragEvent<HTMLDivElement>) {
this.setState({
loading: true,
});
event.preventDefault(); event.preventDefault();
this.setState({ this.setState({
isDragOver: false, isDragOver: false,
}); });
const file = event.dataTransfer.files[0]; const file = event.dataTransfer.files[0];
if (file) this.addFile(file); if (file) this.addFile(file);
else this.setState({ loading: false });
} }
private async addFile(file: File) { private async addFile(file: File) {
const fileAccepted = filesAccepted[file.type];
if (!fileAccepted) {
alert("Ce type de fichier n'est pas accepté");
return false;
}
if (file.size > fileAccepted.size) {
alert("Ce fichier est trop volumineux");
return false;
}
this.setState({
loading: true,
});
const formData = new FormData(); const formData = new FormData();
formData.append("file", file, file.name); formData.append("file", file, file.name);
const query = JSON.stringify({ document: { uid: this.props.document.uid } }); const query = JSON.stringify({ document: { uid: this.props.document.uid } });
@ -302,10 +342,7 @@ export default class DepositDocument extends React.Component<IProps, IState> {
const newFile = await Files.getInstance().post(formData); const newFile = await Files.getInstance().post(formData);
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile]; const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
this.setState({ const newFileList = [
currentFiles: files,
loading: false,
files: [
...this.state.files, ...this.state.files,
{ {
index: this.index++, index: this.index++,
@ -314,10 +351,19 @@ export default class DepositDocument extends React.Component<IProps, IState> {
archived: null, archived: null,
fileName: newFile?.file_name ?? "", fileName: newFile?.file_name ?? "",
}, },
], ];
}); this.setState(
{
currentFiles: files,
loading: false,
files: newFileList,
},
() => {
if (this.props.onChange) this.props.onChange(newFileList.map((file) => file.file));
},
);
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file)); return true;
} }
private async removeFile(e: any) { private async removeFile(e: any) {
@ -336,9 +382,6 @@ export default class DepositDocument extends React.Component<IProps, IState> {
private async onFileChange() { private async onFileChange() {
if (!this.inputRef.current) return; if (!this.inputRef.current) return;
this.setState({
loading: true,
});
const files = this.inputRef.current.files; const files = this.inputRef.current.files;
if (!files) { if (!files) {
this.setState({ loading: false }); this.setState({ loading: false });
@ -346,12 +389,16 @@ export default class DepositDocument extends React.Component<IProps, IState> {
} }
const file = files[0]; const file = files[0];
try {
if (file) this.addFile(file); if (file) this.addFile(file);
else this.setState({ loading: false }); } catch (e) {
console.log(e);
}
} }
private addDocument() { private addDocument() {
if (!this.inputRef.current) return; if (!this.inputRef.current) return;
this.inputRef.current.value = "";
this.inputRef.current.click(); this.inputRef.current.click();
} }

View File

@ -0,0 +1,106 @@
.container {
.root {
padding: 24px;
background-color: var(--white);
border: 1px dashed #e7e7e7;
height: fit-content;
&[data-drag-over="true"] {
border: 1px dashed var(--grey);
}
&.validated {
border: 1px dashed var(--green-flash);
}
.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(--green-flash);
}
.refused-button {
font-size: 14px;
color: var(--re-hover);
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(--red-flash);
margin-top: 8px;
}
}

View File

@ -0,0 +1,303 @@
import DepositDocumentIcon from "@Assets/Icons/deposit-document.svg";
import CrossIcon from "@Assets/Icons/cross.svg";
import DocumentCheckIcon from "@Assets/Icons/document-check.svg";
import PlusIcon from "@Assets/Icons/plus.svg";
import Image from "next/image";
import React from "react";
import Typography, { ITypo, ITypoColor } from "../Typography";
import classes from "./classes.module.scss";
import { Document } from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import classNames from "classnames";
import Button, { EButtonVariant } from "../Button";
import Confirm from "../Modal/Confirm";
import Documents from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
type IProps = {
onChange?: (files: File[]) => void;
open: boolean;
onClose?: () => void;
document: Document;
customer_uid: string;
folder_uid: string | string[];
};
type IFile = {
index: number;
file: File;
fileName: string;
};
type IState = {
files: IFile[];
isDragOver: boolean;
currentFiles?: IFile[];
refusedReason?: string;
isShowRefusedReasonModalVisible: boolean;
isAddDocumentModalVisible: boolean;
};
export default class DepositOtherDocument extends React.Component<IProps, IState> {
private inputRef = React.createRef<HTMLInputElement>();
private index = 0;
public constructor(props: IProps) {
super(props);
this.state = {
isAddDocumentModalVisible: this.props.open,
files: [],
isDragOver: false,
refusedReason: "",
isShowRefusedReasonModalVisible: false,
};
this.addDocument = this.addDocument.bind(this);
this.onFileChange = this.onFileChange.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.onAccept = this.onAccept.bind(this);
}
private async onAccept() {
const filesArray = this.state.currentFiles;
if (!filesArray) return;
const documentCreated = await Documents.getInstance().post({
folder: {
uid: this.props.folder_uid,
},
depositor: {
uid: this.props.customer_uid,
},
});
console.log(documentCreated);
filesArray.forEach(async (file) => {
const formData = new FormData();
formData.append("file", file.file, file.fileName);
const query = JSON.stringify({ document: { uid: documentCreated.uid } });
formData.append("q", query);
const newFile = await Files.getInstance().post(formData);
console.log(newFile);
});
this.props.onClose!();
}
public override render(): JSX.Element {
return (
<Confirm
isOpen={this.state.isAddDocumentModalVisible}
onClose={this.props.onClose!}
onAccept={this.onAccept}
closeBtn
header={"Ajouter un document"}
cancelText={"Annuler"}
confirmText={"Déposer le document"}>
<div className={classes["modal-content"]}>
<div className={classes["container"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Vous souhaitez envoyer un autre document à votre notaire ?
</Typography>
<Typography typo={ITypo.P_16} 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>
<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} />
<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={ITypo.P_SB_16} color={ITypoColor.BLACK} className={classes["title"]}>
<div
className={
this.props.document.document_status === EDocumentStatus.VALIDATED
? classes["validated"]
: ""
}>
{this.props.document.document_type?.name}
</div>
</Typography>
<Typography color={ITypoColor.GREY} typo={ITypo.CAPTION_14}>
Sélectionnez des TEST documents .jpg, .pdf ou .png
</Typography>
</div>
</div>
{this.state.currentFiles && this.state.currentFiles.length > 0 && (
<div className={classes["documents-container"]}>
{this.state.currentFiles.map((file) => {
console.log(file);
const fileObj = file.file;
return (
<div className={classes["file-container"]} key={fileObj.name}>
<div className={classes["left-part"]}>
<Image src={DocumentCheckIcon} alt="Document check" />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
{this.shortName(fileObj.name)}
</Typography>
</div>
<Image
src={CrossIcon}
alt="Cross icon"
className={classes["cross"]}
onClick={this.removeFile}
data-file={file.index}
/>
</div>
);
})}
</div>
)}
<div className={classes["bottom-container"]}>
<Button variant={EButtonVariant.LINE} className={classes["add-button"]} onClick={this.addDocument}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.PINK_FLASH} className={classes["add-document"]}>
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
</Typography>
</Button>
</div>
</div>
</div>
</div>
</Confirm>
);
}
public override componentDidMount(): void {}
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 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 iFile: IFile = {
file: file,
fileName: file.name,
index: this.index++,
};
const tmpArray: IFile[] = this.state.currentFiles || [];
tmpArray.push(iFile);
this.setState({
currentFiles: tmpArray,
});
console.log(this.state.currentFiles);
// const formData = new FormData();
// formData.append("file", file, file.name);
// const query = JSON.stringify({ document: { uid: this.props.document.uid } });
// formData.append("q", query);
// const newFile = await Files.getInstance().post(formData);
// const newFile: FileCustomer = {
// file_name: file.name,
// }
// const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
// this.setState({
// currentFiles: files,
// loading: false,
// files: [
// ...this.state.files,
// {
// index: this.index++,
// file: file,
// uid: newFile.uid!,
// archived: null,
// fileName: newFile?.file_name ?? "",
// },
// ],
// });
// if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
}
private async removeFile(e: any) {
const image = e.target as HTMLElement;
const indexToRemove = image.getAttribute("data-file");
console.log(indexToRemove);
if (!indexToRemove) return;
const file = this.state.currentFiles!.find((file) => file.index === parseInt(indexToRemove));
if (!file) return;
this.setState({
currentFiles: this.state.currentFiles!.filter((file) => file.index !== parseInt(indexToRemove)),
});
if (this.props.onChange) this.props.onChange(this.state.currentFiles!.map((file) => file.file));
}
private async onFileChange() {
if (!this.inputRef.current) return;
const files = this.inputRef.current.files;
if (!files) {
return;
}
const file = files[0];
if (file) this.addFile(file);
}
private addDocument() {
if (!this.inputRef.current) return;
this.inputRef.current.click();
}
// private formatDate(date: Date) {
// const dateToConvert = new Date(date);
// return dateToConvert.toLocaleDateString("fr-FR");
// }
}

View File

@ -9,6 +9,8 @@ import BlockList, { IBlock } from "../BlockList";
import Button from "../Button"; import Button from "../Button";
import SearchBar from "../SearchBar"; import SearchBar from "../SearchBar";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
type IProps = { type IProps = {
folders: OfficeFolder[]; folders: OfficeFolder[];
@ -19,6 +21,7 @@ type IProps = {
type IPropsClass = IProps & { type IPropsClass = IProps & {
router: NextRouter; router: NextRouter;
selectedFolder: string;
}; };
type IState = { type IState = {
@ -52,9 +55,18 @@ class FolderListContainerClass extends React.Component<IPropsClass, IState> {
</div> </div>
{!this.props.isArchived && ( {!this.props.isArchived && (
<div> <div>
<Rules
mode={RulesMode.NECESSARY}
rules={[
{
action: AppRuleActions.create,
name: AppRuleNames.officeFolders,
},
]}>
<Link href={navigatePath}> <Link href={navigatePath}>
<Button fullwidth={true}>Créer un dossier</Button> <Button fullwidth={true}>Créer un dossier</Button>
</Link> </Link>
</Rules>
</div> </div>
)} )}
</div> </div>
@ -85,7 +97,11 @@ class FolderListContainerClass extends React.Component<IPropsClass, IState> {
}); });
return [...pendingFolders, ...otherFolders].map((folder) => { return [...pendingFolders, ...otherFolders].map((folder) => {
return { id: folder.uid!, name: folder.folder_number! + " - " + folder.name!, selected: false }; return {
id: folder.uid!,
name: folder.folder_number! + " - " + folder.name!,
selected: this.props.selectedFolder === folder.uid,
};
}); });
} }
private onSelectedFolder(block: IBlock) { private onSelectedFolder(block: IBlock) {
@ -118,5 +134,6 @@ class FolderListContainerClass extends React.Component<IPropsClass, IState> {
export default function FolderListContainer(props: IProps) { export default function FolderListContainer(props: IProps) {
const router = useRouter(); const router = useRouter();
return <FolderListContainerClass {...props} router={router} />; const { folderUid } = router.query;
return <FolderListContainerClass {...props} router={router} selectedFolder={folderUid as string} />;
} }

View File

@ -16,17 +16,8 @@
&:disabled { &:disabled {
cursor: not-allowed; cursor: not-allowed;
background: white;
opacity: 0.6;
~ .fake-placeholder {
color: rgba($grey, 0.6);
/**
TODO
* 1. Add styles so the placeholder has the same color as the background when disabled
*/
// background: transparent;
}
} }
&:focus { &:focus {
~ .fake-placeholder { ~ .fake-placeholder {
transform: translateY(-35px); transform: translateY(-35px);
@ -91,12 +82,21 @@
} }
&[data-is-errored="true"] { &[data-is-errored="true"] {
.input{ .input {
border: 1px solid var(--red-flash); border: 1px solid var(--red-flash);
~ .fake-placeholder{ ~ .fake-placeholder {
color: var(--red-flash); color: var(--red-flash);
} }
} }
}
.copy-icon {
cursor: pointer;
height: 24px;
width: 24px;
position: absolute;
top: 50%;
right: 24px;
transform: translate(0, -50%);
} }
} }

View File

@ -1,15 +1,19 @@
import React from "react"; import React from "react";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import { ReactNode } from "react"; import { ReactNode } from "react";
import CopyIcon from "@Assets/Icons/copy.svg";
import BaseField, { IProps as IBaseFieldProps } from "../BaseField"; import BaseField, { IProps as IBaseFieldProps } from "../BaseField";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import classnames from "classnames"; import classnames from "classnames";
import Image from "next/image";
export type IProps = IBaseFieldProps & {}; export type IProps = IBaseFieldProps & {
canCopy?: boolean;
password?: boolean;
};
export default class TextField extends BaseField<IProps> { export default class TextField extends BaseField<IProps> {
constructor(props: IProps){ constructor(props: IProps) {
super(props); super(props);
this.state = this.getDefaultState(); this.state = this.getDefaultState();
} }
@ -28,13 +32,26 @@ export default class TextField extends BaseField<IProps> {
onFocus={this.onFocus} onFocus={this.onFocus}
onBlur={this.onBlur} onBlur={this.onBlur}
name={this.props.name} name={this.props.name}
disabled={this.props.disabled}
type={this.props.password ? "password" : "text"}
/> />
<div className={classes["fake-placeholder"]}> <div className={classes["fake-placeholder"]}>
{this.props.placeholder} {!this.props.required && " (Facultatif)"} {this.props.placeholder} {!this.props.required && " (Facultatif)"}
</div> </div>
{this.props.canCopy && (
<div className={classes["copy-icon"]} onClick={this.onCopyClick}>
<Image src={CopyIcon} alt="Copy icon" />
</div>
)}
</div> </div>
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>} {this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</Typography> </Typography>
); );
} }
private onCopyClick = (): void => {
if (this.props.canCopy) {
navigator.clipboard.writeText(this.state.value ?? "");
}
};
} }

View File

@ -3,6 +3,12 @@
gap: 16px; gap: 16px;
cursor: pointer; cursor: pointer;
width: fit-content; width: fit-content;
.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.switch-container { .switch-container {
position: relative; position: relative;
width: 46px; width: 46px;

View File

@ -7,8 +7,9 @@ type IProps = {
onChange?: (checked: boolean) => void; onChange?: (checked: boolean) => void;
checked?: boolean; checked?: boolean;
label: string; label: string;
disabled?: boolean;
}; };
export default function Switch({ onChange, checked, label }: IProps) { export default function Switch({ onChange, checked, label, disabled }: IProps) {
const [isChecked, setIsChecked] = useState<boolean>(checked ? checked : false); const [isChecked, setIsChecked] = useState<boolean>(checked ? checked : false);
useEffect(() => { useEffect(() => {
@ -21,10 +22,12 @@ export default function Switch({ onChange, checked, label }: IProps) {
}, [isChecked, onChange]); }, [isChecked, onChange]);
return ( return (
<div className={classes["root"]} onClick={handleChange}> <div className={classes["root"]}>
<div className={disabled ? classes["disabled"] : classes["root"]} onClick={disabled ? () => {} : handleChange}>
<div className={classes["switch-container"]} data-checked={isChecked.toString()}> <div className={classes["switch-container"]} data-checked={isChecked.toString()}>
<div className={classes["round"]} /> <div className={classes["round"]} />
</div> </div>
</div>
<Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}> <Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}>
{label} {label}
</Typography> </Typography>

View File

@ -7,6 +7,7 @@ type IProps = {
children: React.ReactNode; children: React.ReactNode;
color?: ITypoColor; color?: ITypoColor;
className?: string; className?: string;
title?: string;
}; };
type IState = {}; type IState = {};
@ -50,7 +51,8 @@ export default class Typography extends React.Component<IProps, IState> {
classes[this.props.typo], classes[this.props.typo],
classes[this.props.color ?? ""], classes[this.props.color ?? ""],
this.props.className ?? "", this.props.className ?? "",
)}> )}
title={this.props.title}>
{this.props.children} {this.props.children}
</div> </div>
); );

View File

@ -0,0 +1,45 @@
import React, { useCallback, useEffect } from "react";
import { useRouter } from "next/router";
import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService";
import { IAppRule } from "@Front/Api/Entities/rule";
export enum RulesMode {
OPTIONAL = "optional",
NECESSARY = "necessary",
}
type IProps = {
isPage?: boolean;
mode: RulesMode;
rules: IAppRule[];
no?: boolean;
children: JSX.Element;
};
export default function Rules(props: IProps) {
const router = useRouter();
const getShowValue = useCallback(() => {
if (props.mode === RulesMode.NECESSARY) {
return props.rules.every((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
}
return !!props.rules.find((rule) => JwtService.getInstance().hasRule(rule.name, rule.action));
}, [props.mode, props.rules]);
const show = getShowValue();
const [isShowing, setIsShowing] = React.useState(props.no ? !show : show);
useEffect(() => {
setIsShowing(props.no ? !show : show);
}, [props.no, show]);
if (!isShowing && props.isPage) {
router.push(Module.getInstance().get().modules.pages.Home.props.path);
}
if (!JwtService.getInstance().decodeJwt() || !isShowing) {
return null;
}
return props.children;
}

View File

@ -7,7 +7,7 @@ import BackArrow from "@Front/Components/Elements/BackArrow";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import WindowStore from "@Front/Stores/WindowStore"; import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames"; import classNames from "classnames";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/Admin";
import Image from "next/image"; import Image from "next/image";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
@ -91,7 +91,7 @@ export default class DefaultCollaboratorDashboard extends React.Component<IProps
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const query: IGetUsersparams = { const query: IGetUsersparams = {
where: { office_uid: jwt!.office_Id }, where: { office_uid: jwt.office_Id },
include: { contact: true }, include: { contact: true },
}; };

View File

@ -1,12 +1,12 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import ChevronIcon from "@Assets/Icons/chevron.svg";
import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Admin/DeedTypes/DeedTypes"; import DeedTypes, { IGetDeedTypesParams } from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header"; import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version"; import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore"; import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames"; import classNames from "classnames";
import { Deed, DeedType } from "le-coffre-resources/dist/Admin"; import { Deed, DeedType } from "le-coffre-resources/dist/Notary";
import Image from "next/image"; import Image from "next/image";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";

View File

@ -1,5 +1,5 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import ChevronIcon from "@Assets/Icons/chevron.svg";
import DocumentTypes from "@Front/Api/LeCoffreApi/SuperAdmin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Header from "@Front/Components/DesignSystem/Header"; import Header from "@Front/Components/DesignSystem/Header";
import Version from "@Front/Components/DesignSystem/Version"; import Version from "@Front/Components/DesignSystem/Version";

View File

@ -1,5 +1,5 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import ChevronIcon from "@Assets/Icons/chevron.svg";
import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders, { IGetFoldersParams } from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import FolderListContainer from "@Front/Components/DesignSystem/FolderListContainer"; import FolderListContainer from "@Front/Components/DesignSystem/FolderListContainer";
import Header from "@Front/Components/DesignSystem/Header"; import Header from "@Front/Components/DesignSystem/Header";

View File

@ -6,7 +6,7 @@ import Version from "@Front/Components/DesignSystem/Version";
import BackArrow from "@Front/Components/Elements/BackArrow"; import BackArrow from "@Front/Components/Elements/BackArrow";
import WindowStore from "@Front/Stores/WindowStore"; import WindowStore from "@Front/Stores/WindowStore";
import classNames from "classnames"; import classNames from "classnames";
import User from "le-coffre-resources/dist/Notary"; import User from "le-coffre-resources/dist/SuperAdmin";
import Image from "next/image"; import Image from "next/image";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";

View File

@ -25,8 +25,17 @@
} }
} }
.folder-number {
margin-top: 16px;
}
.office-name {
margin-top: 8px;
text-transform: uppercase;
}
.subtitle { .subtitle {
margin: 32px 0; margin: 64px 0 32px 0;
} }
} }
@ -43,9 +52,6 @@
@media (max-width: $screen-s) { @media (max-width: $screen-s) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
.component-to-replace { .component-to-replace {
@ -78,9 +84,6 @@
background-color: var(--grey-soft); background-color: var(--grey-soft);
height: 98px; height: 98px;
width: 100%; width: 100%;
} }
} }
} }

View File

@ -3,16 +3,16 @@ import Customers from "@Front/Api/LeCoffreApi/Customer/Customers/Customers";
import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents"; import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/Documents/Documents";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument"; import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer"; import Customer, { Document, DocumentType, OfficeFolder } from "le-coffre-resources/dist/Customer";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
type IProps = {}; type IProps = {};
@ -21,9 +21,12 @@ export default function ClientDashboard(props: IProps) {
let { folderUid } = router.query; let { folderUid } = router.query;
const [documents, setDocuments] = useState<Document[] | null>(null); const [documents, setDocuments] = useState<Document[] | null>(null);
const [customer, setCustomer] = useState<Customer | null>(null); const [customer, setCustomer] = useState<Customer | null>(null);
const [folder, setFolder] = useState<OfficeFolder | null>(null);
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false); const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const onCloseModalAddDocument = useCallback(() => { const onCloseModalAddDocument = useCallback(() => {
console.log("Closing");
setIsAddDocumentModalVisible(false); setIsAddDocumentModalVisible(false);
}, []); }, []);
@ -55,6 +58,8 @@ export default function ClientDashboard(props: IProps) {
const documentList = await Documents.getInstance().get(query); const documentList = await Documents.getInstance().get(query);
const folder = await Folders.getInstance().getByUid(folderUid as string, { q: { office: true } });
setFolder(folder);
setDocuments(documentList); setDocuments(documentList);
setCustomer(actualCustomer); setCustomer(actualCustomer);
} }
@ -63,6 +68,7 @@ export default function ClientDashboard(props: IProps) {
}, [folderUid]); }, [folderUid]);
const renderHeader = useCallback(() => { const renderHeader = useCallback(() => {
console.log("Dossier : ", customer);
return ( return (
<div className={classes["header"]}> <div className={classes["header"]}>
<div className={classes["text-container"]}> <div className={classes["text-container"]}>
@ -71,6 +77,14 @@ export default function ClientDashboard(props: IProps) {
Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)} Bonjour {customer?.contact?.first_name.concat(" ", customer?.contact?.last_name)}
</Typography> </Typography>
<Typography typo={ITypo.P_SB_18} className={classes["folder-number"]} color={ITypoColor.GREY}>
Dossier {folder?.folder_number} - {folder?.name}
</Typography>
<Typography typo={ITypo.P_SB_18} className={classes["office-name"]} color={ITypoColor.GREY}>
{folder?.office?.name}
</Typography>
<Typography typo={ITypo.H2} className={classes["subtitle"]}> <Typography typo={ITypo.H2} className={classes["subtitle"]}>
Documents à envoyer Documents à envoyer
</Typography> </Typography>
@ -89,6 +103,24 @@ export default function ClientDashboard(props: IProps) {
); );
}, [customer]); }, [customer]);
const renderBox = useCallback(() => {
console.log(isAddDocumentModalVisible);
return (
<DepositOtherDocument
folder_uid={folderUid!}
customer_uid={customer!.uid!}
open={isAddDocumentModalVisible}
onClose={onCloseModalAddDocument}
document={Document.hydrate<Document>({
document_type: DocumentType.hydrate<DocumentType>({
name: "Document annexe",
}),
})}
/>
);
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument]);
return ( return (
<DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}> <DefaultTemplate title={"Mon compte"} isPadding={false} hasHeaderLinks={false}>
<div className={classes["root"]}> <div className={classes["root"]}>
@ -107,33 +139,8 @@ export default function ClientDashboard(props: IProps) {
Ajouter d'autres documents Ajouter d'autres documents
</Button> </Button>
</div> </div>
<Confirm
isOpen={isAddDocumentModalVisible}
onClose={onCloseModalAddDocument}
onAccept={onOpenModalAddDocument}
closeBtn
header={"Ajouter un document"}
cancelText={"Annuler"}
confirmText={"Déposer le document"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Vous souhaitez envoyer un autre document à votre notaire ?
</Typography>
<TextField placeholder="Nom du document" />
<Typography typo={ITypo.P_16} 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: "Document annexe",
}),
})}
/>
</div>
</Confirm>
</div> </div>
{isAddDocumentModalVisible && renderBox()}
</DefaultTemplate> </DefaultTemplate>
); );
} }

View File

@ -117,12 +117,6 @@ export default function DeedTypesInformations(props: IProps) {
</div> </div>
<div className={classes["deed-type-container"]}> <div className={classes["deed-type-container"]}>
<div className={classes["infos"]}> <div className={classes["infos"]}>
<div className={classes["box"]}>
<Typography typo={ITypo.NAV_INPUT_16} className={classes["box-title"]} color={ITypoColor.BLACK}>
Nom du type d'acte
</Typography>
<Typography typo={ITypo.P_18}>{deedTypeSelected?.name}</Typography>
</div>
<div className={classNames(classes["middle-box"], classes["box"])}> <div className={classNames(classes["middle-box"], classes["box"])}>
<Typography typo={ITypo.NAV_INPUT_16} className={classes["box-title"]} color={ITypoColor.BLACK}> <Typography typo={ITypo.NAV_INPUT_16} className={classes["box-title"]} color={ITypoColor.BLACK}>
Description Description

View File

@ -1,4 +1,4 @@
import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
@ -8,7 +8,7 @@ import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/Def
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService"; import JwtService from "@Front/Services/JwtService/JwtService";
import { validateOrReject, ValidationError } from "class-validator"; import { validateOrReject, ValidationError } from "class-validator";
import { DocumentType, Office } from "le-coffre-resources/dist/Admin"; import { DocumentType, Office } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@ -24,11 +24,12 @@ export default function DocumentTypesCreate(props: IProps) {
try { try {
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeJwt();
if (!jwt) return; if (!jwt) return;
const office = Office.hydrate<Office>({
uid: jwt.office_Id,
});
const documentToCreate = DocumentType.hydrate<DocumentType>({ const documentToCreate = DocumentType.hydrate<DocumentType>({
...values, ...values,
office: Office.hydrate<Office>({ office: office
uid: jwt.office_Id,
}),
}); });
await validateOrReject(documentToCreate, { groups: ["createDocumentType"] }); await validateOrReject(documentToCreate, { groups: ["createDocumentType"] });
const documentTypeCreated = await DocumentTypes.getInstance().post(documentToCreate); const documentTypeCreated = await DocumentTypes.getInstance().post(documentToCreate);

View File

@ -1,4 +1,4 @@
import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
@ -7,7 +7,7 @@ import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard"; import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { validateOrReject } from "class-validator"; import { validateOrReject } from "class-validator";
import { DocumentType } from "le-coffre-resources/dist/Admin"; import { DocumentType } from "le-coffre-resources/dist/Notary";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";

View File

@ -1,9 +1,9 @@
import PenICon from "@Assets/Icons/pen.svg"; import PenICon from "@Assets/Icons/pen.svg";
import DocumentTypes from "@Front/Api/LeCoffreApi/Admin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard"; import DefaultDocumentTypesDashboard from "@Front/Components/LayoutTemplates/DefaultDocumentTypesDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { DocumentType } from "le-coffre-resources/dist/Admin"; import { DocumentType } from "le-coffre-resources/dist/Notary";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";

View File

@ -1,5 +1,5 @@
import Customers from "@Front/Api/LeCoffreApi/SuperAdmin/Customers/Customers"; import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; import { IOption } from "@Front/Components/DesignSystem/Form/SelectField";

View File

@ -1,8 +1,8 @@
import PlusIcon from "@Assets/Icons/plus.svg"; import PlusIcon from "@Assets/Icons/plus.svg";
import Deeds from "@Front/Api/LeCoffreApi/SuperAdmin/Deeds/Deeds"; import Deeds from "@Front/Api/LeCoffreApi/Notary/Deeds/Deeds";
import Documents from "@Front/Api/LeCoffreApi/SuperAdmin/Documents/Documents"; import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import DocumentTypes from "@Front/Api/LeCoffreApi/SuperAdmin/DocumentTypes/DocumentTypes"; import DocumentTypes from "@Front/Api/LeCoffreApi/Notary/DocumentTypes/DocumentTypes";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";

View File

@ -1,7 +1,7 @@
import backgroundImage from "@Assets/images/404-background-image.jpeg"; import backgroundImage from "@Assets/images/404-background-image.jpeg";
import DeedTypes from "@Front/Api/LeCoffreApi/SuperAdmin/DeedTypes/DeedTypes"; import DeedTypes from "@Front/Api/LeCoffreApi/Notary/DeedTypes/DeedTypes";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Users from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import Button from "@Front/Components/DesignSystem/Button"; import Button from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; import SelectField, { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
@ -152,13 +152,8 @@ class CreateFolderClass extends BasePage<IPropsClass, IState> {
public override async componentDidMount() { public override async componentDidMount() {
const deedTypes = await DeedTypes.getInstance().get(); const deedTypes = await DeedTypes.getInstance().get();
// no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller
// TODO SETUP userStore and get the user's office membership -> Replace IwJ70M471c by the user's office membership uid
const usersMock = await Users.getInstance().get({ include: { office_membership: true } });
const userMock = usersMock[0];
// -------------------
const collaborators = await Users.getInstance().get({ const collaborators = await Users.getInstance().get({
where: { office_membership: { uid: userMock?.office_membership?.uid } },
include: { contact: true }, include: { contact: true },
}); });
this.setState({ this.setState({

View File

@ -1,5 +1,5 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import ChevronIcon from "@Assets/Icons/chevron.svg";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation"; import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";

View File

@ -1,4 +1,4 @@
import Customers from "@Front/Api/LeCoffreApi/SuperAdmin/Customers/Customers"; import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import TextField from "@Front/Components/DesignSystem/Form/TextField";

View File

@ -1,5 +1,5 @@
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Users, { IGetUsersparams } from "@Front/Api/LeCoffreApi/SuperAdmin/Users/Users"; import Users, { IGetUsersParams } from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; import { IOption } from "@Front/Components/DesignSystem/Form/SelectField";
@ -127,10 +127,8 @@ class UpdateFolderCollaboratorsClass extends BasePage<IPropsClass, IState> {
return; return;
} }
const userQuery: IGetUsersparams = { // no need to pass query 'where' param here, default query for notaries include only users which are in the same office as the caller
where: { const userQuery: IGetUsersParams = {
office_uid: folder.office?.uid,
},
include: { include: {
contact: { contact: {
select: { select: {
@ -142,6 +140,7 @@ class UpdateFolderCollaboratorsClass extends BasePage<IPropsClass, IState> {
}; };
const availableCollaborators = await Users.getInstance().get(userQuery); const availableCollaborators = await Users.getInstance().get(userQuery);
console.log(availableCollaborators)
this.setState({ availableCollaborators }); this.setState({ availableCollaborators });
} }

View File

@ -1,4 +1,4 @@
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";

View File

@ -1,4 +1,4 @@
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form"; import Form from "@Front/Components/DesignSystem/Form";
import Select, { IOption } from "@Front/Components/DesignSystem/Form/SelectField"; import Select, { IOption } from "@Front/Components/DesignSystem/Form/SelectField";

View File

@ -1,6 +1,6 @@
import LeftArrowIcon from "@Assets/Icons/left-arrow.svg"; import LeftArrowIcon from "@Assets/Icons/left-arrow.svg";
import RightArrowIcon from "@Assets/Icons/right-arrow.svg"; import RightArrowIcon from "@Assets/Icons/right-arrow.svg";
import Documents from "@Front/Api/LeCoffreApi/SuperAdmin/Documents/Documents"; import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif"; import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
@ -9,8 +9,8 @@ import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import { Document, File } from "le-coffre-resources/dist/Customer"; import { Document, File } from "le-coffre-resources/dist/Notary";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document";
import Image from "next/image"; import Image from "next/image";
import { NextRouter, useRouter } from "next/router"; import { NextRouter, useRouter } from "next/router";
import React from "react"; import React from "react";

View File

@ -1,5 +1,5 @@
import ChevronIcon from "@Assets/Icons/chevron.svg"; import ChevronIcon from "@Assets/Icons/chevron.svg";
import Folders from "@Front/Api/LeCoffreApi/SuperAdmin/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation"; import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation";
import QuantityProgressBar from "@Front/Components/DesignSystem/QuantityProgressBar"; import QuantityProgressBar from "@Front/Components/DesignSystem/QuantityProgressBar";

View File

@ -19,7 +19,10 @@ export default function Login() {
const redirectUserOnConnection = useCallback(() => { const redirectUserOnConnection = useCallback(() => {
async function getUser() { async function getUser() {
try { try {
// Super admin
await UserStore.instance.connect("jelkvelknvlkn"); await UserStore.instance.connect("jelkvelknvlkn");
// Notaire
// await UserStore.instance.connect("ljfeflecnmd");
await JwtService.getInstance().checkJwt(); await JwtService.getInstance().checkJwt();
router.push(Module.getInstance().get().modules.pages.Folder.props.path); router.push(Module.getInstance().get().modules.pages.Folder.props.path);
} catch (e) { } catch (e) {

View File

@ -6,11 +6,23 @@ import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import React from "react"; import React from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import User from "le-coffre-resources/dist/Notary";
import Users from "@Front/Api/LeCoffreApi/Notary/Users/Users";
import JwtService from "@Front/Services/JwtService/JwtService";
type IProps = {}; type IProps = {};
type IState = {}; type IState = {
user: User | null;
};
export default class MyAccount extends Base<IProps, IState> { export default class MyAccount extends Base<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
user: null,
};
}
public override render(): JSX.Element { public override render(): JSX.Element {
return ( return (
<DefaultTemplate title={"Mon compte"}> <DefaultTemplate title={"Mon compte"}>
@ -25,19 +37,33 @@ export default class MyAccount extends Base<IProps, IState> {
</Typography> </Typography>
<Form onSubmit={this.onFormSubmit}> <Form onSubmit={this.onFormSubmit}>
<div className={classes["form-container"]}> <div className={classes["form-container"]}>
<TextField name="name" placeholder="Nom" defaultValue={"BIHR"} disabled /> <TextField
<TextField name="surname" placeholder="Prénom" defaultValue={"Nicolas"} disabled /> name="name"
placeholder="Nom"
defaultValue={this.state.user?.contact?.last_name}
disabled
canCopy
/>
<TextField
name="surname"
placeholder="Prénom"
defaultValue={this.state.user?.contact?.first_name}
disabled
canCopy
/>
<TextField <TextField
name="email" name="email"
placeholder="E-mail" placeholder="E-mail"
defaultValue={"nicolas.bihr@notaires.fr"} defaultValue={this.state.user?.contact?.email}
disabled disabled
canCopy
/> />
<TextField <TextField
name="phone" name="phone"
placeholder="Numéro de téléphone" placeholder="Numéro de téléphone"
defaultValue={"06 74 83 90 23"} defaultValue={this.state.user?.contact?.phone_number as string}
disabled disabled
canCopy
/> />
</div> </div>
</Form> </Form>
@ -51,21 +77,34 @@ export default class MyAccount extends Base<IProps, IState> {
<TextField <TextField
name="office_denomination" name="office_denomination"
placeholder="Dénomination de l'office" placeholder="Dénomination de l'office"
defaultValue="Etude Office notarial du Cormier" defaultValue={this.state.user?.office_membership?.name}
disabled disabled
canCopy
/>
<TextField
name="crpcen"
placeholder="CRPCEN"
defaultValue={this.state.user?.office_membership?.crpcen}
disabled
canCopy
/> />
<TextField name="crpcen" placeholder="CRPCEN" defaultValue="35137" disabled />
<TextField <TextField
name="cp_address" name="cp_address"
placeholder="Adresse CP" placeholder="Adresse CP"
defaultValue="2 RUE DE RENNES" defaultValue={this.state.user?.office_membership?.address?.address}
disabled disabled
canCopy
/> />
<TextField <TextField
name="city" name="city"
placeholder="Ville" placeholder="Ville"
defaultValue="35140 ST AUBIN DU CORMIER" defaultValue={
this.state.user?.office_membership?.address?.zip_code +
" - " +
this.state.user?.office_membership?.address?.city
}
disabled disabled
canCopy
/> />
</div> </div>
</Form> </Form>
@ -76,10 +115,29 @@ export default class MyAccount extends Base<IProps, IState> {
); );
} }
public override async componentDidMount() {
const jwtUncoded = JwtService.getInstance().decodeCustomerJwt();
console.log(jwtUncoded);
if (!jwtUncoded) return;
const user = await Users.getInstance().getByUid(jwtUncoded.userId, {
q: {
office_membership: {
include: {
address: true,
},
},
contact: true,
},
});
if (!user) return;
this.setState({
user,
});
}
private onFormSubmit( private onFormSubmit(
e: React.FormEvent<HTMLFormElement> | null, e: React.FormEvent<HTMLFormElement> | null,
values: { values: {
[key: string]: string; [key: string]: string;
} },
) {} ) {}
} }

View File

@ -0,0 +1,29 @@
@import "@Themes/constants.scss";
.root {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
max-width: 530px;
margin: auto;
.title {
margin: 32px 0;
text-align: center;
@media (max-width: $screen-s) {
font-family: 48px;
}
}
.forget-password {
margin-top: 32px;
margin-bottom: 8px;
}
.form {
display: flex;
}
}

View File

@ -0,0 +1,57 @@
import Module from "@Front/Config/Module";
import { useRouter } from "next/router";
import { useState } from "react";
import Image from "next/image";
import classes from "./classes.module.scss";
import LandingImage from "./landing-connect.jpeg";
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import CoffreIcon from "@Assets/Icons/coffre.svg";
import TextField from "@Front/Components/DesignSystem/Form/TextField";
import Button from "@Front/Components/DesignSystem/Button";
export default function Protect() {
const [password, setPassword] = useState("");
const router = useRouter();
const setPasswordFromInput = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
setPassword(event.target.value);
};
const submitAuth = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (password === "team-fullstack") {
console.log("ok");
setCookie("protect_staging", Date.now().toString());
router.push(Module.getInstance().get().modules.pages.Login.props.path);
} else {
console.log("pas ok");
}
};
const setCookie = (name: string, value: string) => {
if (!name || !value) throw new Error("Cookie name or value is empty");
const date = new Date();
// Set it expire in 7 days
date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
// Set it
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
};
return (
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
<div className={classes["root"]}>
<Image alt="coffre" src={CoffreIcon} />
<Typography typo={ITypo.H1}>
<div className={classes["title"]}>Le site est verrouillé</div>
</Typography>
<form onSubmit={submitAuth} className={classes["form"]}>
<TextField placeholder="Password" onChange={setPasswordFromInput} password />
<Button type="submit">Submit</Button>
</form>
</div>
</DefaultDoubleSidePage>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -16,15 +16,16 @@ export default function SelectFolder() {
useEffect(() => { useEffect(() => {
async function getFolders() { async function getFolders() {
const jwt = JwtService.getInstance().decodeJwt(); const jwt = JwtService.getInstance().decodeCustomerJwt();
if (!jwt) return; if (!jwt) return;
console.log(jwt)
const folders = await Folders.getInstance().get({ const folders = await Folders.getInstance().get({
q: { q: {
where: { where: {
customers: { customers: {
some: { some: {
uid: jwt.userId, uid: jwt.customerId,
}, },
}, },
}, },
@ -37,8 +38,8 @@ export default function SelectFolder() {
}, []); }, []);
const handleSelectBlock = useCallback( const handleSelectBlock = useCallback(
(block: IBlock) => { (folder: IBlock) => {
router.push("/client-dashboard/" + block.id); router.push("/client-dashboard/" + folder.id);
}, },
[router], [router],
); );

View File

@ -81,7 +81,7 @@ export default function UserInformations(props: IProps) {
useEffect(() => { useEffect(() => {
if (!userSelected) return; if (!userSelected) return;
setCurrentAppointment(userSelected?.appointment?.find((appointment) => appointment.status === EAppointmentStatus.OPEN) ?? null); setCurrentAppointment(userSelected?.appointment?.find((appointment) => appointment.status === EAppointmentStatus.OPEN && appointment.votes?.length != 0) ?? null);
}, [userSelected]); }, [userSelected]);
/** Functions for the admin modal */ /** Functions for the admin modal */
@ -282,7 +282,7 @@ export default function UserInformations(props: IProps) {
</div> </div>
<div className={classes["second-line"]}> <div className={classes["second-line"]}>
<Switch label="Admin de son office" checked={isAdminChecked} onChange={handleAdminChanged} /> <Switch label="Admin de son office" checked={isAdminChecked} onChange={handleAdminChanged} />
<Switch label="Super-admin LeCoffre.io" checked={isSuperAdminChecked} onChange={handleSuperAdminChanged} /> <Switch label="Super-admin LeCoffre.io" checked={isSuperAdminChecked} disabled={userHasVoted()} onChange={handleSuperAdminChanged} />
{currentAppointment && ( {currentAppointment && (
<div className={classes["votes-block"]}> <div className={classes["votes-block"]}>
<div className={classes["left"]}> <div className={classes["left"]}>

View File

@ -6,7 +6,7 @@ enum PROVIDER_OPENID {
idNot = "idNot", idNot = "idNot",
} }
interface IUserJwtPayload { export interface IUserJwtPayload {
userId: string; userId: string;
email: string | null; email: string | null;
openId: { openId: {
@ -19,6 +19,11 @@ interface IUserJwtPayload {
exp: number; exp: number;
} }
export interface ICustomerJwtPayload {
userId: string;
email: string;
}
export default class JwtService { export default class JwtService {
private static instance: JwtService; private static instance: JwtService;
private constructor() {} private constructor() {}
@ -33,6 +38,12 @@ export default class JwtService {
return jwt_decode(accessToken); return jwt_decode(accessToken);
} }
public decodeCustomerJwt(): ICustomerJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return;
return jwt_decode(accessToken);
}
/** /**
* @description : set a cookie with a name and a value that expire in 7 days * @description : set a cookie with a name and a value that expire in 7 days
* @throws {Error} If the name or the value is empty * @throws {Error} If the name or the value is empty
@ -55,4 +66,12 @@ export default class JwtService {
} }
} }
} }
public hasRule(name: string, action: string) {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return false;
const decodedToken = this.decodeJwt();
if (!decodedToken) return false;
return decodedToken?.rules?.some((rule: string) => rule === `${action} ${name}`);
}
} }

View File

@ -4,6 +4,7 @@ import User from "@Front/Api/Auth/IdNot/User";
import Customer from "@Front/Api/Auth/franceConnect/Customer"; import Customer from "@Front/Api/Auth/franceConnect/Customer";
import CookieService from "@Front/Services/CookieService/CookieService"; import CookieService from "@Front/Services/CookieService/CookieService";
import EventEmitter from "@Front/Services/EventEmitter"; import EventEmitter from "@Front/Services/EventEmitter";
import JwtService from "@Front/Services/JwtService/JwtService";
export default class UserStore { export default class UserStore {
public static readonly instance = new this(); public static readonly instance = new this();
@ -17,6 +18,11 @@ export default class UserStore {
return !!this.accessToken; return !!this.accessToken;
} }
public getRole(): string | undefined {
const decodedPayload = JwtService.getInstance().decodeJwt();
return decodedPayload?.role;
}
public async connect(idnotUid: string) { public async connect(idnotUid: string) {
try { try {
//call connection function //call connection function

61
src/middleware.ts Normal file
View File

@ -0,0 +1,61 @@
import { ICustomerJwtPayload, IUserJwtPayload } from "@Front/Services/JwtService/JwtService";
import jwt_decode from "jwt-decode";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
const cookieStaging = request.cookies.get("protect_staging");
if (!cookieStaging) return NextResponse.redirect(new URL("/protect", request.url));
// Get the JWT from the cookies
const cookies = request.cookies.get("leCoffreAccessToken");
if (!cookies) return NextResponse.redirect(new URL("/login", request.url));
// Decode it
const userDecodedToken = jwt_decode(cookies.value) as IUserJwtPayload;
const customerDecodedToken = jwt_decode(cookies.value) as ICustomerJwtPayload;
// If no JWT provided, redirect to login page
if (!userDecodedToken && !customerDecodedToken) return NextResponse.redirect(new URL("/login", request.url));
// If JWT expired, redirect to login page
const token = userDecodedToken ?? customerDecodedToken;
const now = Math.floor(Date.now() / 1000);
if (token.exp < now) {
return NextResponse.redirect(new URL("/login", request.url));
}
const requestUrlPath = request.nextUrl.pathname;
if (
requestUrlPath.startsWith("/collaborators") ||
requestUrlPath.startsWith("/deed-types") ||
requestUrlPath.startsWith("/customer") ||
requestUrlPath.startsWith("/offices") ||
requestUrlPath.startsWith("/roles") ||
requestUrlPath.startsWith("/users")
) {
if (userDecodedToken.role !== "admin" && userDecodedToken.role !== "super-admin")
return NextResponse.redirect(new URL("/404", request.url));
}
if ((requestUrlPath.startsWith("/my-account") || requestUrlPath.startsWith("/document-types")) && !userDecodedToken)
return NextResponse.redirect(new URL("/404", request.url));
if (requestUrlPath.startsWith("/client-dashboard") && !customerDecodedToken) return NextResponse.redirect(new URL("/404", request.url));
return NextResponse.next();
}
export const config = {
matcher: [
"/client-dashboard/:path*",
"/collaborators/:path*",
"/customer/:path*",
"/document-types/:path*",
"/deed-types/:path*",
"/folders/:path*",
"/my-account/:path*",
"/offices/:path*",
"/roles/:path*",
"/users/:path*",
"/",
],
};

5
src/pages/protect.tsx Normal file
View File

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