Compare commits

...

3 Commits
main ... mocked

10 changed files with 3476 additions and 1590 deletions

15
docs/mocks.md Normal file
View File

@ -0,0 +1,15 @@
# MSW Mocking - Le Coffre Front
Objectif: mocker les appels API pendant le développement et les tests.
Fichiers principaux:
- `src/mocks/handlers.ts` — définition des endpoints mockés
- `src/mocks/browser.ts` — démarrage du worker dans le navigateur
- `src/mocks/server.ts` — serveur MSW côté Node (pour les tests SSR si nécessaire)
Intégration:
- `_app.tsx` charge dynamiquement les mocks côté client en mode développement.
Exemple de mocking:
- Endpoint GET `*/admin/users` retourne une liste dutilisateurs simulée.
- Endpoint GET `*/admin/users/:uid` retourne les détails dun utilisateur simulé.

View File

@ -1,3 +1,13 @@
module.exports = {
// Allow trusted origins in development to avoid cross-origin warnings
// This option is supported in recent Next.js versions; if not recognized, it will be ignored.
allowedDevOrigins: [
'http://92.243.27.160',
'http://92.243.27.160:3000',
'http://localhost:3000',
],
};
/** @type {import('next').NextConfig} */
const nextConfig = {

4550
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "./scripts/start-dev.sh 0.0.0.0 3000",
"build": "next build",
"start": "next start",
"lint": "next lint",
@ -15,8 +15,6 @@
"@heroicons/react": "^2.1.3",
"@mui/material": "^5.11.13",
"@next/third-parties": "^14.2.3",
"@types/node": "18.15.1",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@uidotdev/usehooks": "^2.4.1",
"class-validator": "^0.14.0",
@ -41,11 +39,14 @@
"react-toastify": "^9.1.3",
"sass": "^1.59.2",
"sharp": "^0.32.1",
"typescript": "4.9.5",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/react-gtm-module": "^2.0.3"
"@types/node": "24.3.1",
"@types/react": "19.1.12",
"@types/react-gtm-module": "^2.0.3",
"msw": "^2.11.1",
"typescript": "5.9.2"
}
}

16
scripts/start-dev.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
bind_addr="${1:-0.0.0.0}"
port="${2:-3000}"
if [ -x "./node_modules/.bin/next" ]; then
exec ./node_modules/.bin/next dev -H "$bind_addr" -p "$port"
else
if command -v npx >/dev/null 2>&1; then
exec npx --yes next dev -H "$bind_addr" -p "$port"
else
echo "Next.js binary not found and npx not available" >&2
exit 1
fi
fi

12
src/mocks/browser.ts Normal file
View File

@ -0,0 +1,12 @@
export const start = async () => {
if (typeof window === 'undefined') return;
try {
const mswModule = await import('msw');
const { setupWorker } = mswModule;
const { handlers } = await import('./handlers');
const worker = setupWorker(...handlers);
worker.start({ onUnhandledRequests: 'warn' });
} catch (err) {
console.warn('MSW not loaded or unavailable', err);
}
};

255
src/mocks/handlers.ts Normal file
View File

