2krika
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:

app/[lang]/(client)/layout.tsx
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.tsx

Conventions de Composants

Server Components (par défaut)

components/features/services/ServiceCard.tsx
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

components/features/chat/ChatBox.tsx
'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           # Statistiques

Pattern Service

services/serviceService.ts
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

services/api.ts
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      # Notifications

Pattern Hook avec React Query

hooks/useServices.ts
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

stores/authStore.ts
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          # Notification

Pattern Modèle

models/service.ts
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 utilitaires

Exemples d'Utilitaires

utils/formatters.ts
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érer unknown

Prochaines Étapes

On this page