diff --git a/apps/web/.storybook/main.ts b/apps/web/.storybook/main.ts
index 74eda5d7a..b7e0334f3 100644
--- a/apps/web/.storybook/main.ts
+++ b/apps/web/.storybook/main.ts
@@ -16,6 +16,7 @@ const config: StorybookConfig = {
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-interactions'),
],
+ "staticDirs": ['../public'],
"framework": getAbsolutePath('@storybook/react-vite'),
"docs": {
"defaultName": "Documentation"
diff --git a/apps/web/.storybook/preview.ts b/apps/web/.storybook/preview.ts
deleted file mode 100644
index 5fa9e7456..000000000
--- a/apps/web/.storybook/preview.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Preview } from '@storybook/react-vite';
-import '../src/index.css';
-
-const preview: Preview = {
- parameters: {
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
- },
- },
-
- a11y: {
- // 'todo' - show a11y violations in the test UI only
- // 'error' - fail CI on a11y violations
- // 'off' - skip a11y checks entirely
- test: 'todo',
- },
- },
-};
-
-export default preview;
\ No newline at end of file
diff --git a/apps/web/.storybook/preview.tsx b/apps/web/.storybook/preview.tsx
index 82b896ab8..095041f18 100644
--- a/apps/web/.storybook/preview.tsx
+++ b/apps/web/.storybook/preview.tsx
@@ -1,9 +1,15 @@
-import type { Preview } from '@storybook/react-vite';
+import type { Preview } from '@storybook/react';
import '../src/index.css';
+import '../src/styles/design-system.css';
+import '../src/styles/global-effects.css';
+import '../src/styles/header.css';
+import '../src/lib/i18n'; // Initialize i18n
import React from 'react';
+import { ThemeProvider } from '../src/components/theme/ThemeProvider';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import { ToastProvider } from '../src/components/feedback/ToastProvider';
+import { AudioProvider } from '../src/context/AudioContext';
// Create a client for stories
const queryClient = new QueryClient({
@@ -50,9 +56,6 @@ const preview: Preview = {
expanded: true,
},
a11y: {
- // 'todo' - show a11y violations in the test UI only
- // 'error' - fail CI on a11y violations
- // 'off' - skip a11y checks entirely
test: 'todo',
},
viewport: {
@@ -77,15 +80,19 @@ const preview: Preview = {
// Apply dark class based on background selection
const isDark = context.globals.backgrounds?.value !== '#ffffff';
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
},
],
diff --git a/apps/web/README.md b/apps/web/README.md
index 07847b17c..dcb529271 100644
--- a/apps/web/README.md
+++ b/apps/web/README.md
@@ -156,7 +156,7 @@ See `eslint.config.js` for full rule configuration.
## Documentation
-- **Architecture**: See `docs/` directory
+- **Architecture Guide**: `docs/ARCHITECTURE.md` (MUST READ)
- **Component Usage**: `src/components/COMPONENT_USAGE.md`
- **Color Usage**: `src/styles/COLOR_USAGE.md`
- **Typography**: `src/styles/TYPOGRAPHY_GUIDE.md`
diff --git a/apps/web/docs/ARCHITECTURE.md b/apps/web/docs/ARCHITECTURE.md
new file mode 100644
index 000000000..1b13c87c7
--- /dev/null
+++ b/apps/web/docs/ARCHITECTURE.md
@@ -0,0 +1,120 @@
+# Veza Frontend Architecture Guide
+
+**Status:** Living Document
+**Version:** 2.0 (Post-Audit 2026)
+
+This document outlines the architectural principles, patterns, and rules that govern the Veza frontend. It supersedes previous ad-hoc audit reports.
+
+---
+
+## 1. Core Philosophy: "Visual First"
+
+> "If it can't be rendered in Storybook, it is architecturally broken."
+
+We follow a **Storybook-Driven Development** (SDD) approach.
+- **Isolation:** Every component must be renderable in isolation. Dependencies (Providers, Router, Store) must be explicit.
+- **Verification:** Storybook is our primary verification tool for UI logic and layout.
+
+---
+
+## 2. State Management Strategy
+
+We distinguish three types of state. Mixing them is strictly forbidden.
+
+### 2.1. Server State (Data) -> `React Query`
+Data that belongs to the backend (Users, Tracks, Playlists).
+- **Tool:** `@tanstack/react-query`
+- **Rule:** Never copy server data into a global store (Zustand) unless strictly necessary for client-side manipulation (e.g., a complex audio editor buffer).
+- **Caching:** Managed automatically by query keys.
+
+### 2.2. Client Global State (App) -> `Zustand`
+Data that is truly global to the client session (Auth Token, Shopping Cart, Audio Player Status).
+- **Tool:** `zustand`
+- **Rule:** Use atomic selectors to prevent render-thrashing.
+- **Structure:**
+ - `authStore`: User session.
+ - `cartStore`: E-commerce state (Items, Total).
+ - `playerStore`: Audio playback state.
+
+**Legacy Note:** `React Context` is BANNED for high-frequency state updates. It is reserved for dependency injection (Theme, i18n).
+
+### 2.3. UI Local State -> `useState` / `useReducer`
+Ephemeral state specific to a component (Modal Open/Close, Form Inputs).
+- **Rule:** If it doesn't need to persist when navigating away, it stays local.
+
+---
+
+## 3. Component Engineering
+
+We adhere to the **Smart vs Dumb** (Container vs Presentational) separation to ensure testability.
+
+### 3.1. Dumb Components (UI)
+- **Role:** Render props into HTML. Emit events via callbacks.
+- **Dependencies:** ZERO. No explicit side-effects, no API calls, no Context consumers (except Theme).
+- **Testing:** Storybook.
+
+```tsx
+// ✅ Correct Dumb Component
+export const ProductCard = ({ title, price, onAddToCart }: Props) => (
+
{title} - {price}
+);
+```
+
+### 3.2. Smart Components (Containers)
+- **Role:** Wire data to UI.
+- **Dependencies:** Allowed (`useQuery`, `useCartStore`, `useParams`).
+- **Testing:** Integration Tests (MSW + Storybook play functions).
+
+```tsx
+// ✅ Correct Smart Component
+export const ProductCardContainer = ({ id }) => {
+ const { data } = useProduct(id);
+ const addToCart = useCartStore(s => s.addItem);
+ return addToCart(data)} />;
+};
+```
+
+---
+
+## 4. Design System & Styling
+
+We use **Tailwind CSS** with a rigorous Design System (Kodo).
+
+- **Tokens Only:** Do not use arbitrary values (e.g., `w-[350px]`). Use design tokens (`w-sidebar`).
+- **Dark Mode:** All UI/Layout components must implement `dark:` variants.
+- **Icons:** `lucide-react`. Icons must inherit color via `currentColor`.
+
+---
+
+## 5. Storybook Usage
+
+Storybook is not optional. It is the definition of "Done".
+
+### 5.1. Decorators
+Use granular decorators from `src/stories/decorators.tsx` instead of global wrapping in `preview.tsx`.
+- `withToast`: Injects ToastProvider.
+- `withRouter`: Injects MemoryRouter.
+- `withStoreState`: Mocks Zustand state.
+
+### 5.2. Interaction Testing
+Critical user flows (e.g., Add to Cart) must have a `.play` function in their story to verify interaction without manual testing.
+
+---
+
+## 6. Testing Pyramid
+
+1. **Unit (Vitest):** Utilities, Store Reducers, Hooks.
+2. **Integration (Storybook + Vitest):** Component wiring, Props interface.
+3. **E2E (Playwright):** Critical Paths (Login, Checkout, Signup).
+
+---
+
+## 7. Anti-Patterns (Dos & Don'ts)
+
+| ❌ Don't | ✅ Do |
+| :--- | :--- |
+| `useContext(CartContext)` | `useCartStore(selector)` |
+| `w-[17px]` | `w-4` or `w-5` (stick to grid) |
+| Props Drilling (> 3 levels) | Composition (Slots) or Context (if static) |
+| API calls in `useEffect` | `useQuery` |
+| `any` type | Generated types from OpenAPI |
diff --git a/apps/web/src/components/admin/AdminDashboardView.stories.tsx b/apps/web/src/components/admin/AdminDashboardView.stories.tsx
index 0421b058e..2c9555750 100644
--- a/apps/web/src/components/admin/AdminDashboardView.stories.tsx
+++ b/apps/web/src/components/admin/AdminDashboardView.stories.tsx
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AdminDashboardView } from './AdminDashboardView';
+import { ToastProvider } from '../../components/feedback/ToastProvider';
/**
* AdminDashboardView - Centre de commande admin
@@ -21,9 +22,11 @@ const meta: Meta = {
tags: ['autodocs'],
decorators: [
(Story) => (
-
-
-
+
+
+
+
+
),
],
};
diff --git a/apps/web/src/components/admin/AdminModerationView.stories.tsx b/apps/web/src/components/admin/AdminModerationView.stories.tsx
index beb9969a0..6588ac832 100644
--- a/apps/web/src/components/admin/AdminModerationView.stories.tsx
+++ b/apps/web/src/components/admin/AdminModerationView.stories.tsx
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AdminModerationView } from './AdminModerationView';
+import { ToastProvider } from '../../components/feedback/ToastProvider';
/**
* AdminModerationView - Queue de modération
@@ -21,9 +22,11 @@ const meta: Meta = {
tags: ['autodocs'],
decorators: [
(Story) => (
-
-
-
+
+
+
+
+
),
],
};
diff --git a/apps/web/src/components/layout/DashboardLayout.stories.tsx b/apps/web/src/components/layout/DashboardLayout.stories.tsx
index e43120c88..fac3332dd 100644
--- a/apps/web/src/components/layout/DashboardLayout.stories.tsx
+++ b/apps/web/src/components/layout/DashboardLayout.stories.tsx
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react';
import { DashboardLayout } from './DashboardLayout';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ToastProvider } from '@/components/feedback/ToastProvider';
+
const queryClient = new QueryClient();
const meta = {
@@ -12,9 +14,9 @@ const meta = {
(Story) => (
-
+
-
+
),
diff --git a/apps/web/src/components/layout/Header.stories.tsx b/apps/web/src/components/layout/Header.stories.tsx
index a8cca5ed5..ae4fd14f7 100644
--- a/apps/web/src/components/layout/Header.stories.tsx
+++ b/apps/web/src/components/layout/Header.stories.tsx
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Header } from './Header';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ToastProvider } from '@/components/feedback/ToastProvider';
+
const queryClient = new QueryClient();
const meta = {
@@ -12,7 +14,7 @@ const meta = {
(Story) => (
-
+
{/* Header is fixed, so we need a container that mimics the body */}
@@ -20,7 +22,7 @@ const meta = {
Page Content
-
+
),
diff --git a/apps/web/src/components/layout/Navbar.stories.tsx b/apps/web/src/components/layout/Navbar.stories.tsx
index 2fbabbdad..52822c23b 100644
--- a/apps/web/src/components/layout/Navbar.stories.tsx
+++ b/apps/web/src/components/layout/Navbar.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Navbar } from './Navbar';
-import { ThemeProvider } from '../../context/ThemeContext';
+import { ThemeProvider } from '../theme/ThemeProvider';
const meta: Meta = {
@@ -12,7 +12,7 @@ const meta: Meta = {
tags: ['autodocs'],
decorators: [
(Story) => (
-
+