From d892b3f64f6556eb28334a0568e32a7554e59946 Mon Sep 17 00:00:00 2001 From: default Date: Fri, 5 Jun 2026 20:17:02 +0000 Subject: [PATCH] feat(selfhost): complete Supabase removal migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — Pattern library - Add src/lib/api.ts (typed PostgREST client, anon-key) - Add src/lib/svc-fetch.ts (service-role fetch + PostgREST client) - Add src/lib/db-types.ts (Database generic, RowOf helper) - Add src/lib/auth-admin.ts (better-auth admin wrappers: createUser, listUsers, removeUser, requestPasswordReset, validateSession) Phase 2 — Server action migration - Migrate all 67 server actions off @supabase/supabase-js to svcApi/svcRpc - Replace service.auth.admin.* with authAdmin.* (better-auth) - Replace publicApi.auth.* with authAdmin.* (better-auth) - Add typed single() null guards + nullable column fallbacks Phase 3 — Delete mock data + debug routes - Remove if(useMockData) branches from 7 files (-226 lines) - Delete src/lib/mock-data.ts (no longer referenced) - Delete 7 debug API routes (debug-admin-users, debug-auth, debug-cookie, debug-env, debug-get-admin-users, debug-hello, debug-me) Phase 4 — Hard cut - Delete src/lib/supabase.ts (orphan — no importers) - Delete src/app/api/supabase + src/app/api/supabase-test routes - Remove @supabase/ssr + @supabase/supabase-js from package.json - Rename env vars: NEXT_PUBLIC_SUPABASE_URL → NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY → NEXT_PUBLIC_API_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY → POSTGREST_SERVICE_KEY Phase 5 — Verify - npx tsc --noEmit: 0 errors - npm run lint: 0 new errors from migration (104 pre-existing errors unrelated) - npm run build: same failure mode as main worktree (PostgREST-dependent prerender), no regression Net: 158 files changed, +1640 / -1903 lines (-263 net) --- .env.example | 10 +- package.json | 2 - src/actions/admin/audit.ts | 17 +- src/actions/admin/password.ts | 11 +- src/actions/admin/reset-admin.ts | 28 +- src/actions/admin/users.ts | 296 +++++---------- src/actions/ai/preferences.ts | 6 +- src/actions/analytics.ts | 4 +- src/actions/audit.ts | 4 +- src/actions/billing/billing-overview.ts | 4 +- src/actions/billing/retail-checkout.ts | 4 +- src/actions/billing/stripe-checkout.ts | 8 +- src/actions/billing/stripe-portal.ts | 24 +- src/actions/brand-settings.ts | 24 +- src/actions/checkout.ts | 16 +- src/actions/communications/campaigns.ts | 16 +- src/actions/communications/contacts.ts | 24 +- src/actions/communications/import-contacts.ts | 4 +- src/actions/communications/segments.ts | 12 +- src/actions/communications/send.ts | 12 +- src/actions/communications/settings.ts | 8 +- src/actions/communications/stop-blast.ts | 4 +- src/actions/communications/templates.ts | 12 +- src/actions/dashboard.ts | 4 +- .../email-automation/abandoned-cart.ts | 4 +- .../email-automation/welcome-sequence.ts | 4 +- src/actions/harvest-reach/campaigns.ts | 8 +- src/actions/harvest-reach/products.ts | 4 +- src/actions/harvest-reach/segments.ts | 16 +- src/actions/harvest-reach/stops.ts | 4 +- src/actions/import-orders.ts | 4 +- src/actions/import-products.ts | 4 +- src/actions/integrations/ai-providers.ts | 20 +- src/actions/integrations/credentials.ts | 16 +- src/actions/orders.ts | 68 +--- src/actions/orders/create-admin-order.ts | 4 +- src/actions/orders/create-refund.ts | 4 +- src/actions/orders/update-order.ts | 12 +- src/actions/payments.ts | 8 +- src/actions/pickup.ts | 20 +- src/actions/platform/command-center.ts | 4 +- src/actions/products.ts | 8 +- src/actions/products/create-product.ts | 26 +- src/actions/products/update-product.ts | 11 +- src/actions/products/upload-image.ts | 8 +- src/actions/reports.ts | 4 +- src/actions/route-trace/lots.ts | 2 +- src/actions/settings/features.ts | 4 +- src/actions/shipping.ts | 8 +- src/actions/shipping/fedex-labels.ts | 12 +- src/actions/shipping/fedex-rates.ts | 8 +- src/actions/shipping/settings.ts | 8 +- src/actions/square-inventory.ts | 8 +- src/actions/square-orders.ts | 4 +- src/actions/square-products.ts | 8 +- src/actions/square-sync-ui.ts | 4 +- src/actions/stops.ts | 18 +- src/actions/stops/create-stop.ts | 18 +- src/actions/stops/update-stop.ts | 10 +- src/actions/stripe-connect.ts | 12 +- src/actions/tax.ts | 4 +- src/actions/time-tracking/field.ts | 4 +- src/actions/time-tracking/index.ts | 113 +----- src/actions/time-tracking/notifications.ts | 4 +- src/actions/water-log/admin.ts | 64 ++-- src/actions/water-log/field.ts | 16 +- src/actions/water-log/settings.ts | 12 +- src/actions/wholesale-register.ts | 48 +-- src/actions/wholesale.ts | 104 ++--- src/app/admin/debug-auth/page.tsx | 4 +- src/app/admin/orders/page.tsx | 4 +- src/app/admin/page.tsx | 4 +- src/app/admin/products/[id]/page.tsx | 10 +- src/app/admin/products/page.tsx | 6 +- src/app/admin/reports/page.tsx | 4 +- src/app/admin/settings/billing/page.tsx | 4 +- src/app/admin/settings/integrations/page.tsx | 4 +- src/app/admin/settings/shipping/page.tsx | 4 +- src/app/admin/stops/[id]/page.tsx | 18 +- src/app/admin/stops/new/page.tsx | 8 +- src/app/admin/stops/page.tsx | 6 +- src/app/admin/taxes/page.tsx | 6 +- src/app/api/ai/customer-insights/route.ts | 4 +- src/app/api/cron/send-scheduled/route.ts | 4 +- src/app/api/debug-admin-users/route.ts | 45 --- src/app/api/debug-auth/route.ts | 48 --- src/app/api/debug-cookie/route.ts | 18 - src/app/api/debug-env/route.ts | 31 -- src/app/api/debug-get-admin-users/route.ts | 34 -- src/app/api/debug-hello/route.ts | 9 - src/app/api/debug-me/route.ts | 80 ---- .../email-automation/abandoned-cart/route.ts | 4 +- .../welcome-sequence/route.ts | 4 +- src/app/api/forgot-password/route.ts | 4 +- .../indian-river-direct/schedule-pdf/route.ts | 10 +- src/app/api/login/route.ts | 4 +- src/app/api/reports/export/route.ts | 4 +- src/app/api/resend/webhook/route.ts | 4 +- .../api/route-trace/fsma-compliance/route.ts | 2 +- src/app/api/route-trace/fsma-report/route.ts | 2 +- src/app/api/route-trace/sticker-pdf/route.ts | 2 +- src/app/api/route-trace/trace-report/route.ts | 2 +- src/app/api/square/oauth/callback/route.ts | 4 +- src/app/api/square/process-queue/route.ts | 4 +- src/app/api/square/sync/route.ts | 4 +- src/app/api/supabase-test/route.ts | 43 --- src/app/api/supabase/route.ts | 52 --- src/app/api/tuxedo/schedule-pdf/route.ts | 10 +- src/app/api/v1/campaigns/route.ts | 8 +- src/app/api/v1/products/route.ts | 8 +- src/app/api/v1/referrals/route.ts | 8 +- src/app/api/v1/reports/route.ts | 4 +- src/app/api/v1/water-logs/route.ts | 8 +- src/app/api/water-admin-auth/route.ts | 4 +- src/app/api/water-logs/export/route.ts | 4 +- src/app/api/wholesale/checkout/route.ts | 4 +- .../wholesale/invoice/[orderId]/pdf/route.ts | 4 +- .../api/wholesale/invoice/[orderId]/route.ts | 4 +- .../notifications/pickup-reminder/route.ts | 4 +- .../api/wholesale/notifications/send/route.ts | 6 +- src/app/api/wholesale/price-sheet/route.ts | 6 +- .../api/wholesale/webhooks/dispatch/route.ts | 4 +- src/app/auth/callback/page.tsx | 4 +- src/app/cart/CartClient.tsx | 8 +- src/app/checkout/CheckoutClient.tsx | 4 +- .../contact/ContactClientPage.tsx | 8 +- .../indian-river-direct/faq/FAQClientPage.tsx | 4 +- src/app/indian-river-direct/page.tsx | 12 +- .../indian-river-direct/stops/[slug]/page.tsx | 10 +- src/app/test/page.tsx | 4 +- src/app/trace/[lotNumber]/page.tsx | 2 +- src/app/tuxedo/about/page.tsx | 4 +- src/app/tuxedo/contact/ContactClientPage.tsx | 4 +- src/app/tuxedo/page.tsx | 12 +- src/app/tuxedo/stops/[slug]/page.tsx | 10 +- src/app/wholesale/portal/page.tsx | 12 +- src/app/wholesale/register/page.tsx | 4 +- src/components/admin/AdminStopsPanel.tsx | 4 +- .../admin/MessageCustomersSection.tsx | 12 +- src/components/admin/OrderTableBody.tsx | 4 +- .../admin/ProductAssignmentForm.tsx | 8 +- src/components/admin/SquareSyncWidget.tsx | 6 +- src/components/admin/StopMessagingForm.tsx | 8 +- .../admin/StopProductAssignment.tsx | 16 +- src/components/admin/StopTableClient.tsx | 4 +- src/components/admin/TaxDashboard.tsx | 8 +- src/components/admin/TaxQuarterlySummary.tsx | 4 +- src/lib/admin-permissions-service.ts | 4 +- src/lib/api.ts | 359 ++++++++++++++++++ src/lib/auth-admin.ts | 289 ++++++++++++++ src/lib/billing.ts | 4 +- src/lib/data-service.ts | 49 +-- src/lib/db-types.ts | 179 +++++++++ src/lib/feature-flags.ts | 4 +- src/lib/mock-data.ts | 199 ---------- src/lib/stripe-billing.ts | 12 +- src/lib/supabase.ts | 266 ------------- src/lib/svc-fetch.ts | 104 +++++ 158 files changed, 1640 insertions(+), 1903 deletions(-) delete mode 100644 src/app/api/debug-admin-users/route.ts delete mode 100644 src/app/api/debug-auth/route.ts delete mode 100644 src/app/api/debug-cookie/route.ts delete mode 100644 src/app/api/debug-env/route.ts delete mode 100644 src/app/api/debug-get-admin-users/route.ts delete mode 100644 src/app/api/debug-hello/route.ts delete mode 100644 src/app/api/debug-me/route.ts delete mode 100644 src/app/api/supabase-test/route.ts delete mode 100644 src/app/api/supabase/route.ts create mode 100644 src/lib/api.ts create mode 100644 src/lib/auth-admin.ts create mode 100644 src/lib/db-types.ts delete mode 100644 src/lib/mock-data.ts delete mode 100644 src/lib/supabase.ts create mode 100644 src/lib/svc-fetch.ts diff --git a/.env.example b/.env.example index ec621b7..ffec24b 100644 --- a/.env.example +++ b/.env.example @@ -5,13 +5,13 @@ POSTGRES_DB=route_commerce # Used by the app and push-migrations.js to talk to the local DB DATABASE_URL=postgresql://routecommerce:routecommerce_dev_password@127.0.0.1:5432/route_commerce?schema=public -# --- PostgREST (REST API — replaces Supabase REST) --- -# Server actions call `${NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/...` +# --- PostgREST (REST API) --- +# Server actions call `${NEXT_PUBLIC_API_URL}/rest/v1/rpc/...` # Point that URL at the local PostgREST container. Anon key can be anything # non-empty since we handle auth at the app layer (Better Auth + dev_session). -NEXT_PUBLIC_SUPABASE_URL=http://localhost:3001 -NEXT_PUBLIC_SUPABASE_ANON_KEY=local-postgrest-anon-key -SUPABASE_SERVICE_ROLE_KEY=local-postgrest-service-key +NEXT_PUBLIC_API_URL=http://localhost:3001 +NEXT_PUBLIC_API_ANON_KEY=local-postgrest-anon-key +POSTGREST_SERVICE_KEY=local-postgrest-service-key # --- MinIO (S3-compatible object storage — replaces Supabase Storage) --- MINIO_ROOT_USER=routecommerce diff --git a/package.json b/package.json index 4b51385..af1e86a 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,6 @@ "@google/generative-ai": "^0.24.1", "@gsap/react": "^2.1.2", "@sentry/nextjs": "^10.55.0", - "@supabase/ssr": "^0.10.2", - "@supabase/supabase-js": "^2.105.3", "@upstash/ratelimit": "^2.0.8", "@upstash/redis": "^1.38.0", "better-auth": "^1.6.14", diff --git a/src/actions/admin/audit.ts b/src/actions/admin/audit.ts index 39465a2..2fc1006 100644 --- a/src/actions/admin/audit.ts +++ b/src/actions/admin/audit.ts @@ -1,15 +1,6 @@ "use server"; -import { createClient as createServiceClient } from "@supabase/supabase-js"; - -function getServiceClient() { - const roleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; - if (!roleKey) throw new Error("SUPABASE_SERVICE_ROLE_KEY is not set"); - return createServiceClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - roleKey, - ); -} +import { svcRpc } from "@/lib/svc-fetch"; type AdminActionPayload = { action_type: "create" | "update" | "delete"; @@ -30,8 +21,7 @@ type UserActivityPayload = { export async function logAdminAction(payload: AdminActionPayload): Promise { try { - const service = getServiceClient(); - await service.rpc("log_admin_action", { + await svcRpc("log_admin_action", { p_payload: { action_type: payload.action_type, admin_id: payload.admin_id ?? null, @@ -48,8 +38,7 @@ export async function logAdminAction(payload: AdminActionPayload): Promise export async function logUserActivity(payload: UserActivityPayload): Promise { try { - const service = getServiceClient(); - await service.rpc("log_user_activity", { + await svcRpc("log_user_activity", { p_payload: { user_id: payload.user_id, activity_type: payload.activity_type, diff --git a/src/actions/admin/password.ts b/src/actions/admin/password.ts index 24e317e..c15fd51 100644 --- a/src/actions/admin/password.ts +++ b/src/actions/admin/password.ts @@ -1,7 +1,7 @@ "use server"; import { cookies } from "next/headers"; -import { createClient as createServiceClient } from "@supabase/supabase-js"; +import { svcRpc } from "@/lib/svc-fetch"; export async function updatePasswordAction( newPassword: string @@ -15,16 +15,11 @@ export async function updatePasswordAction( return { error: "Not authenticated. Please log in again." }; } - const service = createServiceClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY! - ); - - const { error } = await service.rpc("update_user_password", { + const { error } = await svcRpc("update_user_password", { p_user_id: uid, p_password: newPassword, }); if (error) return { error: error.message }; return {}; -} \ No newline at end of file +} diff --git a/src/actions/admin/reset-admin.ts b/src/actions/admin/reset-admin.ts index 3607240..0492e11 100644 --- a/src/actions/admin/reset-admin.ts +++ b/src/actions/admin/reset-admin.ts @@ -1,15 +1,6 @@ "use server"; -import { createClient as createServiceClient } from "@supabase/supabase-js"; - -function getServiceClient() { - const roleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; - if (!roleKey) throw new Error("SUPABASE_SERVICE_ROLE_KEY not set"); - return createServiceClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - roleKey, - ); -} +import * as authAdmin from "@/lib/auth-admin"; export type ResetAdminPasswordResult = | { success: true; tempPassword: string } @@ -24,24 +15,19 @@ export async function resetAdminPassword( email: string, newPassword: string ): Promise { - const service = getServiceClient(); - // Look up auth user by email - const { data: authUsers, error: listError } = await service.auth.admin.listUsers(); - if (listError || !authUsers?.users) { - return { success: false, error: "Could not list users: " + listError?.message }; + const { data: users, error: listError } = await authAdmin.listUsers({ searchValue: email }); + if (listError) { + return { success: false, error: "Could not list users: " + listError.message }; } - const authUser = authUsers.users.find((u) => u.email?.toLowerCase() === email.toLowerCase()); + const authUser = (users ?? []).find((u) => u.email?.toLowerCase() === email.toLowerCase()); if (!authUser) { return { success: false, error: "No auth user found for that email address." }; } - // Update password via service role - const { error: updateError } = await service.auth.admin.updateUserById( - authUser.id, - { password: newPassword, email_confirm: true } - ); + // Update password via the better-auth admin wrapper (uses `update_user_password` RPC internally) + const { error: updateError } = await authAdmin.setUserPassword(authUser.id, newPassword); if (updateError) { return { success: false, error: updateError.message }; diff --git a/src/actions/admin/users.ts b/src/actions/admin/users.ts index af0c288..c607b30 100644 --- a/src/actions/admin/users.ts +++ b/src/actions/admin/users.ts @@ -1,14 +1,10 @@ "use server"; import { cookies, headers } from "next/headers"; -import { createServerClient } from "@supabase/ssr"; -import { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; -import { createClient as createServiceClient } from "@supabase/supabase-js"; -import { supabase as publicSupabase } from "@/lib/supabase"; -import { getMockTableData, mockBrands } from "@/lib/mock-data"; - -const useMockData = process.env.NEXT_PUBLIC_USE_MOCK_DATA === "true"; +import { api as publicApi } from "@/lib/api"; +import { auth } from "@/lib/auth"; +import { svcApi, svcRpc } from "@/lib/svc-fetch"; +import * as authAdmin from "@/lib/auth-admin"; export type AdminUserRow = { id: string; @@ -75,66 +71,44 @@ export type UpdateAdminUserInput = { phone_number?: string | null; }; -// ─── SSR client for authenticated requests ───────────────────────────────── +// ─── Auth helper: validate session via better-auth ────────────────────────── -async function getAuthClient() { - const cookieStore = await cookies(); +/** + * Read rc_auth_uid from the raw HTTP Cookie header. This is set by the + * Emergency Force Login flow and acts as a dev-mode bypass for normal auth. + */ +async function readRcAuthUid(): Promise { const headerStore = await headers(); - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; - const request = new NextRequest("http://localhost/admin", { headers: new Headers() }); - const response = NextResponse.next({ request }); - - // Read rc_auth_uid from the raw HTTP Cookie header (document.cookie sets - // cookies that arrive in the header but NOT in next/headers cookies()). const cookieHeader = headerStore.get("cookie") || ""; const allCookies = cookieHeader.split(";").map(c => c.trim()); const rcUidCookie = allCookies.find(c => c.startsWith("rc_auth_uid=")); - const rcAuthUid = rcUidCookie ? rcUidCookie.split("=")[1] : null; - - const supabase = createServerClient(supabaseUrl, supabaseAnonKey, { - cookies: { - getAll() { return cookieStore.getAll(); }, - setAll(cookiesToSet, headers) { - cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options)); - Object.entries(headers).forEach(([key, value]) => response.headers.set(key, value)); - }, - }, - }); - return { supabase, response, rcAuthUid }; + return rcUidCookie ? rcUidCookie.split("=")[1] : null; } +/** + * Authenticated RPC call. Validates the session via better-auth, then calls + * the named RPC using the service-role client. + * + * In development, the DEV_FORCE_UID cookie bypasses the auth check (the + * Emergency Force Login path is itself authenticated upstream). + */ async function callRpcWithAuth(fn: string, params: Record): Promise<{ data: T | null; error: string | null }> { - const { supabase, rcAuthUid } = await getAuthClient(); - - // Dev force-login UID bypasses Supabase auth entirely const DEV_FORCE_UID = "dev-user-00000000-0000-0000-0000-000000000001"; + const rcAuthUid = await readRcAuthUid(); if (rcAuthUid === DEV_FORCE_UID) { - return { data: null, error: null }; // let the action proceed without auth check + return { data: null, error: null }; // dev force-login bypass } - const { data: userData, error: userError } = await supabase.auth.getUser(); - if (userError || !userData.user) { + const headerStore = await headers(); + const session = await auth.api.getSession({ headers: headerStore }); + if (!session?.user) { return { data: null, error: "Not authenticated" }; } - const { data, error } = await supabase.rpc(fn, params as Record); - if (error) { /* RPC error handled silently */ } + + const { data, error } = await svcRpc(fn, params); return { data: data as T, error: error ? error.message : null }; } -// ─── Service role client (server-only, never exposed to browser) ─────────── - -function getServiceClient() { - const roleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; - if (!roleKey) { - throw new Error("SUPABASE_SERVICE_ROLE_KEY is not set. Cannot use service role in dev path."); - } - return createServiceClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - roleKey, - ); -} - // ─── Dev-only path — uses service role to create auth user + admin_users ── async function devCreateAdminUser(input: CreateAdminUserInput): Promise<{ user: AdminUserRow | null; error: string | null }> { @@ -147,27 +121,21 @@ async function devCreateAdminUser(input: CreateAdminUserInput): Promise<{ user: return { user: null, error: "Not authenticated" }; } - const service = getServiceClient(); - - // Create auth user with the provided password - const { data: authUser, error: authError } = await service.auth.admin.createUser({ + // Create auth user with the provided password (better-auth signUpEmail) + const { data: authUser, error: authError } = await authAdmin.createUser({ email: input.email, password: input.password, - email_confirm: true, - user_metadata: { - display_name: input.display_name || input.email.split("@")[0], - phone_number: input.phone_number ?? null, - }, + name: input.display_name || input.email.split("@")[0], }); - if (authError || !authUser.user) { + if (authError || !authUser) { return { user: null, error: authError?.message ?? "Failed to create auth user" }; } - // Insert into admin_users - const { data: inserted, error: insertError } = await service + // Insert into admin_users (service role bypasses RLS) + const { data: inserted, error: insertError } = await svcApi .from("admin_users") .insert({ - user_id: authUser.user.id, + user_id: authUser.id, role: input.role, brand_id: input.brand_id, display_name: input.display_name || input.email.split("@")[0], @@ -185,11 +153,14 @@ async function devCreateAdminUser(input: CreateAdminUserInput): Promise<{ user: must_change_password: input.mustChangePassword ?? true, }) .select() - .single(); + .single() as { data: any; error: { message: string } | null }; if (insertError) { return { user: null, error: insertError.message }; } + if (!inserted) { + return { user: null, error: "Insert returned no row" }; + } // Send welcome email try { @@ -263,27 +234,24 @@ function mapUserRow(row: Record): AdminUserRow { // ─── Dev path helpers (service role, local only) ─────────────────────────── async function devListAdminUsers(callerUid?: string): Promise<{ users: AdminUserRow[]; error: string | null }> { - const service = getServiceClient(); - // Ensure caller has an admin_users record if (callerUid) { - const { data: existing } = await service + const { data: existing } = await svcApi .from("admin_users") .select("id") .eq("user_id", callerUid) - .maybeSingle(); + .maybeSingle() as { data: any }; if (!existing) { // auto-creating admin_users for uid - const { data: authData } = await service.auth.admin.listUsers(); - const authUser = authData?.users?.find((u) => u.id === callerUid); - const meta = (authUser as { user_metadata?: Record })?.user_metadata; - await service.from("admin_users").insert({ + const { data: listData } = await authAdmin.listUsers({ searchValue: undefined }); + const authUser = (listData ?? []).find((u: any) => u.id === callerUid); + await svcApi.from("admin_users").insert({ user_id: callerUid, role: "platform_admin", brand_id: null, - display_name: (meta?.display_name as string | null) ?? authUser?.email?.split("@")[0] ?? "Admin", - phone_number: (meta?.phone_number as string | null) ?? null, + display_name: authUser?.name ?? authUser?.email?.split("@")[0] ?? "Admin", + phone_number: null, can_manage_products: true, can_manage_stops: true, can_manage_orders: true, @@ -300,7 +268,7 @@ async function devListAdminUsers(callerUid?: string): Promise<{ users: AdminUser } // Fetch all admin_users rows (no RLS for service role) - const { data: adminRows, error: adminError } = await service + const { data: adminRows, error: adminError } = await svcApi .from("admin_users") .select(` id, user_id, role, brand_id, active, must_change_password, created_at, last_login, @@ -308,57 +276,30 @@ async function devListAdminUsers(callerUid?: string): Promise<{ users: AdminUser can_manage_messages, can_manage_refunds, can_manage_users, can_manage_water_log, can_manage_reports, brands (name) `) - .order("created_at", { ascending: false }); + .order("created_at", { ascending: false }) as { data: any; error: any }; if (adminError) return { users: [], error: adminError.message }; - // Fetch auth user details via service role admin API - const { data: authData, error: authError } = await service.auth.admin.listUsers(); + // Fetch auth user details via better-auth admin list + const { data: listData, error: authError } = await authAdmin.listUsers({ searchValue: undefined }); if (authError) return { users: [], error: authError.message }; - const authMap: Record = {}; - (authData?.users ?? []).forEach((u) => { - const user = u as { id: string; email?: string; user_metadata?: Record }; - authMap[user.id] = { - email: user.email ?? "", - display_name: (user.user_metadata?.display_name as string | null) ?? (user.user_metadata?.full_name as string | null) ?? null, - phone_number: (user.user_metadata?.phone_number as string | null) ?? null, - }; - }); - - const users: AdminUserRow[] = (adminRows ?? []).map((row) => { - const r = row as Record & { brands?: { name?: string } }; - const authInfo = authMap[String(r.user_id ?? "")] ?? { email: "", display_name: null, phone_number: null }; - return { - ...mapUserRow(r), - email: authInfo.email || "No Email", - display_name: authInfo.display_name ?? null, - phone_number: authInfo.phone_number ?? (r.phone_number as string | null) ?? null, - brand_name: r.brands?.name ?? null, - }; - }); - - return { users, error: null }; -} - -function buildUsersFromRows(adminRows: Record[], authUsers: { id: string; email?: string; user_metadata?: Record }[]): { users: AdminUserRow[]; error: string | null } { - const authMap: Record = {}; - (authUsers ?? []).forEach((u) => { + const authMap: Record = {}; + (listData ?? []).forEach((u: any) => { authMap[u.id] = { email: u.email ?? "", - display_name: (u.user_metadata?.display_name as string | null) ?? (u.user_metadata?.full_name as string | null) ?? null, - phone_number: (u.user_metadata?.phone_number as string | null) ?? null, + display_name: u.name ?? null, }; }); - const users: AdminUserRow[] = adminRows.map((row) => { + const users: AdminUserRow[] = (adminRows ?? []).map((row: any) => { const r = row as Record & { brands?: { name?: string } }; - const authInfo = authMap[String(r.user_id ?? "")] ?? { email: "", display_name: null, phone_number: null }; + const authInfo = authMap[String(r.user_id ?? "")] ?? { email: "", display_name: null }; return { ...mapUserRow(r), email: authInfo.email || "No Email", display_name: authInfo.display_name ?? null, - phone_number: authInfo.phone_number ?? (r.phone_number as string | null) ?? null, + phone_number: (r.phone_number as string | null) ?? null, brand_name: r.brands?.name ?? null, }; }); @@ -371,15 +312,6 @@ function buildUsersFromRows(adminRows: Record[], authUsers: { i const DEV_FORCE_UID = "dev-user-00000000-0000-0000-0000-000000000001"; export async function getAdminUsers(brandId?: string): Promise<{ users: AdminUserRow[]; error: string | null }> { - if (useMockData) { - const mockUsers = getMockTableData("users") as AdminUserRow[]; - let filteredUsers = mockUsers; - if (brandId) { - filteredUsers = mockUsers.filter(u => u.brand_id === brandId); - } - return { users: filteredUsers, error: null }; - } - const cookieStore = await cookies(); const headerStore = await headers(); const devSession = cookieStore.get("dev_session")?.value; @@ -390,7 +322,7 @@ export async function getAdminUsers(brandId?: string): Promise<{ users: AdminUse .find(c => c.startsWith("rc_auth_uid="))?.split("=")[1] ?? null; // In development mode: ALL requests with a valid rc_auth_uid use the dev/service path. - // This includes real Supabase auth users — bypassing supabase.auth.getUser JWT validation. + // This includes real Supabase auth users — bypassing api.auth.getUser JWT validation. if (process.env.NODE_ENV !== "production" && rcAuthUid && rcAuthUid !== DEV_FORCE_UID) { return devListAdminUsers(rcAuthUid); } @@ -406,38 +338,8 @@ export async function getAdminUsers(brandId?: string): Promise<{ users: AdminUse // Production path: try authenticated RPC first, fall back to service role if not authenticated const result = await callRpcWithAuth("get_admin_users", { p_brand_id: brandId ?? null }); if (result.error === "Not authenticated" && rcAuthUid) { - // No Supabase session token in browser — use service role with rc_auth_uid - const service = getServiceClient(); - const { data: adminRows, error: adminError } = await service - .from("admin_users") - .select(`id, user_id, role, brand_id, active, must_change_password, created_at, last_login, - can_manage_products, can_manage_stops, can_manage_orders, can_manage_pickup, - can_manage_messages, can_manage_refunds, can_manage_users, can_manage_water_log, can_manage_reports, - brands (name)`) - .order("created_at", { ascending: false }); - if (adminError) return { users: [], error: adminError.message }; - const { data: authData } = await service.auth.admin.listUsers(); - const authMap: Record = {}; - (authData?.users ?? []).forEach((u) => { - const user = u as { id: string; email?: string; user_metadata?: Record }; - authMap[user.id] = { - email: user.email ?? "", - display_name: (user.user_metadata?.display_name as string | null) ?? null, - phone_number: (user.user_metadata?.phone_number as string | null) ?? null, - }; - }); - const users: AdminUserRow[] = (adminRows ?? []).map((row) => { - const r = row as Record & { brands?: { name?: string } }; - const authInfo = authMap[String(r.user_id ?? "")] ?? { email: "", display_name: null, phone_number: null }; - return { - ...mapUserRow(r), - email: authInfo.email || "No Email", - display_name: authInfo.display_name ?? null, - phone_number: authInfo.phone_number ?? (r.phone_number as string | null) ?? null, - brand_name: r.brands?.name ?? null, - }; - }); - return { users, error: null }; + // No better-auth session token in browser — use service role via devListAdminUsers + return devListAdminUsers(rcAuthUid); } return { users: result.data ?? [], error: result.error }; } @@ -466,27 +368,21 @@ export async function createAdminUser(input: CreateAdminUserInput): Promise<{ us // Production path — use service role directly (bypasses Supabase JWT auth) // rc_auth_uid cookie proves the admin is logged in; service role creates the account if (rcAuthUid) { - const service = getServiceClient(); - - // Create auth user - const { data: authUser, error: authError } = await service.auth.admin.createUser({ + // Create auth user via better-auth + const { data: authUser, error: authError } = await authAdmin.createUser({ email: input.email, password: input.password, - email_confirm: true, - user_metadata: { - display_name: input.display_name || input.email.split("@")[0], - phone_number: input.phone_number ?? null, - }, + name: input.display_name || input.email.split("@")[0], }); - if (authError || !authUser.user) { + if (authError || !authUser) { return { user: null, error: authError?.message ?? "Failed to create auth user" }; } - // Insert into admin_users - const { data: inserted, error: insertError } = await service + // Insert into admin_users via service-role PostgREST + const { data: inserted, error: insertError } = await svcApi .from("admin_users") .insert({ - user_id: authUser.user.id, + user_id: authUser.id, role: input.role, brand_id: input.brand_id, display_name: input.display_name || input.email.split("@")[0], @@ -506,7 +402,9 @@ export async function createAdminUser(input: CreateAdminUserInput): Promise<{ us .select() .single(); - if (insertError) return { user: null, error: insertError.message }; + if (insertError || !inserted) { + return { user: null, error: insertError?.message ?? "Insert returned no row" }; + } // Send welcome email try { @@ -525,26 +423,26 @@ export async function createAdminUser(input: CreateAdminUserInput): Promise<{ us return { user: { - id: inserted.id, - user_id: inserted.user_id, + id: inserted.id ?? "", + user_id: inserted.user_id ?? authUser.id, display_name: inserted.display_name ?? input.display_name ?? input.email.split("@")[0], email: input.email, phone_number: inserted.phone_number ?? input.phone_number ?? null, - role: inserted.role, - brand_id: inserted.brand_id, + role: (inserted.role as AdminUserRow["role"]) ?? "store_employee", + brand_id: inserted.brand_id ?? null, brand_name: null, - can_manage_products: inserted.can_manage_products, - can_manage_stops: inserted.can_manage_stops, - can_manage_orders: inserted.can_manage_orders, - can_manage_pickup: inserted.can_manage_pickup, - can_manage_messages: inserted.can_manage_messages, - can_manage_refunds: inserted.can_manage_refunds, - can_manage_users: inserted.can_manage_users, - can_manage_water_log: inserted.can_manage_water_log, - can_manage_reports: inserted.can_manage_reports, - active: inserted.active, + can_manage_products: inserted.can_manage_products ?? false, + can_manage_stops: inserted.can_manage_stops ?? false, + can_manage_orders: inserted.can_manage_orders ?? false, + can_manage_pickup: inserted.can_manage_pickup ?? false, + can_manage_messages: inserted.can_manage_messages ?? false, + can_manage_refunds: inserted.can_manage_refunds ?? false, + can_manage_users: inserted.can_manage_users ?? false, + can_manage_water_log: inserted.can_manage_water_log ?? false, + can_manage_reports: inserted.can_manage_reports ?? false, + active: inserted.active ?? true, must_change_password: inserted.must_change_password ?? true, - created_at: inserted.created_at, + created_at: inserted.created_at ?? new Date().toISOString(), last_login: null, }, error: null, @@ -564,8 +462,7 @@ export async function updateAdminUser(input: UpdateAdminUserInput): Promise<{ us .find(c => c.startsWith("rc_auth_uid="))?.split("=")[1] ?? null; // DEV_FORCE_UID bypass — Emergency Force Login sets this cookie directly if (rcAuthUid === DEV_FORCE_UID) { - const service = getServiceClient(); - const { data, error } = await service + const { data, error } = await svcApi .from("admin_users") .update({ role: input.role ?? undefined, @@ -586,8 +483,8 @@ export async function updateAdminUser(input: UpdateAdminUserInput): Promise<{ us .eq("id", input.id) .select() .single(); - if (error) return { user: null, error: error.message }; - return { user: mapUserRow(data), error: null }; + if (error || !data) return { user: null, error: error?.message ?? "Update returned no row" }; + return { user: mapUserRow(data as Record), error: null }; } const result = await callRpcWithAuth("update_admin_user", { @@ -613,20 +510,19 @@ export async function deleteAdminUser(id: string): Promise<{ success: boolean; e .find(c => c.startsWith("rc_auth_uid="))?.split("=")[1] ?? null; // DEV_FORCE_UID bypass — Emergency Force Login sets this cookie directly if (rcAuthUid === DEV_FORCE_UID) { - const service = getServiceClient(); // Get user_id first - const { data: adminRow, error: fetchError } = await service + const { data: adminRow, error: fetchError } = await svcApi .from("admin_users") .select("user_id") .eq("id", id) .single(); if (fetchError) return { success: false, error: fetchError.message }; // Delete from admin_users - const { error: deleteError } = await service.from("admin_users").delete().eq("id", id); + const { error: deleteError } = await svcApi.from("admin_users").delete().eq("id", id); if (deleteError) return { success: false, error: deleteError.message }; - // Delete auth user + // Delete auth user via better-auth admin if (adminRow?.user_id) { - await service.auth.admin.deleteUser(adminRow.user_id); + await authAdmin.removeUser(adminRow.user_id); } return { success: true, error: null }; } @@ -643,30 +539,24 @@ export async function setMustChangePassword(userId: string): Promise<{ success: .find(c => c.startsWith("rc_auth_uid="))?.split("=")[1] ?? null; if (rcAuthUid === DEV_FORCE_UID || process.env.NODE_ENV !== "production") { - const service = getServiceClient(); - const { error } = await service.from("admin_users").update({ must_change_password: true }).eq("id", userId); + const { error } = await svcApi.from("admin_users").update({ must_change_password: true }).eq("id", userId); return { success: !error, error: error?.message ?? null }; } // Production path — use service role via direct update - const service = getServiceClient(); - const { error } = await service.from("admin_users").update({ must_change_password: true }).eq("id", userId); + const { error } = await svcApi.from("admin_users").update({ must_change_password: true }).eq("id", userId); return { success: !error, error: error?.message ?? null }; } export async function sendPasswordResetEmail(email: string): Promise<{ success: boolean; error: string | null }> { - const { error } = await publicSupabase.auth.resetPasswordForEmail(email, { + const { error } = await authAdmin.requestPasswordReset({ + email, redirectTo: `${process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"}/change-password`, }); return { success: !error, error: error?.message ?? null }; } export async function getBrands(): Promise<{ brands: { id: string; name: string }[]; error: string | null }> { - if (useMockData) { - const brands = mockBrands.map(b => ({ id: b.id, name: b.name })); - return { brands, error: null }; - } - - const { data, error } = await publicSupabase.from("brands").select("id, name").order("name"); + const { data, error } = await publicApi.from("brands").select("id, name").order("name"); return { brands: data ?? [], error: error?.message ?? null }; } \ No newline at end of file diff --git a/src/actions/ai/preferences.ts b/src/actions/ai/preferences.ts index 5d19b40..82182a4 100644 --- a/src/actions/ai/preferences.ts +++ b/src/actions/ai/preferences.ts @@ -1,6 +1,6 @@ "use server"; -import { supabase } from "@/lib/supabase"; +import { api } from "@/lib/api"; export type AIAuthConfig = { provider: string; @@ -18,7 +18,7 @@ export async function getAIPreferences(brandId: string): Promise<{ model?: string; max_tokens?: number; } | null> { - const { data, error } = await supabase + const { data, error } = await api .from("brand_ai_settings") .select("api_key, organization_id, base_url, model, max_tokens") .eq("brand_id", brandId) @@ -32,7 +32,7 @@ export async function saveAIPreferences( brandId: string, config: AIAuthConfig ): Promise<{ success: boolean; error?: string }> { - const { error } = await supabase + const { error } = await api .from("brand_ai_settings") .upsert({ brand_id: brandId, diff --git a/src/actions/analytics.ts b/src/actions/analytics.ts index b656b07..08387e8 100644 --- a/src/actions/analytics.ts +++ b/src/actions/analytics.ts @@ -3,8 +3,8 @@ import { getAdminUser } from "@/lib/admin-permissions"; import { svcHeaders } from "@/lib/svc-headers"; -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; +const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; +const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; // ── Types ──────────────────────────────────────────────────────────────────── diff --git a/src/actions/audit.ts b/src/actions/audit.ts index 85ca00f..5389b67 100644 --- a/src/actions/audit.ts +++ b/src/actions/audit.ts @@ -27,8 +27,8 @@ type AuditResult = * Audit writes bypass RLS via the SECURITY DEFINER log_audit_event RPC function. */ export async function logAuditEvent(payload: AuditPayload): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const adminUser = await getAdminUser(); diff --git a/src/actions/billing/billing-overview.ts b/src/actions/billing/billing-overview.ts index 9e12ff7..1d7994a 100644 --- a/src/actions/billing/billing-overview.ts +++ b/src/actions/billing/billing-overview.ts @@ -39,8 +39,8 @@ import { getAdminUser } from "@/lib/admin-permissions"; import { svcHeaders } from "@/lib/svc-headers"; import { ADDONS, PLAN_TIERS, type AddonKey, type PlanTierKey } from "@/lib/pricing"; -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; -const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; +const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; +const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; export type BillingSubscriptionStatus = | "active" diff --git a/src/actions/billing/retail-checkout.ts b/src/actions/billing/retail-checkout.ts index 5708122..e82c07b 100644 --- a/src/actions/billing/retail-checkout.ts +++ b/src/actions/billing/retail-checkout.ts @@ -32,8 +32,8 @@ export async function createRetailStripeCheckoutSession( })); // Get brand name for Stripe metadata - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; let brandName = "Route Commerce"; try { const brandRes = await fetch( diff --git a/src/actions/billing/stripe-checkout.ts b/src/actions/billing/stripe-checkout.ts index 2a6b6f9..12a2c67 100644 --- a/src/actions/billing/stripe-checkout.ts +++ b/src/actions/billing/stripe-checkout.ts @@ -43,8 +43,8 @@ export async function createStripeCheckoutSession( const priceId = getPriceId(priceKey); if (!priceId) return { success: false, error: `No Stripe price configured for "${priceKey}". Set ${priceKey.toUpperCase()}_PRICE in your environment.` }; - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; // Get brand's Stripe customer ID const custRes = await fetch( @@ -123,8 +123,8 @@ export async function cancelAddonSubscription( const stripeKey = process.env.STRIPE_SECRET_KEY; if (!stripeKey) return { success: false, error: "Stripe not configured" }; - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; // Get active subscription for this brand const subRes = await fetch( diff --git a/src/actions/billing/stripe-portal.ts b/src/actions/billing/stripe-portal.ts index 7277ff0..65ea99e 100644 --- a/src/actions/billing/stripe-portal.ts +++ b/src/actions/billing/stripe-portal.ts @@ -13,8 +13,8 @@ export async function getStripeBillingPortalUrl(brandId: string): Promise<{ succ const stripeKey = process.env.STRIPE_SECRET_KEY; if (!stripeKey) return { success: false, error: "Stripe not configured" }; - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; // Get stripe_customer_id from brands table const custRes = await fetch( @@ -49,8 +49,8 @@ export async function updateBrandPlanTier(brandId: string, planTier: string): Pr return { success: false, error: "Invalid plan tier" }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const res = await fetch( `${supabaseUrl}/rest/v1/rpc/update_brand_plan_tier`, @@ -72,8 +72,8 @@ export async function updateBrandStripeCustomerId(brandId: string, stripeCustome return { success: false, error: "Not authorized" }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const res = await fetch( `${supabaseUrl}/rest/v1/rpc/update_brand_stripe_customer_id`, @@ -89,8 +89,8 @@ export async function updateBrandStripeCustomerId(brandId: string, stripeCustome } export async function getBrandPlanInfo(brandId: string): Promise<{ success: boolean; data?: any; error?: string }> { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const res = await fetch( `${supabaseUrl}/rest/v1/rpc/get_brand_plan_info`, @@ -108,8 +108,8 @@ export async function getBrandPlanInfo(brandId: string): Promise<{ success: bool } export async function getEnabledAddons(brandId: string): Promise> { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const res = await fetch( `${supabaseUrl}/rest/v1/rpc/get_brand_features`, @@ -128,8 +128,8 @@ export async function getEnabledAddons(brandId: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const res = await fetch( `${supabaseUrl}/rest/v1/rpc/get_wholesale_orders`, diff --git a/src/actions/brand-settings.ts b/src/actions/brand-settings.ts index 0aa213d..90e3046 100644 --- a/src/actions/brand-settings.ts +++ b/src/actions/brand-settings.ts @@ -50,8 +50,8 @@ export async function uploadBrandLogo( return { success: false, error: `Upload failed: ${(e as Error).message}` }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const saveRes = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_brand_settings`, @@ -112,8 +112,8 @@ export async function uploadOlatheSweetLogo( return { success: false, error: `Upload failed: ${(e as Error).message}` }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const saveRes = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_brand_settings`, @@ -174,8 +174,8 @@ export async function uploadOlatheSweetLogoDark( return { success: false, error: `Upload failed: ${(e as Error).message}` }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const saveRes = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_brand_settings`, @@ -246,8 +246,8 @@ export type SaveBrandSettingsResult = | { success: false; error: string }; export async function getBrandSettings(brandId: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/get_brand_settings`, @@ -265,8 +265,8 @@ export async function getBrandSettings(brandId: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/get_brand_settings_by_slug`, @@ -331,8 +331,8 @@ export async function saveBrandSettings(params: { return { success: false, error: "Not authorized for this brand" }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_brand_settings`, diff --git a/src/actions/checkout.ts b/src/actions/checkout.ts index fbf0d1b..b9eab6e 100644 --- a/src/actions/checkout.ts +++ b/src/actions/checkout.ts @@ -64,8 +64,8 @@ export async function createOrder( brandId?: string, shippingAddress?: ShippingAddress ): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; // ── Calculate tax if brand collects tax ───────────────────────────────── let taxAmount = 0; @@ -150,8 +150,8 @@ export async function createOrder( // ── Cart Persistence ────────────────────────────────────────────────────────── export async function getServerCart(userId: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; try { const response = await fetch( @@ -177,8 +177,8 @@ export async function mergeLocalCart( ): Promise<{ merged: CartItem[] }> { if (!localCart || localCart.length === 0) return { merged: [] }; - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; // Fetch server cart let serverCart: CartItem[] = []; @@ -243,8 +243,8 @@ export async function mergeLocalCart( } export async function clearServerCart(userId: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; try { await fetch( diff --git a/src/actions/communications/campaigns.ts b/src/actions/communications/campaigns.ts index d752956..0ba1a7e 100644 --- a/src/actions/communications/campaigns.ts +++ b/src/actions/communications/campaigns.ts @@ -59,8 +59,8 @@ export async function getCommunicationCampaigns(brandId?: string): Promise { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/get_communication_settings`, @@ -57,8 +57,8 @@ export async function upsertCommunicationSettings(params: { return { success: false, error: "Not authorized to modify this brand's communication settings" }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_communication_settings`, diff --git a/src/actions/communications/stop-blast.ts b/src/actions/communications/stop-blast.ts index fc4edcf..a1857f9 100644 --- a/src/actions/communications/stop-blast.ts +++ b/src/actions/communications/stop-blast.ts @@ -22,8 +22,8 @@ export async function sendStopBlast(params: { return { success: false, error: "Not authorized" }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.POSTGREST_SERVICE_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/send_stop_blast`, diff --git a/src/actions/communications/templates.ts b/src/actions/communications/templates.ts index 0ee0651..d88eb0a 100644 --- a/src/actions/communications/templates.ts +++ b/src/actions/communications/templates.ts @@ -40,8 +40,8 @@ export async function getCommunicationTemplates(brandId?: string): Promise<{ suc return { success: true, templates: [] }; } - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/get_communication_templates`, @@ -70,8 +70,8 @@ export async function upsertTemplate(params: { const adminUser = await getAdminUser(); if (!adminUser) return { success: false, error: "Not authenticated" }; - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!; + const supabaseKey = process.env.NEXT_PUBLIC_API_ANON_KEY!; const response = await fetch( `${supabaseUrl}/rest/v1/rpc/upsert_communication_template`, @@ -104,8 +104,8 @@ export async function getTemplateById(templateId: string): Promise