From c3fa04dde891d5765572123b2186f3682a7e4a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:41:23 +0100 Subject: [PATCH] fix(web): csp font violation, /pricing 401, residual login emoji MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs caught via devtools on live site: **1. CSP 'font-src 'self' data:' violation × 3 per landing load.** BaseLayout was loading Geist + Geist_Mono via next/font/google. In prod builds Next.js self-hosts those under /_next/static, but the generated CSS still references `--font-sans: "Geist", …` which some browsers resolve by re-requesting fonts.gstatic.com. Since we ship Anthropic Sans/Serif/Mono self-hosted already (/fonts/*.woff2 via @font-face in globals.css), the Geist dependency was pure overhead. Removed `next/font/google` imports entirely. Added a `.cm-root` class on that remaps the Tailwind `--font-sans/--font-mono` tokens to our `--cm-font-sans/--cm-font-mono` vars — so every Tailwind `font-sans` / `font-mono` utility now resolves to Anthropic families. No Google Fonts fetch, no CSP violation. **2. /pricing 401 on public visit.** `` calls `useCustomer()` → `GET /api/billing/customer` which needs auth. Unauthed visitor on /pricing → 401 in devtools + wasted round trip. Gated `useCustomer` on `authClient.useSession()` — query `enabled: !!session?.user`. Public visitors now skip the fetch entirely; signed-in users still get their customer record. **3. Residual "Welcome back! 👋" on /auth/login (both locales).** Emoji sweep (e91fc80) missed the i18n translation files. Removed 👋 from en/auth.json + es/auth.json login header titles. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web/src/assets/styles/globals.css | 10 ++++++++++ .../src/modules/billing/hooks/use-customer.ts | 14 +++++++++++++- apps/web/src/modules/common/layout/base.tsx | 17 +---------------- packages/i18n/src/translations/en/auth.json | 2 +- packages/i18n/src/translations/es/auth.json | 2 +- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/apps/web/src/assets/styles/globals.css b/apps/web/src/assets/styles/globals.css index 2c1f546..aadca17 100644 --- a/apps/web/src/assets/styles/globals.css +++ b/apps/web/src/assets/styles/globals.css @@ -154,3 +154,13 @@ :root { color-scheme: dark; } + +/* Override the Tailwind default --font-sans / --font-mono CSS vars + (which BaseLayout used to populate from next/font/google Geist). + We self-host Anthropic Sans/Serif/Mono now — no Google Fonts fetch, + no CSP font-src violation. */ +.cm-root { + --font-sans: var(--cm-font-sans); + --font-mono: var(--cm-font-mono); + --font-serif: var(--cm-font-serif); +} diff --git a/apps/web/src/modules/billing/hooks/use-customer.ts b/apps/web/src/modules/billing/hooks/use-customer.ts index 6d6eccf..fdd7b6e 100644 --- a/apps/web/src/modules/billing/hooks/use-customer.ts +++ b/apps/web/src/modules/billing/hooks/use-customer.ts @@ -1,5 +1,17 @@ import { useQuery } from "@tanstack/react-query"; +import { authClient } from "~/lib/auth/client"; import { billing } from "~/modules/billing/lib/api"; -export const useCustomer = () => useQuery(billing.queries.customer.get); +/** + * Fetches the current user's billing customer. Gated on session + * presence so unauthenticated public pages (landing, /pricing) don't + * fire a 401 just to render plan cards. + */ +export const useCustomer = () => { + const { data: session } = authClient.useSession(); + return useQuery({ + ...billing.queries.customer.get, + enabled: !!session?.user, + }); +}; diff --git a/apps/web/src/modules/common/layout/base.tsx b/apps/web/src/modules/common/layout/base.tsx index b8e95e2..ce12058 100644 --- a/apps/web/src/modules/common/layout/base.tsx +++ b/apps/web/src/modules/common/layout/base.tsx @@ -1,22 +1,7 @@ -import { Geist_Mono, Geist } from "next/font/google"; - import { cn } from "@turbostarter/ui"; import { appConfig } from "~/config/app"; -const sans = Geist({ - subsets: ["latin"], - display: "swap", - variable: "--font-sans", -}); - -const mono = Geist_Mono({ - subsets: ["latin"], - display: "swap", - variable: "--font-mono", - weight: ["300", "400", "500"], -}); - interface BaseLayoutProps { readonly locale: string; readonly children: React.ReactNode; @@ -24,7 +9,7 @@ interface BaseLayoutProps { export const BaseLayout = ({ children, locale }: BaseLayoutProps) => { return ( - +