# PHASE A — ARCHITECTURE FRONTEND > **Scope** : `apps/web/src/` > **Analyse exhaustive** : structure, responsabilités, state, requêtes réseau, routing, erreurs --- ## A1. Structure des dossiers ### Séparation features / shared / core Le projet adopte un **modèle hybride en transition** : | Dossier | Rôle | Fichiers estimés | Verdict | |---------|------|-----------------|---------| | `src/features/` | Feature modules (auth, player, tracks, playlists, chat, etc.) | ~900+ | ✅ Bien structuré | | `src/components/` | Composants "legacy" / partagés | ~500+ | ⚠️ Méga-dossier, 40+ sous-dossiers | | `src/components/ui/` | Primitives UI (Button, Modal, Tabs, etc.) | ~200+ | ✅ Bonne séparation | | `src/services/` | Services partagés (API client, websocket, cache) | ~30+ | ✅ Bien isolés | | `src/stores/` | Stores Zustand globaux | ~6 fichiers | ✅ Correct | | `src/hooks/` | Hooks partagés | ~15+ | ✅ Bien isolés | | `src/utils/` | Utilitaires transverses | ~20+ | ✅ Correct | | `src/schemas/` | Schemas Zod | ~5 fichiers | ✅ Bonne pratique | | `src/types/` | Types globaux + generated | ~10 fichiers | ✅ Correct | ### Problème majeur : dualité `components/` vs `features/` **Duplication de domaines détectée** : | Domaine | `components/` | `features/` | Problème | |---------|--------------|-------------|----------| | Player | `components/player/` | `features/player/` | Deux emplacements concurrents | | Settings | `components/settings/` (7 sous-dossiers) | `features/settings/` | Même domaine, deux endroits | | Auth | `components/auth/` | `features/auth/` | Confusion sur le canonique | | Social | `components/social/` | — | Devrait être dans features/ | | Education | `components/education/` | — | Devrait être dans features/ | | Commerce | `components/commerce/` | — | Devrait être dans features/ | | Gamification | `components/gamification/` | — | Pas de feature associée | | Studio | `components/studio/` | `features/studio/` | Deux emplacements | | Search | `components/search/` | `features/search/` | Deux emplacements | **Verdict** : La migration vers une architecture feature-first est **en cours mais incomplète**. Environ 60% des domaines métier ont leur module dans `features/`, mais des vestiges significatifs restent dans `components/`. [src/components/:ensemble] [src/features/:ensemble] ### Barrel exports Les barrel exports (`index.ts`) sont **présents et cohérents** dans les modules features decomposés : - `features/tracks/components/comment-thread/index.ts` - `features/playlists/components/playlist-list/index.ts` - `features/player/components/player-bar/index.ts` - Tous les sous-composants UI décomposés (accordion, dialog, tabs, etc.) **Manquants** : les composants de `components/` n'ont pas toujours de barrel export cohérent. ### Composants groupés par feature vs par type Le pattern dominant est **par feature** dans `features/`, et **par type/domaine** dans `components/`. L'architecture cible est clairement feature-first. ### Routes colocalisées Les routes sont **séparées** dans `src/router/` — pas colocalisées avec les features. C'est acceptable avec React Router v6, mais les features contiennent leurs propres `pages/` (ex: `features/tracks/pages/`, `features/auth/pages/`) — bonne pratique. ### Dossiers morts / orphelins potentiels | Dossier suspect | Raison | |----------------|--------| | `components/views/` | 20+ sous-dossiers de "views" qui ne sont pas des routes — probablement des prototypes Storybook | | `components/demo/` | Dossier de démo — mort probable | | `components/base/` | Composants "base" — potentiellement redondant avec `components/ui/` | | `stories/` (racine) | Stories globales + assets Storybook default — à nettoyer | --- ## A2. Séparation des responsabilités ### Tableau des composants > 100 lignes | Composant | Lignes | Type | Responsabilités mélangées ? | Verdict | |-----------|--------|------|----------------------------|---------| | `components/developer/DeveloperDashboardView.tsx` | 323 | Container | ✅ Oui — API calls, state, rendering | 🔴 REFACTOR | | `features/dashboard/pages/DashboardPage.tsx` | 329 | Container | ✅ Oui — Data fetching, state, rendering | 🔴 REFACTOR | | `components/layout/Sidebar.tsx` | 295 | Presentation | ❌ Non — Pure UI | ✅ OK | | `components/developer/SwaggerUI.tsx` | 279 | Hybrid | ✅ Oui — Config, error handling, rendering | 🟡 REFACTOR | | `components/upload/metadata/MetadataForm.tsx` | 274 | Container | ✅ Oui — State, modals, form logic | 🟡 REFACTOR | | `features/chat/components/ChatMessages.tsx` | 218 | Container | ✅ Oui — Store access, state, rendering | 🟡 REFACTOR | | `app/App.tsx` | 192 | Hybrid | ✅ Oui — Auth init, theme, i18n, CSRF | 🟡 REFACTOR | | `features/tracks/pages/track-detail-page/TrackDetailPageInfo.tsx` | 178 | Presentation | ❌ Non — Pure UI | ✅ OK | | `components/layout/Header.tsx` | 172 | Hybrid | ✅ Oui — Auth, theme, navigation | 🟡 REFACTOR | | `features/player/components/GlobalPlayer.tsx` | 154 | Container | ⚠️ Partiel — Near limit | ✅ OK (à surveiller) | | `features/tracks/components/TrackSearch.tsx` | 147 | Container | ✅ Oui — Search logic, API calls | 🟡 REFACTOR | | `components/forms/LoginForm.tsx` | 135 | Hybrid | ⚠️ Partiel — Form pattern acceptable | ✅ OK | | `features/tracks/pages/track-detail-page/TrackDetailPageCoverAndActions.tsx` | 133 | Presentation | ❌ Non — Pure UI | ✅ OK | | `features/playlists/components/PlaylistHeader.tsx` | 130 | Presentation | ❌ Non — Pure UI | ✅ OK | | `features/chat/pages/ChatPage.tsx` | 114 | Container | ✅ Oui — API calls, state, rendering | 🟡 REFACTOR | | `features/tracks/components/track-filters/TrackFilters.tsx` | 103 | Hybrid | ⚠️ Partiel — Uses hook | ✅ OK | | `features/player/components/PlayerControls.tsx` | 106 | Presentation | ❌ Non — Pure UI | ✅ OK | | `features/tracks/components/TrackList.tsx` | 291 | Presentation | ❌ Non — Pure UI, 18 props | ✅ OK | **Synthèse** : - **27% des composants > 100 lignes** nécessitent un refactoring - **40% mélangent** des responsabilités (au moins partiellement) - **97% ont leurs props typées** — excellent - Le pattern `useXxxPage()` hook est bien adopté dans les features récentes ### Bonne pratique observée Les pages récentes utilisent un hook dédié pour la logique : - `features/tracks/pages/track-detail-page/useTrackDetailPage.ts` → `TrackDetailPage.tsx` - `features/profile/pages/user-profile-page/useUserProfilePage.ts` → `UserProfilePage.tsx` - `features/library/pages/library-page/useLibraryPage.ts` → `LibraryPage.tsx` Ce pattern de séparation est **exemplaire** et devrait être généralisé aux composants plus anciens. --- ## A3. Gestion d'état ### Couches de state identifiées | Couche | Technologie | Nombre | Scope | |--------|-------------|--------|-------| | State local | `useState` | ~200+ fichiers | Composant | | Context API | `createContext` | 4 contextes | Sous-arbre React | | Store global | Zustand | 7 stores | Application | | Server state | TanStack React Query | ~50+ hooks | Cache serveur | | URL state | React Router | Params + query | URL | ### Stores Zustand détaillés | Store | Fichier | State contenu | Middlewares | Persisté | |-------|---------|--------------|-------------|----------| | UI Store | `stores/ui.ts` | theme, language, sidebarOpen, notifications | persist, devtools, broadcastSync | Oui (theme, language, sidebarOpen) | | Library Store | `stores/library.ts` | filters | persist, devtools | Oui (filters) | | Cart Store | `stores/cartStore.ts` | items[], cart operations | persist | Oui | | Rate Limit Store | `stores/rateLimit.ts` | Rate limit headers | persist | Oui | | Auth Store | `features/auth/store/authStore.ts` | isAuthenticated, isLoading, error | persist, broadcastSync | Oui (isAuthenticated) | | Chat Store | `features/chat/store/chatStore.ts` | userId, conversations[], messages, typingUsers, WS state | devtools, immer | Non | | Player Store | `features/player/store/playerStore.ts` | currentTrack, isPlaying, queue[], volume, repeat, shuffle | persist | Oui (volume, queue, etc.) | ### Contextes React | Contexte | Fichier | Contenu | Hook | |----------|---------|---------|------| | AuthContext | `context/AuthContext.tsx` | user, isAuthenticated, isLoading, login, register, logout | `useAuth()` | | ToastContext | `components/feedback/ToastProvider.tsx` | toasts[], addToast, removeToast | `useToast()` | | ThemeContext | `components/theme/ThemeProvider.tsx` | theme, setTheme | `useTheme()` | | AudioContext | `context/audio-context/AudioContext.tsx` | Audio playback context | `useAudio()` | ### Diagnostic **Conflits entre couches** : - ⚠️ **Auth** : `AuthContext` (Context API) **et** `authStore` (Zustand) gèrent l'authentification — **duplication potentielle**. Le contexte gère `user` + `isAuthenticated`, le store aussi `isAuthenticated`. Source de vérité ambiguë. [context/AuthContext.tsx] [features/auth/store/authStore.ts] - ✅ **Player** : Zustand store uniquement — pas de conflit - ✅ **Server data** : React Query pour les données serveur — bien séparé du state UI **State normalisé ?** : - ⚠️ Le chat store contient `conversations[]` et `messages{}` — normalisé par conversation, mais les messages sont dans un Map — acceptable pour le chat - ✅ Les données serveur (tracks, playlists) sont gérées par React Query (cache automatique) - ❌ Pas de normalisation explicite des entités côté client (pas de store normalisé type normalizr) **Prop drilling > 3 niveaux** : - Pas de chaîne évidente détectée. Les données descendent via hooks (React Query) ou stores (Zustand), rarement par props directes sur plus de 2 niveaux. [DONNÉES INSUFFISANTES — nécessite analyse AST complète] --- ## A4. Gestion des requêtes réseau ### Architecture API **Client centralisé** : `services/api/client.ts` (2 237 lignes) — Axios-based avec intercepteurs complets. **Features du client** : - Token refresh automatique sur 401 avec queue de requêtes - CSRF token injection avec retry sur 403 - Validation requêtes/réponses via Zod schemas - Retry logic : 3 retries, exponential backoff (1s → 10s max) - Request deduplication pour GET - Response caching pour GET - Rate limit tracking - Slow request detection (seuil 1000ms) - Offline queue support ### Inventaire des services **56 fichiers service/API** répartis entre `services/` (partagés) et `features/*/services/` (feature-specific). ### Tableau des patterns d'appels majeurs | Fichier | Pattern | Loading | Error | Cancel | Typé | Centralisé | |---------|---------|---------|-------|--------|------|------------| | `services/api/client.ts` | Axios + interceptors | ✅ | ✅ Centralisé | ✅ AbortController | ✅ | ✅ | | `services/api/auth.ts` | apiClient.post/get | Via RQ | Via interceptor | ❌ | ✅ | ✅ | | `features/tracks/api/trackApi.ts` | apiClient wrappers | Via RQ | Via interceptor | ❌ | ✅ | ✅ | | `features/playlists/services/playlistService.ts` | apiClient.get/post | Via RQ | Via interceptor | ❌ | ✅ | ✅ | | `features/tracks/services/uploadService.ts` | apiClient.post + FormData | useState | try/catch custom | ❌ | ✅ | ✅ | | `services/websocket.ts` | WebSocket natif | Manual | Reconnect auto | ❌ | ⚠️ any×8 | ✅ | | `features/streaming/services/hlsService.ts` | HLS.js | Via RQ | Via HLS.js | ❌ | ✅ | Spécifique | ### React Query configuration - `staleTime`: 1 minute - `gcTime`: 5 minutes - `retry: false` (délégué au retry Axios) - `refetchOnWindowFocus: false` - `refetchOnReconnect: true` ### Patterns d'erreur - **3 patterns coexistent** : 1. Try/catch avec custom error types (trackService) 2. Propagation directe (erreurs gérées par l'intercepteur) 3. `handleApiServiceError()` utility wrapper - **Incohérence notable** : certains services gèrent les erreurs localement, d'autres délèguent entièrement ### AbortController - ✅ Implémenté dans le client (`createCancellableRequest()`, `createRequestWithTimeout()`) - ⚠️ Faiblement adopté dans les services individuels - ⚠️ React Query hooks n'utilisent pas systématiquement le `signal` --- ## A5. Routing ### Solution - **React Router v6** (`react-router-dom ^6.22.0`) - `BrowserRouter` avec future flags activés [src/main.tsx:227] ### Routes | Type | Nombre | Lazy-loaded | |------|--------|-------------| | Routes publiques (auth) | 5 | ✅ Toutes | | Routes publiques standalone | 2 | ✅ Toutes | | Routes protégées | 25 | ✅ Toutes | | Routes d'erreur | 2 | ✅ Toutes | | Catch-all (*) | 1 | Redirect → /404 | | **Total** | **35** | **100% lazy** | ### Guards d'authentification - `ProtectedRoute` : vérifie `isAuthenticated` + `TokenStorage.getAccessToken()` → redirect `/login` [components/auth/ProtectedRoute.tsx] - `PublicRoute` : redirige les utilisateurs connectés vers `/dashboard` [router/PublicRoute.tsx] - `ProtectedLayoutRoute` : wraps routes protégées avec `DashboardLayout` [router/ProtectedLayoutRoute.tsx] ### 404 / Fallback - ✅ Route `/404` avec composant `LazyNotFound` - ✅ Route `/500` avec composant `LazyServerError` - ✅ Catch-all `*` → redirect `/404` - ✅ Error boundaries wrappent les routes d'erreur ### Lazy loading **100% des routes sont lazy-loaded** via `createLazyComponent` [components/ui/lazy-component/lazyExports.ts:3-235]. Implémentation basée sur `React.lazy()` + `Suspense` + `LazyErrorBoundary` — excellente configuration. ### Deep linking - ✅ Routes bookmarkables (paths sémantiques : `/tracks/:id`, `/playlists/*`, `/u/:username`) - ✅ Paramètres URL utilisés (`useParams`, `useSearchParams`) --- ## A6. Gestion des erreurs ### Error boundaries | Boundary | Fichier | Scope | Sentry | |----------|---------|-------|--------| | Main ErrorBoundary | `components/ui/ErrorBoundary.tsx` | Routes + app | ✅ | | Legacy ErrorBoundary | `components/ErrorBoundary.tsx` | Backup | ✅ | | LazyErrorBoundary | `components/ui/lazy-component/LazyErrorBoundary.tsx` | Lazy components | ❌ | | PlaylistErrorBoundary | `features/playlists/components/PlaylistErrorBoundary.tsx` | Feature playlist | ❌ | - ✅ Error boundary au niveau app (App.tsx wrappé) - ✅ Error boundaries au niveau routes - ⚠️ 2 implémentations ErrorBoundary (main + legacy) — duplication ### Erreurs réseau - ✅ Feedback utilisateur via toast notifications (react-hot-toast) - ✅ Parsing centralisé (`parseApiError()`) avec messages user-friendly - ✅ Catégorisation : network, validation, auth, rate_limit, server_error, timeout - ⚠️ Inconsistance : inline error display vs toast-only selon les composants ### Erreurs de formulaire - ✅ React Hook Form + Zod pour la validation client - ✅ Validation timing configurable (onBlur, onChange, onSubmit) - ⚠️ Validation serveur : certains formulaires ne remontent pas les erreurs serveur dans les champs ### Logging frontend - ✅ **Sentry** configuré (`lib/sentry.ts`) avec : - Browser tracing - Session replay - Error filtering (prod uniquement si `VITE_SENTRY_DSN` défini) - Context enrichment via logger - ✅ Logger custom (`utils/logger.ts`) avec Sentry integration dynamique ### Fallback UI - ✅ Skeleton loaders pour les chargements - ✅ `ErrorDisplay` component pour les erreurs - ✅ `ComingSoon` component pour les features non implémentées - ✅ Empty states gérés (composants `*Empty.tsx` dans les views) --- ## SCORE ARCHITECTURE : 7/10 ### Points gagnés | Point | Score | Justification | |-------|-------|---------------| | Routing | +1.0 | 100% lazy-loaded, guards, 404, deep linking — exemplaire | | TypeScript strict | +1.0 | Config stricte complète, types omniprésents | | Server state | +1.0 | React Query bien configuré, staleTime/gcTime cohérents | | Error handling | +0.8 | Sentry + boundaries + parsing centralisé | | Feature modules | +0.7 | features/ bien structuré pour les domaines principaux | | API client | +0.8 | Client robuste : retry, dedup, cache, CSRF, offline queue | | Hooks pattern | +0.7 | useXxxPage() pattern bien adopté dans les features récentes | ### Points perdus | Point | Score | Justification | |-------|-------|---------------| | Dualité components/features | -1.0 | 40% des domaines ont des composants dans les deux dossiers | | Auth state duplication | -0.5 | AuthContext + authStore — source de vérité ambiguë | | `as any` endémique | -0.5 | 706 `as any` casts — ESLint rule explicitement `off` | | Client.ts monstre | -0.5 | 2 237 lignes — devrait être décomposé en modules | | Inconsistance error handling | -0.3 | 3 patterns d'erreur coexistent dans les services | | AbortController sous-utilisé | -0.2 | Implémenté mais faiblement adopté |