Ajout des cgu
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>datasentinel</title>
|
<meta name="description" content="Data Sentinel — Plateforme de monitoring de la qualité des données by XEFI" />
|
||||||
|
<title>Data Sentinel — by XEFI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import Fournisseur from './pages/Fournisseur/Fournisseur';
|
|||||||
import Historique from './pages/Historique/Historique';
|
import Historique from './pages/Historique/Historique';
|
||||||
import Admin from './pages/Admin/Admin';
|
import Admin from './pages/Admin/Admin';
|
||||||
import Users from './pages/Users/Users';
|
import Users from './pages/Users/Users';
|
||||||
|
import Documentation from './pages/Documentation/Documentation';
|
||||||
import MentionsLegales from './pages/MentionsLegales/MentionsLegales';
|
import MentionsLegales from './pages/MentionsLegales/MentionsLegales';
|
||||||
|
import CGU from './pages/CGU/CGU';
|
||||||
|
|
||||||
/* Composant pour protéger les routes nécessitant une authentification */
|
/* Composant pour protéger les routes nécessitant une authentification */
|
||||||
function ProtectedRoute({ children }) {
|
function ProtectedRoute({ children }) {
|
||||||
@@ -31,7 +33,7 @@ function App() {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Route publique : login */}
|
{/* Routes publiques */}
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
@@ -40,9 +42,8 @@ function App() {
|
|||||||
</PublicRoute>
|
</PublicRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Route publique : mentions légales (sans sidebar) */}
|
|
||||||
<Route path="/mentions-legales" element={<MentionsLegales />} />
|
<Route path="/mentions-legales" element={<MentionsLegales />} />
|
||||||
|
<Route path="/cgu" element={<CGU />} />
|
||||||
|
|
||||||
{/* Routes protégées avec Layout */}
|
{/* Routes protégées avec Layout */}
|
||||||
<Route
|
<Route
|
||||||
@@ -62,6 +63,7 @@ function App() {
|
|||||||
<Route path="historique" element={<Historique />} />
|
<Route path="historique" element={<Historique />} />
|
||||||
<Route path="admin" element={<Admin />} />
|
<Route path="admin" element={<Admin />} />
|
||||||
<Route path="utilisateurs" element={<Users />} />
|
<Route path="utilisateurs" element={<Users />} />
|
||||||
|
<Route path="documentation" element={<Documentation />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Redirection par défaut */}
|
{/* Redirection par défaut */}
|
||||||
@@ -73,4 +75,3 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useAuth } from '../../context/AuthContext';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import styles from './Header.module.css';
|
import styles from './Header.module.css';
|
||||||
|
|
||||||
export default function Header({ onToggleSidebar }) {
|
export default function Header({ onToggleSidebar, sidebarOpen }) {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -15,26 +15,45 @@ export default function Header({ onToggleSidebar }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={styles.header}>
|
<header className={styles.header} role="banner">
|
||||||
|
{/* Lien d'évitement pour l'accessibilité clavier */}
|
||||||
|
<a href="#main-content" className={styles.skipLink}>
|
||||||
|
Aller au contenu principal
|
||||||
|
</a>
|
||||||
|
|
||||||
<div className={styles.left}>
|
<div className={styles.left}>
|
||||||
{/* Bouton burger pour mobile */}
|
{/* Bouton burger pour mobile */}
|
||||||
<button className={styles.burgerBtn} onClick={onToggleSidebar} aria-label="Menu">
|
<button
|
||||||
<Menu size={20} color="var(--color-white)" />
|
className={styles.burgerBtn}
|
||||||
|
onClick={onToggleSidebar}
|
||||||
|
aria-label={sidebarOpen ? 'Fermer le menu' : 'Ouvrir le menu'}
|
||||||
|
aria-expanded={sidebarOpen}
|
||||||
|
aria-controls="sidebar"
|
||||||
|
>
|
||||||
|
<Menu size={20} color="var(--color-white)" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo} role="img" aria-label="Data Sentinel">
|
||||||
<span className={styles.logoIcon}>▶</span>
|
<span className={styles.logoIcon} aria-hidden="true">▶</span>
|
||||||
<span className={styles.logoText}>Data Sentinel</span>
|
<span className={styles.logoText}>Data Sentinel</span>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.byXefi}>by XEFI</span>
|
<span className={styles.byXefi} aria-label="by XEFI">by XEFI</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
<button className={styles.iconBtn} aria-label="Notifications">
|
<button className={styles.iconBtn} aria-label="Notifications">
|
||||||
<Bell size={18} color="var(--color-white)" />
|
<Bell size={18} color="var(--color-white)" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
{user && <span className={styles.username}>{user.nom}</span>}
|
{user && (
|
||||||
<button className={styles.logoutBtn} onClick={handleLogout}>
|
<span className={styles.username} aria-label={`Connecté en tant que ${user.nom}`}>
|
||||||
<LogOut size={15} />
|
{user.nom}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className={styles.logoutBtn}
|
||||||
|
onClick={handleLogout}
|
||||||
|
aria-label="Se déconnecter"
|
||||||
|
>
|
||||||
|
<LogOut size={15} aria-hidden="true" />
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -99,3 +99,43 @@
|
|||||||
.logoutBtn:hover {
|
.logoutBtn:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lien d'évitement accessible (skip link) */
|
||||||
|
.skipLink {
|
||||||
|
position: absolute;
|
||||||
|
top: -100px;
|
||||||
|
left: 8px;
|
||||||
|
background: var(--color-white);
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
z-index: 9999;
|
||||||
|
transition: top 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skipLink:focus {
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus visible sur tous les éléments interactifs */
|
||||||
|
.burgerBtn:focus-visible,
|
||||||
|
.iconBtn:focus-visible,
|
||||||
|
.logoutBtn:focus-visible {
|
||||||
|
outline: 2px solid var(--color-white);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive : masquer le nom utilisateur sur très petit écran */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.username {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.byXefi {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ export default function Layout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<Header onToggleSidebar={() => setSidebarOpen(o => !o)} />
|
<Header
|
||||||
|
onToggleSidebar={() => setSidebarOpen(o => !o)}
|
||||||
|
sidebarOpen={sidebarOpen}
|
||||||
|
/>
|
||||||
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||||
<main className={styles.main}>
|
<main id="main-content" className={styles.main} tabIndex="-1">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
List,
|
List,
|
||||||
Book,
|
Book,
|
||||||
Shield,
|
Shield,
|
||||||
|
FileText,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import styles from './Sidebar.module.css';
|
import styles from './Sidebar.module.css';
|
||||||
|
|
||||||
@@ -21,8 +22,9 @@ function NavItem({ to, icon: Icon, label }) {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${styles.navItem} ${isActive ? styles.active : ''}`
|
`${styles.navItem} ${isActive ? styles.active : ''}`
|
||||||
}
|
}
|
||||||
|
aria-current={undefined}
|
||||||
>
|
>
|
||||||
<Icon size={16} />
|
<Icon size={16} aria-hidden="true" />
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
@@ -32,12 +34,23 @@ export default function Sidebar({ isOpen, onClose }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Overlay mobile pour fermer la sidebar */}
|
{/* Overlay mobile pour fermer la sidebar */}
|
||||||
{isOpen && <div className={styles.overlay} onClick={onClose} />}
|
{isOpen && (
|
||||||
|
<div
|
||||||
|
className={styles.overlay}
|
||||||
|
onClick={onClose}
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<aside className={`${styles.sidebar} ${isOpen ? styles.open : ''}`}>
|
<aside
|
||||||
<nav className={styles.nav}>
|
id="sidebar"
|
||||||
|
className={`${styles.sidebar} ${isOpen ? styles.open : ''}`}
|
||||||
|
aria-label="Navigation principale"
|
||||||
|
>
|
||||||
|
<nav className={styles.nav} aria-label="Menu de navigation">
|
||||||
{/* Navigation principale */}
|
{/* Navigation principale */}
|
||||||
<div className={styles.section}>
|
<div className={styles.section} role="group" aria-label="Monitoring">
|
||||||
<NavItem to="/dashboard" icon={LayoutGrid} label="Dashboard" />
|
<NavItem to="/dashboard" icon={LayoutGrid} label="Dashboard" />
|
||||||
<NavItem to="/consolidee" icon={Layers} label="Vue consolidée" />
|
<NavItem to="/consolidee" icon={Layers} label="Vue consolidée" />
|
||||||
<NavItem to="/contrat" icon={List} label="Contrat" />
|
<NavItem to="/contrat" icon={List} label="Contrat" />
|
||||||
@@ -45,28 +58,32 @@ export default function Sidebar({ isOpen, onClose }) {
|
|||||||
<NavItem to="/historique" icon={Clock} label="Historique" />
|
<NavItem to="/historique" icon={Clock} label="Historique" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.separator} />
|
<div className={styles.separator} role="separator" />
|
||||||
|
|
||||||
{/* Section administration */}
|
{/* Section administration */}
|
||||||
<div className={styles.sectionLabel}>ADMIN</div>
|
<div className={styles.sectionLabel} aria-hidden="true">ADMIN</div>
|
||||||
<div className={styles.section}>
|
<div className={styles.section} role="group" aria-label="Administration">
|
||||||
<NavItem to="/admin" icon={Settings} label="Nomenclature" />
|
<NavItem to="/admin" icon={Settings} label="Nomenclature" />
|
||||||
<NavItem to="/utilisateurs" icon={Users} label="Utilisateurs" />
|
<NavItem to="/utilisateurs" icon={Users} label="Utilisateurs" />
|
||||||
<NavItem to="/journal" icon={List} label="Journal" />
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Liens de pied de sidebar */}
|
{/* Liens de pied de sidebar */}
|
||||||
<div className={styles.footer}>
|
<nav className={styles.footer} aria-label="Liens utiles">
|
||||||
<NavLink to="/documentation" className={styles.footerLink}>
|
<NavLink to="/documentation" className={styles.footerLink}>
|
||||||
<Book size={13} />
|
<Book size={13} aria-hidden="true" />
|
||||||
Documentation
|
Documentation
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to="/mentions-legales" className={styles.footerLink}>
|
<NavLink to="/mentions-legales" className={styles.footerLink}>
|
||||||
<Shield size={13} />
|
<Shield size={13} aria-hidden="true" />
|
||||||
Mentions légales
|
Mentions légales
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
<NavLink to="/cgu" className={styles.footerLink}>
|
||||||
|
<FileText size={13} aria-hidden="true" />
|
||||||
|
CGU
|
||||||
|
</NavLink>
|
||||||
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,13 @@
|
|||||||
--shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
--shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
--header-height: 56px;
|
--header-height: 56px;
|
||||||
--sidebar-width: 220px;
|
--sidebar-width: 220px;
|
||||||
|
|
||||||
|
/* Breakpoints (référence pour JS si nécessaire) */
|
||||||
|
--bp-xs: 320px;
|
||||||
|
--bp-sm: 640px;
|
||||||
|
--bp-md: 1024px;
|
||||||
|
--bp-lg: 1280px;
|
||||||
|
--bp-xl: 1920px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset */
|
/* Reset */
|
||||||
@@ -83,3 +90,73 @@ img {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================
|
||||||
|
ACCESSIBILITÉ
|
||||||
|
======================== */
|
||||||
|
|
||||||
|
/* Focus visible global — indispensable pour la navigation clavier (WCAG 2.1 SC 2.4.7) */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Masque le focus sur les clics souris (uniquement clavier) */
|
||||||
|
:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Texte masqué visuellement mais lisible par les lecteurs d'écran */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Réduire les animations pour les utilisateurs sensibles */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================
|
||||||
|
RESPONSIVITÉ GLOBALE
|
||||||
|
======================== */
|
||||||
|
|
||||||
|
/* Tableaux responsifs : scroll horizontal sur petit écran */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs et selects pleine largeur sur mobile */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="search"],
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
// src/pages/CGU/CGU.jsx
|
||||||
|
// Conditions Générales d'Utilisation — page publique sans sidebar
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
import Button from '../../components/common/Button';
|
||||||
|
import styles from './CGU.module.css';
|
||||||
|
|
||||||
|
export default function CGU() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<span className={styles.logo} aria-label="Data Sentinel">▶ Data Sentinel</span>
|
||||||
|
<Button variant="link" onClick={() => navigate(-1)} aria-label="Retour à la page précédente">
|
||||||
|
<ArrowLeft size={14} />
|
||||||
|
Retour
|
||||||
|
</Button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main-content">
|
||||||
|
<h1 className={styles.mainTitle}>Conditions Générales d'Utilisation</h1>
|
||||||
|
<p className={styles.subtitle}>Version 1.0 — en vigueur depuis le 1er janvier 2026</p>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art1">
|
||||||
|
<h2 id="art1">Article 1 — Objet et champ d'application</h2>
|
||||||
|
<p>
|
||||||
|
Les présentes Conditions Générales d'Utilisation (ci-après « CGU ») régissent l'accès et
|
||||||
|
l'utilisation de la plateforme <strong>Data Sentinel</strong>, outil interne de monitoring
|
||||||
|
de la qualité des données développé par COYAUD Anthony pour le compte de <strong>XEFI SA</strong>
|
||||||
|
(ci-après « l'Éditeur »).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Data Sentinel est une application web à usage professionnel interne, accessible uniquement
|
||||||
|
aux collaborateurs habilités de XEFI et de ses filiales. Toute utilisation de la plateforme
|
||||||
|
implique l'acceptation pleine et entière des présentes CGU.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art2">
|
||||||
|
<h2 id="art2">Article 2 — Accès au service</h2>
|
||||||
|
<p>
|
||||||
|
L'accès à Data Sentinel est réservé aux utilisateurs disposant d'un compte valide créé
|
||||||
|
par un administrateur système XEFI. L'authentification s'effectue via un identifiant et
|
||||||
|
un mot de passe fournis par l'entreprise.
|
||||||
|
</p>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li>Chaque utilisateur est responsable de la confidentialité de ses identifiants.</li>
|
||||||
|
<li>Le partage de comptes est strictement interdit.</li>
|
||||||
|
<li>Tout accès non autorisé doit être signalé immédiatement au service informatique.</li>
|
||||||
|
<li>L'Éditeur se réserve le droit de suspendre tout accès en cas d'utilisation non conforme.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art3">
|
||||||
|
<h2 id="art3">Article 3 — Description du service</h2>
|
||||||
|
<p>Data Sentinel offre les fonctionnalités suivantes :</p>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li><strong>Dashboard</strong> — Vue d'ensemble des indicateurs clés de qualité des données (KPI).</li>
|
||||||
|
<li><strong>Monitoring</strong> — Suivi en temps réel des monitorings CRM, Sage et Infocentre.</li>
|
||||||
|
<li><strong>Vue consolidée</strong> — Agrégation des résultats par service et catégorie.</li>
|
||||||
|
<li><strong>Historique</strong> — Consultation et export des données historiques.</li>
|
||||||
|
<li><strong>Administration</strong> — Gestion des nomenclatures, utilisateurs et journaux.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art4">
|
||||||
|
<h2 id="art4">Article 4 — Obligations de l'utilisateur</h2>
|
||||||
|
<p>L'utilisateur s'engage à :</p>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li>Utiliser la plateforme exclusivement dans le cadre de ses missions professionnelles.</li>
|
||||||
|
<li>Ne pas tenter de contourner les mécanismes de sécurité (authentification, contrôle d'accès).</li>
|
||||||
|
<li>Ne pas extraire, reproduire ou diffuser les données sans autorisation.</li>
|
||||||
|
<li>Signaler tout dysfonctionnement ou anomalie de sécurité à l'équipe informatique.</li>
|
||||||
|
<li>Respecter les règles internes XEFI relatives à la sécurité des systèmes d'information.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art5">
|
||||||
|
<h2 id="art5">Article 5 — Protection des données personnelles (RGPD)</h2>
|
||||||
|
<p>
|
||||||
|
Conformément au Règlement Général sur la Protection des Données (UE 2016/679), XEFI SA
|
||||||
|
traite les données personnelles des utilisateurs dans le strict respect de la réglementation.
|
||||||
|
</p>
|
||||||
|
<div className={styles.infoBox} role="note" aria-label="Informations RGPD">
|
||||||
|
<p><strong>Responsable du traitement :</strong> XEFI SA — 2507 Avenue de l'Europe, 69140 Rillieux-la-Pape</p>
|
||||||
|
<p><strong>Délégué à la Protection des Données (DPO) :</strong> dpo@xefi.fr</p>
|
||||||
|
<p><strong>Finalité :</strong> Authentification, traçabilité des actions, sécurité du système.</p>
|
||||||
|
<p><strong>Durée de conservation :</strong> 24 mois à compter de la dernière connexion.</p>
|
||||||
|
<p><strong>Droits :</strong> Accès, rectification, effacement, portabilité — à exercer auprès de dpo@xefi.fr.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art6">
|
||||||
|
<h2 id="art6">Article 6 — Accessibilité</h2>
|
||||||
|
<p>
|
||||||
|
XEFI s'engage à rendre Data Sentinel accessible conformément au Référentiel Général
|
||||||
|
d'Amélioration de l'Accessibilité (RGAA). L'application respecte les principes WCAG 2.1
|
||||||
|
niveau AA : perceptibilité, utilisabilité, compréhension et robustesse.
|
||||||
|
</p>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
<li>Navigation clavier intégrale supportée.</li>
|
||||||
|
<li>Compatibilité avec les lecteurs d'écran (ARIA labels, landmarks, roles).</li>
|
||||||
|
<li>Contrastes de couleurs conformes aux exigences WCAG 2.1 AA (ratio 4.5:1 minimum).</li>
|
||||||
|
<li>Interface responsive adaptée aux écrans de 320px à 1920px.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Pour signaler un problème d'accessibilité : <strong>dpo@xefi.fr</strong>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art7">
|
||||||
|
<h2 id="art7">Article 7 — Disponibilité et maintenance</h2>
|
||||||
|
<p>
|
||||||
|
L'Éditeur s'efforce de maintenir la plateforme disponible 24h/24, 7j/7, sans garantie
|
||||||
|
de continuité de service. Des interruptions peuvent survenir pour maintenance planifiée
|
||||||
|
ou en cas d'incident technique. Les utilisateurs seront prévenus dans la mesure du possible.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art8">
|
||||||
|
<h2 id="art8">Article 8 — Propriété intellectuelle</h2>
|
||||||
|
<p>
|
||||||
|
L'ensemble des éléments composant Data Sentinel (code source, design, données, documentation)
|
||||||
|
est la propriété exclusive de <strong>XEFI SA</strong>. Toute reproduction, modification
|
||||||
|
ou diffusion sans autorisation écrite préalable est interdite.
|
||||||
|
</p>
|
||||||
|
<p>Application développée par <strong>COYAUD Anthony</strong> — Bachelor DBI, Nexa Digital School, 2026.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art9">
|
||||||
|
<h2 id="art9">Article 9 — Modification des CGU</h2>
|
||||||
|
<p>
|
||||||
|
XEFI SA se réserve le droit de modifier les présentes CGU à tout moment. Les utilisateurs
|
||||||
|
seront informés de toute modification substantielle. La poursuite de l'utilisation de la
|
||||||
|
plateforme après notification vaut acceptation des nouvelles conditions.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className={styles.section} aria-labelledby="art10">
|
||||||
|
<h2 id="art10">Article 10 — Droit applicable et contact</h2>
|
||||||
|
<p>Les présentes CGU sont soumises au droit français. Pour toute question :</p>
|
||||||
|
<div className={styles.infoBox} role="note" aria-label="Coordonnées de contact">
|
||||||
|
<p><strong>XEFI SA</strong></p>
|
||||||
|
<p>2507 Avenue de l'Europe — 69140 Rillieux-la-Pape</p>
|
||||||
|
<p>Email : dpo@xefi.fr</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className={styles.footer} role="contentinfo">
|
||||||
|
<p>Data Sentinel v1.0 — © XEFI SA 2026</p>
|
||||||
|
<nav aria-label="Liens légaux">
|
||||||
|
<a href="/mentions-legales">Mentions légales</a>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/* src/pages/CGU/CGU.module.css */
|
||||||
|
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: var(--color-white);
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainTitle {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-left: 14px;
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section p {
|
||||||
|
margin: 8px 0;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
list-style: disc;
|
||||||
|
padding-left: 24px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list li {
|
||||||
|
margin: 6px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoBox {
|
||||||
|
background-color: var(--color-gray-light);
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 16px 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoBox p {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 64px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background-color: var(--color-black);
|
||||||
|
color: var(--color-white);
|
||||||
|
text-align: center;
|
||||||
|
margin-left: -24px;
|
||||||
|
margin-right: -24px;
|
||||||
|
margin-bottom: -24px;
|
||||||
|
font-size: 13px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer nav a {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer nav a:hover {
|
||||||
|
color: var(--color-white);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainTitle {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,3 +108,49 @@
|
|||||||
padding: 32px 0;
|
padding: 32px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.page {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageHeader {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpiGrid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kpiValue {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filtres {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// src/pages/Documentation/Documentation.jsx
|
||||||
|
// Page documentation technique de Data Sentinel
|
||||||
|
import { ExternalLink, Book, Code, Database, Shield } from 'lucide-react';
|
||||||
|
import styles from './Documentation.module.css';
|
||||||
|
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
icon: Book,
|
||||||
|
title: 'Guide utilisateur',
|
||||||
|
items: [
|
||||||
|
{ label: 'Dashboard — Lecture des KPI et monitorings', desc: 'Comprendre les indicateurs clés : total, sans erreur, en erreur, total erreurs.' },
|
||||||
|
{ label: 'Vue consolidée — Agrégation par service', desc: 'Analyse croisée des résultats par service métier et catégorie de monitoring.' },
|
||||||
|
{ label: 'Historique — Export et graphiques', desc: 'Consultation des données passées avec export CSV et visualisation en courbe.' },
|
||||||
|
{ label: 'Gestion des contrats et fournisseurs', desc: 'Référentiel des contrats de service et fournisseurs associés.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Code,
|
||||||
|
title: 'Architecture technique',
|
||||||
|
items: [
|
||||||
|
{ label: 'Frontend — React 18 + Vite', desc: 'SPA React 18 avec CSS Modules, React Router v7, Recharts. Build Vite.' },
|
||||||
|
{ label: 'Backend — API REST', desc: 'API REST avec authentification JWT. Endpoints documentés via Swagger.' },
|
||||||
|
{ label: 'Base de données — SQL Server', desc: 'SQL Server avec vues SQL optimisées et jobs CRON pour les agrégations.' },
|
||||||
|
{ label: 'Infrastructure — NEXERN (XEFI)', desc: 'Hébergement souverain français sur les datacenters NEXERN de XEFI.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Database,
|
||||||
|
title: 'Monitorings disponibles',
|
||||||
|
items: [
|
||||||
|
{ label: 'CRM — Données clients et prospects', desc: 'Surveillance de la cohérence et de la complétude des données CRM.' },
|
||||||
|
{ label: 'Sage — Données comptables et financières', desc: 'Contrôle qualité des flux comptables issus de Sage.' },
|
||||||
|
{ label: 'Infocentre — Reporting décisionnel', desc: 'Validation des données alimentant les rapports de direction.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Shield,
|
||||||
|
title: 'Sécurité et conformité',
|
||||||
|
items: [
|
||||||
|
{ label: 'Authentification JWT', desc: 'Token JWT stocké en sessionStorage, expiration automatique.' },
|
||||||
|
{ label: 'Contrôle d\'accès RBAC', desc: 'Rôles : Administrateur, Superviseur, Consultant. Accès aux fonctions selon le rôle.' },
|
||||||
|
{ label: 'Conformité RGPD', desc: 'Conservation 24 mois, DPO joignable sur dpo@xefi.fr, droits exercés à la demande.' },
|
||||||
|
{ label: 'Accessibilité RGAA / WCAG 2.1 AA', desc: 'Navigation clavier, ARIA, contrastes conformes, responsive 320px–1920px.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Documentation() {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<div className={styles.pageHeader}>
|
||||||
|
<h1 className={styles.title}>Documentation</h1>
|
||||||
|
<p className={styles.subtitle}>Référence technique et guide d'utilisation de Data Sentinel</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{sections.map(({ icon: Icon, title, items }) => (
|
||||||
|
<section key={title} className={styles.card} aria-labelledby={`section-${title.replace(/\s+/g, '-')}`}>
|
||||||
|
<div className={styles.cardHeader}>
|
||||||
|
<Icon size={18} className={styles.cardIcon} aria-hidden="true" />
|
||||||
|
<h2 id={`section-${title.replace(/\s+/g, '-')}`} className={styles.cardTitle}>{title}</h2>
|
||||||
|
</div>
|
||||||
|
<ul className={styles.list}>
|
||||||
|
{items.map(({ label, desc }) => (
|
||||||
|
<li key={label} className={styles.item}>
|
||||||
|
<span className={styles.itemLabel}>{label}</span>
|
||||||
|
<span className={styles.itemDesc}>{desc}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<p>Data Sentinel v1.0 — Développé par COYAUD Anthony, Nexa Digital School 2026</p>
|
||||||
|
<p>
|
||||||
|
<a href="/cgu" className={styles.link}>CGU</a>
|
||||||
|
{' · '}
|
||||||
|
<a href="/mentions-legales" className={styles.link}>Mentions légales</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/* src/pages/Documentation/Documentation.module.css */
|
||||||
|
|
||||||
|
.page {
|
||||||
|
max-width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageHeader {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--color-white);
|
||||||
|
border: 1px solid var(--color-gray-border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--color-gray-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardIcon {
|
||||||
|
color: var(--color-primary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemLabel {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemDesc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
border-top: 1px solid var(--color-gray-border);
|
||||||
|
padding-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// src/pages/Journal/Journal.jsx
|
||||||
|
// Page Journal d'audit — historique des actions utilisateurs
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { getJournal } from '../../services/api';
|
||||||
|
import DataTable from '../../components/common/DataTable';
|
||||||
|
import LoadingSpinner from '../../components/common/LoadingSpinner';
|
||||||
|
import ErrorMessage from '../../components/common/ErrorMessage';
|
||||||
|
import styles from './Journal.module.css';
|
||||||
|
|
||||||
|
export default function Journal() {
|
||||||
|
const [entries, setEntries] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [erreur, setErreur] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setLoading(true);
|
||||||
|
setErreur('');
|
||||||
|
try {
|
||||||
|
const data = await getJournal();
|
||||||
|
setEntries(Array.isArray(data) ? data : []);
|
||||||
|
} catch (e) {
|
||||||
|
setErreur(e.message || 'Erreur lors du chargement du journal.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'date_action', label: 'Date' },
|
||||||
|
{ key: 'utilisateur', label: 'Utilisateur' },
|
||||||
|
{ key: 'action', label: 'Action' },
|
||||||
|
{ key: 'detail', label: 'Détail' },
|
||||||
|
{ key: 'ip', label: 'Adresse IP' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<h1 className={styles.title}>Journal d'audit</h1>
|
||||||
|
<p className={styles.description}>
|
||||||
|
Historique des actions effectuées sur la plateforme par les utilisateurs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<LoadingSpinner message="Chargement du journal..." />
|
||||||
|
) : erreur ? (
|
||||||
|
<ErrorMessage message={erreur} />
|
||||||
|
) : (
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={entries}
|
||||||
|
pageSize={50}
|
||||||
|
emptyMessage="Aucune entrée dans le journal."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/* src/pages/Journal/Journal.module.css */
|
||||||
|
|
||||||
|
.page {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user