feat(credits): solde IA unique (option M), packs Stripe et polish billing

Un pot de crédits global (BASIC 100 / PRO 1 000 / BUSINESS 4 000) remplace
les seaux par fonction côté débit. Packs one-shot S/M/L, admin sans seaux
legacy, usage multi-features, hints de coût, anti-flash thème et hydratation
admin. Inclut aussi le pipeline slides renforcé et la page publique polishée.
This commit is contained in:
Antigravity
2026-07-16 20:43:18 +00:00
parent 704fed1191
commit 556a0b2f3f
77 changed files with 35745 additions and 10430 deletions

View File

@@ -5,14 +5,17 @@ import { SessionProviderWrapper } from "@/components/session-provider-wrapper";
import { getAISettings } from "@/app/actions/ai-settings";
import { getUserSettings } from "@/app/actions/user-settings";
import { ThemeInitializer } from "@/components/theme-initializer";
import { ThemeClassGuard } from "@/components/theme-class-guard";
import { DirectionInitializer } from "@/components/direction-initializer";
import { ErrorReporter } from "@/components/error-reporter";
import { auth } from "@/auth";
import { CookieConsentRoot } from "@/components/legal/cookie-consent-root";
import { LanguageProvider } from "@/lib/i18n/LanguageProvider";
import Script from "next/script";
import type { CSSProperties } from "react";
import { normalizeThemeId } from "@/lib/apply-document-theme";
import { cookies } from "next/headers";
import { normalizeThemeId, THEME_COOKIE_NAME } from "@/lib/apply-document-theme";
import { THEME_INIT_SCRIPT, DIRECTION_INIT_SCRIPT } from "@/lib/inline-boot-scripts";
import { SwCleanup } from "@/components/sw-cleanup";
import { Inter, Manrope, Playfair_Display, JetBrains_Mono } from "next/font/google";
@@ -76,14 +79,20 @@ export default async function RootLayout({
const session = await auth();
const userId = session?.user?.id;
const [aiSettings, userSettings] = await Promise.all([
const [aiSettings, userSettings, cookieStore] = await Promise.all([
getAISettings(userId),
getUserSettings(userId),
cookies(),
])
const htmlTheme = serverHtmlThemeState(userSettings.theme)
// Cookie = préférence immédiate (bascule barre latérale) ; base = profil utilisateur.
// Sans cookie, un dark seulement en localStorage était écrasé par le serveur → flash clair.
const cookieTheme = cookieStore.get(THEME_COOKIE_NAME)?.value
const resolvedTheme = normalizeThemeId(cookieTheme || userSettings.theme || 'light')
const htmlTheme = serverHtmlThemeState(resolvedTheme)
const serverAccent = userSettings.accentColor ?? '#A47148'
const serverTheme = normalizeThemeId(userSettings.theme || 'light')
const serverTheme = resolvedTheme
const htmlStyle = {
'--color-brand-accent': serverAccent,
} as CSSProperties
@@ -97,14 +106,18 @@ export default async function RootLayout({
data-server-accent={serverAccent}
style={htmlStyle}
>
<body className={`${inter.className} ${inter.variable} ${manrope.variable} ${playfair.variable} ${jetbrainsMono.variable} h-screen overflow-hidden`}>
<Script id="theme-init" src="/scripts/theme-init.js" strategy="beforeInteractive" />
<Script id="direction-init" src="/scripts/direction-init.js" strategy="beforeInteractive" />
<Script id="sw-cleanup" src="/scripts/sw-cleanup.js" strategy="afterInteractive" />
<head>
{/* Scripts boot inline : anti-flash thème/RTL, sans next/script (React 19). */}
<script id="theme-init" dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
<script id="direction-init" dangerouslySetInnerHTML={{ __html: DIRECTION_INIT_SCRIPT }} />
</head>
<body className={`${inter.className} ${inter.variable} ${manrope.variable} ${playfair.variable} ${jetbrainsMono.variable} h-screen overflow-hidden bg-background text-foreground`}>
<SessionProviderWrapper>
<SwCleanup />
<ErrorReporter />
<DirectionInitializer />
<ThemeInitializer theme={userSettings.theme} fontSize={aiSettings.fontSize} fontFamily={aiSettings.fontFamily} accentColor={userSettings.accentColor} />
<ThemeInitializer theme={resolvedTheme} fontSize={aiSettings.fontSize} fontFamily={aiSettings.fontFamily} accentColor={userSettings.accentColor} />
<ThemeClassGuard />
{children}
<LanguageProvider initialLanguage="en">
<CookieConsentRoot />