import { createClient, type SupabaseClient } from "@supabase/supabase-js"; import { getMockTableData } from "./mock-data"; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; // Auto-enable mock mode when no Supabase URL is configured (demo/deployment without backend) const useMockData = process.env.NEXT_PUBLIC_USE_MOCK_DATA === "true" || !supabaseUrl || !supabaseUrl.includes("supabase.co"); // Mock query builder that supports all common Supabase methods class MockQueryBuilder { private data: unknown[]; private tableName: string; private filters: { column: string; value: unknown; op: string }[] = []; private selectColumns: string = "*"; private orderColumn?: string; private orderDirection?: "asc" | "desc"; private limitValue?: number; constructor(tableName: string) { this.tableName = tableName; this.data = [...(getMockTableData(tableName) || [])]; } select(columns: string = "*") { this.selectColumns = columns; return this; } eq(column: string, value: unknown) { this.filters.push({ column, value, op: "eq" }); return this; } neq(column: string, value: unknown) { this.filters.push({ column, value, op: "neq" }); return this; } gt(column: string, value: unknown) { this.filters.push({ column, value, op: "gt" }); return this; } gte(column: string, value: unknown) { this.filters.push({ column, value, op: "gte" }); return this; } lt(column: string, value: unknown) { this.filters.push({ column, value, op: "lt" }); return this; } lte(column: string, value: unknown) { this.filters.push({ column, value, op: "lte" }); return this; } is(column: string, value: unknown) { this.filters.push({ column, value, op: "is" }); return this; } like(column: string, value: string) { this.filters.push({ column, value, op: "like" }); return this; } ilike(column: string, value: string) { this.filters.push({ column, value, op: "ilike" }); return this; } in(column: string, values: unknown[]) { this.filters.push({ column, value: values, op: "in" }); return this; } order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean }) { this.orderColumn = column; this.orderDirection = options?.ascending === false ? "desc" : "asc"; return this; } limit(count: number) { this.limitValue = count; return this; } range(min: number, max: number) { // For pagination mock return this; } single() { return this.executeSingle(); } then(resolve: (value: unknown) => void, _reject?: (reason?: unknown) => void) { const result = this.execute(); resolve(result); } async executeSingle() { const result = this.execute(); if (result.data && Array.isArray(result.data) && result.data.length > 0) { return { data: result.data[0], error: null }; } return { data: null, error: null }; } execute() { let filtered = [...this.data]; // Apply filters for (const filter of this.filters) { filtered = filtered.filter((row: any) => { const rowValue = row[filter.column]; switch (filter.op) { case "eq": return rowValue === filter.value; case "neq": return rowValue !== filter.value; case "gt": return (rowValue as number) > (filter.value as number); case "gte": return (rowValue as number) >= (filter.value as number); case "lt": return (rowValue as number) < (filter.value as number); case "lte": return (rowValue as number) <= (filter.value as number); case "is": if (filter.value === null) return rowValue === null; if (filter.value === undefined) return rowValue === undefined; return rowValue === filter.value; case "like": return typeof rowValue === "string" && rowValue.includes((filter.value as string).replace(/%/g, "")); case "ilike": return typeof rowValue === "string" && rowValue.toLowerCase().includes((filter.value as string).replace(/%/g, "").toLowerCase()); case "in": return Array.isArray(filter.value) && filter.value.includes(rowValue); default: return true; } }); } // Apply ordering if (this.orderColumn) { filtered.sort((a: any, b: any) => { const aVal = a[this.orderColumn!]; const bVal = b[this.orderColumn!]; if (aVal < bVal) return this.orderDirection === "desc" ? 1 : -1; if (aVal > bVal) return this.orderDirection === "desc" ? -1 : 1; return 0; }); } // Apply limit if (this.limitValue !== undefined) { filtered = filtered.slice(0, this.limitValue); } return { data: filtered, error: null }; } } // Mock insert/update/delete builders class MockMutationBuilder { private tableName: string; private data: Record | Record[]; constructor(tableName: string, data: Record | Record[]) { this.tableName = tableName; this.data = data; } select() { return new MockQueryBuilder(this.tableName); } then(resolve: (value: unknown) => void) { const items = Array.isArray(this.data) ? this.data : [this.data]; const returning = items.map((item, i) => ({ ...item, id: item.id || `generated-${Date.now()}-${i}`, })); resolve({ data: returning, error: null }); } } // Mock storage builder class MockStorageBuilder { from(bucket: string) { return { upload: async (path: string, _file: unknown) => { return { data: { path }, error: null }; }, download: async (path: string) => { return { data: new Blob(), error: null }; }, remove: async (paths: string[]) => { return { data: { paths }, error: null }; }, list: async () => { return { data: [], error: null }; }, }; } } // Create mock client function createMockClient() { return { from: (table: string) => new MockQueryBuilder(table), insert: (data: Record | Record[]) => new MockMutationBuilder("unknown", data), update: (data: Record) => new MockMutationBuilder("unknown", data), delete: () => ({ eq: () => ({ then: (resolve: (value: unknown) => void) => resolve({ data: null, error: null }) }), in: () => ({ then: (resolve: (value: unknown) => void) => resolve({ data: null, error: null }) }), }), storage: new MockStorageBuilder(), auth: { getSession: async () => ({ data: { session: null }, error: null }), getUser: async () => ({ data: { user: null }, error: null }), signInWithPassword: async () => ({ data: { user: null, session: null }, error: null }), signOut: async () => ({ error: null }), onAuthStateChange: () => ({ data: { subscription: { unsubscribe: () => {} } } }), }, channel: () => ({ on: () => ({ subscribe: () => ({}) }), subscribe: () => ({}), }), }; } // Real Supabase client creation function getSupabase(): SupabaseClient { if (!supabaseUrl || !supabaseAnonKey) { throw new Error( `Missing Supabase env vars: ${[!supabaseUrl && "NEXT_PUBLIC_SUPABASE_URL", !supabaseAnonKey && "NEXT_PUBLIC_SUPABASE_ANON_KEY"].filter(Boolean).join(", ")}. ` + "Check Vercel environment variables for Production environment. " + "Node env: " + (process.env.NODE_ENV ?? "unknown") ); } return createClient(supabaseUrl, supabaseAnonKey); } // Create proxy that routes to real or mock client let realSupabase: SupabaseClient | null = null; if (!useMockData) { try { realSupabase = getSupabase(); } catch { // Will use mock below } } export const supabase: SupabaseClient = useMockData || !realSupabase ? createMockClient() as unknown as SupabaseClient : realSupabase; export { supabaseUrl, supabaseAnonKey, useMockData };