Aller au contenu principal

Frontend Documentation

Overview

The frontend is built with React 18, TypeScript, and modern tooling following best practices.

Project Structure

frontend/src/
├── __tests__/ # Unit tests
├── _generated/ # Generated types from OpenAPI
├── auth/ # Authentication context and hooks
├── client/ # API client hooks (TanStack Query)
├── components/ # Reusable UI components
├── hooks/ # Custom React hooks
├── i18n/ # Internationalization
├── lib/ # Utility functions
├── pages/ # Page components (organized by domain)
│ ├── admin/ # Admin panel pages
│ ├── auth/ # Authentication pages
│ ├── client/ # Client portal pages
│ ├── contacts/ # Contact management pages
│ ├── sessions/ # Session management pages
│ ├── quotes/ # Quote management pages
│ ├── invoices/ # Invoice management pages
│ ├── contracts/ # Contract management pages
│ ├── users/ # User-related pages
│ ├── settings/ # Settings pages
│ ├── dashboard/ # Dashboard pages
│ └── ... # Other domain pages
├── router/ # Route guards
├── router.tsx # Router configuration
└── store/ # Zustand state management

Page Organization: Pages are organized by domain for better maintainability and logical grouping. Each domain contains related pages (list, view, form pages) grouped together.

Key Concepts

Authentication

  • AuthProvider: Authentication context provider
  • useAuth: Hook to access authentication state
  • Token management and refresh
  • User status checks

API Client

  • TanStack Query v5 for data fetching
  • Type-safe API calls using generated types
  • Automatic caching and refetching
  • Error handling

Structure: client/{module}/use{Module}.ts

Conventions de clés React Query : Les hooks utilisent des conventions uniformes pour les clés de cache. Voir QUOTES_INVOICES_HOOKS.md pour un exemple complet.

TanStack Query v5 Mutation Callbacks: All onSuccess and onError callbacks in useMutation hooks must accept 4 arguments: (data/error, variables, context, mutation).

Example:

import { useSessions } from "@/client/sessions/useSessions";

const { data, isLoading, error } = useSessions();
const createSession = useCreateSession({
onSuccess: (data, variables, context, mutation) => {
toast.success("Session created successfully");
void queryClient.invalidateQueries({ queryKey: ["sessions"] });
},
onError: (error, variables, context, mutation) => {
toast.error(
error instanceof Error ? error.message : "Failed to create session"
);
},
});

Routing

  • TanStack Router for type-safe routing
  • Route guards for authentication
  • Status-based redirects

Chaque entité (quotes, invoices, contracts) a son propre scope de navigation indépendant :

  • Routes de vue : /quotes/:id, /invoices/:id, /contracts/:id - Pas de paramètres de recherche
  • Routes d'édition : /quotes/:id/edit, /invoices/:id/edit, /contracts/:id/edit - Pas de paramètres de recherche
  • Routes de création : /quotes/new?sessionId=...&contactId=... - Paramètres de recherche pour pré-remplir les formulaires uniquement

Le breadcrumb et le bouton "back" pointent toujours vers la liste de l'entité, pas vers le contexte parent (session/contact).

Les données de session et contact sont récupérées depuis l'entité elle-même (quote.session_id, quote.contact_id).

Voir QUOTES_INVOICES_HOOKS.md pour plus de détails.

Forms

  • TanStack Form for form management
  • Zod for validation
  • Auto-fill on page load

Example:

const form = useForm({
defaultValues: { name: "" },
onSubmit: async ({ value }) => {
await createSession.mutateAsync(value);
},
});

State Management

  • Zustand for global state
  • Auth store: User and token
  • UI store: Sidebar, theme
  • WebSocket store: Connection state

See: frontend/src/store/README.md for detailed documentation

Components

The frontend uses a component architecture based on shadcn/ui primitives with business-specific wrappers.

