Commit Graph

14 Commits

Author SHA1 Message Date
Arnas Donauskas
8f0f8afae3
fix: use getSession() in useAuth hook (fixes 10s timeout)
PR #5 switched client components to getSession() but missed
src/hooks/use-auth.ts. The auth hook was still calling
supabase.auth.getUser() which makes a network round-trip to
Supabase, and on some deployments the request was hanging long
enough to trigger the 10-second safety timeout:

  [browser] [useAuth] getUser() timed out after 10s, clearing loading state

This caused the dashboard to sit on the loading spinner for the
full 10 seconds on every page load.

Changes:
- Replaced getUser() with getSession() in init() — reads from
  localStorage synchronously (no network).
- Reduced safety timer from 10s to 3s. getSession() shouldn't take
  more than a few hundred ms; 3s is a reasonable ceiling.

Security note (same reasoning as PR #5):
- The middleware server-side STILL uses getUser() to verify the JWT
  against Supabase on every request, so unauthenticated users can't
  reach protected routes.
- RLS on the database is the actual authorization boundary.
- getSession() on the client is only used to decide what UI to render.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:10:49 +03:00
Arnas Donauskas
aaaddf0d5a
Merge pull request #5 from ArnasDon/perf/use-get-session-client-side
perf: use getSession() instead of getUser() in client components
2026-04-16 20:06:11 +03:00
Arnas Donauskas
2e7d7a6282
perf: use getSession() instead of getUser() in client components
Major performance improvement. Every client component that needed the
current user was calling supabase.auth.getUser(), which makes a
network round-trip to Supabase to validate the JWT. On a page that
fetches data conditional on the user, the flow was:

  Page mount
    -> getUser() [network ~150-400ms]
    -> query data [network ~150-400ms]
    -> render

That's 2 serial round-trips before the page renders.

supabase.auth.getSession() reads the session from localStorage with
NO network call. Using it in client code cuts per-page load by one
full round-trip (~150-400ms depending on region latency).

Replaced in 10 client files:
- src/app/(dashboard)/inbox/page.tsx
- src/app/(dashboard)/pipelines/page.tsx
- src/components/contacts/contact-detail-view.tsx
- src/components/contacts/contact-form.tsx
- src/components/contacts/import-modal.tsx
- src/components/inbox/contact-sidebar.tsx
- src/components/pipelines/deal-form.tsx
- src/components/pipelines/pipeline-settings.tsx
- src/components/settings/tag-manager.tsx
- src/components/settings/template-manager.tsx
- src/components/settings/whatsapp-config.tsx

NOT changed (correctly continue using getUser):
- src/middleware.ts (server-side, needs JWT validation)
- src/app/api/whatsapp/*/route.ts (server-side, security-critical)
- src/hooks/use-auth.ts (PR #4 handles this file)

Security note: getSession() trusts the client-stored session. For
API routes and middleware that make authorization decisions, we still
use getUser() which verifies with Supabase. Client components only
use the session's user ID to filter RLS-protected queries — the RLS
on the database is the actual authorization boundary, so a forged
user ID in the browser wouldn't bypass server-side policies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:05:00 +03:00
Arnas Donauskas
928a67e12b
Merge pull request #4 from ArnasDon/fix/auth-hook-infinite-loading
Fix: dashboard stuck on infinite 'Loading...' spinner
2026-04-16 19:53:17 +03:00
Arnas Donauskas
7d7deb5e0b
fix: dashboard stuck on infinite 'Loading...' spinner
Three bugs in src/hooks/use-auth.ts that caused the dashboard layout's
auth loading state to never resolve:

1. fetchProfile queried profiles.id = userId, but profiles.id is the
   profile's own UUID. The correct column is profiles.user_id (FK to
   auth.users.id). This meant no profile was ever found and — combined
   with .single() throwing on 0 rows — could propagate errors.

2. No try/catch/finally around supabase.auth.getUser(). If getUser()
   threw (e.g. during auth lock contention), setLoading(false) never
   ran and the spinner showed forever with no visible error.

3. useEffect had supabase and fetchProfile in its deps. Since
   createClient() previously returned a new instance each call,
   both deps changed every render and the effect re-ran forever,
   subscribing to onAuthStateChange repeatedly and racing getUser()
   calls against the auth lock.

Changes:
- fetchProfile now uses .eq("user_id", userId) and .maybeSingle() so
  a missing profile returns { data: null } instead of throwing.
- init() wrapped in try/catch/finally — setLoading(false) ALWAYS runs.
- 10-second safety timer clears the loading state if anything hangs.
- useEffect runs once on mount (createClient is a singleton; deps
  intentionally empty).
- Logs Supabase error fields explicitly (message/details/hint/code)
  instead of the raw object which serializes as {}.
- AuthSessionMissingError (normal for logged-out users) is suppressed
  from error logs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:52:04 +03:00
Arnas Donauskas
582af0fbca
Merge pull request #3 from ArnasDon/fix/idempotent-migration
Fix: make Supabase migration idempotent
2026-04-16 19:48:49 +03:00
Arnas Donauskas
aee1621e44
fix: make migration idempotent
The Supabase GitHub integration re-runs migrations on every push.
The original 001_initial_schema.sql used plain CREATE TABLE without
IF NOT EXISTS, so it failed with "relation 'profiles' already exists"
after the first successful run.

Changes:
- CREATE TABLE -> CREATE TABLE IF NOT EXISTS (all 16 tables)
- CREATE INDEX -> CREATE INDEX IF NOT EXISTS
- DROP POLICY IF EXISTS before each CREATE POLICY
  (Postgres has no CREATE POLICY IF NOT EXISTS)
- DROP TRIGGER IF EXISTS before each CREATE TRIGGER
- Wrapped ALTER PUBLICATION in DO block that checks pg_publication_tables
- handle_new_user() trigger now uses SECURITY DEFINER, SET search_path,
  EXCEPTION handler, and owner=postgres to properly bypass RLS on the
  profiles INSERT during signup (fixes "Database error saving new user")

The migration is now safe to re-run any number of times.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:46:41 +03:00
Arnas Donauskas
b9647822ff
Merge pull request #2 from ArnasDon/fix/supabase-client-singleton
Fix auth lock contention and empty error logs (runtime errors)
2026-04-16 19:40:46 +03:00
Arnas Donauskas
31e1d83447
Merge pull request #1 from ArnasDon/fix/routing-remove-dashboard-prefix
Fix 404 errors: remove /dashboard prefix from nav links
2026-04-16 19:40:32 +03:00
Arnas Donauskas
4de6b7719e
fix: make Supabase browser client a singleton
Fixes two runtime errors:

1. "Lock 'lock:sb-xxx-auth-token' was released because another request
   stole it" — caused by multiple Supabase client instances competing
   for the same auth storage lock.

2. "Failed to fetch conversations: {}" — related to #1. When the auth
   lock is stolen mid-request, the session becomes temporarily
   unavailable and queries fail.

The codebase had 65 call sites invoking createClient() — each creating
a new browser client with its own auth listener. This change makes
createClient() cache and return a single shared instance.

Also improved error logging in conversation-list.tsx to show Supabase
error fields (message/details/hint/code) instead of an empty {}.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:39:24 +03:00
Arnas Donauskas
a4c966334a
fix: remove /dashboard prefix from nav links and redirects
Next.js route groups like (dashboard) don't appear in URLs, so the
sidebar, header, and dashboard page were linking to /dashboard/inbox,
/dashboard/contacts, etc. which don't exist and returned 404.

Fixed paths in:
- src/components/layout/sidebar.tsx (nav hrefs)
- src/components/layout/header.tsx (page title map)
- src/app/(dashboard)/dashboard/page.tsx (router.push calls)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:29:34 +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
Arnas Donauskas
de3deb32da
feat: initial commit 2026-04-16 17:53:27 +03:00
Arnas Donauskas
c70bfe0139
Initial commit from Create Next App 2026-04-16 17:52:39 +03:00