veza/apps/web/src/components/ui/LazyComponent.tsx
senke c0e06e61b6 feat(legal): versioned terms acceptance ledger (CGU/CGV/mentions)
v1.0.10 légal item 3. RGPD requires explicit re-acceptance of any
terms-of-service-class document on material change. Adds a per-user,
per-document, per-version ledger so disputes can be answered with
evidence (timestamp + originating IP + user-agent).

Backend
  * migrations/991_terms_acceptance.sql — table terms_acceptances with
    UNIQUE (user_id, terms_type, version) so re-accepts are idempotent.
    inet column for IP, varchar(512) for UA, both nullable for the
    internal seed paths.
  * internal/services/terms_service.go — TermsService :
      - CurrentTerms map (ISO date version per class) is the single
        source of truth ; bump on text edit.
      - CurrentVersions(userID) returns versions + the user's
        unaccepted set ; userID==Nil ⇒ versions only (anonymous OK).
      - Accept(userID, []AcceptInput) : validates each (type, version)
        against CurrentTerms (ErrTermsVersionMismatch on stale POST),
        writes one row per accept in a single transaction, idempotent
        via FirstOrCreate against the unique index.
  * internal/handlers/terms_handler.go — REST surface :
      - GET  /api/v1/legal/terms/current  (public, OptionalAuth)
      - POST /api/v1/legal/terms/accept   (RequireAuth)
      - Captures IP via gin's ClientIP() (X-Forwarded-For-aware) and
        UA from the request, truncates UA to fit the column.
  * routes_legal.go — wires the two endpoints. `current` falls back
    to no-middleware when AuthMiddleware is nil so test rigs work.

Frontend
  * features/legal/pages/{CGUPage,CGVPage,MentionsPage}.tsx — initial
    drafts with version constants matching the backend's CurrentTerms.
    Counsel review required before v2.0.0 (text is honest baseline,
    not finalised legal copy).
  * services/api/legalTerms.ts — fetchCurrentTerms() / acceptTerms() ;
    hand-written to keep the consent-modal wiring readable.
  * components/TermsAcceptanceModal.tsx — non-dismissable modal that
    opens on every authenticated session when the unaccepted set is
    non-empty. Per-document checkboxes + single submit ; refusal keeps
    the modal open (no decline-and-continue path because the legal
    contract requires acceptance to use the platform).
  * Mounted in App.tsx alongside CookieBanner ; both must overlay
    every screen.
  * Lazy-component registry + routes for /legal/{cgu,cgv,mentions}.

Operator workflow when text changes :
  1. Edit the text in the relevant page component. Bump the
     `*_VERSION` const in that file.
  2. Bump CurrentTerms[*] in services/terms_service.go to the same
     value.
  3. Deploy. Every existing user gets force-prompted on their next
     session ; new users prompted at registration.

baseline checks : tsc 0 errors, eslint 754, go build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:47:07 +02:00

62 lines
1.2 KiB
TypeScript

export {
// eslint-disable-next-line react-refresh/only-export-components -- lazy-component registry
createLazyComponent,
LazyErrorFallback,
LazyErrorBoundary,
LazyDashboard,
LazyChat,
LazyChatJoin,
LazyLibrary,
LazyProfile,
LazySettings,
LazyLogin,
LazyRegister,
LazyForgotPassword,
LazyVerifyEmail,
LazyResetPassword,
LazySessions,
LazyNotFound,
LazyServerError,
LazyUserProfile,
LazyRoles,
LazyTrackDetail,
LazyPlaylistRoutes,
LazySharedPlaylistPage,
LazyAdminDashboard,
LazyAdminModeration,
LazyAdminPlatform,
LazyAdminTransfers,
LazyAnalytics,
LazyWebhooks,
LazyDesignSystemDemo,
LazySocial,
LazyFeed,
LazyDiscover,
LazyGear,
LazyLive,
LazyGoLive,
LazyListenTogether,
LazyCloud,
LazyQueue,
LazyDeveloper,
LazyNotifications,
LazyMarketplace,
LazySearch,
LazySellerDashboard,
LazyWishlist,
LazyPurchases,
LazyProductDetail,
LazyCheckoutComplete,
LazySubscription,
LazyDistribution,
LazyEducation,
LazySupport,
LazyLanding,
LazyDmca,
LazyDmcaNotice,
LazyPrivacy,
LazyCGU,
LazyCGV,
LazyMentions,
} from './lazy-component';
export type { LazyComponentProps, LazyErrorFallbackProps, LazyErrorBoundaryProps } from './lazy-component';