Component Architecture:

  • shadcn/ui Primitives (components/ui/): Base UI components from shadcn/ui

    • Installed components: Alert, AlertDialog, Avatar, Badge, Breadcrumb, Button, Card, Checkbox, Command, Dialog, DropdownMenu, Input, Label, Popover, Progress, RadioGroup, ScrollArea, Select, Separator, Sheet, Skeleton, Table, Tabs, Textarea, Tooltip
    • These are copied into the project (not npm dependencies) and can be customized
    • All use Radix UI primitives and Tailwind CSS for styling
    • Follow PascalCase naming convention (e.g., Button.tsx, Dialog.tsx)
  • Business Components (components/ui/): Domain-specific wrappers around shadcn primitives

    • Examples: ConfirmationDialog (wraps Dialog), StatusBadge (wraps Badge), ProgressionBar (wraps Progress)
    • Encapsulate business logic and provide simplified APIs for common use cases
    • Should use shadcn primitives internally, not custom implementations
  • Layout Components (components/layout/): Navigation and page structure

    • Sidebar: Desktop sidebar navigation (collapsible)
    • MobileSidebar: Mobile sidebar using Sheet shadcn component
    • Header: Top navigation bar
    • UserDropdown: Desktop user menu dropdown
    • UserMenuMobile: Full-screen mobile user menu
  • Feature Components (components/{feature}/): Feature-specific components

    • Organized by domain/feature (e.g., components/search/, components/files/)
  • Molecule Components (components/molecules/): Reusable composite components

    • Examples: StatusBadge, PageHeader, DataField, PayInvoiceButton, PayInvoiceDialog
  • Organism Components (components/organisms/): Complex layout components

    • Examples: FormPageLayout, ViewPageLayout, ListPageLayout

Layout Components:

  • Sidebar: Desktop sidebar navigation (collapsible)
  • MobileSidebar: Mobile sidebar using Sheet shadcn component with slide-in animation
  • UserDropdown: Desktop user menu dropdown
  • UserMenuMobile: Full-screen mobile user menu with expandable sub-menus
    • Automatically used on mobile devices (< 1024px)
    • Scrollable content for long menus
    • Supports nested menu sections (Mon compte, Paramètres, Système)

StatusBadge: Reusable component for displaying status labels with optional tooltips and dynamic variants. Always use StatusBadge instead of manually creating status badges.

Stripe Payment Components:

  • PayInvoiceButton: Button component that opens a payment dialog for invoices
  • PayInvoiceDialog: Dialog component for processing invoice payments with Stripe Elements
  • PaymentForm: Form component with Stripe Elements for secure card input

Usage Example:

import { PayInvoiceButton } from "@/components/molecules";

<PayInvoiceButton
invoiceId={invoice.id}
amount={invoice.amount}
currency="eur"
onPaymentSuccess={() => {
// Refresh invoice data
void queryClient.invalidateQueries({ queryKey: ["invoices", invoice.id] });
}}
/>;

Stripe Integration

  • Stripe Elements for secure payment processing
  • Payment intents for invoice payments
  • Subscription management hooks
  • Webhook event handling (backend)

Environment Variables:

  • VITE_STRIPE_PUBLISHABLE_KEY - Stripe publishable key (required for frontend)

API Client: client/stripe/useStripe.ts

  • useMySubscription() - Get current subscription
  • usePaymentHistory() - Get payment history
  • useCreateSubscription() - Create a subscription
  • useCancelSubscription() - Cancel a subscription
  • useCreateInvoicePayment() - Create payment intent for invoice

Internationalization

  • react-i18next for translations
  • English and French supported
  • Translation keys in i18n/locales/{lang}/translation.json

Usage:

const { t } = useTranslation();
return <h1>{t("page.title")}</h1>;

