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 provideruseAuth: 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
Navigation par entité
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)
- Installed components:
-
Business Components (
components/ui/): Domain-specific wrappers around shadcn primitives- Examples:
ConfirmationDialog(wrapsDialog),StatusBadge(wrapsBadge),ProgressionBar(wrapsProgress) - Encapsulate business logic and provide simplified APIs for common use cases
- Should use shadcn primitives internally, not custom implementations
- Examples:
-
Layout Components (
components/layout/): Navigation and page structureSidebar: Desktop sidebar navigation (collapsible)MobileSidebar: Mobile sidebar usingSheetshadcn componentHeader: Top navigation barUserDropdown: Desktop user menu dropdownUserMenuMobile: Full-screen mobile user menu
-
Feature Components (
components/{feature}/): Feature-specific components- Organized by domain/feature (e.g.,
components/search/,components/files/)
- Organized by domain/feature (e.g.,
-
Molecule Components (
components/molecules/): Reusable composite components- Examples:
StatusBadge,PageHeader,DataField,PayInvoiceButton,PayInvoiceDialog
- Examples:
-
Organism Components (
components/organisms/): Complex layout components- Examples:
FormPageLayout,ViewPageLayout,ListPageLayout
- Examples:
Layout Components:
Sidebar: Desktop sidebar navigation (collapsible)MobileSidebar: Mobile sidebar usingSheetshadcn component with slide-in animationUserDropdown: Desktop user menu dropdownUserMenuMobile: 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 invoicesPayInvoiceDialog: Dialog component for processing invoice payments with Stripe ElementsPaymentForm: 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 subscriptionusePaymentHistory()- Get payment historyuseCreateSubscription()- Create a subscriptionuseCancelSubscription()- Cancel a subscriptionuseCreateInvoicePayment()- 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(infrontend/src/hooks/useFileUpload.ts) centralizes the presigned upload flow: it requests an upload URL, performs the PUT to R2, falls back to/storage/uploadif CORS/network issues occur, and then creates the database record.useFileDownload(infrontend/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.FileListautomatically usesuseFileDownloadfor its download button, so most pages only need to passfilesand (optionally)onDelete. OverrideonDownloadonly for specialized behavior.- Frontend code must never store signed URLs in state or props. Always request them on demand via
useFileDownloadto keep links fresh and avoid CORS issues whenR2_PUBLIC_URLis not configured.
Development Guidelines
Adding a New Page
- 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
- Choose the domain based on the page's purpose (e.g.,
- Add route in
router.tsx - Add page title in
components/layout/usePageTitle.ts - Add translations in
i18n/locales/
Example: Creating a new contact form page:
- Create:
pages/contacts/ContactFormPage/ContactFormPage.tsx - Add route:
/contacts/newand/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
tokenmust 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
- Create hook file:
client/{module}/use{Module}.ts - Use TanStack Query mutations/queries
- Use generated types from
_generated/types.gen.ts - Export from
client/{module}/index.ts
Creating Reusable Components
- Create component in
components/ui/orcomponents/{feature}/ - Make it responsive (mobile + desktop)
- Add TypeScript types
- Use i18n for all text
- Follow existing component patterns
Form Best Practices
- Use TanStack Form
- Validate with Zod
- Fill form on page load with existing data
- Show validation errors
- 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 navigationUserMenuMobile: Full-screen user menu with expandable sections- Automatic detection:
UserDropdowndetects screen size and usesUserMenuMobileon 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