Compare commits

..

18 Commits

Author SHA1 Message Date
Sosthene
a6a67d4b7c Fix DeedType and DocumentType 2025-09-10 15:56:30 +02:00
Sosthene
caff36edcc Refactor DeedTypeService
* async/await pattern where possible
* remove unused getDeedTypeByUid()
2025-09-10 15:56:30 +02:00
Sosthene
e2cd9a46b0 Update contracts for DeedType 2025-09-10 15:56:30 +02:00
Sosthene
297568d020 Catch empty attributes in completeCollaborators 2025-09-10 15:56:30 +02:00
Sosthene
c511aebbe4 Use the VALIDATOR_ID const 2025-09-10 15:56:30 +02:00
Sosthene
830524a2e9 Add getIdNotUserForOffice 2025-09-10 15:56:30 +02:00
Sosthene
f13eeedf73 LoginCallback heavy refactoring (wip?) 2025-09-10 15:56:30 +02:00
Sosthene
993e26c30b Add addCollaborators method to OfficeService 2025-09-10 15:56:30 +02:00
Sosthene
ceca20c9c7 Remove broken completeOfficeRoles 2025-09-10 15:56:30 +02:00
Sosthene
de2ff30960 MessageBus refactoring
* better error management
* Keep tracks of when messages sent don't have answers
* New convenient methods
2025-09-10 15:56:30 +02:00
Sosthene
68ecdf181f Make AuthModal works with decoupled Pairing creation 2025-09-10 15:56:30 +02:00
Sosthene
3c1196fb44 Heavy refactoring of importData 2025-09-10 15:56:30 +02:00
Sosthene
288f9b97e6 Update of process definition for most process creations 2025-09-10 15:56:30 +02:00
Sosthene
5b9184dfea Proper url for database calls in DatabaseService 2025-09-10 15:56:30 +02:00
Sosthene
1730c1d56f Add AppConstants 2025-09-10 15:56:30 +02:00
Sosthene
8a820d4cd3 Add NEXT_PUBLIC_BACK_API_PORT env variable 2025-09-10 15:56:30 +02:00
Sosthene
e3aadd67d8 Log as client (no verification) 2025-09-10 15:56:30 +02:00
Sosthene
a5ff28539c Set callbackurl to 127.0.0.1:3000 2025-09-10 15:56:30 +02:00
8 changed files with 234 additions and 249 deletions

View File

