Merge branch 'staging' into preprod
This commit is contained in:
commit
16576686fa
@ -8,8 +8,69 @@ jobs:
|
||||
build-push-docker-image:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
parameters:
|
||||
NEXT_PUBLIC_BACK_API_HOST:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_BACK_API_VERSION:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_FC_CLIENT_ID:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_FRONT_APP_HOST:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_FRONT_APP_PORT:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_DOCAPOST_API_URL:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID:
|
||||
type: string
|
||||
default: ""
|
||||
NEXT_PUBLIC_DOCAPOST_APP_ID:
|
||||
type: string
|
||||
default: ""
|
||||
environment:
|
||||
TAG: << pipeline.git.tag >>
|
||||
NEXT_PUBLIC_BACK_API_HOST: << parameters.NEXT_PUBLIC_BACK_API_HOST >>
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: << parameters.NEXT_PUBLIC_BACK_API_PROTOCOL >>
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: << parameters.NEXT_PUBLIC_BACK_API_ROOT_URL >>
|
||||
NEXT_PUBLIC_BACK_API_VERSION: << parameters.NEXT_PUBLIC_BACK_API_VERSION >>
|
||||
NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT: << parameters.NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT >>
|
||||
NEXT_PUBLIC_FC_CLIENT_ID: << parameters.NEXT_PUBLIC_FC_CLIENT_ID >>
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: << parameters.NEXT_PUBLIC_FRONT_APP_HOST >>
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: << parameters.NEXT_PUBLIC_FRONT_APP_PORT >>
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: << parameters.NEXT_PUBLIC_IDNOT_BASE_URL >>
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: << parameters.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT >>
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: << parameters.NEXT_PUBLIC_IDNOT_CLIENT_ID >>
|
||||
NEXT_PUBLIC_DOCAPOST_API_URL: << parameters.NEXT_PUBLIC_DOCAPOST_API_URL >>
|
||||
NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID: << parameters.NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID >>
|
||||
NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID: << parameters.NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID >>
|
||||
NEXT_PUBLIC_DOCAPOST_APP_ID: << parameters.NEXT_PUBLIC_DOCAPOST_APP_ID >>
|
||||
steps:
|
||||
- checkout
|
||||
- add_ssh_keys:
|
||||
@ -52,6 +113,21 @@ workflows:
|
||||
build-and-register-stg:
|
||||
jobs:
|
||||
- build-push-docker-image:
|
||||
NEXT_PUBLIC_BACK_API_HOST: api.stg.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: https://
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: /api
|
||||
NEXT_PUBLIC_BACK_API_VERSION: /v1
|
||||
NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT: https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize
|
||||
NEXT_PUBLIC_FC_CLIENT_ID: 211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: https://app.stg.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: "3000"
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: "https://qual-connexion.idnot.fr"
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: "/IdPOAuth2/authorize/idnot_idp_v1"
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: "4501646203F3EF67"
|
||||
NEXT_PUBLIC_DOCAPOST_API_URL: "https://preprod.id360docaposte.com/api/1.0.0"
|
||||
NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID: 49508376-b160-475d-9224-9bb6511215b8
|
||||
NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID: 54c14875-f864-4819-8cd4-4fc6dd4a947b
|
||||
NEXT_PUBLIC_DOCAPOST_APP_ID: leCoffre@smart_chain
|
||||
context:
|
||||
- sc-shared-prd
|
||||
filters:
|
||||
@ -70,6 +146,21 @@ workflows:
|
||||
build-and-register-ppd:
|
||||
jobs:
|
||||
- build-push-docker-image:
|
||||
NEXT_PUBLIC_BACK_API_HOST: api.ppd.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: https://
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: /api
|
||||
NEXT_PUBLIC_BACK_API_VERSION: /v1
|
||||
NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT: https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize
|
||||
NEXT_PUBLIC_FC_CLIENT_ID: 211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: https://app.ppd.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: "3000"
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: "https://qual-connexion.idnot.fr"
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: "/IdPOAuth2/authorize/idnot_idp_v1"
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: "4501646203F3EF67"
|
||||
NEXT_PUBLIC_DOCAPOST_API_URL: "https://preprod.id360docaposte.com/api/1.0.0"
|
||||
NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID: 49508376-b160-475d-9224-9bb6511215b8
|
||||
NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID: 54c14875-f864-4819-8cd4-4fc6dd4a947b
|
||||
NEXT_PUBLIC_DOCAPOST_APP_ID: leCoffre@smart_chain
|
||||
context:
|
||||
- sc-shared-prd
|
||||
filters:
|
||||
@ -88,6 +179,21 @@ workflows:
|
||||
build-and-register-prd:
|
||||
jobs:
|
||||
- build-push-docker-image:
|
||||
NEXT_PUBLIC_BACK_API_HOST: api.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: https://
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: /api
|
||||
NEXT_PUBLIC_BACK_API_VERSION: /v1
|
||||
NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT: https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize
|
||||
NEXT_PUBLIC_FC_CLIENT_ID: 211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: https://app.lecoffre.smart-chain.fr
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: "3000"
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: "https://qual-connexion.idnot.fr"
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: "/IdPOAuth2/authorize/idnot_idp_v1"
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: "4501646203F3EF67"
|
||||
NEXT_PUBLIC_DOCAPOST_API_URL: "https://preprod.id360docaposte.com/api/1.0.0"
|
||||
NEXT_PUBLIC_DOCAPOST_DOCUMENT_PROCESS_ID: 49508376-b160-475d-9224-9bb6511215b8
|
||||
NEXT_PUBLIC_DOCAPOST_CONNECT_PROCESS_ID: 54c14875-f864-4819-8cd4-4fc6dd4a947b
|
||||
NEXT_PUBLIC_DOCAPOST_APP_ID: leCoffre@smart_chain
|
||||
context:
|
||||
- sc-shared-prd
|
||||
filters:
|
||||
|
@ -22,6 +22,7 @@ WORKDIR leCoffre-front
|
||||
COPY --from=deps leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=deps leCoffre-front/package.json package.json
|
||||
COPY tsconfig.json tsconfig.json
|
||||
COPY next.config.js next.config.js
|
||||
COPY src src
|
||||
|
||||
RUN npm run build
|
||||
@ -36,6 +37,7 @@ RUN adduser -D lecoffreuser --uid 10000 && chown -R lecoffreuser .
|
||||
COPY public ./public
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/node_modules ./node_modules
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/.next ./.next
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/next.config.js ./next.config.js
|
||||
COPY --from=builder --chown=lecoffreuser leCoffre-front/package.json ./package.json
|
||||
|
||||
USER lecoffreuser
|
||||
|
@ -15,6 +15,33 @@ const nextConfig = {
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
},
|
||||
|
||||
serverRuntimeConfig: {
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
NEXT_PUBLIC_BACK_API_HOST: process.env.NEXT_PUBLIC_BACK_API_HOST,
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: process.env.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
NEXT_PUBLIC_BACK_API_VERSION: process.env.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: process.env.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: process.env.NEXT_PUBLIC_FRONT_APP_PORT,
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: process.env.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: process.env.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
},
|
||||
|
||||
env: {
|
||||
NEXT_PUBLIC_BACK_API_PROTOCOL: process.env.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
NEXT_PUBLIC_BACK_API_HOST: process.env.NEXT_PUBLIC_BACK_API_HOST,
|
||||
NEXT_PUBLIC_BACK_API_ROOT_URL: process.env.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
NEXT_PUBLIC_BACK_API_VERSION: process.env.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
NEXT_PUBLIC_FRONT_APP_HOST: process.env.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
NEXT_PUBLIC_FRONT_APP_PORT: process.env.NEXT_PUBLIC_FRONT_APP_PORT,
|
||||
NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT: process.env.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
NEXT_PUBLIC_IDNOT_CLIENT_ID: process.env.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
NEXT_PUBLIC_IDNOT_BASE_URL: process.env.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
NEXT_PUBLIC_DOCAPOSTE_API_URL: process.env.NEXT_PUBLIC_DOCAPOSTE_API_URL,
|
||||
},
|
||||
|
||||
// webpack: config => {
|
||||
// config.node = {
|
||||
// fs: 'empty',
|
||||
|
550
package-lock.json
generated
550
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@
|
||||
"eslint-config-next": "13.2.4",
|
||||
"form-data": "^4.0.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.94",
|
||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.104",
|
||||
"next": "13.2.4",
|
||||
"prettier": "^2.8.7",
|
||||
"react": "18.2.0",
|
||||
|
118
src/front/Api/Auth/Customer/Auth.ts
Normal file
118
src/front/Api/Auth/Customer/Auth.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import BaseApiService from "@Front/Api/BaseApiService";
|
||||
import { ICustomerTokens } from "../Id360/Customers/Customers";
|
||||
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
|
||||
|
||||
export type IMailVerifyParams = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type IMailVerifyReturn = {
|
||||
partialPhoneNumber: string;
|
||||
totpCodeUid: string;
|
||||
};
|
||||
|
||||
export type IVerifyTotpCodeParams = {
|
||||
totpCode: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type IVerifyTotpCodeReturn = {
|
||||
validCode: boolean;
|
||||
reason: TotpCodesReasons;
|
||||
};
|
||||
|
||||
export type ISetPasswordParams = {
|
||||
password: string;
|
||||
email: string;
|
||||
totpCode: string;
|
||||
};
|
||||
|
||||
export type ILoginParams = {
|
||||
password: string;
|
||||
email: string;
|
||||
totpCode: string;
|
||||
};
|
||||
|
||||
export type IAskNewPasswordParams = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type IAskAnotherCodeParams = {
|
||||
email: string;
|
||||
totpCodeUid: string;
|
||||
};
|
||||
|
||||
export type IAskAnotherCodeReturn = {
|
||||
partialPhoneNumber: string;
|
||||
totpCodeUid: string;
|
||||
};
|
||||
|
||||
export default class Auth extends BaseApiService {
|
||||
private static instance: Auth;
|
||||
protected readonly namespaceUrl = this.getBaseUrl().concat("/customer");
|
||||
private readonly baseURl = this.namespaceUrl.concat("/auth");
|
||||
|
||||
public static getInstance() {
|
||||
return (this.instance ??= new this());
|
||||
}
|
||||
|
||||
public async mailVerifySms(body: IMailVerifyParams): Promise<IMailVerifyReturn> {
|
||||
const url = new URL(this.baseURl.concat("/mail/verify-sms"));
|
||||
try {
|
||||
return this.postRequest<IMailVerifyReturn>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async verifyTotpCode(body: IVerifyTotpCodeParams): Promise<IVerifyTotpCodeReturn> {
|
||||
const url = new URL(this.baseURl.concat("/verify-totp-code"));
|
||||
try {
|
||||
return this.postRequest<IVerifyTotpCodeReturn>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async setPassword(body: ISetPasswordParams): Promise<ICustomerTokens> {
|
||||
const url = new URL(this.baseURl.concat("/set-password"));
|
||||
try {
|
||||
return this.postRequest<ICustomerTokens>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async login(body: ILoginParams): Promise<ICustomerTokens> {
|
||||
const url = new URL(this.baseURl.concat("/login"));
|
||||
try {
|
||||
return this.postRequest<ICustomerTokens>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async askNewPassword(body: IAskNewPasswordParams): Promise<IMailVerifyReturn> {
|
||||
const url = new URL(this.baseURl.concat("/ask-new-password"));
|
||||
try {
|
||||
return this.postRequest<IMailVerifyReturn>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async sendAnotherCode(body: IAskAnotherCodeParams): Promise<IAskAnotherCodeReturn> {
|
||||
const url = new URL(this.baseURl.concat("/send-another-code"));
|
||||
try {
|
||||
return this.postRequest<IAskAnotherCodeReturn>(url, body);
|
||||
} catch (err) {
|
||||
this.onError(err);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
@ -138,11 +138,16 @@ export default abstract class BaseApiService {
|
||||
}
|
||||
} else {
|
||||
// Handle error response
|
||||
const responseCopy = response.clone();
|
||||
try {
|
||||
const responseJson = await response.json();
|
||||
return Promise.reject(responseJson);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
const responseText = await responseCopy.text();
|
||||
return Promise.reject({
|
||||
http_status: response.status,
|
||||
message: responseText,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ type IState = {
|
||||
refusedReason?: string;
|
||||
isShowRefusedReasonModalVisible: boolean;
|
||||
showFailedUploaded: string | null;
|
||||
showFailedDocument: string | null;
|
||||
isAddDocumentModalVisible: boolean;
|
||||
isLoading: boolean;
|
||||
};
|
||||
@ -59,6 +60,7 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
|
||||
isShowRefusedReasonModalVisible: false,
|
||||
showFailedUploaded: null,
|
||||
isLoading: false,
|
||||
showFailedDocument: null,
|
||||
};
|
||||
|
||||
this.addDocument = this.addDocument.bind(this);
|
||||
@ -159,11 +161,20 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
|
||||
</div>
|
||||
</div>
|
||||
</Confirm>
|
||||
{this.state.showFailedUploaded && (
|
||||
{this.state.showFailedDocument && (
|
||||
<Alert
|
||||
onClose={this.onCloseAlertUpload}
|
||||
header={"Fichier non autorisé"}
|
||||
isOpen={!!this.state.showFailedUploaded}>
|
||||
header={"L'ajout de Document n'est plus autorisé"}
|
||||
isOpen={!!this.state.showFailedDocument}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ITypo.P_16} className={classes["text"]}>
|
||||
{this.state.showFailedDocument}
|
||||
</Typography>
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
{this.state.showFailedUploaded && (
|
||||
<Alert onClose={this.onCloseAlertUpload} header={"Fichier non autorisé"} isOpen={!!this.state.showFailedUploaded}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ITypo.P_16} className={classes["text"]}>
|
||||
{this.state.showFailedUploaded}
|
||||
@ -188,15 +199,20 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
|
||||
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,
|
||||
},
|
||||
});
|
||||
let documentCreated: Document = {} as Document;
|
||||
try {
|
||||
documentCreated = await Documents.getInstance().post({
|
||||
folder: {
|
||||
uid: this.props.folder_uid,
|
||||
},
|
||||
depositor: {
|
||||
uid: this.props.customer_uid,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({ showFailedDocument: "Le dossier est vérifié aucune modification n'est acceptée", isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filesArray.length; i++) {
|
||||
const formData = new FormData();
|
||||
|
@ -38,6 +38,7 @@ 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`,
|
||||
@ -98,7 +99,8 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
|
||||
this.close();
|
||||
}
|
||||
|
||||
private close() {
|
||||
private async close() {
|
||||
await Toasts.getInstance().markRead(this.props.toast);
|
||||
window.clearTimeout(this.closeTimeout);
|
||||
this.setState({
|
||||
willClose: true,
|
||||
@ -110,7 +112,7 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
|
||||
|
||||
private async handleClick(e: React.MouseEvent) {
|
||||
if (this.props.toast.redirectUrl) {
|
||||
this.onClose(e);
|
||||
await this.onClose(e);
|
||||
await this.props.router.push(this.props.toast.redirectUrl);
|
||||
this.props.router.reload();
|
||||
}
|
||||
|
@ -76,6 +76,30 @@ export default class UserFolderHeader extends React.Component<IProps, IState> {
|
||||
private formatPhoneNumber(phoneNumber: string): string {
|
||||
if (!phoneNumber) return "";
|
||||
phoneNumber = phoneNumber.replace(/ /g, "");
|
||||
phoneNumber = phoneNumber.replace("+33", "0");
|
||||
|
||||
if (phoneNumber.length !== 10) {
|
||||
// split the last 9 digits
|
||||
const lastNineDigits = phoneNumber.slice(-9);
|
||||
|
||||
// get the country code
|
||||
const countryCode = phoneNumber.slice(0, -9);
|
||||
|
||||
// isolate the first digit
|
||||
const firstDigit = lastNineDigits.slice(0, 1);
|
||||
|
||||
// isolate the 8 other ones
|
||||
const lastEightDigits = lastNineDigits.slice(1);
|
||||
|
||||
// make a space every two digits on the last eights
|
||||
const output = lastEightDigits.split("").map((char, index) => {
|
||||
if (index % 2) return char + " ";
|
||||
return char;
|
||||
});
|
||||
// format the phone number
|
||||
phoneNumber = countryCode + " " + firstDigit + " " + output.join("");
|
||||
return phoneNumber;
|
||||
}
|
||||
const output = phoneNumber.split("").map((char, index) => {
|
||||
if (index % 2) return char + " ";
|
||||
return char;
|
||||
|
@ -163,7 +163,7 @@ export default function CollaboratorInformations(props: IProps) {
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
Numéro de téléphone
|
||||
</Typography>
|
||||
<Typography typo={ITypo.P_18}>{userSelected?.contact?.phone_number}</Typography>
|
||||
<Typography typo={ITypo.P_18}>{userSelected?.contact?.cell_phone_number}</Typography>
|
||||
</div>
|
||||
<div className={classes["user-infos-row"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
|
@ -229,6 +229,16 @@ class AddClientToFolderClass extends BasePage<IPropsClass, IState> {
|
||||
|
||||
if (this.state.selectedOption === "new_customer") {
|
||||
try {
|
||||
// remove every space from the phone number
|
||||
values["cell_phone_number"] = values["cell_phone_number"].replace(/\s/g, "");
|
||||
if (values["cell_phone_number"] && values["cell_phone_number"].length === 10) {
|
||||
// get the first digit of the phone number
|
||||
const firstDigit = values["cell_phone_number"].charAt(0);
|
||||
// if the first digit is a 0 replace it by +33
|
||||
if (firstDigit === "0") {
|
||||
values["cell_phone_number"] = "+33" + values["cell_phone_number"].substring(1);
|
||||
}
|
||||
}
|
||||
const contactToCreate = Contact.hydrate<Customer>(values);
|
||||
await contactToCreate.validateOrReject?.({ groups: ["createCustomer"], forbidUnknownValues: false });
|
||||
} catch (validationErrors) {
|
||||
|
@ -107,7 +107,7 @@
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
.loader {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +176,17 @@ class UpdateClientClass extends BasePage<IPropsClass, IState> {
|
||||
[key: string]: string;
|
||||
},
|
||||
) {
|
||||
if (!values["cell_phone_number"]) return;
|
||||
// remove every space from the phone number
|
||||
values["cell_phone_number"] = values["cell_phone_number"].replace(/\s/g, "");
|
||||
if (values["cell_phone_number"] && values["cell_phone_number"].length === 10) {
|
||||
// get the first digit of the phone number
|
||||
const firstDigit = values["cell_phone_number"].charAt(0);
|
||||
// if the first digit is a 0 replace it by +33
|
||||
if (firstDigit === "0") {
|
||||
values["cell_phone_number"] = "+33" + values["cell_phone_number"].substring(1);
|
||||
}
|
||||
}
|
||||
const contact = Contact.hydrate<Contact>({
|
||||
first_name: values["first_name"],
|
||||
last_name: values["last_name"],
|
||||
|
@ -0,0 +1,29 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
|
||||
.password_indication {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
};
|
||||
|
||||
export default function PasswordForgotten(props: IProps) {
|
||||
const { onSubmit, validationErrors } = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Réinitialisez votre mot de passe</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Mot de passe"
|
||||
name="password"
|
||||
validationError={validationErrors.find((error) => error.property === "password")}
|
||||
password
|
||||
/>
|
||||
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.GREY} className={classes["password_indication"]}>
|
||||
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Confirmation du mot de passe"
|
||||
name="confirm_password"
|
||||
validationError={validationErrors.find((error) => error.property === "confirm_password")}
|
||||
password
|
||||
/>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Valider
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 32px;
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.what_is_france_connect {
|
||||
color: var(--light-text-action-high-blue-france, #000091);
|
||||
/* 2.Corps de texte/SM - Texte détail/Desktop & Mobile - Regular */
|
||||
font-family: Marianne;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px; /* 171.429% */
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.or {
|
||||
margin-top: 32px;
|
||||
}
|
||||
.forget-password {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
|
||||
//import Image from "next/image";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
//import franceConnectLogo from "../france-connect.svg";
|
||||
// import { useRouter } from "next/router";
|
||||
// import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
|
||||
import { ValidationError } from "class-validator";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
};
|
||||
|
||||
export default function StepEmail(props: IProps) {
|
||||
const { onSubmit, validationErrors } = props;
|
||||
/* const router = useRouter();
|
||||
const redirectCustomerOnConnection = useCallback(() => {
|
||||
async function getCustomer() {
|
||||
try {
|
||||
const loginRes = await Customers.getInstance().login();
|
||||
router.push(loginRes.enrollment.franceConnectUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
getCustomer();
|
||||
}, [router]); */
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Identifiez-vous</div>
|
||||
</Typography>
|
||||
{/* <Typography typo={ITypo.P_16}>Pour accéder à votre espace de dépôt des documents, veuillez vous identifier.</Typography>
|
||||
<Image alt="france-connect" src={franceConnectLogo} onClick={redirectCustomerOnConnection} className={classes["logo"]} />
|
||||
<div className={classes["what_is_france_connect"]}>Qu'est ce que FranceConnect ?</div>
|
||||
<Typography className={classes["or"]} typo={ITypo.P_16}>
|
||||
Ou
|
||||
</Typography> */}
|
||||
<Typography typo={ITypo.P_16}>Pour accéder à votre espace de dépôt des documents, veuillez vous identifier. </Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="E-mail"
|
||||
name="email"
|
||||
validationError={validationErrors.find((error) => error.property === "email")}
|
||||
/>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Suivant
|
||||
</Button>
|
||||
</Form>
|
||||
{/* <Typography typo={ITypo.P_18}>
|
||||
<div className={classes["forget-password"]}>Vous n'arrivez pas à vous connecter ?</div>
|
||||
</Typography>
|
||||
<Link href="mailto:g.texier@notaires.fr">
|
||||
<Button variant={EButtonVariant.LINE}>Contacter l'administrateur</Button>
|
||||
</Link> */}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
|
||||
.password_indication {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
};
|
||||
|
||||
export default function StepNewPassword(props: IProps) {
|
||||
const { onSubmit, validationErrors } = props;
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Configurez votre mot de passe</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Mot de passe"
|
||||
name="password"
|
||||
validationError={validationErrors.find((error) => error.property === "password")}
|
||||
password
|
||||
/>
|
||||
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.GREY} className={classes["password_indication"]}>
|
||||
Au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.
|
||||
</Typography>
|
||||
<TextField
|
||||
placeholder="Confirmation du mot de passe"
|
||||
name="confirm_password"
|
||||
validationError={validationErrors.find((error) => error.property === "confirm_password")}
|
||||
password
|
||||
/>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Valider
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: auto;
|
||||
margin-top: 220px;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
margin-top: 8px;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
onPasswordForgotClicked: () => void;
|
||||
};
|
||||
|
||||
export default function StepPassword(props: IProps) {
|
||||
const { onSubmit, validationErrors, onPasswordForgotClicked } = props;
|
||||
const [isModalOpened, setIsModalOpened] = React.useState(false);
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpened(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setIsModalOpened(true);
|
||||
};
|
||||
|
||||
const onModalAccept = () => {
|
||||
onPasswordForgotClicked();
|
||||
setIsModalOpened(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Entrez votre mot de passe</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Mot de passe"
|
||||
name="password"
|
||||
validationError={validationErrors.find((error) => error.property === "password")}
|
||||
password
|
||||
/>
|
||||
<div onClick={openModal}>
|
||||
<Typography typo={ITypo.P_16} className={classes["forgot-password"]}>
|
||||
Mot de passe oublié ?
|
||||
</Typography>
|
||||
</div>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Valider
|
||||
</Button>
|
||||
</Form>
|
||||
<Confirm
|
||||
isOpen={isModalOpened}
|
||||
onClose={closeModal}
|
||||
showCancelButton={true}
|
||||
onAccept={onModalAccept}
|
||||
closeBtn
|
||||
header={"Mot de passe oublié ?"}
|
||||
confirmText={"Valider"}
|
||||
cancelText={"Annuler"}>
|
||||
<div className={classes["modal-content"]}>
|
||||
<Typography typo={ITypo.P_16} className={classes["text"]}>
|
||||
Un code à usage unique va vous être envoyé par sms pour réinitialiser votre mot de passe.
|
||||
</Typography>
|
||||
</div>
|
||||
</Confirm>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 530px;
|
||||
margin: 220px auto;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
|
||||
@media (max-width: $screen-s) {
|
||||
font-family: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 32px;
|
||||
.submit_button {
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.ask-another-code {
|
||||
margin-top: 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
|
||||
.new-code-button {
|
||||
&[data-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.new-code-timer {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import React, { useEffect } from "react";
|
||||
import classes from "./classes.module.scss";
|
||||
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||
import Form from "@Front/Components/DesignSystem/Form";
|
||||
import TextField from "@Front/Components/DesignSystem/Form/TextField";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import { ValidationError } from "class-validator";
|
||||
type IProps = {
|
||||
onSubmit: (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => void;
|
||||
validationErrors: ValidationError[];
|
||||
partialPhoneNumber: string;
|
||||
onSendAnotherCode: () => void;
|
||||
};
|
||||
|
||||
export default function StepTotp(props: IProps) {
|
||||
const { onSubmit, validationErrors, partialPhoneNumber, onSendAnotherCode } = props;
|
||||
const [disableNewCodeButton, setDisableNewCodeButton] = React.useState(false);
|
||||
const [secondsBeforeNewCode, setSecondsBeforeNewCode] = React.useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (secondsBeforeNewCode > 0) {
|
||||
setSecondsBeforeNewCode(secondsBeforeNewCode - 1);
|
||||
if (secondsBeforeNewCode === 1) {
|
||||
setDisableNewCodeButton(false);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [secondsBeforeNewCode]);
|
||||
|
||||
const sendAnotherCode = () => {
|
||||
onSendAnotherCode();
|
||||
setDisableNewCodeButton(true);
|
||||
setSecondsBeforeNewCode(30);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes["root"]}>
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>
|
||||
Votre code a été envoyé par SMS au ** ** ** {partialPhoneNumber.replace(/(.{2})/g, "$1 ")}
|
||||
</div>
|
||||
</Typography>
|
||||
<Form className={classes["form"]} onSubmit={onSubmit}>
|
||||
<TextField
|
||||
placeholder="Code à usage unique"
|
||||
name="totpCode"
|
||||
validationError={validationErrors.find((error) => error.property === "totpCode")}
|
||||
/>
|
||||
<Button type="submit" variant={EButtonVariant.PRIMARY} className={classes["submit_button"]}>
|
||||
Suivant
|
||||
</Button>
|
||||
</Form>
|
||||
<div className={classes["ask-another-code"]}>
|
||||
<Typography typo={ITypo.P_16}>Vous n'avez rien reçu ?</Typography>
|
||||
<Button
|
||||
variant={EButtonVariant.LINE}
|
||||
disabled={disableNewCodeButton}
|
||||
data-disabled={disableNewCodeButton.toString()}
|
||||
onClick={sendAnotherCode}
|
||||
className={classes["new-code-button"]}>
|
||||
Envoyer un nouveau code
|
||||
</Button>
|
||||
{secondsBeforeNewCode !== 0 && (
|
||||
<Typography typo={ITypo.P_SB_16} className={classes["new-code-timer"]}>
|
||||
Redemandez un code dans
|
||||
<Typography typo={ITypo.P_16} color={ITypoColor.PINK_FLASH}>
|
||||
00:{secondsBeforeNewCode < 10 ? `0${secondsBeforeNewCode}` : secondsBeforeNewCode}
|
||||
</Typography>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,29 +1,4 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
.logo {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,41 @@
|
||||
import CoffreIcon from "@Assets/Icons/coffre.svg";
|
||||
import franceConnectLogo from "./france-connect.svg";
|
||||
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
|
||||
import DefaultDoubleSidePage from "@Front/Components/LayoutTemplates/DefaultDoubleSidePage";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Customers from "@Front/Api/Auth/Id360/Customers/Customers";
|
||||
|
||||
import classes from "./classes.module.scss";
|
||||
import LandingImage from "./landing-connect.jpeg";
|
||||
import Link from "next/link";
|
||||
import Confirm from "@Front/Components/DesignSystem/Modal/Confirm";
|
||||
import StepEmail from "./StepEmail";
|
||||
import StepTotp from "./StepTotp";
|
||||
import Auth from "@Front/Api/Auth/Customer/Auth";
|
||||
import { ValidationError } from "class-validator";
|
||||
import StepPassword from "./StepPassword";
|
||||
import StepNewPassword from "./StepNewPassword";
|
||||
import CustomerStore from "@Front/Stores/CustomerStore";
|
||||
import Module from "@Front/Config/Module";
|
||||
import { TotpCodesReasons } from "le-coffre-resources/dist/Customer/TotpCodes";
|
||||
import PasswordForgotten from "./PasswordForgotten";
|
||||
|
||||
export enum LoginStep {
|
||||
EMAIL,
|
||||
TOTP,
|
||||
PASSWORD,
|
||||
NEW_PASSWORD,
|
||||
PASSWORD_FORGOTTEN,
|
||||
}
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const error = router.query["error"];
|
||||
|
||||
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
|
||||
|
||||
const redirectCustomerOnConnection = useCallback(() => {
|
||||
async function getCustomer() {
|
||||
try {
|
||||
const loginRes = await Customers.getInstance().login();
|
||||
router.push(loginRes.enrollment.franceConnectUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
getCustomer();
|
||||
}, [router]);
|
||||
const [step, setStep] = useState<LoginStep>(LoginStep.EMAIL);
|
||||
const [totpCodeUid, setTotpCodeUid] = useState<string>("");
|
||||
const [totpCode, setTotpCode] = useState<string>("");
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [partialPhoneNumber, setPartialPhoneNumber] = useState<string>("");
|
||||
const [validationErrors, setValidationErrors] = useState<ValidationError[]>([]);
|
||||
|
||||
const openErrorModal = useCallback(() => {
|
||||
setIsErrorModalOpen(true);
|
||||
@ -42,20 +49,183 @@ export default function Login() {
|
||||
if (error === "1") openErrorModal();
|
||||
}, [error, openErrorModal]);
|
||||
|
||||
const onEmailFormSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
if (!values["email"]) return;
|
||||
setEmail(values["email"]);
|
||||
const res = await Auth.getInstance().mailVerifySms({ email: values["email"] });
|
||||
setPartialPhoneNumber(res.partialPhoneNumber);
|
||||
setTotpCodeUid(res.totpCodeUid);
|
||||
setStep(LoginStep.TOTP);
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "email",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onSmsCodeSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
if (!values["totpCode"]) return;
|
||||
const res = await Auth.getInstance().verifyTotpCode({ totpCode: values["totpCode"], email });
|
||||
|
||||
// If the code is valid setting it in state
|
||||
if (res.validCode) setTotpCode(values["totpCode"]);
|
||||
|
||||
// If it's first connection, show the form for first connection
|
||||
if (res.reason === TotpCodesReasons.FIRST_LOGIN) setStep(LoginStep.NEW_PASSWORD);
|
||||
// If it's password forgotten, show the form for password forgotten
|
||||
else if (res.reason === TotpCodesReasons.RESET_PASSWORD) setStep(LoginStep.PASSWORD_FORGOTTEN);
|
||||
// Else just login normally
|
||||
else setStep(LoginStep.PASSWORD);
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "totpCode",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[email, setStep],
|
||||
);
|
||||
|
||||
const onNewPasswordSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
if (!values["password"] || !values["confirm_password"]) return;
|
||||
if (values["password"] !== values["confirm_password"]) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "confirm_password",
|
||||
constraints: {
|
||||
"400": "Les mots de passe ne correspondent pas.",
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordRegex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d@$!%*?&]{8,}$/);
|
||||
if (!passwordRegex.test(values["password"])) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "password",
|
||||
constraints: {
|
||||
"400": "Le mot de passe doit contenir au moins 8 caractères dont 1 majuscule, 1 minuscule et 1 chiffre.",
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const token = await Auth.getInstance().setPassword({ totpCode, email, password: values["password"] });
|
||||
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
|
||||
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
|
||||
// If set password worked, setting the token and redirecting
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "password",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[totpCode, email, router],
|
||||
);
|
||||
|
||||
const onPasswordSubmit = useCallback(
|
||||
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
|
||||
try {
|
||||
if (!values["password"]) return;
|
||||
const token = await Auth.getInstance().login({ totpCode, email, password: values["password"] });
|
||||
CustomerStore.instance.connect(token.accessToken, token.refreshToken);
|
||||
router.push(Module.getInstance().get().modules.pages.Folder.pages.Select.props.path);
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "password",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[email, router, totpCode],
|
||||
);
|
||||
|
||||
const onPasswordForgotClicked = useCallback(async () => {
|
||||
try {
|
||||
const res = await Auth.getInstance().askNewPassword({ email });
|
||||
setPartialPhoneNumber(res.partialPhoneNumber);
|
||||
setStep(LoginStep.TOTP);
|
||||
} catch (error: any) {
|
||||
// If token already exists and is still valid redirect to the connect/register page
|
||||
if (error.http_status === 425) {
|
||||
setStep(LoginStep.TOTP);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [email]);
|
||||
|
||||
const onSendAnotherCode = useCallback(async () => {
|
||||
try {
|
||||
const res = await Auth.getInstance().sendAnotherCode({ email, totpCodeUid });
|
||||
|
||||
setPartialPhoneNumber(res.partialPhoneNumber);
|
||||
setTotpCodeUid(res.totpCodeUid);
|
||||
} catch (error: any) {
|
||||
setValidationErrors([
|
||||
{
|
||||
property: "totpCode",
|
||||
constraints: {
|
||||
[error.http_status]: error.message,
|
||||
},
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}, [email, totpCodeUid]);
|
||||
|
||||
return (
|
||||
<DefaultDoubleSidePage title={"Login"} image={LandingImage}>
|
||||
<div className={classes["root"]}>
|
||||
<Image alt="coffre" src={CoffreIcon} />
|
||||
<Typography typo={ITypo.H1}>
|
||||
<div className={classes["title"]}>Connexion espace client</div>
|
||||
</Typography>
|
||||
<Image alt="france-connect" src={franceConnectLogo} onClick={redirectCustomerOnConnection} className={classes["logo"]} />
|
||||
<Typography typo={ITypo.P_18}>
|
||||
<div className={classes["forget-password"]}>Vous n'arrivez pas à vous connecter ?</div>
|
||||
</Typography>
|
||||
<Link href="mailto:g.texier@notaires.fr">
|
||||
<Button variant={EButtonVariant.LINE}>Contacter l'administrateur</Button>
|
||||
</Link>
|
||||
{step === LoginStep.EMAIL && <StepEmail onSubmit={onEmailFormSubmit} validationErrors={validationErrors} />}
|
||||
{step === LoginStep.TOTP && (
|
||||
<StepTotp
|
||||
onSubmit={onSmsCodeSubmit}
|
||||
validationErrors={validationErrors}
|
||||
partialPhoneNumber={partialPhoneNumber}
|
||||
onSendAnotherCode={onSendAnotherCode}
|
||||
/>
|
||||
)}
|
||||
{step === LoginStep.PASSWORD && (
|
||||
<StepPassword
|
||||
onSubmit={onPasswordSubmit}
|
||||
validationErrors={validationErrors}
|
||||
onPasswordForgotClicked={onPasswordForgotClicked}
|
||||
/>
|
||||
)}
|
||||
{step === LoginStep.NEW_PASSWORD && <StepNewPassword onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />}
|
||||
{step === LoginStep.PASSWORD_FORGOTTEN && (
|
||||
<PasswordForgotten onSubmit={onNewPasswordSubmit} validationErrors={validationErrors} />
|
||||
)}
|
||||
</div>
|
||||
<Confirm
|
||||
isOpen={isErrorModalOpen}
|
||||
|
@ -61,7 +61,7 @@ export default class MyAccount extends Base<IProps, IState> {
|
||||
<TextField
|
||||
name="phone"
|
||||
placeholder="Numéro de téléphone"
|
||||
defaultValue={this.state.user?.contact?.phone_number as string}
|
||||
defaultValue={this.state.user?.contact?.cell_phone_number as string}
|
||||
disabled
|
||||
canCopy
|
||||
/>
|
||||
|
@ -173,7 +173,7 @@ export default function UserInformations(props: IProps) {
|
||||
const liveVote = await LiveVotes.getInstance().post(vote);
|
||||
|
||||
if (liveVote.appointment.votes?.length === 3) {
|
||||
if(superAdminModalType === "add") {
|
||||
if (superAdminModalType === "add") {
|
||||
Toasts.getInstance().open({
|
||||
title: `Le titre de super-administrateur a été attribué à ${userSelected.contact?.first_name} ${userSelected.contact?.last_name} `,
|
||||
});
|
||||
@ -238,7 +238,7 @@ export default function UserInformations(props: IProps) {
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
Numéro de téléphone
|
||||
</Typography>
|
||||
<Typography typo={ITypo.P_18}>{userSelected?.contact?.phone_number}</Typography>
|
||||
<Typography typo={ITypo.P_18}>{userSelected?.contact?.cell_phone_number}</Typography>
|
||||
</div>
|
||||
<div className={classes["user-infos-row"]}>
|
||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||
|
@ -85,15 +85,18 @@ export default class Toasts {
|
||||
return () => this.close(toast);
|
||||
}
|
||||
|
||||
public markRead(toast: IToast) {
|
||||
if (!toast.uid) return;
|
||||
Notifications.getInstance().put(toast.uid, {
|
||||
read: true,
|
||||
});
|
||||
}
|
||||
|
||||
public close(toast: IToast) {
|
||||
const index = this.toastList.indexOf(toast);
|
||||
if (index === -1) return;
|
||||
this.toastList.splice(index, 1);
|
||||
|
||||
if (toast.uid)
|
||||
Notifications.getInstance().put(toast.uid, {
|
||||
read: true,
|
||||
});
|
||||
this.event.emit("change", this.toastList);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import "@Front/index.scss";
|
||||
import type { NextPage } from "next";
|
||||
import type { AppType, AppProps } from "next/app";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import getConfig from 'next/config'
|
||||
|
||||
export type NextPageWithLayout<TProps = Record<string, unknown>, TInitialProps = TProps> = NextPage<TProps, TInitialProps> & {
|
||||
getLayout?: (page: ReactElement) => ReactNode;
|
||||
@ -25,6 +26,8 @@ type AppPropsWithLayout = AppProps & {
|
||||
docaposteApiUrl: string;
|
||||
};
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
const MyApp = (({
|
||||
Component,
|
||||
pageProps,
|
||||
@ -43,16 +46,16 @@ const MyApp = (({
|
||||
const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>);
|
||||
|
||||
const instance = FrontendVariables.getInstance();
|
||||
instance.BACK_API_PROTOCOL = backApiProtocol ?? "https://";
|
||||
instance.BACK_API_HOST = backApiHost ?? "api.stg.lecoffre.smart-chain.fr";
|
||||
instance.BACK_API_ROOT_URL = backApiRootUrl ?? "/api";
|
||||
instance.BACK_API_VERSION = backApiVersion ?? "/v1";
|
||||
instance.FRONT_APP_HOST = frontAppHost ?? "https://app.stg.lecoffre.smart-chain.fr";
|
||||
instance.IDNOT_BASE_URL = idNotBaseUrl ?? "https://qual-connexion.idnot.fr";
|
||||
instance.IDNOT_AUTHORIZE_ENDPOINT = idNotAuthorizeEndpoint ?? "/IdPOAuth2/authorize/idnot_idp_v1";
|
||||
instance.IDNOT_CLIENT_ID = idNotClientId ?? "4501646203F3EF67";
|
||||
instance.FC_AUTHORIZE_ENDPOINT= fcAuthorizeEndpoint ?? "https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize";
|
||||
instance.FC_CLIENT_ID = fcClientId ?? "211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e";
|
||||
instance.BACK_API_PROTOCOL = backApiProtocol;
|
||||
instance.BACK_API_HOST = backApiHost;
|
||||
instance.BACK_API_ROOT_URL = backApiRootUrl;
|
||||
instance.BACK_API_VERSION = backApiVersion;
|
||||
instance.FRONT_APP_HOST = frontAppHost;
|
||||
instance.IDNOT_BASE_URL = idNotBaseUrl;
|
||||
instance.IDNOT_AUTHORIZE_ENDPOINT = idNotAuthorizeEndpoint;
|
||||
instance.IDNOT_CLIENT_ID = idNotClientId;
|
||||
instance.FC_AUTHORIZE_ENDPOINT= fcAuthorizeEndpoint;
|
||||
instance.FC_CLIENT_ID = fcClientId;
|
||||
instance.DOCAPOST_API_URL = docaposteApiUrl;
|
||||
|
||||
return getLayout(<Component {...pageProps} />);
|
||||
@ -60,18 +63,18 @@ const MyApp = (({
|
||||
|
||||
MyApp.getInitialProps = async () => {
|
||||
return {
|
||||
backApiProtocol: process.env["NEXT_PUBLIC_BACK_API_PROTOCOL"],
|
||||
backApiHost: process.env["NEXT_PUBLIC_BACK_API_HOST"],
|
||||
backApiRootUrl: process.env["NEXT_PUBLIC_BACK_API_ROOT_URL"],
|
||||
backApiVersion: process.env["NEXT_PUBLIC_BACK_API_VERSION"],
|
||||
frontAppHost: process.env["NEXT_PUBLIC_FRONT_APP_HOST"],
|
||||
frontAppPort: process.env["NEXT_PUBLIC_FRONT_APP_PORT"],
|
||||
idNotBaseUrl: process.env["NEXT_PUBLIC_IDNOT_BASE_URL"],
|
||||
idNotAuthorizeEndpoint: process.env["NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT"],
|
||||
idNotClientId: process.env["NEXT_PUBLIC_IDNOT_CLIENT_ID"],
|
||||
fcAuthorizeEndpoint: process.env["NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT"],
|
||||
fcClientId: process.env["NEXT_PUBLIC_FC_CLIENT_ID"],
|
||||
docaposteApiUrl: process.env["NEXT_PUBLIC_DOCAPOST_API_URL"],
|
||||
backApiProtocol: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_PROTOCOL,
|
||||
backApiHost: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_HOST,
|
||||
backApiRootUrl: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_ROOT_URL,
|
||||
backApiVersion: publicRuntimeConfig.NEXT_PUBLIC_BACK_API_VERSION,
|
||||
frontAppHost: publicRuntimeConfig.NEXT_PUBLIC_FRONT_APP_HOST,
|
||||
frontAppPort: publicRuntimeConfig.NEXT_PUBLIC_FRONT_APP_PORT,
|
||||
idNotBaseUrl: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_BASE_URL,
|
||||
idNotAuthorizeEndpoint: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_AUTHORIZE_ENDPOINT,
|
||||
idNotClientId: publicRuntimeConfig.NEXT_PUBLIC_IDNOT_CLIENT_ID,
|
||||
fcAuthorizeEndpoint: publicRuntimeConfig.NEXT_PUBLIC_FC_AUTHORIZE_ENDPOINT,
|
||||
fcClientId: publicRuntimeConfig.NEXT_PUBLIC_FC_CLIENT_ID,
|
||||
docaposteApiUrl: publicRuntimeConfig.NEXT_PUBLIC_DOCAPOST_API_URL,
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user