Commit Graph

7 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
47104042fd
feat(join): /join/[token] page + signup/login invite redirects
PR 7 of the multi-user accounts series. Closes the invite loop —
PR 6 lets admins generate links, this PR is what the recipient
sees when they click one.

What lands
- `src/app/join/layout.tsx` — minimal centered dark shell. Sits
  outside both `(auth)` and `(dashboard)` route groups because
  the page is hybrid: it renders for anonymous AND signed-in
  visitors. Reusing either neighbouring layout would funnel one
  audience through the wrong middleware redirect.
- `src/app/join/[token]/page.tsx` — client component. Calls
  /api/invitations/[token]/peek + supabase.auth.getUser() in
  parallel on mount and routes through four UI states:

    peek          | auth         | render
    --------------|--------------|----------------------------------
    loading       | —            | spinner
    ok:false (any)| —            | friendly error + signup/signin CTAs
    ok:true       | signed out   | "Sign up & join" + "Sign in" links
    ok:true       | signed in    | "Accept invitation" button → redeem

  Redeem is intentionally manual — auto-accepting on mount would
  race with the signup → email-verification → return flow and
  hide what's being accepted from the visitor.

  AuthContext doesn't reach this route group (AuthProvider lives
  inside (dashboard)), so we call Supabase directly here the same
  way /login and /signup already do.

  After a successful accept, the page does a full reload to
  /dashboard (not router.push) so AuthProvider re-fetches the
  profile with the new account_id and account_role.

- `src/app/(auth)/signup/page.tsx`:
  - Reads `?invite=<token>` via useSearchParams.
  - Passes emailRedirectTo: `${origin}/join/<token>` to
    supabase.auth.signUp so the verification email returns the
    user to the redeem step.
  - Re-skins the card header for invite signups ("Create account
    & join", different icon).
  - Preserves the token through the "Sign in" link and the
    "Back to sign in" button on the success screen.
  - Default export now wraps the form in `<Suspense>` — Next 16
    refuses to prerender pages calling useSearchParams without
    it.

- `src/app/(auth)/login/page.tsx`:
  - Reads `?invite=<token>`.
  - On successful sign-in, routes to /join/<token> instead of
    /dashboard.
  - Re-skins the card header for invite logins.
  - Preserves the token through the "Create account" link.
  - Default export wrapped in `<Suspense>` for the same prerender
    reason.

- `src/middleware.ts`:
  - When an authenticated user hits /login?invite=<t> or
    /signup?invite=<t>, redirect to /join/<t> instead of the
    default /dashboard. Without this, a forwarded invite link
    landing on an already-signed-in browser would silently drop
    on /dashboard and the visitor would never see the accept
    prompt.
  - Both pre-existing redirect cases (no invite present, OR
    /forgot-password with an invite — which doesn't make sense)
    fall through to /dashboard as before. The search string is
    cleared on both paths so we don't leak the invite token
    into the dashboard URL bar.

Verification
- `npm run lint`      → 0 errors (same 19 pre-existing warnings)
- `npm run typecheck` → clean
- `npm test`          → 366/366 pass
- `npm run build`     → succeeds:
                          ƒ /join/[token]   — dynamic (peek + auth fetch)
                          ○ /login          — static, hydrates the form
                          ○ /signup         — static, hydrates the form

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 18:30:32 +03:00
Arnas Donauskas
658d070811
Initial scaffold: WhatsApp CRM with Next.js + Supabase
Complete WhatsApp CRM application built with Next.js 14 (App Router),
Supabase (PostgreSQL + Auth + Realtime), and shadcn/ui.

Features:
- Auth: Login, signup, forgot password with Supabase Auth
- Dashboard: Analytics overview with stats, recent activity
- Inbox: Real-time WhatsApp conversations with message threading
- Contacts: CRUD with tags, custom fields, notes, CSV import
- Broadcasts: 4-step wizard with audience selection, template sending
- Pipelines: Kanban board with deal management
- Settings: WhatsApp API config, templates, tags
- WhatsApp API: Webhook (verify + receive), send, broadcast, media proxy
- Database: 16 tables with RLS policies, indexes, realtime subscriptions
- Dark theme throughout with emerald accents

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:17:31 +03:00