403 lines
11 KiB
TypeScript
403 lines
11 KiB
TypeScript
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
import type { Database } from './types';
|
|
|
|
const supabaseUrl = import.meta.env.SUPABASE_URL;
|
|
const supabaseAnonKey = import.meta.env.SUPABASE_ANON_KEY;
|
|
|
|
if (!supabaseUrl || !supabaseAnonKey) {
|
|
console.warn('Supabase credentials not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY in .env');
|
|
}
|
|
|
|
export const supabase: SupabaseClient<Database> = createClient(
|
|
supabaseUrl || 'http://localhost:54321',
|
|
supabaseAnonKey || 'placeholder-anon-key',
|
|
{
|
|
auth: {
|
|
persistSession: true,
|
|
autoRefreshToken: true,
|
|
},
|
|
}
|
|
);
|
|
|
|
// Admin client with service role key - bypasses RLS
|
|
export const supabaseAdmin: SupabaseClient<Database> = createClient(
|
|
supabaseUrl || 'http://localhost:54321',
|
|
supabaseAnonKey || 'placeholder-anon-key',
|
|
{
|
|
auth: {
|
|
persistSession: false,
|
|
},
|
|
}
|
|
);
|
|
|
|
// Database types for Supabase
|
|
export interface Database {
|
|
public: {
|
|
Tables: {
|
|
users: {
|
|
Row: {
|
|
id: string;
|
|
email: string;
|
|
name: string | null;
|
|
role: 'customer' | 'vendor' | 'admin';
|
|
avatar_url: string | null;
|
|
phone: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
email: string;
|
|
name?: string | null;
|
|
role?: 'customer' | 'vendor' | 'admin';
|
|
avatar_url?: string | null;
|
|
phone?: string | null;
|
|
};
|
|
Update: {
|
|
id?: string;
|
|
email?: string;
|
|
name?: string | null;
|
|
role?: 'customer' | 'vendor' | 'admin';
|
|
avatar_url?: string | null;
|
|
phone?: string | null;
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
profiles: {
|
|
Row: {
|
|
id: string;
|
|
user_id: string;
|
|
first_name: string | null;
|
|
last_name: string | null;
|
|
phone: string | null;
|
|
address: string | null;
|
|
city: string | null;
|
|
province: string | null;
|
|
postal_code: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
user_id: string;
|
|
first_name?: string | null;
|
|
last_name?: string | null;
|
|
phone?: string | null;
|
|
address?: string | null;
|
|
city?: string | null;
|
|
province?: string | null;
|
|
postal_code?: string | null;
|
|
};
|
|
Update: {
|
|
first_name?: string | null;
|
|
last_name?: string | null;
|
|
phone?: string | null;
|
|
address?: string | null;
|
|
city?: string | null;
|
|
province?: string | null;
|
|
postal_code?: string | null;
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
categories: {
|
|
Row: {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
description: string | null;
|
|
image_url: string | null;
|
|
parent_id: string | null;
|
|
sort_order: number;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
name: string;
|
|
slug: string;
|
|
description?: string | null;
|
|
image_url?: string | null;
|
|
parent_id?: string | null;
|
|
sort_order?: number;
|
|
is_active?: boolean;
|
|
};
|
|
Update: {
|
|
name?: string;
|
|
slug?: string;
|
|
description?: string | null;
|
|
image_url?: string | null;
|
|
parent_id?: string | null;
|
|
sort_order?: number;
|
|
is_active?: boolean;
|
|
};
|
|
};
|
|
products: {
|
|
Row: {
|
|
id: string;
|
|
vendor_id: string | null;
|
|
category_id: string | null;
|
|
name: string;
|
|
slug: string;
|
|
description: string | null;
|
|
price: number;
|
|
compare_at_price: number | null;
|
|
sku: string | null;
|
|
barcode: string | null;
|
|
inventory: number;
|
|
images: string[];
|
|
status: 'draft' | 'active' | 'archived';
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
vendor_id?: string | null;
|
|
category_id?: string | null;
|
|
name: string;
|
|
slug: string;
|
|
description?: string | null;
|
|
price: number;
|
|
compare_at_price?: number | null;
|
|
sku?: string | null;
|
|
barcode?: string | null;
|
|
inventory?: number;
|
|
images?: string[];
|
|
status?: 'draft' | 'active' | 'archived';
|
|
};
|
|
Update: {
|
|
vendor_id?: string | null;
|
|
category_id?: string | null;
|
|
name?: string;
|
|
slug?: string;
|
|
description?: string | null;
|
|
price?: number;
|
|
compare_at_price?: number | null;
|
|
sku?: string | null;
|
|
barcode?: string | null;
|
|
inventory?: number;
|
|
images?: string[];
|
|
status?: 'draft' | 'active' | 'archived';
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
orders: {
|
|
Row: {
|
|
id: string;
|
|
order_number: string;
|
|
user_id: string | null;
|
|
guest_email: string | null;
|
|
status: 'pending' | 'confirmed' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
|
|
payment_status: 'pending' | 'paid' | 'failed' | 'refunded';
|
|
subtotal: number;
|
|
tax: number;
|
|
shipping: number;
|
|
discount: number;
|
|
total: number;
|
|
shipping_name: string | null;
|
|
shipping_phone: string | null;
|
|
shipping_address: string | null;
|
|
shipping_city: string | null;
|
|
shipping_province: string | null;
|
|
shipping_postal_code: string | null;
|
|
notes: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
order_number: string;
|
|
user_id?: string | null;
|
|
guest_email?: string | null;
|
|
status?: 'pending' | 'confirmed' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
|
|
payment_status?: 'pending' | 'paid' | 'failed' | 'refunded';
|
|
subtotal: number;
|
|
tax: number;
|
|
shipping: number;
|
|
discount?: number;
|
|
total: number;
|
|
shipping_name?: string | null;
|
|
shipping_phone?: string | null;
|
|
shipping_address?: string | null;
|
|
shipping_city?: string | null;
|
|
shipping_province?: string | null;
|
|
shipping_postal_code?: string | null;
|
|
notes?: string | null;
|
|
};
|
|
Update: {
|
|
status?: 'pending' | 'confirmed' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
|
|
payment_status?: 'pending' | 'paid' | 'failed' | 'refunded';
|
|
subtotal?: number;
|
|
tax?: number;
|
|
shipping?: number;
|
|
discount?: number;
|
|
total?: number;
|
|
shipping_name?: string | null;
|
|
shipping_phone?: string | null;
|
|
shipping_address?: string | null;
|
|
shipping_city?: string | null;
|
|
shipping_province?: string | null;
|
|
shipping_postal_code?: string | null;
|
|
notes?: string | null;
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
order_items: {
|
|
Row: {
|
|
id: string;
|
|
order_id: string;
|
|
product_id: string;
|
|
product_name: string;
|
|
product_image: string | null;
|
|
price: number;
|
|
quantity: number;
|
|
total: number;
|
|
created_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
order_id: string;
|
|
product_id: string;
|
|
product_name: string;
|
|
product_image?: string | null;
|
|
price: number;
|
|
quantity: number;
|
|
total: number;
|
|
};
|
|
Update: {
|
|
product_name?: string;
|
|
product_image?: string | null;
|
|
price?: number;
|
|
quantity?: number;
|
|
total?: number;
|
|
};
|
|
};
|
|
cart_items: {
|
|
Row: {
|
|
id: string;
|
|
user_id: string;
|
|
product_id: string;
|
|
quantity: number;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
user_id: string;
|
|
product_id: string;
|
|
quantity: number;
|
|
};
|
|
Update: {
|
|
quantity?: number;
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
payments: {
|
|
Row: {
|
|
id: string;
|
|
order_id: string;
|
|
method: 'promptpay' | 'credit_card' | 'bank_transfer' | 'cash_on_delivery';
|
|
amount: number;
|
|
status: 'pending' | 'processing' | 'completed' | 'failed' | 'refunded';
|
|
reference: string | null;
|
|
metadata: Record<string, unknown> | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
Insert: {
|
|
id?: string;
|
|
order_id: string;
|
|
method: 'promptpay' | 'credit_card' | 'bank_transfer' | 'cash_on_delivery';
|
|
amount: number;
|
|
status?: 'pending' | 'processing' | 'completed' | 'failed' | 'refunded';
|
|
reference?: string | null;
|
|
metadata?: Record<string, unknown> | null;
|
|
};
|
|
Update: {
|
|
status?: 'pending' | 'processing' | 'completed' | 'failed' | 'refunded';
|
|
reference?: string | null;
|
|
metadata?: Record<string, unknown> | null;
|
|
updated_at?: string;
|
|
};
|
|
};
|
|
};
|
|
Views: {
|
|
[_ in never]: never;
|
|
};
|
|
Functions: {
|
|
[_ in never]: never;
|
|
};
|
|
Enums: {
|
|
[_ in never]: never;
|
|
};
|
|
};
|
|
}
|
|
|
|
// Helper functions for common queries
|
|
export async function getProductBySlug(slug: string) {
|
|
const { data, error } = await supabase
|
|
.from('products')
|
|
.select('*, categories(*)')
|
|
.eq('slug', slug)
|
|
.eq('status', 'active')
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
|
|
export async function getProductsByCategory(categorySlug: string, limit = 20, offset = 0) {
|
|
const { data, error } = await supabase
|
|
.from('products')
|
|
.select('*, categories(*)')
|
|
.eq('categories.slug', categorySlug)
|
|
.eq('status', 'active')
|
|
.range(offset, offset + limit - 1);
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
|
|
export async function getFeaturedProducts(limit = 10) {
|
|
const { data, error } = await supabase
|
|
.from('products')
|
|
.select('*, categories(*)')
|
|
.eq('status', 'active')
|
|
.order('created_at', { ascending: false })
|
|
.limit(limit);
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
|
|
export async function getUserOrders(userId: string) {
|
|
const { data, error } = await supabase
|
|
.from('orders')
|
|
.select('*, order_items(*, products(*))')
|
|
.eq('user_id', userId)
|
|
.order('created_at', { ascending: false });
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
|
|
export async function getOrderByNumber(orderNumber: string) {
|
|
const { data, error } = await supabase
|
|
.from('orders')
|
|
.select('*, order_items(*, products(*))')
|
|
.eq('order_number', orderNumber)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|
|
|
|
export async function updateProductInventory(productId: string, quantity: number) {
|
|
const { data, error } = await supabase.rpc('decrement_inventory', {
|
|
product_id: productId,
|
|
quantity: quantity,
|
|
});
|
|
|
|
if (error) throw error;
|
|
return data;
|
|
}
|