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
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
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
- Reusable UI components in
components/ui/ - Layout components in
components/layout/ - Feature components in
components/{feature}/ - Molecule components in
components/molecules/(e.g.,StatusBadge,PageHeader,DataField,PayInvoiceButton,PayInvoiceDialog) - Organism components in
components/organisms/(e.g.,FormPageLayout,ViewPageLayout,ListPageLayout)
Layout Components:
Sidebar: Desktop sidebar navigation (collapsible)MobileSidebar: Mobile sidebar 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>;
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
- Use Tailwind CSS
- Follow existing design patterns
- Make components responsive
- Use shadcn/ui style components
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