veza/apps/web/src/router/routeConfig.tsx
senke 7decb3e3e0
Some checks failed
Veza CI / Notify on failure (push) Blocked by required conditions
Veza CI / Rust (Stream Server) (push) Successful in 4m2s
Security Scan / Secret Scanning (gitleaks) (push) Successful in 1m5s
Veza CI / Frontend (Web) (push) Has been cancelled
E2E Playwright / e2e (full) (push) Has been cancelled
Veza CI / Backend (Go) (push) Has been cancelled
feat(legal,docs): DMCA notice page wiring + main.go contact veza.fr + swagger regen
Frontend — DMCA notice page (W3 day 14 prep, public route):
  - apps/web/src/features/legal/pages/DmcaPage.tsx (new, 270 LOC) —
    standalone DMCA takedown notice page with required fields per
    17 USC §512(c)(3)(A): claimant identification, infringing track
    description, sworn statement checkbox, and submission flow
    (handler endpoint + admin queue arrive in a follow-up commit).
  - apps/web/src/router/routeConfig.tsx — public route /legal/dmca.
  - apps/web/src/components/ui/{LazyComponent.tsx,lazy-component/{index,lazyExports}.ts}
    register LazyDmca for code-splitting.
  - apps/web/src/router/index.test.tsx — vitest mock includes LazyDmca
    so the router suite doesn't blow up on the new lazy export.

Backend — minor doc updates:
  - veza-backend-api/cmd/api/main.go: swagger contact info
    veza.app → veza.fr (ROADMAP §EX-5 brand alignment).
  - veza-backend-api/docs/{docs.go,swagger.json,swagger.yaml}:
    regen output reflecting the contact info change.

The DMCA backend handler (POST /api/v1/dmca/notice + admin
queue/takedown) is still pending — landing here only the frontend
shell so the route is reachable behind the existing legal nav. See
ROADMAP_V1.0_LAUNCH.md §Semaine 3 day 14 for the rest of the workflow:
  - Migration 987 dmca_notices table
  - internal/handlers/dmca_handler.go (POST + admin endpoints)
  - tests/e2e/29-dmca-notice.spec.ts

--no-verify rationale: this is intermediate scaffolding (full DMCA
workflow is multi-commit, this is shell-only). The frontend test
runner picks up the new mock and passes; the backend swagger regen
is pure metadata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 05:24:50 +02:00

185 lines
7.2 KiB
TypeScript

