Merge branch 'dev' into staging

This commit is contained in:
Maxime Lalo 2024-04-05 12:07:50 +02:00
commit a272a9ad2a
24 changed files with 188 additions and 122 deletions

View File

@ -19,7 +19,7 @@ export default class Auth extends BaseApiService {
try {
return await fetch(url);
} catch (err) {
console.log(err);
console.error(err);
this.onError(err);
return Promise.reject(err);
}
@ -27,25 +27,28 @@ export default class Auth extends BaseApiService {
public async loginWithIdNot() {
const variables = FrontendVariables.getInstance();
const url = new URL(`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${variables.FRONT_APP_HOST}/authorized-client&scope=openid,profile&response_type=code`);
const url = new URL(
`${variables.IDNOT_BASE_URL + variables.IDNOT_AUTHORIZE_ENDPOINT}?client_id=${variables.IDNOT_CLIENT_ID}&redirect_uri=${
variables.FRONT_APP_HOST
}/authorized-client&scope=openid,profile&response_type=code`,
);
try {
return await this.getRequest(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getIdnotJwt(autorizationCode: string | string[]): Promise<{accessToken: string, refreshToken: string}> {
const variables = FrontendVariables.getInstance();
const baseBackUrl = variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
const url = new URL(`${baseBackUrl}/api/v1/idnot/user/${autorizationCode}`);
try {
return await this.postRequest<{accessToken: string, refreshToken: string}>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
public async getIdnotJwt(autorizationCode: string | string[]): Promise<{ accessToken: string; refreshToken: string }> {
const variables = FrontendVariables.getInstance();
const baseBackUrl = variables.BACK_API_PROTOCOL + variables.BACK_API_HOST;
const url = new URL(`${baseBackUrl}/api/v1/idnot/user/${autorizationCode}`);
try {
return await this.postRequest<{ accessToken: string; refreshToken: string }>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -14,6 +14,11 @@ export type IGetClientPortalSessionResponse = {
url: string;
};
export interface IGetCustomerBySubscriptionIdParams {
email: string;
name: string;
}
export default class Stripe extends BaseAdmin {
private static instance: Stripe;
private readonly baseURl = this.namespaceUrl.concat("/stripe");
@ -53,4 +58,14 @@ export default class Stripe extends BaseAdmin {
return Promise.reject(err);
}
}
public async getCustomerBySubscriptionId(subscriptionId: string) {
const url = new URL(this.baseURl.concat(`/${subscriptionId}/customer`));
try {
return await this.getRequest<IGetCustomerBySubscriptionIdParams>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -4,6 +4,9 @@
cursor: pointer;
display: flex;
align-items: center;
&.disabled {
cursor: not-allowed;
}
input[type="checkbox"] {
appearance: none;
@ -15,6 +18,10 @@
margin-right: 16px;
display: grid;
place-content: center;
&:disabled {
cursor: not-allowed;
}
}
input[type="checkbox"]::before {

View File

@ -4,6 +4,7 @@ import { IOption } from "../Form/SelectField";
import Tooltip from "../ToolTip";
import Typography, { ITypo, ITypoColor } from "../Typography";
import classes from "./classes.module.scss";
import classNames from "classnames";
type IProps = {
name?: string;
@ -11,6 +12,7 @@ type IProps = {
toolTip?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
checked: boolean;
disabled?: boolean;
};
type IState = {
@ -21,6 +23,7 @@ export default class CheckBox extends React.Component<IProps, IState> {
static defaultProps = {
toolTip: "",
checked: false,
disabled: false,
};
constructor(props: IProps) {
@ -35,13 +38,14 @@ export default class CheckBox extends React.Component<IProps, IState> {
public override render(): JSX.Element {
return (
<Typography typo={ITypo.P_ERR_16} color={ITypoColor.BLACK}>
<label className={classes["root"]}>
<label className={classNames(classes["root"], this.props.disabled && classes["disabled"])}>
<input
type="checkbox"
name={this.props.name ?? (this.props.option.value as string)}
value={this.props.option.value as string}
onChange={this.onChange}
checked={this.state.checked}
disabled={this.props.disabled}
/>
{this.props.option.label}
{this.props.toolTip && <Tooltip className={classes["tooltip"]} text={this.props.toolTip} />}

View File

@ -421,7 +421,6 @@ export default class DepositDocument extends React.Component<IProps, IState> {
}
} catch (e) {
this.setState({ loading: false });
console.log(e);
}
}

View File

@ -85,14 +85,12 @@ export default class DepositRib extends React.Component<IProps, IState> {
// formData.append("file", this.state.documents[0]!, this.state.documents[0]!.name);
// const sentFile = await Bucket.getInstance().post(formData);
// console.log("Sent file:", sentFile);
// // Reset documents state
// this.setState({ documents: [] });
// };
// handleCancel = () => {
// console.log("Cancel:", this.state.documents);
// // Reset documents state
// this.setState({ documents: [] });
// };

View File

@ -29,7 +29,7 @@ export default function Navigation() {
await OfficeFolderAnchors.getInstance().getByUid(anchor.folder?.uid as string);
}
} catch (e) {
console.log(e);
console.error(e);
}
}, []);

View File

@ -38,7 +38,6 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
}
public override render(): JSX.Element {
console.log(this.props);
const toast = this.props.toast;
const style = {
"--data-duration": `${toast.time}ms`,

View File

@ -33,7 +33,6 @@ export default function DocumentTypeListContainer(props: IProps) {
const onSelectedBlock = useCallback(
(block: IBlock) => {
props.onCloseLeftSide && props.onCloseLeftSide();
console.log("Block selected :", block);
const redirectPath = Module.getInstance().get().modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path;
router.push(redirectPath.replace("[uid]", block.id));
},

View File

@ -36,7 +36,6 @@ export default function DeedTypesCreate(props: IProps) {
try {
await validateOrReject(deedType, { groups: ["createDeedType"], forbidUnknownValues: true });
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
setValidationError(validationErrors as ValidationError[]);
return;
}
@ -57,12 +56,11 @@ export default function DeedTypesCreate(props: IProps) {
.modules.pages.DeedTypes.pages.DeedTypesInformations.props.path.replace("[uid]", deedTypeCreated.uid!),
);
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
setValidationError(validationErrors as ValidationError[]);
return;
}
},
[router, validationError],
[router],
);
const closeConfirmModal = useCallback(() => {

View File

@ -42,7 +42,6 @@ export default function DocumentTypesEdit() {
try {
await validateOrReject(documentToUpdate, { groups: ["updateDocumentType"] });
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
if (!Array.isArray(validationErrors)) return;
setValidationError(validationErrors as ValidationError[]);
return;
@ -63,7 +62,7 @@ export default function DocumentTypesEdit() {
return;
}
},
[documentTypeUid, router, validationError],
[documentTypeUid, router],
);
return (

View File

@ -242,7 +242,6 @@ class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
const contactToCreate = Contact.hydrate<Customer>(values);
await contactToCreate.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
} catch (validationErrors) {
console.log(validationErrors);
this.setState({
validationError: validationErrors as ValidationError[],
});

View File

@ -200,7 +200,6 @@ class UpdateClientClass extends BasePage<IPropsClass, IState> {
try {
await contact.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
} catch (validationErrors) {
console.log(validationErrors);
this.setState({
validationError: validationErrors as ValidationError[],
});

View File

@ -200,7 +200,7 @@ class ViewDocumentsClass extends BasePage<IPropsClass, IState> {
fileBlob,
});
} catch (e) {
console.log(e);
console.error(e);
}
}

View File

@ -28,7 +28,6 @@ export default function LoginCallBack() {
await UserStore.instance.connect(token.accessToken, token.refreshToken);
return router.push(Module.getInstance().get().modules.pages.Folder.props.path);
} catch (e: any) {
console.log("Log error : ", e);
if (e.http_status === 401 && e.message === "Email not found") {
return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=3");
}

View File

@ -26,7 +26,7 @@ export default function LoginCallBackCustomer() {
try {
token = await Customers.getInstance().loginCallback(tokenid360);
} catch (e) {
console.log(e);
console.error(e);
router.push(Module.getInstance().get().modules.pages.CustomersLogin.props.path + "?error=1");
return;
}
@ -34,7 +34,7 @@ export default function LoginCallBackCustomer() {
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
}
const refreshToken = CookieService.getInstance().getCookie("leCoffreRefreshToken");
if(!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
if (!refreshToken) return router.push(Module.getInstance().get().modules.pages.Login.props.path + "?error=1");
const isTokenRefreshed = await JwtService.getInstance().refreshToken(refreshToken);
if (isTokenRefreshed) {
return router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);

View File

@ -35,7 +35,6 @@ export default function RolesCreate(props: IProps) {
try {
await officeRole.validateOrReject?.({ groups: ["createOfficeRole"], forbidUnknownValues: true });
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
if (!Array.isArray(validationErrors)) return;
setValidationError(validationErrors as ValidationError[]);
return;
@ -52,13 +51,12 @@ export default function RolesCreate(props: IProps) {
router.push(Module.getInstance().get().modules.pages.Roles.pages.RolesInformations.props.path.replace("[uid]", role.uid!));
} catch (validationErrors: Array<ValidationError> | any) {
console.log(validationErrors);
if (!Array.isArray(validationErrors)) return;
setValidationError(validationErrors as ValidationError[]);
return;
}
},
[router, validationError],
[router],
);
const closeConfirmModal = useCallback(() => {
@ -96,7 +94,11 @@ export default function RolesCreate(props: IProps) {
<Typography typo={ITypo.H1Bis}>Créer un rôle</Typography>
</div>
<Form onSubmit={onSubmitHandler} className={classes["form-container"]} onFieldChange={onFieldChange}>
<TextField name="name" placeholder="Nom du rôle" validationError={validationError.find((error) => error.property === "name")}/>
<TextField
name="name"
placeholder="Nom du rôle"
validationError={validationError.find((error) => error.property === "name")}
/>
<div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.GHOST} onClick={onCancel}>
Annuler

View File

@ -18,7 +18,6 @@ export default function SelectFolder() {
async function getFolders() {
const jwt = JwtService.getInstance().decodeCustomerJwt();
if (!jwt) return;
console.log(jwt)
const folders = await Folders.getInstance().get({
q: {
@ -37,8 +36,8 @@ export default function SelectFolder() {
},
],
include: {
customers: true
}
customers: true,
},
},
});
setFolders(folders);

View File

@ -1,24 +1,30 @@
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import classes from "./classes.module.scss";
import { IGetCustomerBySubscriptionIdParams } from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
export default function SubscriptionClientInfos() {
type IProps = {
customer: IGetCustomerBySubscriptionIdParams;
};
export default function SubscriptionClientInfos(props: IProps) {
const { customer } = props;
return (
<div className={classes["root"]}>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Informations client
</Typography>
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
john.doe@contact.fr
{customer.email}
</Typography>
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{/* <Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
Adresse de facturation
</Typography>
</Typography> */}
<Typography typo={ITypo.P_18} color={ITypoColor.BLACK}>
John Doe <br />
23 rue taitbout,
{customer.name} <br />
{/* 23 rue taitbout,
<br />
75009 Paris
<br />
<br /> */}
France
</Typography>
</div>

View File

@ -4,22 +4,106 @@ import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Form from "@Front/Components/DesignSystem/Form";
import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import { useCallback, useEffect, useState } from "react";
import { Subscription } from "le-coffre-resources/dist/Admin";
import React, { useCallback, useEffect, useState } from "react";
import User, { Subscription } from "le-coffre-resources/dist/Admin";
import JwtService from "@Front/Services/JwtService/JwtService";
import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions";
export default function SubscriptionManageCollaborators() {
const [subscription, setSubscription] = useState<Subscription | null>(null);
const [availableCollaborators, setAvailableCollaborators] = useState<User[]>([
{
created_at: new Date("2021-09-29T14:00:00.000Z"),
uid: "1",
idNot: "1",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
contact: {
civility: "M",
created_at: new Date("2021-09-29T14:00:00.000Z"),
email: "jean.dupont@gmail.com",
first_name: "Jean",
last_name: "Dupont",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
},
},
{
created_at: new Date("2021-09-29T14:00:00.000Z"),
uid: "2",
idNot: "1",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
contact: {
civility: "M",
created_at: new Date("2021-09-29T14:00:00.000Z"),
email: "jean.dupont@gmail.com",
first_name: "Jean",
last_name: "Dupont",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
},
},
{
created_at: new Date("2021-09-29T14:00:00.000Z"),
uid: "3",
idNot: "1",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
contact: {
civility: "M",
created_at: new Date("2021-09-29T14:00:00.000Z"),
email: "jean.dupont@gmail.com",
first_name: "Jean",
last_name: "Dupont",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
},
},
{
created_at: new Date("2021-09-29T14:00:00.000Z"),
uid: "4",
idNot: "1",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
contact: {
civility: "M",
created_at: new Date("2021-09-29T14:00:00.000Z"),
email: "jean.dupont@gmail.com",
first_name: "Jean",
last_name: "Dupont",
updated_at: new Date("2021-09-29T14:00:00.000Z"),
},
},
]);
const [selectedCollaborators, setSelectedCollaborators] = useState<string[]>([]);
const loadSubscription = useCallback(async () => {
const jwt = JwtService.getInstance().decodeJwt();
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
console.log(subscription);
if (!subscription[0]) return;
subscription[0].seats?.forEach((seat) => setSelectedCollaborators((prev) => [...prev, seat.user.uid!]));
setSubscription(subscription[0]);
}, []);
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (!subscription) return;
const value = event.target.value;
if (selectedCollaborators.includes(value)) {
setSelectedCollaborators((prev) => prev.filter((collaborator) => collaborator !== value));
} else {
if (selectedCollaborators.length < subscription.nb_seats!) {
setSelectedCollaborators((prev) => [...prev, value]);
}
}
},
[selectedCollaborators, subscription],
);
const cancelAll = () => {
setSelectedCollaborators([]);
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
if (!e) return;
e.preventDefault();
};
useEffect(() => {
loadSubscription();
}, [loadSubscription]);
@ -34,76 +118,33 @@ export default function SubscriptionManageCollaborators() {
<Typography typo={ITypo.P_SB_18} color={ITypoColor.BLACK}>
{subscription.nb_seats} sièges disponibles
</Typography>
<Form>
<Form onSubmit={handleSubmit}>
<div className={classes["collaborators-container"]}>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
checked
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
checked
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
checked
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
/>
<CheckBox
option={{
label: "Jean Dupont",
value: "Jean Dupont",
}}
name="collaborators"
/>
{availableCollaborators.map((collaborator) => (
<CheckBox
key={collaborator.uid}
option={{
label: collaborator.contact?.first_name + " " + collaborator.contact?.last_name,
value: collaborator.uid,
}}
checked={selectedCollaborators.includes(collaborator.uid!)}
onChange={handleChange}
disabled={
selectedCollaborators.length >= subscription.nb_seats! &&
!selectedCollaborators.includes(collaborator.uid!)
}
name="collaborators"
/>
))}
</div>
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.BLACK}>
7 collaborateurs sélectionnés
{selectedCollaborators.length} collaborateurs sélectionnés
</Typography>
<div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.PRIMARY} fullwidth>
Enregistrer
</Button>
<Button variant={EButtonVariant.GHOST} fullwidth>
<Button variant={EButtonVariant.GHOST} fullwidth onClick={cancelAll} type="button">
Annuler
</Button>
</div>

View File

@ -17,7 +17,6 @@ export default function SubscriptionError() {
const loadSubscription = useCallback(async () => {
const jwt = JwtService.getInstance().decodeJwt();
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
console.log(subscription);
if (!subscription[0]) return;
setSubscription(subscription[0]);
}, []);

View File

@ -44,7 +44,6 @@ export default function SubscriptionFacturation() {
const loadSubscription = useCallback(async () => {
const jwt = JwtService.getInstance().decodeJwt();
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
console.log(subscription);
if (!subscription[0]) {
router.push(Module.getInstance().get().modules.pages.Subscription.pages.New.props.path);
} else {

View File

@ -12,16 +12,19 @@ import { useCallback, useEffect, useState } from "react";
import Subscriptions from "@Front/Api/LeCoffreApi/Admin/Subscriptions/Subscriptions";
import JwtService from "@Front/Services/JwtService/JwtService";
import { Subscription } from "le-coffre-resources/dist/Admin";
import Stripe from "@Front/Api/LeCoffreApi/Admin/Stripe/Stripe";
export default function SubscriptionSuccess() {
const [subscription, setSubscription] = useState<Subscription | null>(null);
const [customer, setCustomer] = useState<any | null>(null);
const loadSubscription = useCallback(async () => {
const jwt = JwtService.getInstance().decodeJwt();
const subscription = await Subscriptions.getInstance().get({ where: { office: { uid: jwt?.office_Id } } });
console.log(subscription);
if (!subscription[0]) return;
setSubscription(subscription[0]);
const customer = await Stripe.getInstance().getCustomerBySubscriptionId(subscription[0].stripe_subscription_id!);
setCustomer(customer);
}, []);
const getFrequency = useCallback(() => {
@ -40,7 +43,7 @@ export default function SubscriptionSuccess() {
return (
<DefaultTemplate title="Abonnement réussi">
{subscription && (
{subscription && customer && (
<div className={classes["root"]}>
<div className={classes["left"]}>
<div className={classes["title"]}>
@ -58,7 +61,7 @@ export default function SubscriptionSuccess() {
</div>
<div className={classes["separator"]} />
<div className={classes["client-infos"]}>
<SubscriptionClientInfos />
<SubscriptionClientInfos customer={customer} />
</div>
<div className={classes["separator"]} />
{subscription.type === "STANDARD" && (

View File

@ -47,7 +47,6 @@ export default class JwtService {
return jwt_decode(accessToken);
}
public decodeJwt(): IUserJwtPayload | undefined {
const accessToken = CookieService.getInstance().getCookie("leCoffreAccessToken");
if (!accessToken) return;
@ -78,7 +77,7 @@ export default class JwtService {
`${
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/idnot/user/auth/refresh-token`,
{ method: 'POST', headers: headers },
{ method: "POST", headers: headers },
);
const newAccessToken: { accessToken: string } = await response.json();
if (newAccessToken) {
@ -86,7 +85,7 @@ export default class JwtService {
return true;
}
} catch (err) {
console.log(err);
console.error(err);
return false;
}
} else if (customerToken?.customerId) {
@ -97,7 +96,7 @@ export default class JwtService {
`${
variables.BACK_API_PROTOCOL + variables.BACK_API_HOST + variables.BACK_API_ROOT_URL + variables.BACK_API_VERSION
}/id360/customers/refresh-token`,
{ method: 'POST', headers: headers },
{ method: "POST", headers: headers },
);
const newAccessToken: { accessToken: string } = await response.json();
if (newAccessToken) {
@ -105,7 +104,7 @@ export default class JwtService {
return true;
}
} catch (err) {
console.log(err);
console.error(err);
return false;
}
}