File Uploads & Downloads

  • useFileUpload (in frontend/src/hooks/useFileUpload.ts) centralizes the presigned upload flow: it requests an upload URL, performs the PUT to R2, falls back to /storage/upload if CORS/network issues occur, and then creates the database record.
  • useFileDownload (in frontend/src/hooks/useFileDownload.ts) handles download clicks: it requests /storage/signed-url/:key, opens the short-lived link in a new tab, and surfaces toast errors. Use this hook whenever you need to let a user download any stored file.
  • FileList automatically uses useFileDownload for its download button, so most pages only need to pass files and (optionally) onDelete. Override onDownload only for specialized behavior.
  • Frontend code must never store signed URLs in state or props. Always request them on demand via useFileDownload to keep links fresh and avoid CORS issues when R2_PUBLIC_URL is not configured.

Development Guidelines

Adding a New Page

  1. Create page directory in the appropriate domain: pages/{domain}/{PageName}/{PageName}.tsx
    • Choose the domain based on the page's purpose (e.g., contacts/, sessions/, quotes/, invoices/, contracts/, users/, settings/, admin/, auth/, etc.)
    • If creating a new domain, create the domain directory first
  2. Add route in router.tsx
  3. Add page title in components/layout/usePageTitle.ts
  4. Add translations in i18n/locales/

Example: Creating a new contact form page:

  • Create: pages/contacts/ContactFormPage/ContactFormPage.tsx
  • Add route: /contacts/new and /contacts/$id/edit
  • Add page title configuration
  • Add translations for the form

Client portal (/client)

  • Public routes:
    • /client?token=... renders the dashboard.
    • /client/quotes/:quoteId?token=... renders a focused quote view (with accept CTA).
  • Resolve tokens with useClientAccess(token, quoteId?) (client/client-access) instead of using the authenticated API client.
  • Query param token must be preserved; AppLayout treats /client (and /client/quotes/*) as public (no login redirect).
  • Keep actions stacked on mobile (flex-col sm:flex-row) and design for touch first.

Adding a New API Hook

  1. Create hook file: client/{module}/use{Module}.ts
  2. Use TanStack Query mutations/queries
  3. Use generated types from _generated/types.gen.ts
  4. Export from client/{module}/index.ts

Creating Reusable Components

  1. Create component in components/ui/ or components/{feature}/
  2. Make it responsive (mobile + desktop)
  3. Add TypeScript types
  4. Use i18n for all text
  5. Follow existing component patterns

Form Best Practices

  1. Use TanStack Form
  2. Validate with Zod
  3. Fill form on page load with existing data
  4. Show validation errors
  5. Disable submit button during submission

Styling

  • Tailwind CSS: Utility-first CSS framework for all styling
  • shadcn/ui: All base UI components use shadcn/ui primitives
    • Migration to shadcn/ui completed ✅
    • Components are copied into the project and can be customized
    • Consistent design system across the application
  • Design Patterns: Follow existing component patterns and shadcn/ui conventions
  • Responsive Design: All components must be mobile-responsive (see Mobile Responsiveness section)
  • Component Variants: Use consistent variant patterns (e.g., Button variants: default, destructive, outline, secondary, ghost, link)

Mobile Responsiveness

Mobile-First Approach:

  • All components should be mobile-responsive
  • Use Tailwind breakpoints: sm:, md:, lg: (1024px), xl:
  • Mobile sidebar uses slide-in animation
  • User menu automatically switches to full-screen on mobile
  • Touch-friendly interactions (larger tap targets, proper spacing)

Mobile Navigation:

  • MobileSidebar: Slide-in sidebar for mobile navigation
  • UserMenuMobile: Full-screen user menu with expandable sections
  • Automatic detection: UserDropdown detects screen size and uses UserMenuMobile on mobile

Type Safety

  • Always use types from _generated/types.gen.ts
  • Don't create duplicate types
  • Use TypeScript strictly

Testing

Run tests:

npm run test

Write tests for:

  • Components
  • Hooks
  • Utility functions
  • API client

Environment Variables

See .env.example for required variables:

  • API base URL
  • WebSocket URL
  • Feature flags