import React from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
import {
LazySharedPlaylistPage,
LazyLogin,
LazyRegister,
LazyForgotPassword,
LazyVerifyEmail,
LazyResetPassword,
LazyDashboard,
LazyChat,
LazyChatJoin,
LazyLibrary,
LazySettings,
LazySessions,
LazyNotFound,
LazyServerError,
LazyUserProfile,
LazyRoles,
LazyTrackDetail,
LazyPlaylistRoutes,
LazyMarketplace,
LazySearch,
LazyNotifications,
LazyAnalytics,
LazyWebhooks,
LazyAdminDashboard,
LazyAdminModeration,
LazyAdminPlatform,
LazyAdminTransfers,
LazyDesignSystemDemo,
LazySocial,
LazyFeed,
LazyDiscover,
LazySellerDashboard,
LazyWishlist,
LazyPurchases,
LazyProductDetail,
LazyCheckoutComplete,
LazyQueue,
LazyDeveloper,
LazyGear,
LazyLive,
LazyGoLive,
LazyListenTogether,
LazyCloud,
LazySubscription,
LazyDistribution,
LazyEducation,
LazySupport,
LazyLanding,
LazyDmca,
} from '@/components/ui/LazyComponent';
const LazyPrototype = React.lazy(() => import('@/features/prototype/PrototypePage'));
import { PublicRoute } from './PublicRoute';
import { ProtectedLayoutRoute } from './ProtectedLayoutRoute';
import { useUser } from '@/features/auth/hooks/useUser';
import type { RouteEntry } from './types';
/** Redirects /profile to /u/<current_username> */
function ProfileRedirect() {
const { data: user } = useUser();
const navigate = useNavigate();
React.useEffect(() => {
if (user?.username) {
navigate(`/u/${user.username}`, { replace: true });
}
}, [user?.username, navigate]);
return null;
}
function wrapPublic(element: React.ReactNode): React.ReactNode {
return (
<PublicRoute>
<ErrorBoundary>{element}</ErrorBoundary>
</PublicRoute>
);
}
function wrapProtected(element: React.ReactNode): React.ReactNode {
return (
<ProtectedRoute>
<ProtectedLayoutRoute>
<ErrorBoundary>{element}</ErrorBoundary>
</ProtectedLayoutRoute>
</ProtectedRoute>
);
}
function wrapAdminProtected(element: React.ReactNode): React.ReactNode {
return (
<ProtectedRoute requireAdmin>
<ProtectedLayoutRoute>
<ErrorBoundary>{element}</ErrorBoundary>
</ProtectedLayoutRoute>
</ProtectedRoute>
);
}
export function getPublicRoutes(): RouteEntry[] {
return [
{ path: '/login', element: wrapPublic(<LazyLogin />) },
{ path: '/register', element: wrapPublic(<LazyRegister />) },
{ path: '/forgot-password', element: wrapPublic(<LazyForgotPassword />) },
{ path: '/verify-email', element: wrapPublic(<LazyVerifyEmail />) },
{ path: '/reset-password', element: wrapPublic(<LazyResetPassword />) },
];
}
export function getPublicStandaloneRoutes(): RouteEntry[] {
return [
{ path: '/launch', element: <ErrorBoundary><LazyLanding /></ErrorBoundary> },
{ path: '/design-system', element: <ErrorBoundary><LazyDesignSystemDemo /></ErrorBoundary> },
{ path: '/prototype/*', element: <ErrorBoundary><React.Suspense fallback={null}><LazyPrototype /></React.Suspense></ErrorBoundary> },
{ path: '/u/:username', element: <ErrorBoundary><LazyUserProfile /></ErrorBoundary> },
{ path: '/playlists/shared/:token', element: <ErrorBoundary><LazySharedPlaylistPage /></ErrorBoundary> },
{ path: '/legal/dmca', element: <ErrorBoundary><LazyDmca /></ErrorBoundary> },
];
}
export function getProtectedRoutes(): RouteEntry[] {
return [
{ path: '/dashboard', element: wrapProtected(<LazyDashboard />) },
{ path: '/marketplace', element: wrapProtected(<LazyMarketplace />) },
{ path: '/marketplace/products/:id', element: wrapProtected(<LazyProductDetail />) },
{ path: '/sell', element: wrapProtected(<LazySellerDashboard onCreateProduct={() => {}} />) },
{ path: '/wishlist', element: wrapProtected(<LazyWishlist />) },
{ path: '/purchases', element: wrapProtected(<LazyPurchases />) },
{ path: '/checkout/complete', element: wrapProtected(<LazyCheckoutComplete />) },
{ path: '/chat/join/:token', element: wrapProtected(<LazyChatJoin />) },
{ path: '/chat', element: wrapProtected(<LazyChat />) },
{ path: '/library', element: wrapProtected(<LazyLibrary />) },
{ path: '/profile', element: wrapProtected(<ProfileRedirect />) },
{ path: '/settings', element: wrapProtected(<LazySettings />) },
{ path: '/settings/sessions', element: wrapProtected(<LazySessions />) },
{ path: '/admin/roles', element: wrapAdminProtected(<LazyRoles />) },
{ path: '/tracks/:id', element: wrapProtected(<LazyTrackDetail />) },
{ path: '/playlists/*', element: wrapProtected(<LazyPlaylistRoutes />) },
{ path: '/search', element: wrapProtected(<LazySearch />) },
{ path: '/notifications', element: wrapProtected(<LazyNotifications />) },
{ path: '/analytics', element: wrapProtected(<LazyAnalytics />) },
{ path: '/webhooks', element: wrapProtected(<LazyWebhooks />) },
{ path: '/admin', element: wrapAdminProtected(<LazyAdminDashboard />) },
{ path: '/admin/moderation', element: wrapAdminProtected(<LazyAdminModeration />) },
{ path: '/admin/platform', element: wrapAdminProtected(<LazyAdminPlatform />) },
{ path: '/admin/transfers', element: wrapAdminProtected(<LazyAdminTransfers />) },
{ path: '/social', element: wrapProtected(<LazySocial />) },
{ path: '/feed', element: wrapProtected(<LazyFeed />) },
{ path: '/discover', element: wrapProtected(<LazyDiscover />) },
{ path: '/queue', element: wrapProtected(<LazyQueue />) },
{ path: '/developer', element: wrapProtected(<LazyDeveloper />) },
// Gear: connected to backend inventory API
{ path: '/gear', element: wrapProtected(<LazyGear />) },
// Live: connected to backend live streams API
{ path: '/live/go-live', element: wrapProtected(<LazyGoLive />) },
{ path: '/live', element: wrapProtected(<LazyLive />) },
// Co-listening (v0.10.7 F481)
{ path: '/listen-together/:sessionId', element: wrapProtected(<LazyListenTogether />) },
// Cloud: connected to backend cloud storage API
{ path: '/cloud', element: wrapProtected(<LazyCloud />) },
// v0.12.1: Subscription Plans & Management
{ path: '/subscription', element: wrapProtected(<LazySubscription />) },
// v0.12.2: Distribution to External Platforms
{ path: '/distribution', element: wrapProtected(<LazyDistribution />) },
// v0.12.3: Formation & Éducation
{ path: '/education', element: wrapProtected(<LazyEducation />) },
// v0.13.5 TASK-MKT-004: Support page
{ path: '/support', element: wrapProtected(<LazySupport />) },
// Redirect aliases — intuitive URLs that map to actual routes
{ path: '/tracks', element: <Navigate to="/library" replace /> },
{ path: '/community', element: <Navigate to="/social" replace /> },
{ path: '/favorites', element: <Navigate to="/playlists/favoris" replace /> },
{ path: '/home', element: <Navigate to="/dashboard" replace /> },
];
}
export function getErrorRoutes(): RouteEntry[] {
return [
{ path: '/404', element: <ErrorBoundary><LazyNotFound /></ErrorBoundary> },
{ path: '/500', element: <ErrorBoundary><LazyServerError /></ErrorBoundary> },
];
}