@ -0,0 +1,255 @@
import { rest } from 'msw';
export const handlers = [
// Mock: Get all users (Admin)
rest.get('*/admin/users', (req, res, ctx) => {
const users = [
{ uid: 'u1', idNot: 'id1', name: 'Alice Admin', email: 'alice@example.com' },
{ uid: 'u2', idNot: 'id2', name: 'Bob Admin', email: 'bob@example.com' }
];
return res(ctx.status(200), ctx.json(users));
}),
// Mock: Get user by UID (Admin)
rest.get('*/admin/users/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Demo Admin', email: 'demo@example.com' }));
}),
// Mock: Documents (Admin)
rest.get('*/admin/documents', (req, res, ctx) => {
const docs = [
{ uid: 'doc1', title: 'Contrat', status: 'DRAFT' },
{ uid: 'doc2', title: 'Pacte', status: 'PUBLISHED' }
];
return res(ctx.status(200), ctx.json(docs));
}),
rest.get('*/admin/documents/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const doc = { uid, title: 'Demo Document', status: 'PUBLISHED' };
return res(ctx.status(200), ctx.json(doc));
}),
rest.post('*/admin/documents', (req, res, ctx) => {
const body = (req as any).body || {};
const newDoc = { uid: 'doc-new', ...body } as any;
return res(ctx.status(201), ctx.json(newDoc));
}),
rest.put('*/admin/documents/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const doc = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(doc));
}),
rest.delete('*/admin/documents/:uid', (req, res, ctx) => {
return res(ctx.status(204));
}),
// Mock: Document Types (Admin)
rest.get('*/admin/document-types', (req, res, ctx) => {
const items = [ { uid: 'dt1', name: 'Type A' }, { uid: 'dt2', name: 'Type B' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Type Doc', public_description: 'desc' }));
}),
rest.post('*/admin/document-types', (req, res, ctx) => {
const body = (req as any).body || {};
const dt = { uid: 'dt-new', ...body } as any;
return res(ctx.status(201), ctx.json(dt));
}),
rest.put('*/admin/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const dt = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(dt));
}),
// Mock: Roles (Admin)
rest.get('*/admin/roles', (req, res, ctx) => {
const roles = [ { uid: 'r1', name: 'Admin' }, { uid: 'r2', name: 'Editor' } ];
return res(ctx.status(200), ctx.json(roles));
}),
rest.get('*/admin/roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Role Demo' }));
}),
rest.post('*/admin/roles', (req, res, ctx) => {
const body = (req as any).body || {};
const role = { uid: 'r-new', ...body } as any;
return res(ctx.status(201), ctx.json(role));
}),
rest.put('*/admin/roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const role = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(role));
}),
// Mock: Office Roles (Admin)
rest.get('*/admin/office-roles', (req, res, ctx) => {
const items = [ { uid: 'or1', name: 'Office A' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/office-roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Office Role Demo' }));
}),
rest.post('*/admin/office-roles', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'or-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/office-roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Mock: Deeds (Admin)
rest.get('*/admin/deeds', (req, res, ctx) => {
const deeds = [ { uid: 'd1', name: 'Deed 1' } ];
return res(ctx.status(200), ctx.json(deeds));
}),
rest.get('*/admin/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Deed Demo' }));
}),
rest.post('*/admin/deeds', (req, res, ctx) => {
const body = (req as any).body || {};
const deed = { uid: 'd-new', ...body } as any;
return res(ctx.status(201), ctx.json(deed));
}),
rest.put('*/admin/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const deed = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(deed));
}),
// Mock: Deed Types (Admin)
rest.get('*/admin/deed-types', (req, res, ctx) => {
const items = [ { uid: 'dt1', name: 'Deed Type 1' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/deed-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Deed Type Demo' }));
}),
rest.post('*/admin/deed-types', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'dt-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/deed-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Mock: Rules (Admin)
rest.get('*/admin/rules', (req, res, ctx) => {
const rules = [ { uid: 'rul1', name: 'Rule 1' } ];
return res(ctx.status(200), ctx.json(rules));
}),
rest.get('*/admin/rules/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Rule Demo' }));
}),
rest.post('*/admin/rules', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'rul-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/rules/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Notary - Users
rest.get('*/notary/users', (req, res, ctx) => {
const users = [ { uid: 'nu1', name: 'Notary User 1' }, { uid: 'nu2', name: 'Notary User 2' } ];
return res(ctx.status(200), ctx.json(users));
}),
rest.get('*/notary/users/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary User Demo' }));
}),
// Notary - Customers
rest.get('*/notary/customers', (req, res, ctx) => {
const customers = [ { uid: 'nc1', first_name: 'Demo', last_name: 'Customer', email: 'demo@example.com' } ];
return res(ctx.status(200), ctx.json(customers));
}),
rest.get('*/notary/customers/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, first_name: 'Demo', last_name: 'Customer', email: 'demo@example.com' }));
}),
rest.post('*/notary/customers', (req, res, ctx) => {
const body = (req as any).body || {};
const customer = { uid: 'nc-new', ...body } as any;
return res(ctx.status(201), ctx.json(customer));
}),
rest.put('*/notary/customers/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const customer = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(customer));
}),
// Notary - Offices
rest.get('*/notary/offices', (req, res, ctx) => {
const offices = [ { uid: 'no1', name: 'Notary Office 1' } ];
return res(ctx.status(200), ctx.json(offices));
}),
rest.get('*/notary/offices/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Office Demo' }));
}),
// Notary - Document Types
rest.get('*/notary/document-types', (req, res, ctx) => {
const items = [ { uid: 'ndt1', name: 'Notary Doc Type 1' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/notary/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Doc Type Demo' }));
}),
rest.post('*/notary/document-types', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'ndt-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/notary/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Notary - Folders
rest.get('*/notary/folders', (req, res, ctx) => {
const folders = [ { uid: 'nf1', name: 'Notary Folder 1', customers: [] } ];
return res(ctx.status(200), ctx.json(folders));
}),
rest.get('*/notary/folders/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Folder Demo', customers: [] }));
}),
rest.post('*/notary/folders', (req, res, ctx) => {
const body = (req as any).body || {};
const folder = { uid: 'nf-new', ...body } as any;
return res(ctx.status(201), ctx.json(folder));
}),
rest.put('*/notary/folders/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const folder = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(folder));
}),
rest.delete('*/notary/folders/:uid', (req, res, ctx) => {
return res(ctx.status(204));
}),
// Notary - Deeds
rest.get('*/notary/deeds', (req, res, ctx) => {
const deeds = [ { uid: 'nd1', name: 'Notary Deed 1' } ];
return res(ctx.status(200), ctx.json(deeds));
}),
rest.get('*/notary/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Deed Demo' }));
}),
// End of mocks
];

4
src/mocks/server.ts Normal file
View File

@ -0,0 +1,4 @@
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

View File