@ -261,26 +261,34 @@ export default class CollaboratorService extends AbstractService {
if (process.processData.office) {
const office: any = (await OfficeService.getOfficeByUid(process.processData.office.uid)).processData;
process.processData.office = {
uid: office.uid,
idNot: office.idNot,
crpcen: office.crpcen,
name: office.name,
office_status: office.office_status
};
if (office) {
process.processData.office = {
uid: office.uid,
idNot: office.idNot,
crpcen: office.crpcen,
name: office.name,
office_status: office.office_status
};
if (progressCallback) {
progressiveProcess.processData.office = process.processData.office;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
if (progressCallback) {
progressiveProcess.processData.office = process.processData.office;
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
} else {
console.error('Office not found');
}
}
if (process.processData.role) {
const role: any = (await RoleService.getRoleByUid(process.processData.role.uid)).processData;
process.processData.role = {
uid: role.uid,
name: role.name
};
if (!role) {
console.error('Role not found');
} else {
process.processData.role = {
uid: role.uid,
name: role.name
};
}
if (progressCallback) {
progressiveProcess.processData.role = process.processData.role;
@ -290,16 +298,20 @@ export default class CollaboratorService extends AbstractService {
if (process.processData.office_role) {
const officeRole: any = (await OfficeRoleService.getOfficeRoleByUid(process.processData.office_role.uid)).processData;
process.processData.office_role = {
uid: officeRole.uid,
name: officeRole.name,
rules: officeRole.rules?.map((rule: any) => {
return {
uid: rule.uid,
name: rule.name
};
})
};
if (!officeRole) {
console.error('Office role not found');
} else {
process.processData.office_role = {
uid: officeRole.uid,
name: officeRole.name,
rules: officeRole.rules?.map((rule: any) => {
return {
uid: rule.uid,
name: rule.name
};
})
};
}
if (progressCallback) {
progressiveProcess.processData.office_role = process.processData.office_role;

View File

@ -4,7 +4,7 @@ import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import DocumentTypeService from './DocumentTypeService';
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
export default class DeedTypeService extends AbstractService {
@ -12,7 +12,7 @@ export default class DeedTypeService extends AbstractService {
super();
}
public static createDeedType(deedTypeData: any, validatorId: string): Promise<any> {
public static async createDeedType(deedTypeData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
@ -25,105 +25,65 @@ export default class DeedTypeService extends AbstractService {
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const allFields: string[] = [...privateFields, 'roles'];
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
members: [ownerId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
members: [ownerId, validatorId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
min_sig_member: 1,
quorum: 0.01,
fields: allFields,
min_sig_member: 0.01,
},
],
storages: []
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
{
quorum: 0.0,
fields: [...privateFields],
min_sig_member: 0,
},
],
storages: []
storages: [...DEFAULT_STORAGE_URLS]
},
apophis: {
members: [ownerId],
members: [ownerId, validatorId],
validation_rules: [],
storages: []
}
};
return new Promise<any>((resolve: (processCreated: any) => void, reject: (error: string) => void) => {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
this.getDeedTypeByUid(processCreated.processData.uid).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
try {
const processCreated = await this.messageBus.createProcess(processData, privateFields, roles);
await this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id);
await this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id);
const finalProcessData = await this.messageBus.getProcessData(processCreated.processId);
return { processId: processCreated.processId, processData: finalProcessData };
} catch (error) {
throw error;
}
}
public static getDeedTypes(callback: (processes: any[]) => void, waitForAll: boolean = false): void {
public static getDeedTypes(callback: (processes: any[]) => void): void {
// Check if we have valid cache
const items: any[] = this.getItems('_deed_types_');
if (items.length > 0 && !waitForAll) {
if (items.length > 0) {
setTimeout(() => callback([...items]), 0);
}
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'deedType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
this.messageBus.getProcessesDecoded((values: any) => {
return values['utype'] === 'deedType'
&& values['isDeleted'] === 'false';
}).then(async (processes: any) => {
if (processes.length === 0) {
if (waitForAll) {
callback([...items]);
}
callback([...items]);
return;
}
const updatedItems: any[] = [...items];
for (let processIndex = 0; processIndex < processes.length; processIndex++) {
let process = processes[processIndex];
if (!waitForAll) {
process = await this.completeDeedType(process, (processInProgress: any) => {
const currentItems: any[] = [...updatedItems];
const existingIndex: number = currentItems.findIndex(item => item.processData?.uid === processInProgress.processData?.uid);
if (existingIndex >= 0) {
currentItems[existingIndex] = processInProgress;
} else {
currentItems.push(processInProgress);
}
callback(currentItems);
});
} else {
process = await this.completeDeedType(process);
}
const process = processes[processIndex];
// Update cache
this.setItem('_deed_types_', process);
@ -133,90 +93,33 @@ export default class DeedTypeService extends AbstractService {
} else {
updatedItems.push(process);
}
if (!waitForAll) {
callback([...updatedItems]);
}
}
if (waitForAll) {
callback([...updatedItems]);
}
callback([...updatedItems]);
});
}
public static getDeedTypeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_deed_types_', uid);
if (item) {
return Promise.resolve(item);
public static async updateDeedType(processId: string, newData: any): Promise<void> {
try {
const processUpdated = await this.messageBus.updateProcess(
processId,
{ updated_at: new Date().toISOString(), ...newData },
[],
null
);
const newStateId: string = processUpdated.diffs[0]?.state_id;
await this.messageBus.notifyUpdate(processId, newStateId);
await this.messageBus.validateState(processId, newStateId);
const processData = await this.messageBus.getProcessData(processId);
// Update cache
this.setItem('_deed_types_', processData);
} catch (error) {
console.error('Failed to update deed type:', error);
throw error; // Re-throw to allow caller to handle
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['uid'] === uid &&
publicValues['utype'] &&
publicValues['utype'] === 'deedType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false'
).then(async (processes: any[]) => {
if (processes.length === 0) {
resolve(null);
} else {
let process: any = processes[0];
process = await this.completeDeedType(process);
// Update cache
this.setItem('_deed_types_', process);
resolve(process);
}
}).catch(reject);
});
}
public static updateDeedType(process: any, newData: any): Promise<void> {
return new Promise<void>((resolve: () => void) => {
this.messageBus.updateProcess(process.processId, { updated_at: new Date().toISOString(), ...newData }, [], null).then((processUpdated: any) => {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then(() => {
// Update cache
this.setItem('_deed_types_', process);
resolve();
}).catch((error) => console.error('Failed to validate state', error));
}).catch((error) => console.error('Failed to notify update', error));
}).catch((error) => console.error('Failed to update', error));
});
}
private static async completeDeedType(process: any, progressCallback?: (processInProgress: any) => void): Promise<any> {
const progressiveProcess: any = JSON.parse(JSON.stringify(process));
if (process.processData.document_types && process.processData.document_types.length > 0) {
progressiveProcess.processData.document_types = [];
if (progressCallback) {
progressCallback(progressiveProcess);
}
for (const document_type of process.processData.document_types) {
const documentTypeData = (await DocumentTypeService.getDocumentTypeByUid(document_type.uid)).processData;
progressiveProcess.processData.document_types.push(documentTypeData);
// Remove duplicates
progressiveProcess.processData.document_types = progressiveProcess.processData.document_types
.filter((item: any, index: number) => progressiveProcess.processData.document_types.findIndex((t: any) => t.uid === item.uid) === index);
if (progressCallback) {
progressCallback(JSON.parse(JSON.stringify(progressiveProcess)));
}
}
process.processData.document_types = progressiveProcess.processData.document_types;
}
return process;
}
}

View File

@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
import User from 'src/sdk/User';
import AbstractService from './AbstractService';
import { DEFAULT_STORAGE_URLS } from '@Front/Config/AppConstants';
export default class DocumentTypeService extends AbstractService {
@ -10,7 +11,7 @@ export default class DocumentTypeService extends AbstractService {
super();
}
public static createDocumentType(documentTypeData: any, validatorId: string): Promise<any> {
public static createDocumentType(documentTypeData: any, validatorId: string): Promise<{ processId: string, processData: any }> {
const ownerId: string = User.getInstance().getPairingId()!;
const processData: any = {
@ -23,45 +24,27 @@ export default class DocumentTypeService extends AbstractService {
};
const privateFields: string[] = Object.keys(processData);
privateFields.splice(privateFields.indexOf('uid'), 1);
privateFields.splice(privateFields.indexOf('utype'), 1);
privateFields.splice(privateFields.indexOf('isDeleted'), 1);
const allFields: string[] = [...privateFields, 'roles'];
const roles: any = {
demiurge: {
members: [...[ownerId], validatorId],
members: [ownerId],
validation_rules: [],
storages: []
},
owner: {
members: [ownerId],
members: [ownerId, validatorId],
validation_rules: [
{
quorum: 0.5,
fields: [...privateFields, 'roles', 'uid', 'utype'],
min_sig_member: 1,
quorum: 0.01,
fields: allFields,
min_sig_member: 0.01,
},
],
storages: []
},
validator: {
members: [validatorId],
validation_rules: [
{
quorum: 0.5,
fields: ['idCertified', 'roles'],
min_sig_member: 1,
},
{
quorum: 0.0,
fields: [...privateFields],
min_sig_member: 0,
},
],
storages: []
storages: [...DEFAULT_STORAGE_URLS]
},
apophis: {
members: [ownerId],
members: [ownerId, validatorId],
validation_rules: [],
storages: []
}
@ -71,39 +54,80 @@ export default class DocumentTypeService extends AbstractService {
this.messageBus.createProcess(processData, privateFields, roles).then((processCreated: any) => {
this.messageBus.notifyUpdate(processCreated.processId, processCreated.process.states[0].state_id).then(() => {
this.messageBus.validateState(processCreated.processId, processCreated.process.states[0].state_id).then((_stateValidated: any) => {
this.getDocumentTypeByUid(processCreated.processData.uid).then(resolve).catch(reject);
this.messageBus.getProcessData(processCreated.processId).then((processData: any) => {
resolve({ processId: processCreated.processId, processData: processData });
}).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);
});
}
public static getDocumentTypes(): Promise<any[]> {
public static getDocumentTypes(callback: (processes: any[]) => void): void {
// Check if we have valid cache
const items: any[] = this.getItems('_document_types_');
return this.messageBus.getProcessesDecoded((publicValues: any) =>
publicValues['uid'] &&
publicValues['utype'] &&
publicValues['utype'] === 'documentType' &&
publicValues['isDeleted'] &&
publicValues['isDeleted'] === 'false' &&
!items.map((item: any) => item.processData.uid).includes(publicValues['uid'])
).then(async (processes: any[]) => {
if (items.length > 0) {
setTimeout(() => callback([...items]), 0);
}
this.messageBus.getProcessesDecoded((values: any) => {
return values['utype'] === 'documentType'
&& values['isDeleted'] === 'false';
}).then(async (processes: any) => {
if (processes.length === 0) {
return items;
} else {
for (const process of processes) {
// Update cache
this.setItem('_document_types_', process);
items.push(process);
}
return items;
callback([...items]);
return;
}
const updatedItems: any[] = [...items];
for (let processIndex = 0; processIndex < processes.length; processIndex++) {
const process = processes[processIndex];
// Update cache
this.setItem('_document_types_', process);
const existingIndex: number = updatedItems.findIndex(item => item.processId === process.processId);
if (existingIndex >= 0) {
updatedItems[existingIndex] = process;
} else {
updatedItems.push(process);
}
}
callback([...updatedItems]);
});
}
public static getDocumentTypeByProcessId(processId: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_document_types_', processId);
if (item) {
return Promise.resolve(item);
}
return new Promise<any>((resolve: (process: any) => void, reject: (error: string) => void) => {
this.messageBus.getProcessesDecoded((publicValues: any) => {
return publicValues['utype'] === 'documentType' && publicValues['isDeleted'] === 'false';
}).then((processes: any[]) => {
// Find the process with matching processId
const process = processes.find((p: any) => p.processId === processId);
if (!process) {
resolve(null);
return;
}
// Update cache
this.setItem('_document_types_', process);
resolve(process);
}).catch(reject);
});
}
// Keep the old method for backward compatibility but mark as deprecated
/** @deprecated Use getDocumentTypeByProcessId instead */
public static getDocumentTypeByUid(uid: string): Promise<any> {
// Check if we have valid cache
const item: any = this.getItem('_document_types_', uid);
@ -133,10 +157,9 @@ export default class DocumentTypeService extends AbstractService {
const newStateId: string = processUpdated.diffs[0]?.state_id;
this.messageBus.notifyUpdate(process.processId, newStateId).then(() => {
this.messageBus.validateState(process.processId, newStateId).then((_stateValidated) => {
const documentTypeUid: string = process.processData.uid;
this.removeItem('_document_types_', documentTypeUid);
this.removeItem('_document_types_', process.processId);
this.getDocumentTypeByUid(documentTypeUid).then(resolve).catch(reject);
this.getDocumentTypeByProcessId(process.processId).then(resolve).catch(reject);
}).catch(reject);
}).catch(reject);
}).catch(reject);

View File

@ -28,6 +28,8 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
deedTypes.sort((a: any, b: any) => a.name.localeCompare(b.name));
setDeedTypes(deedTypes);
} else {
console.log('[DefaultDeedTypeDashboard] No deed types found');
}
});
}, []);
@ -51,7 +53,7 @@ export default function DefaultDeedTypeDashboard(props: IProps) {
}
bottomButton={{
link: Module.getInstance().get().modules.pages.DeedTypes.pages.Create.props.path,
text: "Créer une liste de pièces",
text: "Créer une liste de pièces", // TODO I think this is misleading, should be "Créer un type d'acte"
}}
/>
);

View File

@ -24,44 +24,74 @@ export default function DocumentTypesCreate(props: IProps) {
const [validationError, setValidationError] = useState<ValidationError[]>([]);
const router = useRouter();
const handleCancel = useCallback(() => {
router.push(Module.getInstance().get().modules.pages.DocumentTypes.props.path);
}, [router]);
const onSubmitHandler = useCallback(
async (e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) => {
try {
const user: any = UserStore.instance.getUser();
const officeId: string = user.office.uid;
if (!user) {
console.error("DocumentTypesCreate: User not found - user is null or undefined");
return;
}
const office = UserStore.instance.getOffice();
if (!office) {
console.error("DocumentTypesCreate: office not found - office is undefined or null");
return;
}
const officeId = office.processId;
const officeIdNot = office.processData.idNot;
const documentFormModel = DocumentType.hydrate<DocumentType>({
...values,
office: Office.hydrate<Office>({
uid: officeId,
})
});
await validateOrReject(documentFormModel, { groups: ["createDocumentType"] });
// const documentFormModel = DocumentType.hydrate<DocumentType>({
// ...values,
// office: Office.hydrate<Office>({
// uid: officeId,
// })
// });
// await validateOrReject(documentFormModel, { groups: ["createDocumentType"] });
const documentTypeData: any = {
...values,
office: {
uid: officeId,
idNot: officeIdNot,
}
};
LoaderService.getInstance().show();
DocumentTypeService.createDocumentType(documentTypeData, DEFAULT_VALIDATOR_ID).then((processCreated: any) => {
try {
const processCreated = await DocumentTypeService.createDocumentType(documentTypeData, DEFAULT_VALIDATOR_ID);
ToasterService.getInstance().success({
title: "Succès !",
description: "Type de document créé avec succès"
});
const documentTypeUid = processCreated.processId.split(':')[0];
if (!documentTypeUid) {
console.error("DocumentTypesCreate: documentTypeUid is undefined - processCreated.processId is missing");
return;
}
router.push(
Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", processCreated.processData.uid),
.modules.pages.DocumentTypes.pages.DocumentTypesInformations.props.path.replace("[uid]", documentTypeUid),
);
} catch (apiError) {
ToasterService.getInstance().error({
title: "Erreur !",
description: "Une erreur est survenue lors de la création du type de document"
});
console.error("Document type creation error:", apiError);
} finally {
LoaderService.getInstance().hide();
});
}
} catch (e) {
if (e instanceof Array) {
setValidationError(e);
}
LoaderService.getInstance().hide();
}
},
[router],
@ -90,7 +120,7 @@ export default function DocumentTypesCreate(props: IProps) {
validationError={validationError.find((error) => error.property === "public_description")}
/>
<div className={classes["buttons-container"]}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED}>
<Button variant={EButtonVariant.PRIMARY} styletype={EButtonstyletype.OUTLINED} onClick={handleCancel}>
Annuler
</Button>
<Button type="submit">Créer le document</Button>

View File

@ -26,9 +26,12 @@ export default function DocumentTypesEdit() {
async function getDocumentType() {
if (!documentTypeUid) return;
LoaderService.getInstance().show();
DocumentTypeService.getDocumentTypeByUid(documentTypeUid as string).then((process: any) => {
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
if (process) {
const documentType: any = process.processData;
const documentType: any = {
...process.processData,
processId: process.processId
};
setDocumentTypeSelected(documentType);
}
LoaderService.getInstance().hide();
@ -53,7 +56,7 @@ export default function DocumentTypesEdit() {
return;
}
LoaderService.getInstance().show();
DocumentTypeService.getDocumentTypeByUid(documentTypeUid as string).then((process: any) => {
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
if (process) {
DocumentTypeService.updateDocumentType(process, values).then(() => {
router.push(

View File

@ -22,13 +22,23 @@ export default function DocumentTypesInformations() {
useEffect(() => {
async function getDocument() {
if (!documentTypeUid) return;
if (!documentTypeUid) {
console.log('DocumentTypesInformations: documentTypeUid is not available yet');
return;
}
DocumentTypeService.getDocumentTypeByUid(documentTypeUid as string).then((process: any) => {
DocumentTypeService.getDocumentTypeByProcessId(documentTypeUid as string).then((process: any) => {
if (process) {
const document: any = process.processData;
const document: any = {
...process.processData,
processId: process.processId
};
setDocumentSelected(document);
} else {
console.log('DocumentTypesInformations: No process found for processId:', documentTypeUid);
}
}).catch((error) => {
console.error('DocumentTypesInformations: Error fetching document:', error);
});
}
@ -69,13 +79,15 @@ export default function DocumentTypesInformations() {
</div>
</div>
<div className={classes["right"]}>
<Link
href={Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", documentSelected?.uid ?? "")}
className={classes["edit-icon-container"]}>
<Image src={PenICon} alt="edit informations" />
</Link>
{(documentSelected as any)?.processId && (
<Link
href={Module.getInstance()
.get()
.modules.pages.DocumentTypes.pages.Edit.props.path.replace("[uid]", (documentSelected as any).processId)}
className={classes["edit-icon-container"]}>
<Image src={PenICon} alt="edit informations" />
</Link>
)}
</div>
</div>
</div>

View File

@ -51,7 +51,7 @@ export default function StepEmail(props: IProps) {
);
*/
router.push(
`https://qual-connexion.idnot.fr/user/IdPOAuth2/authorize/idnot_idp_v1?client_id=B3CE56353EDB15A9&redirect_uri=http://local.lecoffreio.4nkweb:3000/authorized-client&scope=openid,profile&response_type=code`,
`https://qual-connexion.idnot.fr/user/IdPOAuth2/authorize/idnot_idp_v1?client_id=B3CE56353EDB15A9&redirect_uri=http://127.0.0.1:3000/authorized-client&scope=openid,profile&response_type=code`,
);
}, [router]);