Commit Graph

8 Commits

Author SHA1 Message Date
Arnas Donauskas
2fbeeee3ac
Revert "feat(i18n): re-attempt next-intl with hardened artifact tracing" 2026-05-31 21:05:34 +03:00
Arnas Donauskas
0fa06d8b14
feat(i18n): harden build artifact tracing for Hostinger deploy
Re-applies PR #182's next-intl scaffold with defensive measures aimed
at the most-likely-but-unconfirmed causes of the 503 that hit prod
after that PR merged. PR #183 reverted #182 to restore service; this
branch carries the same i18n payload plus the safeguards below.

Defensive changes vs PR #182:

1. **`outputFileTracingRoot: __dirname`** in next.config.ts.
   Builds were warning that Next.js inferred the workspace root as
   the parent GHWebhosting directory (because of a stray lockfile
   there). With the wrong root, file tracing for the production
   artifact can include the wrong files or skip ours. Pinning the
   root to the wacrm directory silences the warning and makes tracing
   deterministic across local / CI / Hostinger.

2. **`outputFileTracingIncludes: { "/**/*": ["./messages/**/*.json"] }`**.
   Belt-and-suspenders: explicitly force the i18n message dictionaries
   into the production artifact even if static-import tracing somehow
   misses them in Hostinger's build environment.

3. **Static locale import map in `src/i18n/request.ts`**.
   Replaces `import(\`../../messages/\${locale}.json\`)` with an
   explicit `{ en: () => import(...), es: ..., lt: ... }` map.
   Template-literal dynamic imports are well-supported by Turbopack's
   tracer but the static form leaves zero room for the tracer to
   skip a locale.

4. **Try/catch around the intl middleware call**.
   If `intlMiddleware(request)` ever throws at runtime, fall through
   to auth-only handling and log the error rather than 503-ing every
   page request behind a single bad call.

5. **Try/catch around `LOADERS[locale]()`** with English fallback.
   Same idea: a missing locale bundle at runtime returns English
   content instead of crashing the render path.

6. **`engines.node: ">=20.10.0"`** (was `>=20.0.0`). Next 16.2's
   actual minimum. Tells Hostinger's Node.js app picker not to land
   us on a too-old subversion.

Verified locally:
- npm run lint → 0 errors (same 18 pre-existing warnings)
- npm run typecheck → clean
- npm run test → 366 passing
- npm run build → 62 prerendered pages across en/es/lt; workspace
  root warning gone; messages bundled into `.next/server/chunks/ssr/
  messages_{en,es,lt}_json_[json]_cjs_*.js`
- npm start probes:
    /              → 307 → /en
    /en/login      → 200
    /es/login      → 200
    /lt/login      → 200
    /en/dashboard  → 307 → /en/login (signed out)
    /api/whatsapp/templates → 401

If 503 reappears in prod after this lands, the local repro is clean,
so the next step is hPanel build/runtime logs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 21:01:13 +03:00
Arnas Donauskas
8124640d80
Revert "Merge pull request #183 from ArnasDon/revert/i18n-pr-182"
This reverts commit 872f751c07, reversing
changes made to 5e8076cee9.
2026-05-31 20:56:54 +03:00
Arnas Donauskas
a30f04b5a4
Revert "Merge pull request #182 from ArnasDon/feat/i18n-next-intl"
This reverts commit 5e8076cee9, reversing
changes made to 962e4fb27d.
2026-05-31 20:42:14 +03:00
Arnas Donauskas
8a8758a487
feat(i18n): scaffold next-intl with [locale] routing + extract auth/layout
Introduces an internationalization layer so wacrm can ship in multiple
languages — initially en (canonical), es, lt (untranslated mirrors of
en, ready for translators).

Foundation
- next-intl ^4.13 (Next 16 + React 19 compatible)
- src/i18n/{routing,navigation,request}.ts — locale config, locale-aware
  Link/router/redirect, server-side message loader
