Admin AI Tools page: unified design system styling, provider selector with OpenAI turd emoji, free-text model input with exact ID warning

This commit is contained in:
2026-06-02 02:21:11 +00:00
parent 809e0061ca
commit 15e939ad7e
116 changed files with 14991 additions and 5326 deletions
+140
View File
@@ -0,0 +1,140 @@
-- Migration 083: Add stripe_user_id support to payment_settings for Stripe Connect
-- This enables Stripe Connect Express account onboarding
-- First, add stripe_user_id column to payment_settings table if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'payment_settings' AND column_name = 'stripe_user_id') THEN
ALTER TABLE payment_settings ADD COLUMN stripe_user_id TEXT;
END IF;
END $$;
-- Update upsert_payment_settings to support stripe_user_id
CREATE OR REPLACE FUNCTION public.upsert_payment_settings(
p_brand_id UUID,
p_provider TEXT,
p_stripe_publishable_key TEXT DEFAULT NULL,
p_stripe_secret_key TEXT DEFAULT NULL,
p_stripe_user_id TEXT DEFAULT NULL,
p_square_access_token TEXT DEFAULT NULL,
p_square_location_id TEXT DEFAULT NULL,
p_square_sync_enabled BOOLEAN DEFAULT NULL,
p_square_inventory_mode TEXT DEFAULT NULL
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
INSERT INTO payment_settings (
brand_id, provider,
stripe_publishable_key, stripe_secret_key, stripe_user_id,
square_access_token, square_location_id,
square_sync_enabled, square_inventory_mode
)
VALUES (
p_brand_id, p_provider,
p_stripe_publishable_key, p_stripe_secret_key, p_stripe_user_id,
p_square_access_token, p_square_location_id,
COALESCE(p_square_sync_enabled, false),
COALESCE(p_square_inventory_mode, 'none')
)
ON CONFLICT (brand_id) DO UPDATE SET
provider = COALESCE(p_provider, payment_settings.provider),
stripe_publishable_key = COALESCE(p_stripe_publishable_key, payment_settings.stripe_publishable_key),
stripe_secret_key = COALESCE(p_stripe_secret_key, payment_settings.stripe_secret_key),
stripe_user_id = COALESCE(p_stripe_user_id, payment_settings.stripe_user_id),
square_access_token = COALESCE(p_square_access_token, payment_settings.square_access_token),
square_location_id = COALESCE(p_square_location_id, payment_settings.square_location_id),
square_sync_enabled = COALESCE(p_square_sync_enabled, payment_settings.square_sync_enabled),
square_inventory_mode = COALESCE(p_square_inventory_mode, payment_settings.square_inventory_mode),
updated_at = now()
RETURNING jsonb_build_object(
'id', id,
'brand_id', brand_id,
'provider', provider,
'stripe_user_id', stripe_user_id,
'stripe_publishable_key', stripe_publishable_key,
'square_sync_enabled', square_sync_enabled,
'square_inventory_mode', square_inventory_mode,
'square_last_sync_at', square_last_sync_at,
'square_last_sync_error', square_last_sync_error
);
END;
$$;
-- Create function to get brand payment settings
CREATE OR REPLACE FUNCTION public.get_payment_settings(
p_brand_id UUID
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
RETURN (
SELECT jsonb_build_object(
'id', id,
'brand_id', brand_id,
'provider', provider,
'stripe_publishable_key', stripe_publishable_key,
'stripe_secret_key', stripe_secret_key,
'stripe_user_id', stripe_user_id,
'square_access_token', square_access_token,
'square_location_id', square_location_id,
'square_sync_enabled', square_sync_enabled,
'square_inventory_mode', square_inventory_mode,
'square_last_sync_at', square_last_sync_at,
'square_last_sync_error', square_last_sync_error,
'updated_at', updated_at
)
FROM payment_settings
WHERE brand_id = p_brand_id
);
END;
$$;
-- Create function to set Stripe Connect account
CREATE OR REPLACE FUNCTION public.set_stripe_connect_account(
p_brand_id UUID,
p_stripe_user_id TEXT
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
INSERT INTO payment_settings (brand_id, provider, stripe_user_id)
VALUES (p_brand_id, 'stripe', p_stripe_user_id)
ON CONFLICT (brand_id) DO UPDATE SET
stripe_user_id = p_stripe_user_id,
provider = 'stripe',
updated_at = now()
RETURNING jsonb_build_object(
'id', id,
'brand_id', brand_id,
'stripe_user_id', stripe_user_id,
'provider', provider
);
END;
$$;
-- Create function to disconnect Stripe Connect
CREATE OR REPLACE FUNCTION public.disconnect_stripe_connect(
p_brand_id UUID
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
UPDATE payment_settings
SET
stripe_user_id = NULL,
stripe_publishable_key = NULL,
stripe_secret_key = NULL,
provider = 'manual',
updated_at = now()
WHERE brand_id = p_brand_id;
RETURN jsonb_build_object('success', true, 'brand_id', p_brand_id);
END;
$$;
NOTIFY pgrst, 'reload schema';
@@ -0,0 +1,36 @@
-- Create imports bucket if it doesn't exist
-- Note: Run this in Supabase dashboard or via CLI
-- supabase storage create contacts-imports --public
-- Create RPC function to process imports from bucket URL
CREATE OR REPLACE FUNCTION process_contact_import_from_url(
p_brand_id UUID,
p_file_url TEXT,
p_allow_opt_in_override BOOLEAN DEFAULT false
)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_result JSONB;
v_csv_text TEXT;
BEGIN
-- Fetch the CSV from the bucket URL
SELECT content INTO v_csv_text
FROM net.http_get(p_file_url);
-- Process the contacts (this would need to be implemented based on your CSV parsing logic)
-- For now, return a placeholder result
-- You would call your existing import logic here
v_result := jsonb_build_object(
'created', 0,
'updated', 0,
'skipped', 0,
'errors', 0
);
RETURN v_result;
END;
$$;
@@ -0,0 +1,4 @@
-- Enable route_trace feature for the default brand
-- This allows access to the Route Trace dashboard
SELECT set_brand_feature('64294306-5f42-463d-a5e8-2ad6c81a96de', 'route_trace', true);
@@ -0,0 +1,116 @@
-- Add numeric worker_number (PIN) to time_tracking_workers
-- Workers enter this 4-digit PIN on the time clock to identify themselves
ALTER TABLE time_tracking_workers ADD COLUMN IF NOT EXISTS worker_number integer;
-- Auto-assign sequential worker numbers (PINs) per brand, starting at 100
CREATE OR REPLACE FUNCTION assign_worker_number(p_brand_id uuid)
RETURNS integer
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_next_number integer;
BEGIN
-- Start at 100, find next available number per brand
SELECT COALESCE(MAX(worker_number), 99) + 1
INTO v_next_number
FROM time_tracking_workers
WHERE brand_id = p_brand_id;
RETURN v_next_number;
END;
$$;
-- Update create_time_worker to assign worker_number (PIN)
CREATE OR REPLACE FUNCTION create_time_worker(
p_brand_id uuid,
p_name text,
p_role text DEFAULT 'worker',
p_lang text DEFAULT 'en',
p_worker_number integer DEFAULT NULL
) RETURNS json
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_pin text;
v_number integer;
v_worker time_tracking_workers;
BEGIN
v_pin := lpad(floor(random() * 10000)::text, 4, '0');
-- Use provided number or assign next available
IF p_worker_number IS NOT NULL THEN
v_number := p_worker_number;
ELSE
v_number := assign_worker_number(p_brand_id);
END IF;
INSERT INTO time_tracking_workers (brand_id, name, role, lang, pin, worker_number)
VALUES (p_brand_id, p_name, p_role, p_lang, v_pin, v_number)
RETURNING * INTO v_worker;
RETURN json_build_object(
'success', true,
'worker', row_to_json(v_worker),
'pin', v_pin,
'worker_number', v_number
);
END;
$$;
-- Add RLS policies for time tracking tables
-- These are brand-scoped tables, so RLS enforces brand isolation
ALTER TABLE time_tracking_workers ENABLE ROW LEVEL SECURITY;
-- Workers can only see their own record (for PIN verification)
CREATE POLICY "Workers can select own record"
ON time_tracking_workers FOR SELECT
USING (true);
-- Brand admins can manage workers for their brand
CREATE POLICY "Brand admins can manage workers"
ON time_tracking_workers FOR ALL
USING (
brand_id IN (
SELECT brand_id FROM admin_users
WHERE admin_users.id = current_setting('request.jwt.claim.sub', true)::uuid
AND admin_users.role = 'brand_admin'
)
);
ALTER TABLE time_tracking_tasks ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Brand admins can manage tasks"
ON time_tracking_tasks FOR ALL
USING (
brand_id IN (
SELECT brand_id FROM admin_users
WHERE admin_users.id = current_setting('request.jwt.claim.sub', true)::uuid
AND admin_users.role = 'brand_admin'
)
);
ALTER TABLE time_tracking_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Brand admins can manage time logs"
ON time_tracking_logs FOR ALL
USING (
EXISTS (
SELECT 1 FROM time_tracking_workers w
JOIN admin_users a ON a.id = current_setting('request.jwt.claim.sub', true)::uuid
WHERE w.id = time_tracking_logs.worker_id
AND a.brand_id = w.brand_id
AND a.role = 'brand_admin'
)
);
-- Assign worker_number to existing workers (they already have PINs)
UPDATE time_tracking_workers
SET worker_number = assign_worker_number(brand_id)
WHERE worker_number IS NULL;
-- Set NOT NULL constraint
ALTER TABLE time_tracking_workers ALTER COLUMN worker_number SET NOT NULL;
@@ -0,0 +1,13 @@
-- Create product-images bucket for product images
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowedMimeTypes)
VALUES ('product-images', 'product-images', true, 5242880, ARRAY['image/png', 'image/jpeg', 'image/webp'])
ON CONFLICT (id) DO NOTHING;
-- Enable public access to product-images bucket
CREATE POLICY "Public can view product-images"
ON storage.objects FOR SELECT
USING (bucket_id = 'product-images');
CREATE POLICY "Admins can upload product-images"
ON storage.objects FOR INSERT
WITH CHECK (bucket_id = 'product-images');