This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ShopCall.ai is an AI-powered calling system integrated with e-commerce platforms. The project consists of:
shopcall/
├── shopcall.ai-main/ # Frontend application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── context/ # React context providers (AuthContext)
│ │ │ └── ui/ # shadcn-ui component library
│ │ ├── pages/ # Route pages (Dashboard, Login, CallLogs, etc.)
│ │ ├── hooks/ # Custom React hooks
│ │ ├── i18n/ # Internationalization (i18next)
│ │ ├── lib/ # Utility functions
│ │ └── App.tsx # Main application with routing
│ └── package.json
└── supabase/ # Supabase Edge Functions
└── functions/ # Backend API functions
└── _shared/ # Shared utilities across functions
cd shopcall.ai-main
npm install # Install dependencies
npm run dev # Start dev server on port 8080
npm run build # Production build
npm run build:dev # Development build
npm run lint # Run ESLint
npm run preview # Preview production build
cd supabase
# Deploy Edge Functions
npx supabase functions deploy <function-name>
# Deploy with project ref
npx supabase functions deploy <function-name> --project-ref <project-ref>
# Test locally
npx supabase functions serve
# Login (if needed)
npx supabase login
# Link to project (if needed)
npx supabase link --project-ref <project-ref>
Some Edge Functions have built-in JWT/auth verification and require Supabase's default JWT verification to be disabled in config.toml. This is configured with verify_jwt = false.
| Function | Reason for Disabled Supabase JWT |
|---|---|
auth |
Handles login/signup - no token yet |
shopify-oauth |
OAuth callback from Shopify |
woocommerce-oauth |
OAuth callback from WooCommerce |
oauth-shoprenter-init |
OAuth initiation for ShopRenter |
oauth-shoprenter-callback |
OAuth callback from ShopRenter |
webhook-shoprenter-uninstall |
Webhook from ShopRenter (HMAC verified) |
validate-shoprenter-hmac |
HMAC validation endpoint |
gdpr-webhooks |
GDPR webhooks from platforms |
shop-data-api |
Public API with custom API key auth |
woocommerce-scheduled-sync |
Internal scheduled sync (service role) |
shoprenter-scheduled-sync |
Internal scheduled sync (service role) |
Important: When adding new Edge Functions that handle OAuth callbacks, webhooks, or have custom authentication, add them to supabase/config.toml with verify_jwt = false.
Authentication Flow:
src/components/context/AuthContext.tsx) manages global auth statesession_dataRouting Structure:
/ (Index) → Landing page
/signup → User registration
/login → User login
/otp → OTP verification
/dashboard (protected) → Main dashboard
/call-logs (protected) → Call history
/analytics (protected) → Analytics dashboard
/webshops (protected) → E-commerce integrations
/phone-numbers (protected) → Phone number management
/ai-config (protected) → AI configuration
/onboarding (protected) → User onboarding flow
/about, /privacy, /terms → Public pages
Component Organization:
src/pages/ handle routingDashboardContent.tsx) contain page logicsrc/components/ui/ are reusable shadcn componentsApp.tsxInternationalization (i18n):
src/i18n/config.tssrc/i18n/locales/ (JSON files per language)i18next with react-i18next and browser language detectionuseTranslation() hook: const { t } = useTranslation()Supabase Edge Functions: Backend logic implemented as serverless Edge Functions
Authentication:
E-commerce Integrations:
oauth-shopify (OAuth 2.0 with HMAC verification)webhooks-shopify (customers/data_request, customers/redact, shop/redact)_shared/shopify-client.tsshopify-sync (manual sync for products, orders, customers)shopify-scheduled-sync (automated via pg_cron) - NOT YET IMPLEMENTEDoauth-woocommerce with action connect_manual_shared/woocommerce-client.tswoocommerce-sync (manual sync for products, orders, customers)woocommerce-scheduled-sync (automated via pg_cron, hourly)/wp-json/wc/v3/)/wc-api/v*) are deprecated and NOT supportedoauth-shoprenter-init, oauth-shoprenter-callbackwebhook-shoprenter-uninstallshoprenter-products, shoprenter-orders, shoprenter-customersshoprenter-syncshoprenter-scheduled-sync (automated via pg_cron, hourly)_shared/shoprenter-client.tsstores tableMCP Server Functions (Model Context Protocol):
mcp-shopify - MCP server for Shopify data accessmcp-woocommerce - MCP server for WooCommerce data accessmcp-shoprenter - MCP server for ShopRenter data access_shared/mcp-*.ts (types, helpers, SSE)VAPI Integration (Voice AI):
vapi-webhook - Receives end-of-call reports from VAPIcall_logs tabledocs/VAPI_INTEGRATION.md for detailed documentationCustom Content Management:
custom-content-create - Create new custom content entriescustom-content-upload - Upload files (PDF, documents)custom-content-process - Process and vectorize contentcustom-content-list - List user's custom contentcustom-content-view - View content detailscustom-content-delete - Delete contentcustom-content-retry - Retry failed processingcustom-content-sync-status - Check processing statusAI Context & Vector Search:
get-ai-context - Build AI context from store data and custom content_shared/qdrant-client.ts)_shared/text-chunker.ts)_shared/pdf-processor.ts)Shared Utilities (supabase/functions/_shared/):
cors.ts - CORS headers handlingerror-handler.ts - Standardized error responsesunified-response.ts - Consistent API response formatapi-key-auth.ts - API key authenticationplatform-adapters.ts - E-commerce platform data normalizationaccess-policy-helpers.ts - GDPR data access policy enforcementstores table:
- id: UUID (primary key)
- user_id: UUID (FK to profiles.id)
- platform_name: text (e.g., 'shopify', 'woocommerce', 'shoprenter')
- store_name: text
- store_url: text
- api_key: text (Consumer Key for WooCommerce, Access Token for ShopRenter/Shopify)
- api_secret: text (Consumer Secret for WooCommerce, Refresh Token for ShopRenter)
- access_token: text (for platforms using separate access_token field)
- refresh_token: text (for platforms using separate refresh_token field)
- token_expires_at: timestamp (token expiration time)
- scopes: text[]
- alt_data: jsonb (platform-specific data, e.g., expires_at, last_sync_at, wcVersion, wpVersion)
- connected_at: timestamp (when store was connected)
- is_active: boolean (default: true)
- phone_number: text
- package: text
- sync_status: text ('idle', 'syncing', 'completed', 'error')
- sync_started_at: timestamptz (when current/last sync started)
- sync_completed_at: timestamptz (when last sync completed)
- sync_error: text (error message from last failed sync)
- created_at: timestamp
- updated_at: timestamp
oauth_states table (for OAuth flow state management):
- id: UUID (primary key)
- state: text (UUID for CSRF protection, unique)
- user_id: UUID (FK to auth.users, nullable)
- platform: text (e.g., 'shopify', 'woocommerce', 'shoprenter')
- shopname: text (nullable)
- expires_at: timestamptz
- created_at: timestamptz
pending_shoprenter_installs table (temporary storage during OAuth):
- id: UUID (primary key)
- installation_id: text (UUID, unique)
- shopname: text
- access_token: text
- refresh_token: text (nullable)
- token_type: text (nullable, default: 'Bearer')
- expires_in: integer (nullable)
- scopes: text[] (nullable)
- expires_at: timestamptz
- created_at: timestamptz
oauth_nonces table (stores OAuth nonce values for OAuth flows):
- id: UUID (primary key)
- nonce: text (unique)
- user_id: UUID (FK to auth.users, nullable)
- platform: text
- shop: text (nullable)
- app_url: text (nullable)
- shopname: text (nullable)
- created_at: timestamptz
- expires_at: timestamptz
pending_signups table (stores pending signup data with OTP for email verification):
- id: UUID (primary key)
- signup_id: text (unique)
- email: text
- password: text
- full_name: text
- company_name: text
- user_name: text
- otp: text
- created_at: timestamptz
- expires_at: timestamptz (expires after 15 minutes)
profiles table (user profile information):
- id: UUID (primary key, FK to auth.users)
- full_name: text (nullable)
- username: text (nullable, unique)
- email: text (nullable)
- company_name: text (nullable)
- is_verified: boolean (nullable)
- created_at: timestamp (default: now())
- updated_at: timestamp (default: now())
shoprenter_products_cache table (cached product data):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- shoprenter_product_id: varchar
- product_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- updated_at: timestamptz
shoprenter_tokens table (ShopRenter token management):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- access_token: text
- refresh_token: text (nullable)
- expires_at: timestamptz (nullable)
- scopes: text[] (nullable)
- shopname: varchar
- shop_domain: varchar
- is_active: boolean (default: true)
- last_sync_at: timestamptz (nullable)
- created_at: timestamptz
- updated_at: timestamptz
shoprenter_webhooks table (webhook registrations):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- webhook_id: varchar
- topic: varchar
- webhook_url: text
- is_active: boolean (default: true)
- created_at: timestamptz
- updated_at: timestamptz
woocommerce_products_cache table (cached WooCommerce products):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- wc_product_id: text
- name: text
- sku: text
- price: decimal
- currency: text
- description: text
- short_description: text
- stock_quantity: integer
- stock_status: text
- type: text ('simple', 'variable', 'grouped', 'external')
- categories: jsonb
- images: jsonb
- raw_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- UNIQUE(store_id, wc_product_id)
woocommerce_orders_cache table (cached WooCommerce orders):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- wc_order_id: text
- order_number: text
- status: text
- total: decimal
- currency: text
- customer_name: text
- customer_email: text
- line_items: jsonb
- billing_address: jsonb
- shipping_address: jsonb
- created_at: timestamptz
- raw_data: jsonb
- last_synced_at: timestamptz
- UNIQUE(store_id, wc_order_id)
woocommerce_customers_cache table (cached WooCommerce customers):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- wc_customer_id: text
- email: text
- first_name: text
- last_name: text
- username: text
- billing_address: jsonb
- shipping_address: jsonb
- orders_count: integer
- total_spent: decimal
- raw_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- UNIQUE(store_id, wc_customer_id)
shopify_products_cache table (cached Shopify products):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- shopify_product_id: text
- title: text
- handle: text
- vendor: text
- product_type: text
- status: text
- price: decimal
- compare_at_price: decimal
- currency: text
- sku: text
- inventory_quantity: integer
- description: text
- images: jsonb
- variants: jsonb
- options: jsonb
- tags: text[]
- raw_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- UNIQUE(store_id, shopify_product_id)
shopify_orders_cache table (cached Shopify orders):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- shopify_order_id: text
- order_number: text
- name: text (e.g., "#1001")
- email: text
- phone: text
- financial_status: text
- fulfillment_status: text
- total_price: decimal
- subtotal_price: decimal
- total_tax: decimal
- currency: text
- customer_name: text
- customer_email: text
- line_items: jsonb
- billing_address: jsonb
- shipping_address: jsonb
- note: text
- tags: text[]
- order_created_at: timestamptz
- order_updated_at: timestamptz
- raw_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- UNIQUE(store_id, shopify_order_id)
shopify_customers_cache table (cached Shopify customers):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- shopify_customer_id: text
- email: text
- first_name: text
- last_name: text
- phone: text
- accepts_marketing: boolean
- orders_count: integer
- total_spent: decimal
- currency: text
- state: text (enabled, disabled, declined, invited)
- addresses: jsonb
- default_address: jsonb
- tags: text[]
- note: text
- customer_created_at: timestamptz
- customer_updated_at: timestamptz
- raw_data: jsonb
- last_synced_at: timestamptz
- created_at: timestamptz
- UNIQUE(store_id, shopify_customer_id)
gdpr_requests table (GDPR compliance tracking):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- request_type: text ('data_request', 'customer_redact', 'shop_redact')
- customer_id: text
- shop_domain: text
- request_payload: jsonb
- status: text ('pending', 'processing', 'completed', 'failed')
- processed_at: timestamptz
- error_message: text
- created_at: timestamptz
- updated_at: timestamptz
shopify_webhooks table (webhook registration tracking):
- id: UUID (primary key)
- store_id: UUID (FK to stores)
- shopify_webhook_id: text
- topic: text (e.g., 'orders/create', 'products/update')
- address: text (webhook callback URL)
- format: text (default: 'json')
- is_active: boolean (default: true)
- last_received_at: timestamptz (nullable)
- total_received: integer (default: 0)
- created_at: timestamptz
- updated_at: timestamptz
- UNIQUE(store_id, shopify_webhook_id)
system_config table (system-wide configuration):
- key: text (primary key)
- value: text
- description: text (nullable)
- created_at: timestamptz
- updated_at: timestamptz
call_logs table (call history and analytics):
- id: UUID (primary key)
- user_id: UUID (FK to auth.users, nullable)
- contact_id: UUID (nullable)
- workspace_id: UUID
- status: call_status_enum (pending, scheduled, in_progress, completed, failed, cancelled)
- attempt_number: integer
- scheduled_at: timestamptz (nullable)
- started_at: timestamptz (nullable)
- ended_at: timestamptz (nullable)
- duration_seconds: integer (nullable)
- transcript: text (nullable)
- summary: text (nullable)
- call_outcome: call_outcome_enum (pending, resolved, interested, potential, not_interested, no_answer, voicemail, busy, callback_requested, false)
- assistant_id: text (nullable)
- assistant_name: text (nullable)
- callprovider_id: text (nullable)
- phone_number_id: text (nullable)
- customer_number: text (nullable)
- assistant_phone_number: text (nullable)
- call_type: call_type_enum (inbound, outbound, inbound_dummy)
- cost_stt: numeric (nullable)
- cost_llm: numeric (nullable)
- cost_tts: numeric (nullable)
- cost_total: numeric (nullable)
- cost_twilio: numeric (nullable)
- cost_totals: numeric (nullable)
- ended_reason: text (nullable)
- recording_url: text (nullable)
- created_at: timestamptz
- updated_at: timestamptz
sync_logs table (scheduled sync execution logs):
- id: UUID (primary key)
- sync_type: text ('manual', 'scheduled', 'webhook')
- platform: text ('shopify', 'woocommerce', 'shoprenter')
- stores_processed: integer
- results: jsonb (detailed results per store)
- started_at: timestamptz
- completed_at: timestamptz
- created_at: timestamptz
store_sync_config table (per-store sync configuration):
- id: UUID (primary key)
- store_id: UUID (FK to stores, unique)
- enabled: boolean (default: true)
- sync_frequency: text ('15min', '30min', 'hourly', '6hours', 'daily')
- last_sync_at: timestamptz
- next_sync_at: timestamptz (auto-calculated)
- products_access_policy: data_access_policy enum ('sync', 'api_only', 'not_allowed') - GDPR-compliant access control
- customers_access_policy: data_access_policy enum ('sync', 'api_only', 'not_allowed') - GDPR-compliant access control
- orders_access_policy: data_access_policy enum ('sync', 'api_only', 'not_allowed') - GDPR-compliant access control
- created_at: timestamptz
- updated_at: timestamptz
shopcall.ai-main/.env.example to .env and fill in your valuessupabase/.env.example to .env and fill in your values.env (shopcall.ai-main)Required environment variables:
# Supabase Configuration (from Supabase Dashboard → Settings → API)
VITE_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
# Backend API Base URL (typically VITE_SUPABASE_URL + /functions/v1)
VITE_API_URL=https://YOUR_PROJECT.supabase.co/functions/v1
# Frontend URL (for OAuth callbacks)
VITE_FRONTEND_URL=http://localhost:8080 # or https://yourdomain.com for production
# GDPR Compliance Settings (optional)
# Hide data access policy settings from store owners for stricter GDPR compliance
# When set to 'true', the corresponding settings will be hidden in the UI
VITE_HIDE_ORDERS_ACCESS_SETTINGS=true # Hide orders access policy settings
VITE_HIDE_CUSTOMERS_ACCESS_SETTINGS=true # Hide customers access policy settings
Note: VITE_API_URL is derived from VITE_SUPABASE_URL by appending /functions/v1
GDPR Settings: When VITE_HIDE_ORDERS_ACCESS_SETTINGS and VITE_HIDE_CUSTOMERS_ACCESS_SETTINGS are set to true, store owners won't see these settings in the UI, and new stores will default to api_only mode (direct API access, no local caching of PII).
.env (supabase)Required environment variables:
# Supabase Configuration (from Supabase Dashboard → Settings → API)
SUPABASE_URL=https://YOUR_PROJECT.supabase.co
SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
# Frontend URL (same as VITE_FRONTEND_URL in frontend .env)
FRONTEND_URL=http://localhost:8080 # or https://yourdomain.com for production
# E-commerce Platform OAuth Credentials
SHOPIFY_API_KEY=your_shopify_api_key
SHOPIFY_API_SECRET=your_shopify_api_secret
SHOPRENTER_CLIENT_ID=your_shoprenter_client_id
SHOPRENTER_CLIENT_SECRET=your_shoprenter_client_secret
# Internal Security (generate with: openssl rand -hex 32)
INTERNAL_SYNC_SECRET=your_random_secure_secret
# Email Configuration (from https://resend.com/api-keys)
RESEND_API_KEY=your_resend_api_key
CRITICAL: Configure in Supabase Dashboard → Project Settings → Database → Custom Postgres Configuration:
app.internal_sync_secret = 'same_as_INTERNAL_SYNC_SECRET_above'
app.supabase_url = 'https://YOUR_PROJECT.supabase.co'
Why this is required: The pg_cron jobs run database-side trigger functions that need to make HTTP requests to Edge Functions. These settings allow the trigger functions to authenticate and call the scheduled sync Edge Functions.
Without these settings:
To verify settings are configured:
SELECT
current_setting('app.internal_sync_secret', true) as internal_secret,
current_setting('app.supabase_url', true) as supabase_url;
Both values should be non-NULL.
npm run build generates static files in dist/supabase functions deploy commandIMPORTANT: When working with Supabase in Claude Code, ALWAYS use the Supabase MCP tools instead of CLI commands. These tools provide direct integration with Supabase without requiring manual authentication.
Edge Functions Management:
mcp__supabase__list_edge_functions - List all deployed Edge Functionsmcp__supabase__deploy_edge_function - Deploy an Edge Function (with files array)mcp__supabase__get_edge_function - Get Edge Function codeDatabase Operations:
mcp__supabase__list_tables - List database tablesmcp__supabase__list_extensions - List database extensionsmcp__supabase__list_migrations - List all migrationsmcp__supabase__apply_migration - Apply a new migration (DDL operations)mcp__supabase__execute_sql - Execute raw SQL queriesProject Information:
mcp__supabase__get_project_url - Get the project API URLmcp__supabase__get_anon_key - Get the anonymous API keymcp__supabase__generate_typescript_types - Generate TypeScript types from databaseMonitoring & Debugging:
mcp__supabase__get_logs - Get logs for Edge Functions or other servicesmcp__supabase__get_advisors - Check for security/performance issuesBranching (Development):
mcp__supabase__create_branch - Create a development branchmcp__supabase__list_branches - List all branchesmcp__supabase__merge_branch - Merge branch to productionmcp__supabase__delete_branch - Delete a development branchCheck Edge Function logs when debugging deployment issues:
mcp__supabase__get_logs(service: "edge-function")
Monitor deployments by listing Edge Functions after deployment
Environment variables are configured via .env files in both frontend and supabase directories
For production deployment, ensure both .env files are properly configured
Deploying Edge Functions via CLI:
cd supabase
# Deploy a specific function
npx supabase functions deploy <function-name> --project-ref <project-ref>
# Deploy multiple functions
npx supabase functions deploy api oauth-woocommerce oauth-shopify --project-ref <project-ref>
Checking Deployment Status:
Use the MCP tool mcp__supabase__list_edge_functions to verify deployment status and version numbers.
Debugging 404/500 Errors:
mcp__supabase__list_edge_functionsmcp__supabase__get_logs(service: "edge-function")supabase/.envWhen making changes:
shopcall.ai-main/src/supabase/functions/<PrivateRoute> wrappersupabase/functions/IMPORTANT: After modifying any Supabase Edge Function code, you MUST redeploy the function to Supabase for the changes to take effect!
Steps to deploy Edge Functions:
Via MCP Tools (PREFERRED for Claude Code):
Use mcp__supabase__deploy_edge_function with:
- name: function name (e.g., "trigger-sync", "api", "oauth-shopify")
- files: array of files with name and content properties
Example:
mcp__supabase__deploy_edge_function(
name: "trigger-sync",
files: [{"name": "index.ts", "content": "..."}]
)
Benefits: No manual authentication, automatic project selection, works directly in Claude Code
Via CLI (alternative, requires manual authentication):
cd supabase
# Deploy a specific function
npx supabase functions deploy <function-name> --project-ref <project-ref>
# Deploy multiple functions at once
npx supabase functions deploy oauth-woocommerce oauth-shopify api --project-ref <project-ref>
Verify deployment:
mcp__supabase__list_edge_functionsmcp__supabase__get_logs(service: "edge-function")Common mistake: Forgetting to redeploy after code changes means the old version continues running in production!
Which functions to deploy:
oauth-woocommerce/index.ts), deploy that function_shared/woocommerce-client.ts), deploy ALL functions that import itstores tableFrontend uses TypeScript path alias:
@/ → ./src/Example: import { Button } from "@/components/ui/button"
The project includes automated background sync capabilities for ShopRenter and WooCommerce using PostgreSQL's pg_cron extension. Shopify scheduled sync is prepared in the migration but not yet implemented.
How it works:
sync_logs tableImplemented Scheduled Syncs:
shoprenter-scheduled-sync (runs hourly at minute 0)woocommerce-scheduled-sync (runs hourly at minute 5)Sync Frequencies (configurable per store in store_sync_config):
15min - Every 15 minutes (high-frequency updates)30min - Every 30 minuteshourly - Every hour (default, recommended)6hours - Every 6 hoursdaily - Once per dayConfiguration:
store_sync_config tableMonitoring:
sync_logs table tracks all sync executionssync_status, sync_started_at, sync_completed_at, sync_error fieldsSetup Requirements:
supabase/migrations/20250129_shoprenter_scheduled_sync.sqlsupabase/migrations/20251030_woocommerce_scheduled_sync.sqlsupabase/migrations/20251030_shopify_integration.sqlsupabase functions deploy shoprenter-scheduled-syncsupabase functions deploy woocommerce-scheduled-syncsupabase functions deploy shopify-scheduled-sync (when implemented)INTERNAL_SYNC_SECRET in Edge Functions environmentsync_logs table for execution resultsManual Control:
-- Enable/disable sync for a store
SELECT set_store_sync_enabled('store-uuid', true);
-- Change sync frequency
SELECT set_store_sync_frequency('store-uuid', 'hourly');
-- View recent sync logs
SELECT * FROM sync_logs ORDER BY created_at DESC LIMIT 10;
-- View sync statistics (if view exists)
SELECT * FROM sync_statistics;
-- Check current pg_cron jobs
SELECT * FROM cron.job;
Security:
INTERNAL_SYNC_SECRET prevents unauthorized sync triggersTrigger Functions:
trigger_shoprenter_scheduled_sync() - Triggers ShopRenter synctrigger_woocommerce_scheduled_sync() - Triggers WooCommerce synctrigger_shopify_scheduled_sync() - Triggers Shopify sync (when implemented)