From a15162fc2b308034b7f4c4546a955209aa832ae6 Mon Sep 17 00:00:00 2001 From: tips-of-mine <54597409+tips-of-mine@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:38:15 +0200 Subject: [PATCH] Correction des fichiers manquants --- src/components/layouts/PublicLayout.tsx | 95 ++++++++++++ src/contexts/AuthContext.tsx | 80 ++++++++++ src/contexts/I18nContext.tsx | 194 ++++++++++++++++++++++++ src/contexts/ThemeContext.tsx | 69 +++++++++ src/services/api/perimeters.ts | 40 +++++ 5 files changed, 478 insertions(+) create mode 100644 src/components/layouts/PublicLayout.tsx create mode 100644 src/contexts/AuthContext.tsx create mode 100644 src/contexts/I18nContext.tsx create mode 100644 src/contexts/ThemeContext.tsx create mode 100644 src/services/api/perimeters.ts diff --git a/src/components/layouts/PublicLayout.tsx b/src/components/layouts/PublicLayout.tsx new file mode 100644 index 0000000..5f6bede --- /dev/null +++ b/src/components/layouts/PublicLayout.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { useI18nContext } from '../../contexts/I18nContext'; +import { useThemeContext } from '../../contexts/ThemeContext'; + +interface PublicLayoutProps { + children: React.ReactNode; +} + +export const PublicLayout: React.FC = ({ children }) => { + const { t, language, setLanguage } = useI18nContext(); + const { theme, toggleTheme } = useThemeContext(); + + const supportedLanguages: Array<{ code: string; name: string }> = [ + { code: 'fr', name: 'FR' }, + { code: 'en', name: 'EN' }, + { code: 'de', name: 'DE' }, + { code: 'it', name: 'IT' }, + { code: 'pt', name: 'PT' }, + { code: 'es', name: 'ES' }, + { code: 'ja', name: '日本語' }, + { code: 'ru', name: 'RU' }, + { code: 'ar', name: 'العربية' }, + { code: 'hi', name: 'हिन्दी' }, + { code: 'zh', name: '中文' }, + ]; + + return ( +
+ {/* Header */} +
+
+
+ {/* Logo and title */} +
+
+

+ {t('app_name')} +

+
+
+ + {/* Controls */} +
+ {/* Language selector */} +
+ +
+ + {/* Theme toggle */} + +
+
+
+
+ + {/* Main content */} +
+ {children} +
+ + {/* Footer */} +
+
+
+ © {new Date().getFullYear()} {t('app_name')}. Tous droits réservés. +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..3d81728 --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,80 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { User } from '../types'; +import { authApi } from '../services/api'; + +interface AuthContextType { + user: User | null; + isLoading: boolean; + login: (username: string, password: string) => Promise; + logout: () => void; + isAuthenticated: boolean; +} + +const AuthContext = createContext(undefined); + +export const useAuthContext = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuthContext must be used within an AuthProvider'); + } + return context; +}; + +interface AuthProviderProps { + children: ReactNode; +} + +export const AuthProvider: React.FC = ({ children }) => { + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // Check if user is already logged in + const token = localStorage.getItem('auth_token'); + if (token) { + // Validate token and get user info + authApi.me() + .then(userData => { + setUser(userData); + }) + .catch(() => { + localStorage.removeItem('auth_token'); + }) + .finally(() => { + setIsLoading(false); + }); + } else { + setIsLoading(false); + } + }, []); + + const login = async (username: string, password: string) => { + setIsLoading(true); + try { + const response = await authApi.login({ username, password }); + localStorage.setItem('auth_token', response.token); + setUser(response.user); + } finally { + setIsLoading(false); + } + }; + + const logout = () => { + localStorage.removeItem('auth_token'); + setUser(null); + }; + + const value: AuthContextType = { + user, + isLoading, + login, + logout, + isAuthenticated: !!user, + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/contexts/I18nContext.tsx b/src/contexts/I18nContext.tsx new file mode 100644 index 0000000..557e3e5 --- /dev/null +++ b/src/contexts/I18nContext.tsx @@ -0,0 +1,194 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +type Language = 'fr' | 'en' | 'de' | 'it' | 'pt' | 'es' | 'ja' | 'ru' | 'ar' | 'hi' | 'zh'; + +interface I18nContextType { + language: Language; + setLanguage: (lang: Language) => void; + t: (key: string, params?: Record) => string; +} + +const I18nContext = createContext(undefined); + +export const useI18nContext = () => { + const context = useContext(I18nContext); + if (context === undefined) { + throw new Error('useI18nContext must be used within an I18nProvider'); + } + return context; +}; + +// Basic translations for key UI elements +const translations: Record> = { + fr: { + 'app_name': 'Gestion Certificat', + 'login': 'Connexion', + 'logout': 'Déconnexion', + 'username': 'Nom d\'utilisateur', + 'password': 'Mot de passe', + 'dashboard': 'Tableau de bord', + 'certificates': 'Certificats', + 'users': 'Utilisateurs', + 'loading': 'Chargement...', + }, + en: { + 'app_name': 'Certificate Management', + 'login': 'Login', + 'logout': 'Logout', + 'username': 'Username', + 'password': 'Password', + 'dashboard': 'Dashboard', + 'certificates': 'Certificates', + 'users': 'Users', + 'loading': 'Loading...', + }, + de: { + 'app_name': 'Zertifikatsverwaltung', + 'login': 'Anmelden', + 'logout': 'Abmelden', + 'username': 'Benutzername', + 'password': 'Passwort', + 'dashboard': 'Dashboard', + 'certificates': 'Zertifikate', + 'users': 'Benutzer', + 'loading': 'Wird geladen...', + }, + it: { + 'app_name': 'Gestione Certificati', + 'login': 'Accesso', + 'logout': 'Esci', + 'username': 'Nome utente', + 'password': 'Password', + 'dashboard': 'Dashboard', + 'certificates': 'Certificati', + 'users': 'Utenti', + 'loading': 'Caricamento...', + }, + pt: { + 'app_name': 'Gestão de Certificados', + 'login': 'Login', + 'logout': 'Sair', + 'username': 'Nome de Utilizador', + 'password': 'Palavra-passe', + 'dashboard': 'Painel de Controlo', + 'certificates': 'Certificados', + 'users': 'Utilizadores', + 'loading': 'Carregando...', + }, + es: { + 'app_name': 'Gestión de Certificados', + 'login': 'Iniciar Sesión', + 'logout': 'Cerrar Sesión', + 'username': 'Nombre de usuario', + 'password': 'Contraseña', + 'dashboard': 'Panel de Control', + 'certificates': 'Certificados', + 'users': 'Usuarios', + 'loading': 'Cargando...', + }, + ja: { + 'app_name': '証明書管理', + 'login': 'ログイン', + 'logout': 'ログアウト', + 'username': 'ユーザー名', + 'password': 'パスワード', + 'dashboard': 'ダッシュボード', + 'certificates': '証明書', + 'users': 'ユーザー', + 'loading': '読み込み中...', + }, + ru: { + 'app_name': 'Управление Сертификатами', + 'login': 'Вход', + 'logout': 'Выход', + 'username': 'Имя пользователя', + 'password': 'Пароль', + 'dashboard': 'Панель управления', + 'certificates': 'Сертификаты', + 'users': 'Пользователи', + 'loading': 'Загрузка...', + }, + ar: { + 'app_name': 'إدارة الشهادات', + 'login': 'تسجيل الدخول', + 'logout': 'تسجيل الخروج', + 'username': 'اسم المستخدم', + 'password': 'كلمة المرور', + 'dashboard': 'لوحة التحكم', + 'certificates': 'الشهادات', + 'users': 'المستخدمون', + 'loading': 'جار التحميل...', + }, + hi: { + 'app_name': 'प्रमाणपत्र प्रबंधन', + 'login': 'लॉगिन', + 'logout': 'लॉगआउट', + 'username': 'उपयोगकर्ता नाम', + 'password': 'पासवर्ड', + 'dashboard': 'डैशबोर्ड', + 'certificates': 'प्रमाणपत्र', + 'users': 'उपयोगकर्ता', + 'loading': 'लोड हो रहा है...', + }, + zh: { + 'app_name': '证书管理', + 'login': '登录', + 'logout': '注销', + 'username': '用户名', + 'password': '密码', + 'dashboard': '仪表板', + 'certificates': '证书', + 'users': '用户', + 'loading': '加载中...', + }, +}; + +interface I18nProviderProps { + children: ReactNode; +} + +export const I18nProvider: React.FC = ({ children }) => { + const [language, setLanguageState] = useState('fr'); + + useEffect(() => { + // Load language from localStorage or browser preference + const savedLanguage = localStorage.getItem('language') as Language; + if (savedLanguage && Object.keys(translations).includes(savedLanguage)) { + setLanguageState(savedLanguage); + } else { + const browserLang = navigator.language.split('-')[0] as Language; + if (Object.keys(translations).includes(browserLang)) { + setLanguageState(browserLang); + } + } + }, []); + + const setLanguage = (lang: Language) => { + setLanguageState(lang); + localStorage.setItem('language', lang); + }; + + const t = (key: string, params?: Record): string => { + let translation = translations[language]?.[key] || key; + + if (params) { + Object.entries(params).forEach(([param, value]) => { + translation = translation.replace(`{${param}}`, value); + }); + } + + return translation; + }; + + const value: I18nContextType = { + language, + setLanguage, + t, + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx new file mode 100644 index 0000000..ee355b6 --- /dev/null +++ b/src/contexts/ThemeContext.tsx @@ -0,0 +1,69 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; + setTheme: (theme: Theme) => void; +} + +const ThemeContext = createContext(undefined); + +export const useThemeContext = () => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useThemeContext must be used within a ThemeProvider'); + } + return context; +}; + +interface ThemeProviderProps { + children: ReactNode; +} + +export const ThemeProvider: React.FC = ({ children }) => { + const [theme, setThemeState] = useState('light'); + + useEffect(() => { + // Load theme from localStorage or system preference + const savedTheme = localStorage.getItem('theme') as Theme; + if (savedTheme) { + setThemeState(savedTheme); + } else { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + setThemeState(prefersDark ? 'dark' : 'light'); + } + }, []); + + useEffect(() => { + // Apply theme to document + const root = document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + localStorage.setItem('theme', theme); + }, [theme]); + + const toggleTheme = () => { + setThemeState(prev => prev === 'light' ? 'dark' : 'light'); + }; + + const setTheme = (newTheme: Theme) => { + setThemeState(newTheme); + }; + + const value: ThemeContextType = { + theme, + toggleTheme, + setTheme, + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/services/api/perimeters.ts b/src/services/api/perimeters.ts new file mode 100644 index 0000000..4f726cd --- /dev/null +++ b/src/services/api/perimeters.ts @@ -0,0 +1,40 @@ +import { apiClient } from './client'; +import { FunctionalPerimeter } from '../../types'; + +export interface CreatePerimeterRequest { + name: string; + intermediate_passphrase?: string; +} + +export interface PerimeterResponse { + success: boolean; + data: FunctionalPerimeter; + message?: string; +} + +export interface PerimetersListResponse { + success: boolean; + data: FunctionalPerimeter[]; + message?: string; +} + +export const perimetersApi = { + async getAll(): Promise { + const response = await apiClient.get('/perimeters'); + return response.data.data; + }, + + async getById(id: number): Promise { + const response = await apiClient.get(`/perimeters/${id}`); + return response.data.data; + }, + + async create(data: CreatePerimeterRequest): Promise { + const response = await apiClient.post('/perimeters', data); + return response.data.data; + }, + + async delete(id: number): Promise { + await apiClient.delete(`/perimeters/${id}`); + }, +}; \ No newline at end of file