App Customers
Structure du Code
Organisation et architecture du code de l'application Customers
Structure du Code
Organisation des Fichiers
L'application suit une organisation claire et modulaire pour faciliter la maintenance et l'évolution du code.
Routes et Pages
Structure des Routes
app/[lang]/
├── (auth)/ # Routes publiques d'authentification
│ ├── login/
│ ├── register/
│ └── forgot-password/
├── (client)/ # Routes espace client
│ ├── page.tsx # Page d'accueil
│ ├── services/ # Liste des services
│ ├── service/[id]/ # Détail d'un service
│ ├── category/[slug]/ # Services par catégorie
│ ├── profile/[id]/ # Profil utilisateur
│ ├── purchases/ # Mes achats
│ └── settings/ # Paramètres
├── (seller)/ # Routes espace vendeur
│ ├── sales/ # Dashboard principal
│ │ ├── page.tsx # Vue d'ensemble
│ │ ├── services/ # Gestion des services
│ │ │ ├── page.tsx # Liste
│ │ │ ├── create/ # Créer un service
│ │ │ └── [id]/edit/ # Éditer un service
│ │ ├── orders/ # Gestion des commandes
│ │ ├── invoices/ # Factures
│ │ ├── wallet/ # Portefeuille
│ │ └── settings/ # Paramètres vendeur
│ └── become-seller/ # Processus KYC
│ ├── step-1/
│ └── step-2/
└── (checkout)/ # Flow d'achat
└── checkout/[serviceId]/Layouts
Chaque route group possède son propre layout:
export default function ClientLayout({ children }: { children: ReactNode }) {
return (
<>
<ClientHeader />
<main>{children}</main>
<ClientFooter />
</>
);
}Composants
Organisation des Composants
components/
├── ui/ # Composants UI de base
│ ├── Button.tsx
│ ├── Input.tsx
│ ├── Card.tsx
│ └── Modal.tsx
├── layout/ # Composants de layout
│ ├── Header.tsx
│ ├── Footer.tsx
│ ├── Sidebar.tsx
│ └── Navigation.tsx
├── features/ # Composants métier
│ ├── services/
│ │ ├── ServiceCard.tsx
│ │ ├── ServiceGrid.tsx
│ │ └── ServiceFilters.tsx
│ ├── orders/
│ │ ├── OrderCard.tsx
│ │ └── OrderStatus.tsx
│ ├── chat/
│ │ ├── ChatBox.tsx
│ │ ├── MessageList.tsx
│ │ └── MessageInput.tsx
│ └── profile/
│ ├── UserAvatar.tsx
│ └── ProfileCard.tsx
└── shared/ # Composants partagés
├── Loading.tsx
├── ErrorBoundary.tsx
└── EmptyState.tsxConventions de Composants
Server Components (par défaut)
import { Service } from '@/models/service';
interface ServiceCardProps {
service: Service;
}
export function ServiceCard({ service }: ServiceCardProps) {
return (
<div className="service-card">
<h3>{service.title}</h3>
<p>{service.price}</p>
</div>
);
}Client Components
'use client';
import { useState } from 'react';
import { useSocket } from '@/hooks/useSocket';
export function ChatBox() {
const [message, setMessage] = useState('');
const socket = useSocket();
// Logic here...
}Services API
Structure des Services
services/
├── api.ts # Configuration Axios
├── authService.ts # Authentification
├── serviceService.ts # Services marketplace
├── orderService.ts # Commandes
├── userService.ts # Utilisateurs
├── categoryService.ts # Catégories
├── chatService.ts # Messages
├── walletService.ts # Wallet
└── statsService.ts # StatistiquesPattern Service
import api from './api';
import { Service, CreateServiceDto, UpdateServiceDto } from '@/models/service';
export const serviceService = {
// Get all services
getAll: async (): Promise<Service[]> => {
const { data } = await api.get('/services');
return data;
},
// Get service by ID
getById: async (id: string): Promise<Service> => {
const { data } = await api.get(`/services/${id}`);
return data;
},
// Create service
create: async (dto: CreateServiceDto): Promise<Service> => {
const { data } = await api.post('/services', dto);
return data;
},
// Update service
update: async (id: string, dto: UpdateServiceDto): Promise<Service> => {
const { data } = await api.put(`/services/${id}`, dto);
return data;
},
// Delete service
delete: async (id: string): Promise<void> => {
await api.delete(`/services/${id}`);
},
// Get services by category
getByCategory: async (categoryId: string): Promise<Service[]> => {
const { data } = await api.get(`/services/category/${categoryId}`);
return data;
},
};Configuration API
import axios from 'axios';
import { getToken, refreshToken } from '@/utils/auth';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor: Add token
api.interceptors.request.use((config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor: Handle 401
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
await refreshToken();
return api.request(error.config);
}
return Promise.reject(error);
}
);
export default api;Hooks Personnalisés
Structure des Hooks
hooks/
├── useAuth.ts # Authentification
├── useUser.ts # Utilisateur courant
├── useSocket.ts # Socket.io
├── useServices.ts # Services marketplace
├── useOrders.ts # Commandes
├── useCategories.ts # Catégories
├── useWallet.ts # Wallet
└── useNotifications.ts # NotificationsPattern Hook avec React Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { serviceService } from '@/services/serviceService';
import { CreateServiceDto } from '@/models/service';
export function useServices() {
return useQuery({
queryKey: ['services'],
queryFn: serviceService.getAll,
});
}
export function useService(id: string) {
return useQuery({
queryKey: ['service', id],
queryFn: () => serviceService.getById(id),
enabled: !!id,
});
}
export function useCreateService() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateServiceDto) => serviceService.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['services'] });
},
});
}
export function useUpdateService(id: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateServiceDto) =>
serviceService.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['services'] });
queryClient.invalidateQueries({ queryKey: ['service', id] });
},
});
}State Management
Zustand Stores
stores/
├── authStore.ts # État d'authentification
├── uiStore.ts # État UI (modals, drawers)
└── cartStore.ts # Panier (si applicable)Pattern Zustand Store
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { User } from '@/models/user';
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
setUser: (user: User) => void;
setToken: (token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
setUser: (user) => set({ user, isAuthenticated: true }),
setToken: (token) => set({ token }),
logout: () => set({ user: null, token: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
}
)
);Modèles TypeScript
Structure des Modèles
models/
├── user.ts # User, Profile
├── service.ts # Service, Package
├── order.ts # Order, OrderItem
├── category.ts # Category
├── message.ts # Message, Conversation
├── review.ts # Review
├── wallet.ts # Wallet, Transaction
├── invoice.ts # Invoice
└── notification.ts # NotificationPattern Modèle
export interface Service {
id: string;
userId: string;
categoryId: string;
title: string;
description: string;
shortDescription: string;
images: string[];
video?: string;
status: ServiceStatus;
packages: ServicePackage[];
tags: string[];
rating: number;
reviewCount: number;
orderCount: number;
createdAt: string;
updatedAt: string;
}
export interface ServicePackage {
id: string;
name: string;
description: string;
price: number;
deliveryDays: number;
revisions: number;
features: string[];
}
export enum ServiceStatus {
DRAFT = 'draft',
PENDING = 'pending',
ACTIVE = 'active',
PAUSED = 'paused',
REJECTED = 'rejected',
}
export interface CreateServiceDto {
categoryId: string;
title: string;
description: string;
shortDescription: string;
packages: Omit<ServicePackage, 'id'>[];
tags: string[];
}
export type UpdateServiceDto = Partial<CreateServiceDto>;Utilitaires
Structure des Utils
utils/
├── formatters.ts # Formatage (dates, prix, etc.)
├── validators.ts # Validations
├── constants.ts # Constantes
├── helpers.ts # Helpers génériques
└── types.ts # Types utilitairesExemples d'Utilitaires
import { format } from 'date-fns';
import { fr, enUS } from 'date-fns/locale';
export function formatPrice(price: number, currency = 'XOF'): string {
return new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency,
}).format(price);
}
export function formatDate(date: string, locale = 'fr'): string {
return format(new Date(date), 'PP', {
locale: locale === 'fr' ? fr : enUS,
});
}
export function formatRelativeTime(date: string): string {
const now = new Date();
const then = new Date(date);
const diff = now.getTime() - then.getTime();
const minutes = Math.floor(diff / 60000);
if (minutes < 60) return `Il y a ${minutes} min`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `Il y a ${hours}h`;
const days = Math.floor(hours / 24);
return `Il y a ${days}j`;
}Configuration
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.2krikaservices.cloud',
},
],
},
webpack: (config, { dev }) => {
if (dev) {
config.cache = false;
}
return config;
},
};
export default nextConfig;tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
// ... autres nuances
},
},
},
},
plugins: [],
};
export default config;Bonnes Pratiques
Nommage
- Composants: PascalCase (
UserProfile.tsx) - Hooks: camelCase avec
use(useAuth.ts) - Services: camelCase avec
Service(userService.ts) - Utils: camelCase (
formatDate.ts) - Constantes: UPPER_SNAKE_CASE (
MAX_FILE_SIZE)
Organisation
- Un fichier = une responsabilité
- Composants petits et réutilisables
- Séparer la logique métier des composants UI
- Utiliser des index.ts pour les exports
TypeScript
- Toujours typer les props
- Utiliser des interfaces pour les objets
- Typer les retours de fonctions
- Éviter
any, préférerunknown