@ -1,106 +1,118 @@
import "@Front/index.scss";
import { DefaultLayout } from "@Front/Components/LayoutTemplates/DefaultLayout";
import { FrontendVariables } from "@Front/Config/VariablesFront";
import type { NextPage } from "next";
import type { AppType, AppProps } from "next/app";
import { useEffect, type ReactElement, type ReactNode } from "react";
import getConfig from "next/config";
import { GoogleTagManager } from "@next/third-parties/google";
import { hotjar } from "react-hotjar";
import '@Front/index.scss';
import { DefaultLayout } from '@Front/Components/LayoutTemplates/DefaultLayout';
import { FrontendVariables } from '@Front/Config/VariablesFront';
import type { NextPage } from 'next';
import type { AppType, AppProps } from 'next/app';
import { useEffect, type ReactElement, type ReactNode } from 'react';
import getConfig from 'next/config';
import { GoogleTagManager } from '@next/third-parties/google';
import { hotjar } from 'react-hotjar';
export type NextPageWithLayout<TProps = Record<string, unknown>, TInitialProps = TProps> = NextPage<TProps, TInitialProps> & {
getLayout?: (page: ReactElement) => ReactNode;
};
export type NextPageWithLayout<TProps = Record<string, unknown>, TInitialProps = TProps> =
NextPage<TProps, TInitialProps> & {
getLayout?: (page: ReactElement) => ReactNode;
};
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
Component: NextPageWithLayout;
} & {
backApiProtocol: string;
backApiHost: string;
backApiRootUrl: string;
backApiVersion: string;
frontAppHost: string;
idNotBaseUrl: string;
idNotAuthorizeEndpoint: string;
idNotClientId: string;
fcAuthorizeEndpoint: string;
fcClientId: string;
docaposteApiUrl: string;
hotjarSiteId: number;
hotjarVersion: number;
backApiProtocol: string;
backApiHost: string;
backApiRootUrl: string;
backApiVersion: string;
frontAppHost: string;
idNotBaseUrl: string;
idNotAuthorizeEndpoint: string;
idNotClientId: string;
fcAuthorizeEndpoint: string;
fcClientId: string;
docaposteApiUrl: string;
hotjarSiteId: number;
hotjarVersion: number;
};
const { publicRuntimeConfig } = getConfig();
const MyApp = (({
Component,
pageProps,
backApiProtocol,
backApiHost,
backApiRootUrl,
backApiVersion,
frontAppHost,
idNotBaseUrl,
idNotAuthorizeEndpoint,
idNotClientId,
fcAuthorizeEndpoint,
fcClientId,
docaposteApiUrl,
hotjarSiteId,
hotjarVersion,
Component,
pageProps,
backApiProtocol,
backApiHost,
backApiRootUrl,
backApiVersion,
frontAppHost,
idNotBaseUrl,
idNotAuthorizeEndpoint,
idNotClientId,
fcAuthorizeEndpoint,
fcClientId,
docaposteApiUrl,
hotjarSiteId,
hotjarVersion,
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>);
const getLayout = Component.getLayout ?? ((page) => <DefaultLayout children={page}></DefaultLayout>);
const instance = FrontendVariables.getInstance();
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;
instance.HOTJAR_SITE_ID = hotjarSiteId;
instance.HOJAR_VERSION = hotjarVersion;
useEffect(() => {
if (!hotjarSiteId || !hotjarVersion) {
console.warn("No hotjar site id or version provided");
return;
}
console.log("Intializing hotjar");
hotjar.initialize({
id: hotjarSiteId,
sv: hotjarVersion,
});
}, [hotjarSiteId, hotjarVersion]);
const instance = FrontendVariables.getInstance();
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;
instance.HOTJAR_SITE_ID = hotjarSiteId;
instance.HOJAR_VERSION = hotjarVersion;
return getLayout(
<Component {...pageProps}>
<GoogleTagManager gtmId="GTM-5GLJN86P" />
</Component>,
);
useEffect(() => {
if (!hotjarSiteId || !hotjarVersion) {
console.warn("No hotjar site id or version provided");
return;
}
console.log("Intializing hotjar");
hotjar.initialize({
id: hotjarSiteId,
sv: hotjarVersion,
});
}, [hotjarSiteId, hotjarVersion]);
// Bootstrap MSW in development to mock API calls
useEffect(() => {
if (typeof window === 'undefined') return;
import('../mocks/browser')
.then((m) => {
if (typeof m?.start === 'function') m.start();
})
.catch(() => {});
}, []);
return getLayout(
<Component {...pageProps}>
<GoogleTagManager gtmId="GTM-5GLJN86P" />
</Component>,
);
}) as AppType;
MyApp.getInitialProps = async () => {
return {
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,
hotjarSiteId: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_SITE_ID,
hotjarVersion: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_VERSION,
};
return {
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,
hotjarSiteId: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_SITE_ID,
hotjarVersion: publicRuntimeConfig.NEXT_PUBLIC_HOTJAR_VERSION,
};
};
export default MyApp;

5
tests/README.md Normal file
View File

@ -0,0 +1,5 @@
# Tests avec MSW
- Installer MSW (déjà ajouté dans les devDependencies).
- Démarrer les mocks dans le navigateur via `_app.tsx` (setup décrit dans docs/mocks.md).
- Eventuellement ajouter des tests dintégration qui utilisent les handlers MSW.