diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 6113f4a..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useEffect } from 'react' -import { Routes, Route, Navigate } from 'react-router-dom' -import { useAuth } from './hooks/useAuth' -import { useTheme } from './hooks/useTheme' - -// Layout Components -import { PublicLayout } from './components/layouts/PublicLayout' -import { ProtectedLayout } from './components/layouts/ProtectedLayout' - -// Pages -import { LoginPage } from './pages/auth/LoginPage' -import { DashboardPage } from './pages/dashboard/DashboardPage' -import { CertificatesPage } from './pages/certificates/CertificatesPage' -import { CreateCertificatePage } from './pages/certificates/CreateCertificatePage' -import { PerimetersPage } from './pages/perimeters/PerimetersPage' -import { CreatePerimeterPage } from './pages/perimeters/CreatePerimeterPage' -import { UsersPage } from './pages/users/UsersPage' -import { CreateUserPage } from './pages/users/CreateUserPage' -import { EditUserPasswordPage } from './pages/users/EditUserPasswordPage' -import { NotFoundPage } from './pages/NotFoundPage' - -// Components -import { LoadingSpinner } from './components/ui/LoadingSpinner' - -function App() { - const { isAuthenticated, isLoading } = useAuth() - const { theme } = useTheme() - - useEffect(() => { - // Apply theme to document - if (theme === 'dark') { - document.documentElement.classList.add('dark') - } else { - document.documentElement.classList.remove('dark') - } - }, [theme]) - - if (isLoading) { - return ( -
- -
- ) - } - - return ( - - {/* Public Routes */} - - - - ) : ( - - ) - } /> - - {/* Protected Routes */} - - ) : ( - - ) - }> - } /> - } /> - - - } /> - } /> - - - - } /> - } /> - - - - } /> - } /> - } /> - - - - {/* 404 */} - } /> - - ) -} - -export default App \ No newline at end of file diff --git a/src/components/layouts/PublicLayout.tsx b/src/components/layouts/PublicLayout.tsx deleted file mode 100644 index 5f6bede..0000000 --- a/src/components/layouts/PublicLayout.tsx +++ /dev/null @@ -1,95 +0,0 @@ -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/components/ui/Button.tsx b/src/components/ui/Button.tsx deleted file mode 100644 index 9182486..0000000 --- a/src/components/ui/Button.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react' -import { cn } from '../../lib/utils' -import { LoadingSpinner } from './LoadingSpinner' - -interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' - size?: 'sm' | 'md' | 'lg' - loading?: boolean - leftIcon?: React.ReactNode - rightIcon?: React.ReactNode -} - -export const Button = React.forwardRef( - ({ - className, - variant = 'primary', - size = 'md', - loading = false, - leftIcon, - rightIcon, - children, - disabled, - ...props - }, ref) => { - const variants = { - primary: 'btn-primary', - secondary: 'btn-secondary', - destructive: 'btn-destructive', - outline: 'btn-outline', - ghost: 'btn-ghost', - } - - const sizes = { - sm: 'btn-sm', - md: '', - lg: 'btn-lg', - } - - return ( - - ) - } -) - -Button.displayName = 'Button' \ No newline at end of file diff --git a/src/components/ui/LoadingSpinner.tsx b/src/components/ui/LoadingSpinner.tsx deleted file mode 100644 index 8538e9d..0000000 --- a/src/components/ui/LoadingSpinner.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { cn } from '../../lib/utils' - -interface LoadingSpinnerProps { - size?: 'sm' | 'md' | 'lg' - className?: string -} - -export const LoadingSpinner: React.FC = ({ - size = 'md', - className -}) => { - const sizes = { - sm: 'w-4 h-4', - md: 'w-6 h-6', - lg: 'w-8 h-8', - } - - return ( -
- Loading... -
- ) -} \ No newline at end of file diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx deleted file mode 100644 index 3d81728..0000000 --- a/src/contexts/AuthContext.tsx +++ /dev/null @@ -1,80 +0,0 @@ -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 deleted file mode 100644 index 557e3e5..0000000 --- a/src/contexts/I18nContext.tsx +++ /dev/null @@ -1,194 +0,0 @@ -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 deleted file mode 100644 index ee355b6..0000000 --- a/src/contexts/ThemeContext.tsx +++ /dev/null @@ -1,69 +0,0 @@ -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/hooks/useAuth.ts b/src/hooks/useAuth.ts deleted file mode 100644 index eaa0c51..0000000 --- a/src/hooks/useAuth.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { create } from 'zustand' -import { persist } from 'zustand/middleware' -import { AuthUser, LoginCredentials } from '../types' -import { authApi } from '../services/api' -import toast from 'react-hot-toast' - -interface AuthState { - user: AuthUser | null - isAuthenticated: boolean - isLoading: boolean - login: (credentials: LoginCredentials) => Promise - logout: () => void - updateUser: (user: Partial) => void -} - -export const useAuth = create()( - persist( - (set, get) => ({ - user: null, - isAuthenticated: false, - isLoading: false, - - login: async (credentials: LoginCredentials) => { - set({ isLoading: true }) - try { - const response = await authApi.login(credentials) - if (response.success && response.data) { - set({ - user: response.data, - isAuthenticated: true, - isLoading: false, - }) - toast.success('Successfully logged in!') - return true - } else { - toast.error(response.message || 'Login failed') - set({ isLoading: false }) - return false - } - } catch (error) { - console.error('Login error:', error) - toast.error('Login failed. Please try again.') - set({ isLoading: false }) - return false - } - }, - - logout: () => { - authApi.logout().catch(console.error) - set({ - user: null, - isAuthenticated: false, - isLoading: false, - }) - toast.success('Successfully logged out!') - }, - - updateUser: (userData: Partial) => { - const currentUser = get().user - if (currentUser) { - set({ - user: { ...currentUser, ...userData }, - }) - } - }, - }), - { - name: 'auth-storage', - partialize: (state) => ({ - user: state.user, - isAuthenticated: state.isAuthenticated, - }), - } - ) -) \ No newline at end of file diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts deleted file mode 100644 index 58696ad..0000000 --- a/src/hooks/useTheme.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { create } from 'zustand' -import { persist } from 'zustand/middleware' -import { Theme } from '../types' - -interface ThemeState { - theme: Theme - setTheme: (theme: Theme) => void - toggleTheme: () => void -} - -export const useTheme = create()( - persist( - (set, get) => ({ - theme: 'light', - - setTheme: (theme: Theme) => { - set({ theme }) - }, - - toggleTheme: () => { - const currentTheme = get().theme - set({ theme: currentTheme === 'light' ? 'dark' : 'light' }) - }, - }), - { - name: 'theme-storage', - } - ) -) \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index 87d4b24..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type ClassValue, clsx } from 'clsx' -import { twMerge } from 'tailwind-merge' - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} - -export function formatDate(date: string | Date, options?: Intl.DateTimeFormatOptions): string { - const dateObject = typeof date === 'string' ? new Date(date) : date - - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - ...options, - }).format(dateObject) -} - -export function formatDateTime(date: string | Date): string { - return formatDate(date, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }) -} - -export function downloadBlob(blob: Blob, filename: string): void { - const url = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = url - link.download = filename - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - window.URL.revokeObjectURL(url) -} - -export function isValidEmail(email: string): boolean { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ - return emailRegex.test(email) -} - -export function capitalizeFirst(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1) -} - -export function debounce any>( - func: T, - wait: number -): (...args: Parameters) => void { - let timeout: NodeJS.Timeout | null = null - - return (...args: Parameters) => { - if (timeout) clearTimeout(timeout) - timeout = setTimeout(() => func(...args), wait) - } -} - -export function truncate(str: string, length: number): string { - if (str.length <= length) return str - return str.slice(0, length) + '...' -} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx deleted file mode 100644 index 71d920d..0000000 --- a/src/main.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import { QueryClient, QueryClientProvider } from 'react-query' -import { BrowserRouter } from 'react-router-dom' -import { Toaster } from 'react-hot-toast' - -import App from './App' -import { AuthProvider } from './contexts/AuthContext' -import { ThemeProvider } from './contexts/ThemeContext' -import { I18nProvider } from './contexts/I18nContext' - -import './styles/globals.css' - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: 1, - refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, // 5 minutes - }, - }, -}) - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - - - - - , -) \ No newline at end of file diff --git a/src/services/api/auth.ts b/src/services/api/auth.ts deleted file mode 100644 index 7821cd3..0000000 --- a/src/services/api/auth.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiResponse, AuthUser, LoginCredentials } from '../../types' -import { apiClient } from './client' - -export const authApi = { - login: async (credentials: LoginCredentials): Promise> => { - const response = await apiClient.post('/auth/login', credentials) - return response.data - }, - - logout: async (): Promise => { - const response = await apiClient.post('/auth/logout') - return response.data - }, - - me: async (): Promise> => { - const response = await apiClient.get('/auth/me') - return response.data - }, -} \ No newline at end of file diff --git a/src/services/api/certificates.ts b/src/services/api/certificates.ts deleted file mode 100644 index 7fb1dc9..0000000 --- a/src/services/api/certificates.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ApiResponse, - Certificate, - CreateCertificateData, - DownloadCertificateParams, - PaginatedResponse -} from '../../types' -import { apiClient } from './client' - -export const certificatesApi = { - getAll: async (page = 1, perPage = 50): Promise>> => { - const response = await apiClient.get('/certificates', { - params: { page, per_page: perPage } - }) - return response.data - }, - - create: async (data: CreateCertificateData): Promise> => { - const response = await apiClient.post('/certificates', data) - return response.data - }, - - revoke: async (certificateId: number): Promise => { - const response = await apiClient.post(`/certificates/${certificateId}/revoke`) - return response.data - }, - - download: async (params: DownloadCertificateParams): Promise => { - const response = await apiClient.get('/certificates/download', { - params, - responseType: 'blob', - }) - return response.data - }, - - getStats: async (): Promise> => { - const response = await apiClient.get('/certificates/stats') - return response.data - }, -} \ No newline at end of file diff --git a/src/services/api/client.ts b/src/services/api/client.ts deleted file mode 100644 index 6d166fc..0000000 --- a/src/services/api/client.ts +++ /dev/null @@ -1,57 +0,0 @@ -import axios from 'axios' -import toast from 'react-hot-toast' - -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' - -export const apiClient = axios.create({ - baseURL: API_BASE_URL, - timeout: 30000, - headers: { - 'Content-Type': 'application/json', - }, -}) - -// Request interceptor -apiClient.interceptors.request.use( - (config) => { - // Add auth token if available - const authData = localStorage.getItem('auth-storage') - if (authData) { - try { - const parsed = JSON.parse(authData) - if (parsed.state?.user?.token) { - config.headers.Authorization = `Bearer ${parsed.state.user.token}` - } - } catch (error) { - console.error('Error parsing auth data:', error) - } - } - return config - }, - (error) => { - return Promise.reject(error) - } -) - -// Response interceptor -apiClient.interceptors.response.use( - (response) => { - return response - }, - (error) => { - if (error.response?.status === 401) { - // Clear auth state and redirect to login - localStorage.removeItem('auth-storage') - window.location.href = '/login' - toast.error('Session expired. Please login again.') - } else if (error.response?.status === 403) { - toast.error('You do not have permission to perform this action.') - } else if (error.response?.status >= 500) { - toast.error('Server error. Please try again later.') - } else if (error.code === 'ECONNABORTED') { - toast.error('Request timeout. Please check your connection.') - } - - return Promise.reject(error) - } -) \ No newline at end of file diff --git a/src/services/api/index.ts b/src/services/api/index.ts deleted file mode 100644 index c1d205b..0000000 --- a/src/services/api/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { authApi } from './auth' -export { certificatesApi } from './certificates' -export { perimetersApi } from './perimeters' -export { usersApi } from './users' -export { dashboardApi } from './dashboard' -export { apiClient } from './client' \ No newline at end of file diff --git a/src/services/api/perimeters.ts b/src/services/api/perimeters.ts deleted file mode 100644 index 4f726cd..0000000 --- a/src/services/api/perimeters.ts +++ /dev/null @@ -1,40 +0,0 @@ -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 diff --git a/src/styles/globals.css b/src/styles/globals.css deleted file mode 100644 index 75052f4..0000000 --- a/src/styles/globals.css +++ /dev/null @@ -1,182 +0,0 @@ -@import 'tailwindcss/base'; -@import 'tailwindcss/components'; -@import 'tailwindcss/utilities'; - -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - --primary: 221.2 83.2% 53.3%; - --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96%; - --secondary-foreground: 222.2 84% 4.9%; - --muted: 210 40% 96%; - --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96%; - --accent-foreground: 222.2 84% 4.9%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 221.2 83.2% 53.3%; - --radius: 0.75rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 217.2 91.2% 59.8%; - --primary-foreground: 222.2 84% 4.9%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 224.3 76.3% 94.1%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground font-sans; - font-feature-settings: "rlig" 1, "calt" 1; - } - - h1, h2, h3, h4, h5, h6 { - @apply font-semibold tracking-tight; - } - - h1 { - @apply text-3xl lg:text-4xl; - } - - h2 { - @apply text-2xl lg:text-3xl; - } - - h3 { - @apply text-xl lg:text-2xl; - } - - h4 { - @apply text-lg lg:text-xl; - } -} - -@layer components { - .btn { - @apply inline-flex items-center justify-center rounded-md text-sm font-medium - transition-colors focus-visible:outline-none focus-visible:ring-2 - focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 - disabled:pointer-events-none ring-offset-background; - } - - .btn-primary { - @apply btn bg-primary text-primary-foreground hover:bg-primary/90 h-10 py-2 px-4; - } - - .btn-secondary { - @apply btn bg-secondary text-secondary-foreground hover:bg-secondary/80 h-10 py-2 px-4; - } - - .btn-destructive { - @apply btn bg-destructive text-destructive-foreground hover:bg-destructive/90 h-10 py-2 px-4; - } - - .btn-outline { - @apply btn border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 py-2 px-4; - } - - .btn-ghost { - @apply btn hover:bg-accent hover:text-accent-foreground h-10 py-2 px-4; - } - - .btn-sm { - @apply h-9 px-3 text-xs; - } - - .btn-lg { - @apply h-11 px-8; - } - - .input { - @apply flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm - ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium - placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 - focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed - disabled:opacity-50; - } - - .card { - @apply rounded-lg border bg-card text-card-foreground shadow-sm; - } - - .card-header { - @apply flex flex-col space-y-1.5 p-6; - } - - .card-content { - @apply p-6 pt-0; - } - - .card-footer { - @apply flex items-center p-6 pt-0; - } -} - -/* Custom scrollbar */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - @apply bg-secondary; -} - -::-webkit-scrollbar-thumb { - @apply bg-muted-foreground/50 rounded-full; -} - -::-webkit-scrollbar-thumb:hover { - @apply bg-muted-foreground/70; -} - -/* Animation utilities */ -.animate-in { - animation: animateIn 0.3s ease-out; -} - -@keyframes animateIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Focus ring for accessibility */ -.focus-ring { - @apply focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background; -} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 3c1986a..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -export interface User { - id: number - username: string - role: 'admin' | 'user' - created_at: string -} - -export interface AuthUser extends User { - token?: string -} - -export interface LoginCredentials { - username: string - password: string -} - -export interface Certificate { - id: number - name: string - type: 'root' | 'intermediate' | 'simple' - functional_perimeter_id?: number - perimeter_name?: string - expiration_date: string - is_revoked: boolean - revoked_at?: string - created_at: string -} - -export interface CreateCertificateData { - subdomain_name: string - functional_perimeter_id: number -} - -export interface FunctionalPerimeter { - id: number - name: string - intermediate_cert_name: string - created_at: string -} - -export interface CreatePerimeterData { - name: string - intermediate_passphrase?: string -} - -export interface CreateUserData { - username: string - password: string - role: 'admin' | 'user' -} - -export interface UpdatePasswordData { - user_id: number - new_password: string - confirm_password: string -} - -export interface DashboardStats { - total_certificates: number - active_certificates: number - revoked_certificates: number - total_perimeters: number - total_users: number - expiring_soon: Certificate[] -} - -export interface ApiResponse { - success: boolean - data?: T - message?: string - errors?: Record -} - -export interface PaginatedResponse { - data: T[] - current_page: number - per_page: number - total: number - last_page: number -} - -export type Theme = 'light' | 'dark' - -export type Language = 'en' | 'fr' | 'de' | 'es' | 'it' | 'pt' | 'ja' | 'ru' | 'ar' | 'hi' | 'zh' - -export interface DownloadCertificateParams { - type: 'root' | 'intermediate' | 'simple' - file: string - perimeter?: string -} \ No newline at end of file