feat(selfhost): complete Supabase removal migration
Deploy to route.crispygoat.com / deploy (push) Failing after 9s

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)
This commit is contained in:
2026-06-05 20:17:02 +00:00
parent e7ac495831
commit d892b3f64f
158 changed files with 1640 additions and 1903 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ export default async function DebugAuthPage() {
let adminUsersResult: string | null = null;
if (rcAuthUid) {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL;
const serviceKey = process.env.POSTGREST_SERVICE_KEY;
if (supabaseUrl && serviceKey) {
try {
const res = await fetch(
+2 -2
View File
@@ -4,7 +4,7 @@ import { getAdminOrders } from "@/actions/orders";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import { PageHeader } from "@/components/admin/design-system";
import { redirect } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
export const dynamic = "force-dynamic";
@@ -33,7 +33,7 @@ export default async function AdminOrdersPage() {
// Fetch active products for this brand (for admin "New Order" item picker)
let brandProducts: Array<{ id: string; name: string; price: number; type?: string | null; active?: boolean }> = [];
try {
let prodQuery = supabase
let prodQuery = api
.from("products")
.select("id, name, price, type, active")
.eq("active", true)
+2 -2
View File
@@ -1,5 +1,5 @@
import Link from "next/link";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import { isFeatureEnabled } from "@/lib/feature-flags";
import { getBillingOverview } from "@/actions/billing/billing-overview";
@@ -30,7 +30,7 @@ export default async function AdminPage() {
// (0/0/0) usage values and contradicts the billing page's "Products 1/25".
let dashboardBrandId: string | null = adminUser?.brand_id ?? null;
if (!dashboardBrandId && adminUser?.role === "platform_admin") {
const { data: firstBrand } = await supabase
const { data: firstBrand } = await api
.from("brands")
.select("id")
.limit(1)
+5 -5
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import ProductEditForm from "@/components/admin/ProductEditForm";
import { getAdminUser } from "@/lib/admin-permissions";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
@@ -14,10 +14,10 @@ type ProductDetailPageProps = {
export default async function ProductDetailPage({ params }: ProductDetailPageProps) {
const { id } = await params;
const [{ data: product, error }, { data: brands }] = await Promise.all([
supabase.from("products").select("*, brands(name, slug)").eq("id", id).single(),
supabase.from("brands").select("id, name, slug"),
]);
const [{ data: product, error }, { data: brands }] = (await Promise.all([
api.from("products").select("*, brands(name, slug)").eq("id", id).single(),
api.from("brands").select("id, name, slug"),
])) as any;
const adminUser = await getAdminUser();
+3 -3
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import ProductsClient from "@/components/admin/ProductsClient";
@@ -31,7 +31,7 @@ export default async function AdminProductsPage() {
const brandId = adminUser.brand_id;
let query = supabase
let query = api
.from("products")
.select(`
id,
@@ -52,7 +52,7 @@ export default async function AdminProductsPage() {
query = query.eq("brand_id", adminUser.brand_id);
}
const { data: products, error } = await query;
const { data: products, error } = (await query) as any;
if (error) {
return (
+2 -2
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import ReportsDashboard from "@/components/admin/ReportsDashboard";
@@ -12,7 +12,7 @@ export default async function ReportsPage() {
const isPlatformAdmin = adminUser.role === "platform_admin";
const { data: brands } = await supabase
const { data: brands } = await api
.from("brands")
.select("id, name")
.order("name");
+2 -2
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import { getBillingOverview } from "@/actions/billing/billing-overview";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
@@ -18,7 +18,7 @@ export default async function BillingPage({ params }: Props) {
let resolvedBrandId = effectiveBrandId;
if (isPlatformAdmin && !resolvedBrandId) {
const { data: firstBrand } = await supabase
const { data: firstBrand } = await api
.from("brands")
.select("id")
.limit(1)
+2 -2
View File
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import IntegrationsClientPage from "./IntegrationsClientPage";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
@@ -18,7 +18,7 @@ export default async function IntegrationsPage() {
// Platform admins: fetch all brands for the picker
const brands = isPlatformAdmin
? (await supabase.from("brands").select("id, name").order("name")).data ?? []
? (await api.from("brands").select("id, name").order("name")).data ?? []
: [];
return (
+2 -2
View File
@@ -1,5 +1,5 @@
import { redirect } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import { getShippingSettings } from "@/actions/shipping/settings";
import ShippingSettingsForm from "@/components/admin/ShippingSettingsForm";
@@ -17,7 +17,7 @@ export default async function ShippingSettingsPage() {
// Platform admins: fetch all brands for the picker
const brands = isPlatformAdmin
? (await supabase.from("brands").select("id, name").order("name")).data ?? []
? (await api.from("brands").select("id, name").order("name")).data ?? []
: [];
const effectiveBrandId = brandId || (brands[0]?.id ?? "");
+9 -9
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import StopEditForm from "@/components/admin/StopEditForm";
import StopProductAssignment from "@/components/admin/StopProductAssignment";
import MessageCustomersSection from "@/components/admin/MessageCustomersSection";
@@ -16,14 +16,14 @@ type StopDetailPageProps = {
export default async function StopDetailPage({ params }: StopDetailPageProps) {
const { id } = await params;
const [{ data: stop, error }, { data: brands }] = await Promise.all([
supabase
const [{ data: stop, error }, { data: brands }] = (await Promise.all([
api
.from("stops")
.select("*, brands(name, slug)")
.eq("id", id)
.single(),
supabase.from("brands").select("id, name, slug"),
]);
api.from("brands").select("id, name, slug"),
])) as any;
const adminUser = await getAdminUser();
@@ -56,17 +56,17 @@ export default async function StopDetailPage({ params }: StopDetailPageProps) {
);
}
const [{ data: allProducts }, { data: productStops }] = await Promise.all([
supabase
const [{ data: allProducts }, { data: productStops }] = (await Promise.all([
api
.from("products")
.select("id, name, type, price")
.eq("brand_id", stop.brand_id)
.eq("active", true),
supabase
api
.from("product_stops")
.select("id, product_id, products(id, name, type, price)")
.eq("stop_id", id),
]);
])) as any;
const assignedProducts = (productStops ?? [])
.map((ps: any) => ps)
+4 -4
View File
@@ -1,4 +1,4 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import NewStopForm from "@/components/admin/NewStopForm";
import StopProductAssignment from "@/components/admin/StopProductAssignment";
import { getAdminUser } from "@/lib/admin-permissions";
@@ -32,11 +32,11 @@ export default async function NewStopPage({
let duplicateFrom: Stop | null = null;
if (duplicate) {
const { data } = await supabase
const { data } = (await api
.from("stops")
.select("city, state, location, date, time, brand_id, active, address, zip, cutoff_time")
.eq("id", duplicate)
.single();
.single()) as { data: Stop | null };
duplicateFrom = data;
}
@@ -44,7 +44,7 @@ export default async function NewStopPage({
adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [{ data: allProducts }] = await Promise.all([
supabase
api
.from("products")
.select("id, name, type, price")
.eq("brand_id", brandId)
+3 -3
View File
@@ -1,6 +1,6 @@
import StopTableClient from "@/components/admin/StopTableClient";
import StopsHeaderActions from "@/components/admin/StopsHeaderActions";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getAdminUser } from "@/lib/admin-permissions";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import { PageHeader } from "@/components/admin/design-system";
@@ -22,7 +22,7 @@ export default async function AdminStopsPage() {
redirect("/admin/pickup");
}
let query = supabase
let query = api
.from("stops")
.select(`
id,
@@ -49,7 +49,7 @@ export default async function AdminStopsPage() {
query = query.eq("brand_id", adminUser.brand_id);
}
const { data: stops, error } = await query;
const { data: stops, error } = (await query) as any;
if (error) {
return (
+3 -3
View File
@@ -1,7 +1,7 @@
"use server";
import { getAdminUser } from "@/lib/admin-permissions";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import TaxDashboard from "@/components/admin/TaxDashboard";
import { PageHeader } from "@/components/admin/design-system";
@@ -20,7 +20,7 @@ export default async function TaxesPage({ params }: Props) {
let resolvedBrandId = effectiveBrandId;
if (isPlatformAdmin && !resolvedBrandId) {
const { data: firstBrand } = await supabase
const { data: firstBrand } = await api
.from("brands")
.select("id")
.limit(1)
@@ -54,7 +54,7 @@ export default async function TaxesPage({ params }: Props) {
if (!resolvedBrandId) return <AdminAccessDenied />;
const { data: brands } = await supabase
const { data: brands } = await api
.from("brands")
.select("id, name")
.order("name");
+2 -2
View File
@@ -82,8 +82,8 @@ Use "recent_orders" for recent order questions.`;
const queryType = parsed.queryType ?? "recent_orders";
const days = parsed.days ?? 30;
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;
// Route to appropriate RPC based on query type
let rpcName = "get_recent_orders_insights";
+2 -2
View File
@@ -2,8 +2,8 @@ import { NextResponse } from "next/server";
import { svcHeaders } from "@/lib/svc-headers";
export async function GET() {
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;
if (!supabaseUrl || !supabaseKey) {
return NextResponse.json({ error: "Missing configuration" }, { status: 500 });
-45
View File
@@ -1,45 +0,0 @@
import { NextResponse } from "next/server";
export async function GET() {
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY ?? "";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
if (!serviceKey || !supabaseUrl) {
return NextResponse.json({ error: "Missing env vars", serviceKey: !!serviceKey, supabaseUrl: !!supabaseUrl }, { status: 500 });
}
// Test 1: just a simple health endpoint that doesn't require the key
let healthResult = null;
try {
const res = await fetch(`${supabaseUrl}/rest/v1/`, {
headers: { apikey: serviceKey },
});
healthResult = { status: res.status, ok: res.ok };
} catch (e: any) {
healthResult = { error: e?.message };
}
// Test 2: try admin_users with POST (bypasses RLS select policies)
let adminResult = null;
try {
const res = await fetch(
`${supabaseUrl}/rest/v1/admin_users?select=id,user_id,role,email,display_name&limit=5`,
{
method: "GET",
headers: {
apikey: serviceKey,
"Content-Type": "application/json",
Prefer: "return=representation",
},
}
);
const body = await res.text();
let parsed = null;
try { parsed = JSON.parse(body); } catch { parsed = body; }
adminResult = { status: res.status, body: parsed };
} catch (e: any) {
adminResult = { error: e?.message };
}
return NextResponse.json({ healthResult, adminResult, serviceKeyLen: serviceKey.length });
}
-48
View File
@@ -1,48 +0,0 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function GET() {
const cookieStore = await cookies();
const allCookies = cookieStore.getAll();
const rcAuthUid = cookieStore.get("rc_auth_uid")?.value;
const rcUid = cookieStore.get("rc_uid")?.value;
const rcAccessToken = cookieStore.get("rc_access_token")?.value;
let adminUsersStatus = "not_tried";
let adminUsersResult: string | null = null;
if (rcAuthUid) {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (supabaseUrl && serviceKey) {
try {
const res = await fetch(
`${supabaseUrl}/rest/v1/admin_users?user_id=eq.${rcAuthUid}&limit=1`,
{ headers: { apikey: serviceKey, "Content-Type": "application/json" } }
);
adminUsersStatus = String(res.status);
const data = await res.json().catch(() => null);
adminUsersResult = JSON.stringify(data);
} catch (e) {
adminUsersStatus = "error: " + (e instanceof Error ? e.message : String(e));
}
} else {
adminUsersStatus = "missing_env_vars";
}
} else {
adminUsersStatus = "no_rc_auth_uid_cookie";
}
return NextResponse.json({
cookies: {
rc_auth_uid: rcAuthUid ? `${rcAuthUid.slice(0, 10)}...` : null,
rc_uid: rcUid ? `${rcUid.slice(0, 10)}...` : null,
rc_access_token: rcAccessToken ? "present" : null,
all_cookie_names: allCookies.map(c => c.name),
},
admin_users: {
status: adminUsersStatus,
result: adminUsersResult,
},
});
}
-18
View File
@@ -1,18 +0,0 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export const dynamic = "force-dynamic";
export async function GET() {
const cookieStore = await cookies();
const allCookies = cookieStore.getAll();
const rcAuthUid = cookieStore.get("rc_auth_uid")?.value;
const rcUid = cookieStore.get("rc_uid")?.value;
return NextResponse.json({
received_cookies: allCookies.map(c => ({ name: c.name, value_len: c.value.length })),
rc_auth_uid: rcAuthUid ? `${rcAuthUid.slice(0, 8)}...` : "MISSING",
rc_uid: rcUid ? `${rcUid.slice(0, 8)}...` : "MISSING",
raw_rc_auth_uid: rcAuthUid ?? null,
});
}
-31
View File
@@ -1,31 +0,0 @@
import { NextResponse } from "next/server";
export async function GET() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
const nodeEnv = process.env.NODE_ENV;
const result = {
NODE_ENV: nodeEnv,
NEXT_PUBLIC_SUPABASE_URL: url ? `${url.substring(0, 40)}... (SET)` : "MISSING",
NEXT_PUBLIC_SUPABASE_ANON_KEY: key ? `${key.substring(0, 20)}... (SET)` : "MISSING",
SUPABASE_SERVICE_ROLE_KEY: serviceKey ? `${serviceKey.substring(0, 20)}... (SET)` : "MISSING",
supabaseClientCanCreate: false as boolean,
error: null as string | null,
};
if (url && key) {
try {
const { createClient } = await import("@supabase/supabase-js");
const client = createClient(url, key);
result.supabaseClientCanCreate = true;
} catch (e: any) {
result.error = e?.message ?? String(e);
}
} else {
result.error = "Missing env vars";
}
return NextResponse.json(result, { status: 200 });
}
@@ -1,34 +0,0 @@
import { NextResponse } from "next/server";
import { getAdminUser } from "@/lib/admin-permissions";
export async function GET() {
// Test the REST call directly from this endpoint
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const uid = "c023efb3-e3ef-4156-bed6-d17b92ea8aca";
let restResult = null;
try {
const res = await fetch(
`${supabaseUrl}/rest/v1/admin_users?user_id=eq.${uid}&limit=1`,
{ headers: { apikey: serviceKey, "Content-Type": "application/json" } }
);
const data = await res.json().catch(() => null);
restResult = { status: res.status, data, keyLen: serviceKey.length };
} catch (e: any) {
restResult = { error: e?.message };
}
const adminUser = await getAdminUser();
return NextResponse.json({
adminUser: adminUser ? {
id: adminUser.id,
user_id: adminUser.user_id,
role: adminUser.role,
brand_id: adminUser.brand_id,
active: adminUser.active,
} : null,
restResult,
supabaseUrl: supabaseUrl ? "SET" : "MISSING",
});
}
-9
View File
@@ -1,9 +0,0 @@
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({
ts: new Date().toISOString(),
deployment: "debug-hello",
msg: "hello from latest build"
});
}
-80
View File
@@ -1,80 +0,0 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export const dynamic = "force-dynamic";
export async function GET() {
const cookieStore = await cookies();
const uid = cookieStore.get("rc_auth_uid")?.value ?? cookieStore.get("rc_uid")?.value;
if (!uid) {
return NextResponse.json({ error: "No uid cookie found" }, { status: 400 });
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !serviceKey) {
return NextResponse.json({
uid,
error: "Missing env vars",
supabaseUrl: supabaseUrl ?? "MISSING",
serviceKeyPresent: !!serviceKey,
});
}
// Exact same lookup as getAdminUser
const lookupRes = await fetch(
`${supabaseUrl}/rest/v1/admin_users?user_id=eq.${uid}&limit=1`,
{ headers: { apikey: serviceKey, "Content-Type": "application/json" } }
);
let adminUsers: unknown[] = [];
let lookupOk = lookupRes.ok;
let lookupStatus = lookupRes.status;
let lookupData: unknown = null;
if (lookupRes.ok) {
lookupData = await lookupRes.json().catch(() => []);
adminUsers = Array.isArray(lookupData) ? lookupData : [];
} else {
lookupData = await lookupRes.text().catch(() => "unknown error");
}
if (adminUsers.length > 0) {
return NextResponse.json({
uid,
result: "found",
adminUser: adminUsers[0],
});
}
// Try auto-create
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!UUID_REGEX.test(uid)) {
return NextResponse.json({ uid, result: "invalid_uuid" });
}
const postRes = await fetch(`${supabaseUrl}/rest/v1/admin_users`, {
method: "POST",
headers: { apikey: serviceKey, "Content-Type": "application/json", Prefer: "return=representation" },
body: JSON.stringify({
user_id: uid, role: "platform_admin", brand_id: null, active: true,
can_manage_products: true, can_manage_stops: true, can_manage_orders: true,
can_manage_pickup: true, can_manage_messages: true, can_manage_refunds: true,
can_manage_users: true, can_manage_water_log: true, can_manage_reports: true,
can_manage_settings: true, must_change_password: false
}),
});
return NextResponse.json({
uid,
result: "auto_created",
lookupOk,
lookupStatus,
adminUsersFound: adminUsers.length,
postStatus: postRes.status,
postOk: postRes.ok,
postData: postRes.ok ? await postRes.json().catch(() => null) : await postRes.text().catch(() => null),
});
}
@@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
import { svcHeaders } from "@/lib/svc-headers";
import { sendAbandonedCartEmail, type AbandonedCart } from "@/actions/email-automation/abandoned-cart";
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 BRAND_IDS = [
{ id: "64294306-5f42-463d-a5e8-2ad6c81a96de", name: "Tuxedo Corn" },
@@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
import { svcHeaders } from "@/lib/svc-headers";
import { sendWelcomeEmail, type WelcomeSequenceEntry } from "@/actions/email-automation/welcome-sequence";
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 BRAND_IDS = [
{ id: "64294306-5f42-463d-a5e8-2ad6c81a96de", name: "Tuxedo Corn" },
+2 -2
View File
@@ -9,8 +9,8 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Email is required." }, { status: 400 });
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_API_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
return NextResponse.json({ error: "Server misconfiguration." }, { status: 500 });
}
@@ -1,11 +1,11 @@
import { NextResponse } from "next/server";
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
export async function GET() {
const brandSlug = "indian-river-direct";
const { data: brand } = await supabase
const { data: brand } = await api
.from("brands")
.select("id, name")
.eq("slug", brandSlug)
@@ -15,14 +15,14 @@ export async function GET() {
return new NextResponse("Brand not found", { status: 404 });
}
const { data: stops } = await supabase
const { data: stops } = (await api
.from("stops")
.select("*")
.eq("brand_id", brand.id)
.eq("active", true)
.order("date", { ascending: true });
.order("date", { ascending: true })) as { data: Array<{ city: string; state: string; date: string; time: string; location: string }> | null };
const { data: settings } = await supabase
const { data: settings } = await api
.from("brand_settings")
.select("logo_url, email, phone, schedule_pdf_notes")
.eq("brand_id", brand.id)
+2 -2
View File
@@ -8,8 +8,8 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Email and password are required." }, { status: 400 });
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_API_ANON_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
return NextResponse.json({ error: "Server misconfiguration." }, { status: 500 });
}
+2 -2
View File
@@ -19,8 +19,8 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
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 orders report data
const response = await fetch(
+2 -2
View File
@@ -49,8 +49,8 @@ export async function POST(req: NextRequest) {
}
const { type, data } = event;
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!;
// Look up by customer_email + subject (event_id is not populated by send_campaign)
// Filter to recent logs (last 7 days) to avoid stale matches
@@ -1,6 +1,6 @@
import { svcHeaders } from "@/lib/svc-headers";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_URL = process.env.NEXT_PUBLIC_API_URL!;
const SUPABASE_PAT = process.env.SUPABASE_PAT!;
interface LotRow {
+1 -1
View File
@@ -1,6 +1,6 @@
import { svcHeaders } from "@/lib/svc-headers";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_URL = process.env.NEXT_PUBLIC_API_URL!;
const SUPABASE_PAT = process.env.SUPABASE_PAT!;
type LotRow = {
+1 -1
View File
@@ -2,7 +2,7 @@ import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
import QRCode from "qrcode";
import { svcHeaders } from "@/lib/svc-headers";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_URL = process.env.NEXT_PUBLIC_API_URL!;
const SUPABASE_PAT = process.env.SUPABASE_PAT!;
async function getLotDetail(lotId: string) {
@@ -1,7 +1,7 @@
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
import { svcHeaders } from "@/lib/svc-headers";
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_URL = process.env.NEXT_PUBLIC_API_URL!;
const SUPABASE_PAT = process.env.SUPABASE_PAT!;
async function getLotWithChain(lotId: string) {
+2 -2
View File
@@ -98,8 +98,8 @@ export async function GET(request: Request) {
}
// Store token + location_id in payment_settings via upsert
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!;
try {
const upsertResponse = await fetch(
+2 -2
View File
@@ -58,8 +58,8 @@ export async function POST(request: Request) {
}
// Update square_last_sync_at and square_last_sync_error
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 lastError = syncErrors.length > 0 ? syncErrors.slice(0, 5).join("; ") : null;
await fetch(`${supabaseUrl}/rest/v1/rpc/upsert_payment_settings`, {
+2 -2
View File
@@ -18,8 +18,8 @@ export async function GET(request: Request) {
return NextResponse.json({ error: "brand_id required" }, { status: 400 });
}
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 claimRes = await fetch(
`${supabaseUrl}/rest/v1/rpc/claim_square_sync_queue`,
-43
View File
@@ -1,43 +0,0 @@
import { createClient } from "@supabase/supabase-js";
const url = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "";
export async function GET() {
// Try creating supabase client — if it throws, capture the exact error
let status: string;
let canCreate = false;
let errMessage = "";
if (!url || !key) {
status = "MISSING_ENV_VARS";
errMessage = `url=${url ? "SET" : "MISSING"}, key=${key ? "SET" : "MISSING"}`;
} else {
try {
createClient(url, key);
canCreate = true;
status = "OK";
} catch (e: any) {
errMessage = e?.message ?? String(e);
status = "CREATE_CLIENT_FAILED";
}
}
const body = JSON.stringify({
status,
canCreate,
errMessage,
envVars: {
NODE_ENV: process.env.NODE_ENV,
urlSet: !!url,
urlPrefix: url ? url.substring(0, 30) : null,
keySet: !!key,
keyPrefix: key ? key.substring(0, 10) : null,
},
}, null, 2);
return new Response(body, {
status: status === "OK" ? 200 : 500,
headers: { "Content-Type": "application/json" },
});
}
-52
View File
@@ -1,52 +0,0 @@
import { NextResponse } from "next/server";
import { svcHeaders } from "@/lib/svc-headers";
// Server-side proxy for Supabase REST calls from Client Components.
// Client components cannot import "use server" modules, so they route
// all Supabase calls through here to avoid Bearer JWT header issues
// on Vercel Edge Runtime.
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
export async function POST(request: Request) {
try {
const { table, method = "GET", body, params, headers: extraHeaders } = await request.json();
if (!table) {
return NextResponse.json({ error: "table is required" }, { status: 400 });
}
// Build URL — params are query string key=value pairs
let url = `${SUPABASE_URL}/rest/v1/${table}`;
if (params) {
const qs = Object.entries(params as Record<string, string>)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join("&");
if (qs) url += `?${qs}`;
}
// Determine which key to use — prefer ANON_KEY for client-facing reads
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const fetchOptions: RequestInit = {
method: method.toUpperCase(),
headers: {
...svcHeaders(supabaseKey),
"Content-Type": "application/json",
...extraHeaders,
},
};
if (body && !["GET", "HEAD"].includes(method.toUpperCase())) {
fetchOptions.body = JSON.stringify(body);
}
const res = await fetch(url, fetchOptions);
const data = await res.json().catch(() => null);
return NextResponse.json(data, { status: res.status });
} catch (err) {
const msg = err instanceof Error ? err.message : "Proxy error";
return NextResponse.json({ error: msg }, { status: 500 });
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
type Settings = {
logo_url: string | null;
@@ -13,7 +13,7 @@ type Settings = {
export async function GET() {
const brandSlug = "tuxedo";
const { data: brand } = await supabase
const { data: brand } = await api
.from("brands")
.select("id, name")
.eq("slug", brandSlug)
@@ -23,14 +23,14 @@ export async function GET() {
return new NextResponse("Brand not found", { status: 404 });
}
const { data: stops } = await supabase
const { data: stops } = (await api
.from("stops")
.select("*")
.eq("brand_id", brand.id)
.eq("active", true)
.order("date", { ascending: true });
.order("date", { ascending: true })) as { data: Array<{ city: string; state: string; date: string; time: string; location: string }> | null };
const { data: settings } = await supabase
const { data: settings } = await api
.from("brand_settings")
.select("logo_url, email, phone, schedule_pdf_notes")
.eq("brand_id", brand.id)
+4 -4
View File
@@ -49,8 +49,8 @@ export async function POST(req: NextRequest) {
return validationError(validation.error);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceKey = process.env.POSTGREST_SERVICE_KEY!;
// Create campaign via RPC
const res = await fetch(
@@ -102,8 +102,8 @@ export async function GET(req: NextRequest) {
return apiError("brand_id is required", 400);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const anonKey = process.env.NEXT_PUBLIC_API_ANON_KEY!;
const res = await fetch(
`${supabaseUrl}/rest/v1/communication_campaigns?brand_id=eq.${brand_id}&select=*&order=created_at.desc`,
+4 -4
View File
@@ -55,8 +55,8 @@ export async function GET(req: NextRequest) {
return validationError(validation.error);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const anonKey = process.env.NEXT_PUBLIC_API_ANON_KEY!;
// Build query
let query = `${supabaseUrl}/rest/v1/products?select=*&order=name.asc&limit=${validation.data.limit}&offset=${validation.data.offset}`;
@@ -114,8 +114,8 @@ export async function POST(req: NextRequest) {
return apiError("brand_id and name are required", 400);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceKey = process.env.POSTGREST_SERVICE_KEY!;
const res = await fetch(
`${supabaseUrl}/rest/v1/products`,
+4 -4
View File
@@ -43,8 +43,8 @@ export async function POST(req: NextRequest) {
return validationError(validation.error);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceKey = process.env.POSTGREST_SERVICE_KEY!;
// Redeem referral via RPC
const res = await fetch(
@@ -89,8 +89,8 @@ export async function GET(req: NextRequest) {
return apiError("brand_id is required", 400);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const anonKey = process.env.NEXT_PUBLIC_API_ANON_KEY!;
const res = await fetch(
`${supabaseUrl}/rest/v1/referral_codes?brand_id=eq.${brand_id}&select=*&order=created_at.desc`,
+2 -2
View File
@@ -50,8 +50,8 @@ export async function GET(req: NextRequest) {
return validationError(validation.error);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceKey = process.env.POSTGREST_SERVICE_KEY!;
// Get report via RPC
const res = await fetch(
+4 -4
View File
@@ -48,8 +48,8 @@ export async function POST(req: NextRequest) {
return validationError(validation.error);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceKey = process.env.POSTGREST_SERVICE_KEY!;
// Create water log via RPC
const res = await fetch(
@@ -97,8 +97,8 @@ export async function GET(req: NextRequest) {
return apiError("brand_id is required", 400);
}
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const anonKey = process.env.NEXT_PUBLIC_API_ANON_KEY!;
const res = await fetch(
`${supabaseUrl}/rest/v1/water_logs?brand_id=eq.${brand_id}&select=*&order=logged_at.desc&limit=100`,
+2 -2
View File
@@ -9,8 +9,8 @@ export async function POST(request: Request) {
return NextResponse.json({ success: false, error: "Missing params" }, { status: 400 });
}
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!;
// Get admin settings
const settingsRes = await fetch(
+2 -2
View File
@@ -18,8 +18,8 @@ export async function GET(request: NextRequest) {
// Use brand_id from session (always Tuxedo for water log) or fallback to env
const brandId = adminUser.brand_id ?? process.env.TUXEDO_BRAND_ID ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
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_water_entries`,
+2 -2
View File
@@ -9,8 +9,8 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "orderId and customerId are required" }, { status: 400 });
}
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!;
// ── 1. Fetch order and brand info ──────────────────────────────────────────
// Use direct select with both orderId AND customerId filters to prevent cross-brand access
@@ -11,8 +11,8 @@ export async function GET(
const { orderId } = await params;
const token = req.nextUrl.searchParams.get("token");
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!;
// ── Token-based customer download ────────────────────────────────────────────
if (token) {
@@ -9,8 +9,8 @@ export async function GET(
const { orderId } = await params;
const token = req.nextUrl.searchParams.get("token");
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!;
if (!supabaseUrl || !supabaseKey) {
return new NextResponse("Server misconfiguration", { status: 500 });
@@ -9,8 +9,8 @@ import { svcHeaders } from "@/lib/svc-headers";
export const dynamic = "force-dynamic";
export async function POST() {
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!;
// Find orders: fulfilled status, past anticipated pickup date, not yet picked up
const ordersRes = await fetch(
@@ -18,8 +18,8 @@ export async function POST() {
);
}
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 pendingRes = await fetch(
`${supabaseUrl}/rest/v1/rpc/get_wholesale_pending_notifications`,
@@ -180,7 +180,7 @@ async function sendOneEmail(
async function triggerPickupReminder() {
try {
await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL!}/api/wholesale/notifications/pickup-reminder`, {
await fetch(`${process.env.NEXT_PUBLIC_API_URL!}/api/wholesale/notifications/pickup-reminder`, {
method: "POST",
headers: { "Content-Type": "application/json" },
});
+3 -3
View File
@@ -39,8 +39,8 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: "customerIds array and brandId are required" }, { status: 400 });
}
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!;
// Fetch brand settings for branding + pickup location
const settings = await getWholesaleSettings(effectiveBrandId);
@@ -179,7 +179,7 @@ ${hasCustomNote ? ` <p style="margin: 0 0 12px; color: #1e293b; font-size:
}
// Fire-and-forget trigger to process the queue
fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL!}/api/wholesale/notifications/send`, {
fetch(`${process.env.NEXT_PUBLIC_API_URL!}/api/wholesale/notifications/send`, {
method: "POST",
headers: { "Content-Type": "application/json" },
}).catch(() => {});
@@ -6,8 +6,8 @@ import { svcHeaders } from "@/lib/svc-headers";
// Processes pending webhook events from wholesale_sync_log and dispatches to configured URLs.
// Called by a cron job or manually after enqueue_wholesale_webhook has queued events.
export async function POST() {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
const supabaseUrl = process.env.NEXT_PUBLIC_API_URL!;
const serviceRoleKey = process.env.POSTGREST_SERVICE_KEY!;
if (!supabaseUrl || !serviceRoleKey) {
return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
+2 -2
View File
@@ -24,9 +24,9 @@ export default function AuthCallback() {
}
// Validate token by fetching user info from Supabase
fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/user`, {
fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/v1/user`, {
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
apikey: process.env.NEXT_PUBLIC_API_ANON_KEY!,
Authorization: `Bearer ${accessToken}`,
},
})
+4 -4
View File
@@ -55,8 +55,8 @@ export default function CartClient() {
// eslint-disable-next-line react-hooks/set-state-in-effect
setLoadingStops(true);
fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/stops?active=eq.true&brand_id=eq.${cartBrandId}&select=id,city,state,date,time,location,brand_id&order=date`,
{ headers: { apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! } }
`${process.env.NEXT_PUBLIC_API_URL}/rest/v1/stops?active=eq.true&brand_id=eq.${cartBrandId}&select=id,city,state,date,time,location,brand_id&order=date`,
{ headers: { apikey: process.env.NEXT_PUBLIC_API_ANON_KEY! } }
)
.then((r) => r.json())
.then((data) => setStops(data ?? []))
@@ -75,11 +75,11 @@ export default function CartClient() {
if (hasPickupItems) {
const pickupProductIds = cart.filter((i) => i.fulfillment === "pickup").map((i) => i.id);
fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/check_stop_product_availability`,
`${process.env.NEXT_PUBLIC_API_URL}/rest/v1/rpc/check_stop_product_availability`,
{
method: "POST",
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
apikey: process.env.NEXT_PUBLIC_API_ANON_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ p_stop_id: stop.id, p_product_ids: pickupProductIds }),
+2 -2
View File
@@ -33,10 +33,10 @@ export default function CheckoutClient() {
useEffect(() => {
if (!cartBrandId) return;
fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/stops?active=eq.true&brand_id=eq.${cartBrandId}&select=id,city,state,date,location,brand_id&order=date`,
`${process.env.NEXT_PUBLIC_API_URL}/rest/v1/stops?active=eq.true&brand_id=eq.${cartBrandId}&select=id,city,state,date,location,brand_id&order=date`,
{
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
apikey: process.env.NEXT_PUBLIC_API_ANON_KEY!,
},
}
)
@@ -5,7 +5,7 @@ import { motion } from "framer-motion";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
type BrandSettings = {
invoice_business_name: string | null;
@@ -34,12 +34,12 @@ export default function IndianRiverContactPage() {
const [contactPhone, setContactPhone] = useState<string | null>(null);
useEffect(() => {
supabase.from("brands").select("id").eq("slug", "indian-river-direct").single().then(({ data: brand }) => {
api.from("brands").select("id").eq("slug", "indian-river-direct").single().then(({ data: brand }) => {
if (!brand?.id) return;
supabase.from("wholesale_settings")
api.from("wholesale_settings")
.select("invoice_business_name, invoice_business_address, invoice_business_phone, invoice_business_email, invoice_business_website")
.eq("brand_id", brand.id).single().then(({ data }) => setBrandSettings(data ?? null));
supabase.from("brand_settings").select("logo_url, custom_footer_text, email, phone")
api.from("brand_settings").select("logo_url, custom_footer_text, email, phone")
.eq("brand_id", brand.id).single().then(({ data: s }) => {
if (s) { setLogoUrl(s.logo_url ?? null); setCustomFooterText(s.custom_footer_text ?? null); setContactEmail(s.email ?? null); setContactPhone(s.phone ?? null); }
});
@@ -6,7 +6,7 @@ import { motion } from "framer-motion";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
type FAQCategory = {
category: string;
@@ -136,7 +136,7 @@ export default function IndianRiverFAQPage() {
const [searchQuery, setSearchQuery] = useState("");
useEffect(() => {
supabase.from("brand_settings").select("phone").eq("brand_id",
api.from("brand_settings").select("phone").eq("brand_id",
"b1cb7a96-d82b-40b1-80b1-d6dd26c56e28"
).single().then(({ data }) => {
if (data?.phone) setFaqCategories(buildFaqCategories(data.phone));
+6 -6
View File
@@ -8,7 +8,7 @@ import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import WhyIndianRiverDirect from "@/components/storefront/WhyIndianRiverDirect";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getBrandSettingsPublic } from "@/actions/brand-settings";
type Brand = { id: string; name: string; slug: string };
@@ -86,7 +86,7 @@ export default function IndianRiverDirectPage() {
async function load() {
const slug = "indian-river-direct";
const [brandResult, settingsResult] = await Promise.all([
supabase.from("brands").select("*").eq("slug", slug).single(),
api.from("brands").select("*").eq("slug", slug).single(),
getBrandSettingsPublic(slug),
]);
@@ -110,10 +110,10 @@ export default function IndianRiverDirectPage() {
} catch { /* not logged in */ }
if (brandData?.id) {
const [{ data: stopsData }, { data: productsData }] = await Promise.all([
supabase.from("stops").select("*").eq("brand_id", brandData.id).eq("active", true),
supabase.from("products").select("*").eq("brand_id", brandData.id).eq("active", true),
]);
const [{ data: stopsData }, { data: productsData }] = (await Promise.all([
api.from("stops").select("*").eq("brand_id", brandData.id).eq("active", true),
api.from("products").select("*").eq("brand_id", brandData.id).eq("active", true),
])) as any;
setStops(stopsData ?? []);
setProducts(productsData ?? []);
}
@@ -9,7 +9,7 @@ import StopSetEffect from "@/components/storefront/StopSetEffect";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { formatDate } from "@/lib/format-date";
type Product = {
@@ -43,12 +43,12 @@ export default function StopPage() {
useEffect(() => {
async function load() {
const { data: stopData } = await supabase
const { data: stopData } = (await api
.from("stops")
.select("*")
.eq("slug", slug)
.eq("active", true)
.single();
.single()) as any;
if (!stopData) return;
@@ -57,11 +57,11 @@ export default function StopPage() {
setBrandSlug(isIndian ? "indian-river-direct" : "tuxedo");
setBrandAccent(isIndian ? "blue" : "green");
const { data: productsData } = await supabase
const { data: productsData } = (await api
.from("products")
.select("*")
.eq("brand_id", stopData.brand_id)
.eq("active", true);
.eq("active", true)) as any;
setProducts(productsData ?? []);
}
load();
+2 -2
View File
@@ -1,9 +1,9 @@
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
export const dynamic = "force-dynamic";
export default async function TestPage() {
const { data, error } = await supabase
const { data, error } = await api
.from("brands")
.select("*");
+1 -1
View File
@@ -70,7 +70,7 @@ function FieldIcon({ className }: { className?: string }) {
export default async function TracePage({ params }: { params: Promise<{ lotNumber: string }> }) {
const { lotNumber } = await params;
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const SUPABASE_URL = process.env.NEXT_PUBLIC_API_URL!;
const SUPABASE_PAT = process.env.SUPABASE_PAT!;
let lotId: string | null = null;
+2 -2
View File
@@ -4,7 +4,7 @@ import { useEffect, useState, Suspense, lazy } from "react";
import { motion } from "framer-motion";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getBrandSettingsPublic } from "@/actions/brand-settings";
// Lazy load heavy sections
@@ -47,7 +47,7 @@ export default function TuxedoAboutPage() {
setContactPhone(s.phone ?? null);
setShowWholesaleLink(s.show_wholesale_link ?? true);
if (s.brand_id) {
const { data: ws } = await supabase
const { data: ws } = await api
.from("wholesale_settings")
.select("invoice_business_address")
.eq("brand_id", s.brand_id)
+2 -2
View File
@@ -5,7 +5,7 @@ import Link from "next/link";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
type BrandSettings = {
invoice_business_name: string | null;
@@ -30,7 +30,7 @@ export default function TuxedoContactPage() {
useEffect(() => {
const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de";
supabase
api
.from("wholesale_settings")
.select("invoice_business_name, invoice_business_address, invoice_business_phone, invoice_business_email, invoice_business_website")
.eq("brand_id", TUXEDO_BRAND_ID)
+6 -6
View File
@@ -12,7 +12,7 @@ import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import BrandStylesProvider from "@/components/storefront/BrandStylesProvider";
import { ScrollReveal, ParallaxLayer, FadeOnScroll } from "@/components/ui/ScrollAnimations";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { getBrandSettingsPublic } from "@/actions/brand-settings";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
@@ -449,7 +449,7 @@ export default function TuxedoPage() {
const slug = "tuxedo";
const [brandResult, settingsResult] = await Promise.all([
supabase.from("brands").select("*").eq("slug", slug).single(),
api.from("brands").select("*").eq("slug", slug).single(),
getBrandSettingsPublic(slug),
]);
@@ -484,10 +484,10 @@ export default function TuxedoPage() {
}
if (brandData?.id) {
const [{ data: stopsData }, { data: productsData }] = await Promise.all([
supabase.from("stops").select("*").eq("brand_id", brandData.id).eq("active", true),
supabase.from("products").select("*").eq("brand_id", brandData.id).eq("active", true),
]);
const [{ data: stopsData }, { data: productsData }] = (await Promise.all([
api.from("stops").select("*").eq("brand_id", brandData.id).eq("active", true),
api.from("products").select("*").eq("brand_id", brandData.id).eq("active", true),
])) as any;
setStops(stopsData ?? []);
setProducts(productsData ?? []);
}
+5 -5
View File
@@ -8,7 +8,7 @@ import StopSetEffect from "@/components/storefront/StopSetEffect";
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { supabase } from "@/lib/supabase";
import { api } from "@/lib/api";
import { formatDate } from "@/lib/format-date";
type Product = {
@@ -42,12 +42,12 @@ export default function StopPage() {
useEffect(() => {
async function load() {
const { data: stopData } = await supabase
const { data: stopData } = (await api
.from("stops")
.select("*")
.eq("slug", slug)
.eq("active", true)
.single();
.single()) as any;
if (!stopData) return;
@@ -56,11 +56,11 @@ export default function StopPage() {
setBrandSlug(isIndian ? "indian-river-direct" : "tuxedo");
setBrandAccent(isIndian ? "blue" : "green");
const { data: productsData } = await supabase
const { data: productsData } = (await api
.from("products")
.select("*")
.eq("brand_id", stopData.brand_id)
.eq("active", true);
.eq("active", true)) as any;
setProducts(productsData ?? []);
}
load();
+6 -6
View File
@@ -281,8 +281,8 @@ export default function WholesalePortalPage() {
setLoading(false);
return;
}
const { supabase } = await import("@/lib/supabase");
const { data: ws } = await supabase
const { api } = await import("@/lib/api");
const { data: ws } = await api
.from("wholesale_settings")
.select("wholesale_enabled, online_payment_enabled")
.eq("brand_id", cust.brand_id)
@@ -327,8 +327,8 @@ export default function WholesalePortalPage() {
return;
}
const { supabase } = await import("@/lib/supabase");
const { data: ws } = await supabase
const { api } = await import("@/lib/api");
const { data: ws } = await api
.from("wholesale_settings")
.select("wholesale_enabled, online_payment_enabled")
.eq("brand_id", cust.brand_id)
@@ -349,8 +349,8 @@ export default function WholesalePortalPage() {
}, [router]);
async function loadBrandName(brandId: string) {
const { supabase } = await import("@/lib/supabase");
const { data: brand } = await supabase
const { api } = await import("@/lib/api");
const { data: brand } = await api
.from("brands")
.select("name")
.eq("id", brandId)
+2 -2
View File
@@ -48,8 +48,8 @@ export default function WholesaleRegisterPage() {
useEffect(() => {
async function checkEnabled(brandId: string) {
const { supabase } = await import("@/lib/supabase");
const { data: ws } = await supabase
const { api } = await import("@/lib/api");
const { data: ws } = await api
.from("wholesale_settings")
.select("wholesale_enabled")
.eq("brand_id", brandId)