- next.config.ts wired with `createNextIntlPlugin`
- src/app/* moved under src/app/[locale]/ (excluding /api and globals.css)
- [locale]/layout.tsx is now the root layout: <html lang={locale}>,
  hasLocale guard, setRequestLocale, NextIntlClientProvider,
  generateStaticParams for build-time prerendering of all locales
- middleware.ts composes next-intl's createMiddleware with the existing
  Supabase auth check: intl middleware runs first to handle locale
  prefix; auth redirects retain the active locale (e.g. signed-out user
  on /es/dashboard → /es/login); intl cookies (NEXT_LOCALE) are carried
  onto auth-issued responses so the sticky preference survives

Strings extracted (~80 keys across en/es/lt.json)
- common, nav (sidebar + header), roles (Owner/Admin/Agent/Viewer chips)
- auth.login, auth.signup, auth.forgotPassword (all forms + success states)
- join (all four UI states: loading, fail, accept, sign-up CTA + conflict modal)
- localeSwitcher

UI
- New `<LocaleSwitcher />` in the header — globe icon + locale dropdown
  using next-intl's useLocale + locale-aware useRouter

Out of scope for this PR (intentional — keeps diff reviewable)
- Dashboard feature pages (dashboard, inbox, contacts, pipelines,
  broadcasts, automations, flows, settings) and their components/
  namespaces still have hardcoded English; they continue to render
  fine because next/link redirects through the locale middleware
- Shared UI a11y strings (dialog close, etc.)
- Translating es.json / lt.json — they currently mirror en.json so the
  app runs in any locale without crashes (English fallback)
- Renaming middleware.ts → proxy.ts per Next 16 deprecation

Verification
- npm run build → 62 static pages prerendered across en/es/lt
- npm run typecheck → clean
- npm run test → 366 passing
- npm run lint → 0 errors (18 pre-existing warnings untouched)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 20:17:49 +03:00
Arnas Donauskas
f199b7a7ed
security: baseline response headers (CSP report-only + OWASP set)
Adds a catch-all rule in next.config.ts that merges a set of baseline
security headers into every response — served alongside the existing
Cache-Control rules, not instead of them.

Enforced today:
  - Strict-Transport-Security: 2yr, includeSubDomains, preload-eligible.
    No-op on http://localhost.
  - X-Content-Type-Options: nosniff
  - X-Frame-Options: DENY  (same defense as frame-ancestors below, but
    older browsers only read this one)
  - Referrer-Policy: strict-origin-when-cross-origin
  - Permissions-Policy: deny camera/mic/geolocation/payment/usb. We
    don't use any of those APIs; closing them off prevents a supply-
    chain compromise or a forgotten plugin from silently enabling them.

Report-only:
  - Content-Security-Policy-Report-Only with a pragmatic allowlist:
      default-src 'self'
      script-src  'self' 'unsafe-inline' 'unsafe-eval'
      style-src   'self' 'unsafe-inline'
      img-src     'self' data: blob: https:
      font-src    'self' data:
      connect-src 'self' https://*.supabase.co wss://*.supabase.co
      frame-ancestors 'none'
      base-uri    'self'
      form-action 'self'
    'unsafe-inline' / 'unsafe-eval' are there because Next's inline
    hydration script + dev-mode require them; nonce-based CSP is a
    later project. Violations appear in the browser console but
    nothing is blocked until we flip the header name to
    `Content-Security-Policy`.

Verified against the running dev server:
  - /, /docs, /login, /api/whatsapp/webhook, /_next/static/* all
    return the six headers.
  - No CSP violations on / or /docs in the browser console.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:24:41 +03:00
Arnas Donauskas
56c76af739
fix(deploy): Cache-Control headers so CDN stops serving stale HTML
Root cause: Hostinger's CDN was applying s-maxage=31536000 (1 year)
to prerendered HTML pages by default. Each deploy produced fresh
Turbopack chunk hashes, but the edge kept serving year-old HTML that
referenced chunk filenames the new build no longer had on disk —
resulting in HTML 200 + every /_next/static/*.{js,css} 404, page
rendered unstyled. Private/incognito didn't help because the cache
is at the edge, not the browser.

Fix: explicit Cache-Control via next.config.ts headers():
- /_next/static/*  — public, max-age=31536000, immutable
  Content-hashed filenames; safe forever.
- /api/*           — no-store
  Per-user, must never be shared at the edge.
- everything else  — public, max-age=0, s-maxage=300,
                     stale-while-revalidate=86400
  Edge serves instantly from cache for 5 min, then keeps serving
  cached content while refreshing in the background for up to 24h.
  Chunk-hash drift after a deploy self-heals within ~5 min.

Verification after deploy:
  curl -I https://<host>/login | grep -i cache-control
  Expect: public, max-age=0, s-maxage=300, stale-while-revalidate=86400
  (If Hostinger overwrites this, fall back to configuring
  purge-on-deploy at the platform layer.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:34:46 +03:00
Arnas Donauskas
c70bfe0139
Initial commit from Create Next App 2026-04-16 17:52:39 +03:00