Explorar o código

Initial commit: ShopCall.ai monorepo

This commit includes:
- Frontend application (React + Vite + TypeScript)
- Backend API (Express.js)
- Supabase Edge Functions
- Complete documentation
- Development scripts and configuration

Project structure:
- shopcall.ai-main: Frontend React application
- shopcall.ai-backend-main: Express.js backend API
- supabase: Edge Functions and configuration
- Documentation: CLAUDE.md, deployment guides, TODO tracking
Fszontagh hai 5 meses
achega
9f4449040f
Modificáronse 100 ficheiros con 15753 adicións e 0 borrados
  1. 64 0
      .gitignore
  2. 230 0
      CLAUDE.md
  3. 257 0
      DEPLOYMENT_GUIDE.md
  4. 141 0
      FIX_JWT_ISSUE.md
  5. 882 0
      MISSING_FEATURES.md
  6. 128 0
      README.md
  7. 309 0
      REFACTORING_SUMMARY.md
  8. 1501 0
      SHOPRENTER.md
  9. 255 0
      TODO.md
  10. 18 0
      deploy-functions.sh
  11. 74 0
      fix-jwt.sh
  12. 5 0
      package.json
  13. 36 0
      shopcall.ai-backend-main/.gitignore
  14. 1753 0
      shopcall.ai-backend-main/api/index.js
  15. 21 0
      shopcall.ai-backend-main/package.json
  16. 159 0
      shopcall.ai-backend-main/public/index.html
  17. 602 0
      shopcall.ai-backend-main/public/login.html
  18. 482 0
      shopcall.ai-backend-main/public/signup.html
  19. 4 0
      shopcall.ai-backend-main/vercel.json
  20. 9 0
      shopcall.ai-main/.env.example
  21. 50 0
      shopcall.ai-main/.gitignore
  22. 73 0
      shopcall.ai-main/README.md
  23. 20 0
      shopcall.ai-main/components.json
  24. 29 0
      shopcall.ai-main/eslint.config.js
  25. 22 0
      shopcall.ai-main/index.html
  26. 54 0
      shopcall.ai-main/nginx.conf.example
  27. 83 0
      shopcall.ai-main/package.json
  28. 6 0
      shopcall.ai-main/postcss.config.js
  29. 33 0
      shopcall.ai-main/public/.htaccess
  30. BIN=BIN
      shopcall.ai-main/public/og-image.png
  31. 0 0
      shopcall.ai-main/public/placeholder.svg
  32. 14 0
      shopcall.ai-main/public/robots.txt
  33. BIN=BIN
      shopcall.ai-main/public/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png
  34. BIN=BIN
      shopcall.ai-main/public/uploads/eb42e6bb-fd6a-4d23-b49a-a411466c0265.png
  35. 42 0
      shopcall.ai-main/src/App.css
  36. 62 0
      shopcall.ai-main/src/App.tsx
  37. 261 0
      shopcall.ai-main/src/components/AIConfigContent.tsx
  38. 243 0
      shopcall.ai-main/src/components/AnalyticsContent.tsx
  39. 138 0
      shopcall.ai-main/src/components/AppSidebar.tsx
  40. 180 0
      shopcall.ai-main/src/components/CallDetailsModal.tsx
  41. 257 0
      shopcall.ai-main/src/components/CallLogsContent.tsx
  42. 18 0
      shopcall.ai-main/src/components/ChartsSection.tsx
  43. 21 0
      shopcall.ai-main/src/components/DashboardContent.tsx
  44. 112 0
      shopcall.ai-main/src/components/DashboardHeader.tsx
  45. 386 0
      shopcall.ai-main/src/components/IntegrationsContent.tsx
  46. 116 0
      shopcall.ai-main/src/components/KPICards.tsx
  47. 454 0
      shopcall.ai-main/src/components/LandingPage.tsx
  48. 338 0
      shopcall.ai-main/src/components/OnboardingContent.tsx
  49. 379 0
      shopcall.ai-main/src/components/PhoneNumbersContent.tsx
  50. 87 0
      shopcall.ai-main/src/components/PrivateRoute.tsx
  51. 176 0
      shopcall.ai-main/src/components/RecentCallsTable.tsx
  52. 82 0
      shopcall.ai-main/src/components/ResolutionRateChart.tsx
  53. 231 0
      shopcall.ai-main/src/components/ShopRenterConnect.tsx
  54. 65 0
      shopcall.ai-main/src/components/TopCallIntents.tsx
  55. 123 0
      shopcall.ai-main/src/components/context/AuthContext.tsx
  56. 85 0
      shopcall.ai-main/src/components/context/DashboardContext.tsx
  57. 56 0
      shopcall.ai-main/src/components/ui/accordion.tsx
  58. 139 0
      shopcall.ai-main/src/components/ui/alert-dialog.tsx
  59. 59 0
      shopcall.ai-main/src/components/ui/alert.tsx
  60. 5 0
      shopcall.ai-main/src/components/ui/aspect-ratio.tsx
  61. 48 0
      shopcall.ai-main/src/components/ui/avatar.tsx
  62. 36 0
      shopcall.ai-main/src/components/ui/badge.tsx
  63. 115 0
      shopcall.ai-main/src/components/ui/breadcrumb.tsx
  64. 56 0
      shopcall.ai-main/src/components/ui/button.tsx
  65. 64 0
      shopcall.ai-main/src/components/ui/calendar.tsx
  66. 79 0
      shopcall.ai-main/src/components/ui/card.tsx
  67. 260 0
      shopcall.ai-main/src/components/ui/carousel.tsx
  68. 363 0
      shopcall.ai-main/src/components/ui/chart.tsx
  69. 28 0
      shopcall.ai-main/src/components/ui/checkbox.tsx
  70. 9 0
      shopcall.ai-main/src/components/ui/collapsible.tsx
  71. 153 0
      shopcall.ai-main/src/components/ui/command.tsx
  72. 198 0
      shopcall.ai-main/src/components/ui/context-menu.tsx
  73. 120 0
      shopcall.ai-main/src/components/ui/dialog.tsx
  74. 116 0
      shopcall.ai-main/src/components/ui/drawer.tsx
  75. 198 0
      shopcall.ai-main/src/components/ui/dropdown-menu.tsx
  76. 176 0
      shopcall.ai-main/src/components/ui/form.tsx
  77. 27 0
      shopcall.ai-main/src/components/ui/hover-card.tsx
  78. 69 0
      shopcall.ai-main/src/components/ui/input-otp.tsx
  79. 22 0
      shopcall.ai-main/src/components/ui/input.tsx
  80. 24 0
      shopcall.ai-main/src/components/ui/label.tsx
  81. 234 0
      shopcall.ai-main/src/components/ui/menubar.tsx
  82. 128 0
      shopcall.ai-main/src/components/ui/navigation-menu.tsx
  83. 117 0
      shopcall.ai-main/src/components/ui/pagination.tsx
  84. 30 0
      shopcall.ai-main/src/components/ui/popover.tsx
  85. 26 0
      shopcall.ai-main/src/components/ui/progress.tsx
  86. 42 0
      shopcall.ai-main/src/components/ui/radio-group.tsx
  87. 43 0
      shopcall.ai-main/src/components/ui/resizable.tsx
  88. 46 0
      shopcall.ai-main/src/components/ui/scroll-area.tsx
  89. 158 0
      shopcall.ai-main/src/components/ui/select.tsx
  90. 29 0
      shopcall.ai-main/src/components/ui/separator.tsx
  91. 131 0
      shopcall.ai-main/src/components/ui/sheet.tsx
  92. 761 0
      shopcall.ai-main/src/components/ui/sidebar.tsx
  93. 15 0
      shopcall.ai-main/src/components/ui/skeleton.tsx
  94. 26 0
      shopcall.ai-main/src/components/ui/slider.tsx
  95. 29 0
      shopcall.ai-main/src/components/ui/sonner.tsx
  96. 27 0
      shopcall.ai-main/src/components/ui/switch.tsx
  97. 117 0
      shopcall.ai-main/src/components/ui/table.tsx
  98. 53 0
      shopcall.ai-main/src/components/ui/tabs.tsx
  99. 24 0
      shopcall.ai-main/src/components/ui/textarea.tsx
  100. 127 0
      shopcall.ai-main/src/components/ui/toast.tsx

+ 64 - 0
.gitignore

@@ -0,0 +1,64 @@
+# Dependencies
+node_modules/
+package-lock.json
+bun.lockb
+
+# Environment variables
+.env
+.env.*
+!.env.example
+
+# Build output
+dist/
+dist-ssr/
+build/
+*.local
+
+# Logs
+logs/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# OS files
+.DS_Store
+Thumbs.db
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+
+# IDE files
+.vscode/
+!.vscode/extensions.json
+.idea/
+*.sw?
+*.swp
+*.swo
+*~
+
+# Testing
+coverage/
+*.test.js.snap
+.nyc_output/
+
+# Vercel
+.vercel
+
+# TypeScript
+*.tsbuildinfo
+
+# Temporary files
+.temp/
+tmp/
+temp/
+
+# Supabase
+.branches/
+
+# Lock files (keep only npm)
+yarn.lock
+pnpm-lock.yaml

+ 230 - 0
CLAUDE.md

@@ -0,0 +1,230 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+ShopCall.ai is a dual-repository application for AI-powered calling system integrated with e-commerce platforms. The project consists of:
+
+- **shopcall.ai-main**: React/Vite frontend application with TypeScript
+- **shopcall.ai-backend-main**: Express.js backend API
+
+## Repository Structure
+
+```
+/data/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
+│   │   ├── lib/               # Utility functions
+│   │   └── App.tsx            # Main application with routing
+│   └── package.json
+└── shopcall.ai-backend-main/  # Backend API
+    ├── api/
+    │   └── index.js           # Main Express server (950 lines)
+    ├── public/                # Static files
+    └── package.json
+```
+
+## Development Commands
+
+### Frontend (shopcall.ai-main)
+```bash
+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
+```
+
+### Backend (shopcall.ai-backend-main)
+```bash
+cd shopcall.ai-backend-main
+npm install              # Install dependencies
+npm start                # Start server (node api/index)
+```
+
+## Technology Stack
+
+### Frontend
+- **Framework**: React 18 with Vite
+- **Language**: TypeScript
+- **UI Library**: shadcn-ui with Radix UI primitives
+- **Styling**: Tailwind CSS
+- **Routing**: React Router v6
+- **State Management**: React Query (@tanstack/react-query)
+- **Form Handling**: React Hook Form with Zod validation
+- **Theme**: next-themes for dark mode support
+
+### Backend
+- **Framework**: Express.js v5
+- **Language**: JavaScript (Node.js)
+- **Database**: Supabase (PostgreSQL with auth)
+- **Email**: Nodemailer (Gmail)
+- **CORS**: Enabled for cross-origin requests
+
+## Architecture
+
+### Frontend Architecture
+
+**Authentication Flow**:
+- AuthContext provider (`src/components/context/AuthContext.tsx`) manages global auth state
+- Token stored in localStorage as `session_data`
+- PrivateRoute component protects authenticated routes
+- Auth validation checks session token via `/auth/check` endpoint
+
+**Routing 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**:
+- Page components in `src/pages/` handle routing
+- Content components (e.g., `DashboardContent.tsx`) contain page logic
+- UI components in `src/components/ui/` are reusable shadcn components
+- Context providers wrap the app in `App.tsx`
+
+### Backend Architecture
+
+**Single Server File**: All API logic is in `api/index.js` (~950 lines)
+
+**Core Middleware**:
+- CORS enabled globally
+- Raw body parser for `/gdpr` webhooks
+- JSON/urlencoded parsers for other routes
+- `securedSession` middleware validates Supabase auth tokens
+
+**Authentication Endpoints**:
+- `POST /auth/signup` - Step 1: Store user data, generate and send OTP
+- `POST /auth/signup/verify` - Step 2: Verify OTP and create Supabase user
+- `POST /auth/signup/resend-otp` - Resend OTP email
+- `POST /auth/login` - Email/password login via Supabase
+- `POST /auth/logout` - Sign out
+- `GET /auth/check` - Validate bearer token
+
+**E-commerce Integration Endpoints**:
+- `GET /auth/shopify` - Initialize Shopify OAuth flow
+- `GET /auth/shopify/callback` - Handle Shopify OAuth callback
+- `GET /auth/woocommerce` - Initialize WooCommerce OAuth (requires auth)
+- `POST /auth/woocommerce/callback` - Handle WooCommerce credentials
+
+**Webhook Endpoints** (GDPR compliance):
+- `POST /gdpr/customers-data-request` - Customer data request
+- `POST /gdpr/customers-redact` - Customer redaction
+- `POST /gdpr/shop-redact` - Shop redaction
+- HMAC verification via `verifyWebhook` middleware
+
+**Other Endpoints**:
+- `GET /health` - Health check
+
+**Key Functions**:
+- `generateOTP()` - Creates 6-digit OTP
+- `sendOTPEmail(email, otp, userName)` - Sends styled verification email
+- `normalizeShopUrl(shop)` - Cleans Shopify URLs
+- `isValidShopUrl(shop)` - Validates Shopify domain format
+- `processWebhook(req, res, topic)` - Async webhook processing
+
+**State Management**:
+- `pendingSignups` Map - Temporary storage for unverified signups (15 min TTL)
+- `nonceStore` Map - OAuth state validation (10 min TTL)
+- ⚠️ Production should use Redis/database instead of in-memory Maps
+
+### Database Schema (Supabase)
+
+**stores table**:
+```
+- user_id: UUID (FK to auth.users)
+- platform_name: text (e.g., 'shopify', 'woocommerce')
+- store_name: text
+- store_url: text
+- api_key: text
+- api_secret: text
+- scopes: text[]
+- alt_data: jsonb (platform-specific data)
+- phone_number: text
+- package: text
+```
+
+## Environment Configuration
+
+### Backend `.env` (shopcall.ai-backend-main)
+```bash
+SUPABASE_URL=https://ztklqodcdjeqpsvhlpud.supabase.co
+SUPABASE_ANON_KEY=<anon_key>
+SHOPIFY_API_KEY=<shopify_api_key>
+SHOPIFY_API_SECRET=<shopify_api_secret>
+EMAIL_USER=<gmail_address>
+EMAIL_PASSWORD=<gmail_app_password>
+NODE_ENV=production|development
+```
+
+### Frontend Environment
+- No `.env` file in shopcall.ai-main
+- Backend API URL hardcoded: `https://shopcall-ai-backend.vercel.app`
+- Frontend URL hardcoded: `https://shopcall.ai`
+
+## Deployment
+
+Both applications deploy to Vercel:
+
+**Backend** (`vercel.json`):
+```json
+{
+  "version": 2,
+  "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
+}
+```
+- All routes proxy to `/api/index.js`
+- Deployed at: `https://shopcall-ai-backend.vercel.app`
+
+**Frontend** (`vercel.json`):
+```json
+{
+  "rewrites": [{ "source": "/(.*)", "destination": "/" }]
+}
+```
+- SPA routing handled by React Router
+- Deployed at: `https://shopcall.ai`
+
+## Development Workflow
+
+When making changes across both repositories:
+
+1. **Backend Changes**: Modify `shopcall.ai-backend-main/api/index.js`
+2. **Frontend Changes**: Work in `shopcall.ai-main/src/`
+3. **Auth Flow Changes**: Update both AuthContext and backend `/auth/*` endpoints
+4. **New Protected Routes**: Add to App.tsx inside `<PrivateRoute>` wrapper
+5. **New API Endpoints**: Add to `api/index.js` with `securedSession` if auth required
+
+## Important Notes
+
+- All backend logic is in a single file (`api/index.js`)
+- In-memory stores (`pendingSignups`, `nonceStore`) will reset on serverless function cold starts
+- Frontend expects backend at `https://shopcall-ai-backend.vercel.app` (hardcoded)
+- Supabase handles user authentication and data storage
+- Email verification required for signup (OTP sent via Gmail)
+- Shopify/WooCommerce OAuth flows store credentials in Supabase `stores` table
+
+## Path Aliases
+
+Frontend uses TypeScript path alias:
+- `@/` → `./src/`
+
+Example: `import { Button } from "@/components/ui/button"`

+ 257 - 0
DEPLOYMENT_GUIDE.md

@@ -0,0 +1,257 @@
+# ShopCall.ai Deployment Guide
+
+This guide explains how to deploy ShopCall.ai after the migration from Vercel to Supabase Edge Functions with static hosting.
+
+## Architecture Overview
+
+### New Architecture
+- **Frontend**: Static React build (Vite) - Can be hosted on any static hosting provider (Apache, Nginx, Cloudflare Pages, etc.)
+- **Backend**: Supabase Edge Functions (Deno runtime)
+- **Database**: Supabase PostgreSQL
+- **Authentication**: Supabase Auth
+
+### Key Changes from Previous Architecture
+- ✅ Replaced Vercel serverless functions with Supabase Edge Functions
+- ✅ Replaced in-memory stores with database tables (`pending_signups`, `oauth_nonces`)
+- ✅ Migrated from Nodemailer to Resend API for email sending
+- ✅ Environment variable-based configuration for easy deployment
+- ✅ Static hosting compatible frontend
+
+## Prerequisites
+
+1. Supabase account and project
+2. Web hosting with Apache or Nginx (for static files)
+3. Resend API key for email sending (or alternative email service)
+4. Shopify API credentials (if using Shopify integration)
+5. Node.js and npm installed locally for building
+
+## Database Setup
+
+The database migrations have already been applied. The following tables were created:
+
+### 1. pending_signups
+Stores temporary signup data with OTP for email verification (15-minute expiration).
+
+### 2. oauth_nonces
+Stores OAuth state/nonce values for secure OAuth flows (10-minute expiration).
+
+## Edge Functions Deployment
+
+### Deployed Edge Functions
+
+1. **auth** - `/functions/v1/auth/*`
+   - `/auth/signup` - Create new user account with OTP
+   - `/auth/signup/verify` - Verify OTP and complete registration
+   - `/auth/signup/resend-otp` - Resend OTP email
+   - `/auth/login` - Email/password login
+   - `/auth/logout` - Sign out
+   - `/auth/check` - Validate session token
+
+2. **shopify-oauth** - `/functions/v1/shopify-oauth/*`
+   - `/shopify-oauth/init` - Initialize Shopify OAuth flow
+   - `/shopify-oauth/callback` - Handle Shopify OAuth callback
+
+3. **woocommerce-oauth** - `/functions/v1/woocommerce-oauth/*`
+   - `/woocommerce-oauth/init` - Initialize WooCommerce OAuth flow
+   - `/woocommerce-oauth/callback` - Handle WooCommerce callback
+
+4. **gdpr-webhooks** - `/functions/v1/gdpr-webhooks/*`
+   - `/gdpr-webhooks/customers-data-request` - Handle customer data requests
+   - `/gdpr-webhooks/customers-redact` - Handle customer data redaction
+   - `/gdpr-webhooks/shop-redact` - Handle shop data redaction
+
+### Environment Variables for Edge Functions
+
+Configure these in your Supabase project settings under Edge Functions:
+
+```bash
+# Supabase (automatically available)
+SUPABASE_URL=https://YOUR_PROJECT.supabase.co
+SUPABASE_ANON_KEY=your_anon_key
+
+# Email Service (Resend)
+RESEND_API_KEY=re_YOUR_API_KEY
+
+# Shopify Integration
+SHOPIFY_API_KEY=your_shopify_api_key
+SHOPIFY_API_SECRET=your_shopify_api_secret
+SHOPIFY_REDIRECT_URI=https://YOUR_PROJECT.supabase.co/functions/v1/shopify-oauth/callback
+
+# Frontend URL (for OAuth redirects)
+FRONTEND_URL=https://yourdomain.com
+
+# Edge Function Base URL
+EDGE_FUNCTION_BASE_URL=https://YOUR_PROJECT.supabase.co/functions/v1
+```
+
+## Frontend Deployment
+
+### Step 1: Configure Environment Variables
+
+Create or update `.env` file in `shopcall.ai-main/`:
+
+```bash
+# Backend API Base URL (Supabase Edge Functions)
+VITE_API_URL=https://YOUR_PROJECT.supabase.co/functions/v1
+
+# Frontend URL (for OAuth callbacks)
+VITE_FRONTEND_URL=https://yourdomain.com
+```
+
+### Step 2: Build the Frontend
+
+```bash
+cd shopcall.ai-main
+npm install
+npm run build
+```
+
+This creates a `dist/` directory with your static files.
+
+### Step 3: Deploy Static Files
+
+#### Option A: Apache Hosting
+
+1. Upload the contents of `dist/` to your web server (e.g., `/var/www/html/`)
+2. Ensure `.htaccess` file is in the root directory (already created in `public/`)
+3. Make sure `mod_rewrite` is enabled:
+   ```bash
+   sudo a2enmod rewrite
+   sudo systemctl restart apache2
+   ```
+
+#### Option B: Nginx Hosting
+
+1. Upload the contents of `dist/` to your web server (e.g., `/var/www/shopcall.ai/dist/`)
+2. Copy `nginx.conf.example` to your nginx sites-available directory:
+   ```bash
+   sudo cp nginx.conf.example /etc/nginx/sites-available/shopcall.ai
+   sudo ln -s /etc/nginx/sites-available/shopcall.ai /etc/nginx/sites-enabled/
+   ```
+3. Update the configuration with your domain and SSL certificates
+4. Test and reload nginx:
+   ```bash
+   sudo nginx -t
+   sudo systemctl reload nginx
+   ```
+
+#### Option C: Cloudflare Pages / Netlify / Similar
+
+1. Connect your Git repository
+2. Set build command: `npm run build`
+3. Set publish directory: `dist`
+4. Configure environment variables in the platform's dashboard
+
+## Email Service Setup (Resend)
+
+1. Sign up for [Resend](https://resend.com/)
+2. Create an API key
+3. Add the API key to your Supabase Edge Function secrets:
+   ```bash
+   supabase secrets set RESEND_API_KEY=your_api_key
+   ```
+4. Verify your sending domain in Resend (optional but recommended for production)
+
+## OAuth Configuration
+
+### Shopify
+
+1. In your Shopify Partner dashboard, update the app settings:
+   - **App URL**: `https://yourdomain.com`
+   - **Allowed redirection URL(s)**:
+     - `https://YOUR_PROJECT.supabase.co/functions/v1/shopify-oauth/callback`
+   - **GDPR webhooks**:
+     - Customer data request: `https://YOUR_PROJECT.supabase.co/functions/v1/gdpr-webhooks/customers-data-request`
+     - Customer data erasure: `https://YOUR_PROJECT.supabase.co/functions/v1/gdpr-webhooks/customers-redact`
+     - Shop data erasure: `https://YOUR_PROJECT.supabase.co/functions/v1/gdpr-webhooks/shop-redact`
+
+### WooCommerce
+
+No special configuration needed. The OAuth flow is initiated from the dashboard.
+
+## Testing the Deployment
+
+### 1. Test Authentication
+
+```bash
+# Test signup
+curl -X POST https://YOUR_PROJECT.supabase.co/functions/v1/auth/signup \
+  -H "Content-Type: application/json" \
+  -d '{
+    "email": "test@example.com",
+    "password": "testpass123",
+    "full_name": "Test User",
+    "company_name": "Test Company",
+    "user_name": "testuser"
+  }'
+
+# Test login
+curl -X POST https://YOUR_PROJECT.supabase.co/functions/v1/auth/login \
+  -H "Content-Type: application/json" \
+  -d '{
+    "email": "test@example.com",
+    "password": "testpass123"
+  }'
+```
+
+### 2. Test OAuth Flow
+
+1. Navigate to your frontend URL
+2. Try connecting a Shopify or WooCommerce store
+3. Verify the OAuth callback works correctly
+
+### 3. Test Frontend Routing
+
+1. Navigate to various routes (e.g., `/dashboard`, `/call-logs`)
+2. Refresh the page to ensure routing works with static hosting
+3. Check browser console for any errors
+
+## Troubleshooting
+
+### Common Issues
+
+**Issue**: 404 errors on routes when refreshing
+- **Solution**: Ensure `.htaccess` (Apache) or nginx configuration is properly set up for client-side routing
+
+**Issue**: CORS errors when calling Edge Functions
+- **Solution**: Verify CORS headers are set in Edge Functions (`corsHeaders` in each function)
+
+**Issue**: Email not sending
+- **Solution**: Check Resend API key is properly configured and verify the email service is active
+
+**Issue**: OAuth callback fails
+- **Solution**: Verify redirect URIs match exactly in your OAuth provider settings
+
+## Monitoring and Maintenance
+
+1. **Edge Functions Logs**: Monitor in Supabase Dashboard > Edge Functions > Logs
+2. **Database**: Check Supabase Dashboard > Database for any issues
+3. **Cleanup**: The database has automatic cleanup for expired records via the functions:
+   - `delete_expired_pending_signups()`
+   - `delete_expired_oauth_nonces()`
+
+## Rollback Plan
+
+If you need to rollback to the old Vercel deployment:
+
+1. Update frontend `.env` to point back to Vercel backend:
+   ```bash
+   VITE_API_URL=https://shopcall-ai-backend.vercel.app
+   ```
+2. Rebuild and redeploy frontend
+3. The old backend code is still available in `shopcall.ai-backend-main/`
+
+## Security Considerations
+
+1. **Environment Variables**: Never commit `.env` files to version control
+2. **API Keys**: Rotate API keys regularly
+3. **HTTPS**: Always use HTTPS in production
+4. **Rate Limiting**: Consider adding rate limiting to Edge Functions for production
+5. **CORS**: Restrict CORS origins to your actual frontend domain in production
+
+## Support
+
+For issues or questions:
+- Check Supabase logs for Edge Function errors
+- Review browser console for frontend errors
+- Verify environment variables are correctly set

+ 141 - 0
FIX_JWT_ISSUE.md

@@ -0,0 +1,141 @@
+# Fix JWT Verification Issue - Quick Guide
+
+## Problem
+The Edge Functions are rejecting public requests (like `/auth/signup`) because JWT verification is enabled by default.
+
+## Solution Options
+
+### Option 1: Manual Dashboard Update (If Available in Future)
+1. Go to: https://supabase.com/dashboard/project/ztklqodcdjeqpsvhlpud/settings/functions
+2. Look for Edge Functions settings
+3. Disable JWT verification for each function
+
+---
+
+### Option 2: Using Supabase CLI (Recommended)
+
+#### Step 1: Install Supabase CLI
+```bash
+# On Linux/macOS
+npm install -g supabase
+
+# Or with homebrew (macOS)
+brew install supabase/tap/supabase
+```
+
+#### Step 2: Login to Supabase
+```bash
+supabase login
+```
+This will open your browser to authenticate.
+
+#### Step 3: Link Your Project
+```bash
+cd /data/shopcall
+supabase link --project-ref ztklqodcdjeqpsvhlpud
+```
+
+#### Step 4: Redeploy Functions with Correct Configuration
+```bash
+# The config.toml file has already been created with verify_jwt = false
+
+# Redeploy all functions
+cd /data/shopcall
+supabase functions deploy auth --no-verify-jwt
+supabase functions deploy shopify-oauth --no-verify-jwt
+supabase functions deploy woocommerce-oauth --no-verify-jwt
+supabase functions deploy gdpr-webhooks --no-verify-jwt
+```
+
+---
+
+### Option 3: Using Management API (Advanced)
+
+If you have your Supabase service role key, you can update via API:
+
+```bash
+# Get your service role key from:
+# https://supabase.com/dashboard/project/ztklqodcdjeqpsvhlpud/settings/api
+
+# Set environment variable
+export SUPABASE_SERVICE_KEY="your_service_role_key_here"
+
+# Update each function
+for func in auth shopify-oauth woocommerce-oauth gdpr-webhooks; do
+  curl -X PATCH "https://api.supabase.com/v1/projects/ztklqodcdjeqpsvhlpud/functions/$func" \
+    -H "Authorization: Bearer $SUPABASE_SERVICE_KEY" \
+    -H "Content-Type: application/json" \
+    -d '{"verify_jwt": false}'
+done
+```
+
+---
+
+### Option 4: Quick Fix Script
+
+I've created a script for you. Save this as `fix-jwt.sh`:
+
+```bash
+#!/bin/bash
+
+echo "Installing Supabase CLI..."
+npm install -g supabase
+
+echo "Please login to Supabase (browser will open)..."
+supabase login
+
+echo "Linking to your project..."
+cd /data/shopcall
+supabase link --project-ref ztklqodcdjeqpsvhlpud
+
+echo "Deploying functions with JWT verification disabled..."
+supabase functions deploy auth --no-verify-jwt
+supabase functions deploy shopify-oauth --no-verify-jwt
+supabase functions deploy woocommerce-oauth --no-verify-jwt
+supabase functions deploy gdpr-webhooks --no-verify-jwt
+
+echo "Done! Try signup again at https://shopcall.ai/signup"
+```
+
+Then run:
+```bash
+chmod +x fix-jwt.sh
+./fix-jwt.sh
+```
+
+---
+
+## Verification
+
+After applying the fix, test the signup:
+1. Go to: https://shopcall.ai/signup
+2. Fill in the form and submit
+3. You should receive an OTP email
+4. No more "401 Unauthorized" errors
+
+## What Changed
+
+The configuration file at `/data/shopcall/supabase/config.toml` now includes:
+
+```toml
+[functions.auth]
+verify_jwt = false
+
+[functions.shopify-oauth]
+verify_jwt = false
+
+[functions.woocommerce-oauth]
+verify_jwt = false
+
+[functions.gdpr-webhooks]
+verify_jwt = false
+```
+
+This allows the functions to handle authentication internally without requiring a JWT token for every request.
+
+## Need Help?
+
+If you encounter any issues:
+1. Check the Edge Functions logs: https://supabase.com/dashboard/project/ztklqodcdjeqpsvhlpud/logs/edge-functions
+2. Verify the function is deployed correctly
+3. Check the browser console for detailed error messages

+ 882 - 0
MISSING_FEATURES.md

@@ -0,0 +1,882 @@
+# ShopCall.ai - Missing Features & Incomplete Implementation Report
+
+**Generated:** 2025-10-22
+**Project:** ShopCall.ai (AI-powered calling system for e-commerce)
+**Repositories:**
+- Frontend: `shopcall.ai-main` (React/Vite/TypeScript)
+- Backend: `shopcall.ai-backend-main` (Express.js/Node.js)
+
+---
+
+## 🔴 **CRITICAL MISSING FEATURES**
+
+### 1. **Onboarding Flow - Not Connected to Backend**
+
+**Location:** `shopcall.ai-main/src/components/OnboardingContent.tsx`
+
+**Status:** UI complete, backend integration missing
+
+**Issues:**
+- ❌ No API integration for submitting onboarding data
+- ❌ Shopify connection flow exists in UI but doesn't save to backend
+- ❌ Phone number selection doesn't persist
+- ❌ Package selection doesn't create subscription
+- ❌ `handleFinish()` function only redirects to homepage (line 100-108)
+
+**Missing Backend Endpoints:**
+```
+POST /api/onboarding/complete
+  Body: {
+    shopifyUrl: string,
+    selectedPhone: string,
+    selectedPackage: string
+  }
+```
+
+**Impact:** Users can complete onboarding but nothing is saved. On next login, they'll need to onboard again.
+
+**Effort:** Medium (2-3 days)
+
+---
+
+### 2. **Shopify OAuth Integration Incomplete**
+
+**Location:** `shopcall.ai-backend-main/api/index.js:532-597`
+
+**Status:** OAuth flow works, but tokens not persisted
+
+**Issues:**
+- ✅ OAuth authorization flow implemented
+- ✅ Token exchange working
+- ❌ **TODO Comment (line 586):** "Save tokenJson.access_token securely" - tokens not being stored
+- ❌ No store record created in `stores` table after successful OAuth
+- ❌ Redirect goes to homepage (`https://shopcall.ai/`) instead of dashboard
+- ❌ User-to-store relationship not established
+
+**Current Code:**
+```javascript
+// Line 586
+// TODO: Save tokenJson.access_token securely
+console.log(`Successfully authenticated shop: ${normalizedShop}`);
+
+res.redirect(`https://shopcall.ai/`);
+```
+
+**Required Fix:**
+```javascript
+// Save to database
+const { data: storeData, error: storeError } = await supabase
+  .from('stores')
+  .insert({
+    user_id: nonceData.userId, // from nonce
+    platform_name: 'shopify',
+    store_name: normalizedShop.split('.')[0],
+    store_url: `https://${normalizedShop}`,
+    access_token: tokenJson.access_token,
+    scopes: tokenJson.scope.split(','),
+    connected_at: new Date().toISOString()
+  });
+
+res.redirect(`https://shopcall.ai/dashboard?connected=true`);
+```
+
+**Impact:** Shopify stores cannot be properly connected. OAuth succeeds but connection is lost immediately.
+
+**Effort:** Small (1 day)
+
+---
+
+### 3. **Phone Number Management - UI Only**
+
+**Location:** `shopcall.ai-main/src/components/PhoneNumbersContent.tsx`
+
+**Status:** Complete UI mockup with static data
+
+**Issues:**
+- ❌ All data is hardcoded in `connectedShops`, `availableCountries`, `carriers` arrays
+- ❌ No backend API for phone number operations
+- ❌ "Get Number" button (line 278) - no handler
+- ❌ "Assign" button (line 196-199) - no handler
+- ❌ "Connect" carrier buttons (line 231-234) - no handler
+- ❌ Cannot actually purchase or assign phone numbers
+
+**Missing Backend Endpoints:**
+```
+GET    /api/phone-numbers              # List phone numbers for user
+POST   /api/phone-numbers              # Purchase new phone number
+PUT    /api/phone-numbers/:id/assign   # Assign number to store
+DELETE /api/phone-numbers/:id          # Release phone number
+
+POST   /api/carriers/connect            # Connect external carrier (Twilio, etc.)
+GET    /api/carriers                    # List connected carriers
+```
+
+**Missing Database Tables:**
+```sql
+CREATE TABLE phone_numbers (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  user_id UUID REFERENCES profiles(id),
+  store_id UUID REFERENCES stores(id),
+  number VARCHAR(20) NOT NULL,
+  country VARCHAR(2) NOT NULL,
+  type VARCHAR(20), -- 'local', 'toll-free'
+  carrier VARCHAR(50), -- 'internal', 'twilio', 'telnyx', etc.
+  status VARCHAR(20), -- 'active', 'pending_kyc', 'inactive'
+  monthly_cost DECIMAL(10,2),
+  carrier_config JSONB, -- carrier-specific data
+  created_at TIMESTAMP DEFAULT NOW(),
+  assigned_at TIMESTAMP
+);
+
+CREATE TABLE carrier_integrations (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  user_id UUID REFERENCES profiles(id),
+  carrier_name VARCHAR(50), -- 'twilio', 'telnyx', 'vonage', 'zadarma'
+  credentials JSONB, -- encrypted API keys
+  is_active BOOLEAN DEFAULT true,
+  created_at TIMESTAMP DEFAULT NOW()
+);
+```
+
+**Impact:** Core feature completely non-functional. Users cannot set up phone numbers which are essential for the service to work.
+
+**Effort:** Large (5-7 days including carrier API integrations)
+
+---
+
+### 4. **AI Configuration - UI Only**
+
+**Location:** `shopcall.ai-main/src/components/AIConfigContent.tsx`
+
+**Status:** Complete UI for AI settings, no backend persistence
+
+**Issues:**
+- ❌ All settings stored only in component state
+- ❌ "Save Configuration" button (line 55) - no handler
+- ❌ "Sync Store Data" button (line 247) - no handler
+- ❌ "Test Configuration" button (line 253) - no handler
+- ❌ "Copy from Another Shop" button (line 52) - no handler
+- ❌ Voice settings, greeting messages, escalation policy, knowledge base - none saved
+
+**Missing Backend Endpoints:**
+```
+GET  /api/ai-config/:storeId           # Get AI config for store
+POST /api/ai-config/:storeId           # Create/update AI config
+POST /api/ai-config/:storeId/test      # Test AI configuration
+POST /api/ai-config/:storeId/sync      # Sync store data (products, policies)
+POST /api/ai-config/:storeId/copy      # Copy config from another store
+```
+
+**Missing Database Table:**
+```sql
+CREATE TABLE ai_configurations (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID REFERENCES stores(id) UNIQUE,
+
+  -- Voice settings
+  voice_type VARCHAR(50) DEFAULT 'sarah',
+  speaking_speed VARCHAR(20) DEFAULT 'normal',
+  accent VARCHAR(50) DEFAULT 'us-english',
+
+  -- Conversation behavior
+  greeting_message TEXT,
+  business_hours_mode BOOLEAN DEFAULT true,
+  local_currency_support BOOLEAN DEFAULT true,
+  escalation_policy VARCHAR(20) DEFAULT 'medium',
+
+  -- Knowledge base
+  product_catalog_synced BOOLEAN DEFAULT false,
+  product_catalog_last_sync TIMESTAMP,
+  store_policies TEXT,
+  faq_database JSONB,
+  custom_knowledge JSONB,
+
+  created_at TIMESTAMP DEFAULT NOW(),
+  updated_at TIMESTAMP DEFAULT NOW()
+);
+```
+
+**Impact:** AI cannot be customized per webshop. All stores would use default settings, making the service less valuable.
+
+**Effort:** Medium-Large (4-5 days)
+
+---
+
+## 🟡 **HIGH PRIORITY MISSING FEATURES**
+
+### 5. **Analytics Data - All Mocked**
+
+**Location:** `shopcall.ai-main/src/components/AnalyticsContent.tsx`
+
+**Status:** Complete UI with charts, all using static data
+
+**Issues:**
+- ❌ `weeklyData`, `resolutionData`, `callDurationData`, `topIntentsData`, etc. - all hardcoded (lines 6-58)
+- ❌ Charts display fake data
+- ❌ No real metrics calculation
+- ❌ Peak hours heatmap generated randomly (line 130)
+
+**Missing Backend Endpoints:**
+```
+GET /api/analytics/overview?timeRange=week       # KPIs and overview charts
+GET /api/analytics/trends?timeRange=month        # Trend analysis
+GET /api/analytics/performance?timeRange=week    # Performance metrics
+GET /api/analytics/call-volume?groupBy=day       # Call volume data
+GET /api/analytics/intents?limit=10              # Top call intents
+```
+
+**Required Aggregation Logic:**
+- Call volume by day/hour/week
+- Resolution rate calculation
+- Average call duration by intent
+- Peak hours analysis
+- Sentiment distribution
+- Cost analysis
+
+**Impact:** Dashboard shows misleading fake data. Users cannot make data-driven decisions.
+
+**Effort:** Medium (3-4 days for proper aggregation queries)
+
+---
+
+### 6. **Dashboard KPIs - Hardcoded**
+
+**Location:**
+- `shopcall.ai-main/src/components/KPICards.tsx`
+- `shopcall.ai-main/src/components/ChartsSection.tsx`
+- `shopcall.ai-main/src/components/RecentCallsTable.tsx`
+
+**Status:** Complete UI, static mock data
+
+**Issues:**
+- ❌ All KPI values hardcoded
+- ❌ Charts show static data
+- ❌ Recent calls table shows hardcoded 5 calls (lines 8-64 in RecentCallsTable.tsx)
+- ❌ No refresh functionality
+
+**Missing Backend Endpoint:**
+```
+GET /api/dashboard/summary?timeRange=24h
+  Response: {
+    totalCalls: number,
+    resolvedCalls: number,
+    activeConversations: number,
+    avgResponseTime: number,
+    resolutionRate: number,
+    avgCallDuration: number,
+    totalCost: number,
+    chartData: {
+      callVolume: [...],
+      resolutionRate: [...]
+    },
+    recentCalls: [...]
+  }
+```
+
+**Impact:** Dashboard is just a demo, not showing real data.
+
+**Effort:** Small-Medium (2-3 days, reuses analytics logic)
+
+---
+
+### 7. **Webshop Integration Management - Partially Implemented**
+
+**Location:** `shopcall.ai-main/src/components/IntegrationsContent.tsx`
+
+**Status:** Backend APIs exist, frontend not connected
+
+**Issues:**
+- ✅ Backend has `GET /api/stores` (line 800)
+- ✅ Backend has `PUT /api/stores/:id` (line 855)
+- ✅ Backend has `DELETE /api/stores/:id` (line 893)
+- ❌ Frontend shows hardcoded `connectedShops` array (lines 10-86)
+- ❌ "Connect Webshop" button (line 96-99) doesn't trigger OAuth
+- ❌ Settings buttons (line 202) don't open edit modal
+- ❌ No fetch call to `/api/stores`
+
+**Required Frontend Changes:**
+```typescript
+const [stores, setStores] = useState([]);
+
+useEffect(() => {
+  const fetchStores = async () => {
+    const response = await fetch('https://shopcall-ai-backend.vercel.app/api/stores', {
+      headers: { 'Authorization': `Bearer ${token}` }
+    });
+    const data = await response.json();
+    setStores(data.stores);
+  };
+  fetchStores();
+}, []);
+```
+
+**Impact:** Users see fake store list instead of their actual connected stores.
+
+**Effort:** Small (1 day to connect frontend to existing backend)
+
+---
+
+### 8. **Call Recording Playback**
+
+**Location:** `shopcall.ai-main/src/components/CallDetailsModal.tsx`
+
+**Status:** UI exists, no audio implementation
+
+**Issues:**
+- ❌ Play button exists (line 145-152) but no audio player
+- ❌ `recording_url` field in `call_logs` table not used
+- ❌ No audio streaming functionality
+- ❌ No waveform visualization
+- ❌ Progress bar not functional (line 159-161)
+
+**Required Implementation:**
+```typescript
+const [audio] = useState(new Audio());
+const [isPlaying, setIsPlaying] = useState(false);
+const [currentTime, setCurrentTime] = useState(0);
+const [duration, setDuration] = useState(0);
+
+useEffect(() => {
+  if (call.recording_url) {
+    audio.src = call.recording_url;
+    audio.addEventListener('loadedmetadata', () => {
+      setDuration(audio.duration);
+    });
+    audio.addEventListener('timeupdate', () => {
+      setCurrentTime(audio.currentTime);
+    });
+  }
+}, [call.recording_url]);
+
+const handlePlayPause = () => {
+  if (isPlaying) {
+    audio.pause();
+  } else {
+    audio.play();
+  }
+  setIsPlaying(!isPlaying);
+};
+```
+
+**Impact:** Cannot review call recordings, limiting quality assurance capabilities.
+
+**Effort:** Small (1-2 days)
+
+---
+
+### 9. **WooCommerce Integration - Incomplete**
+
+**Location:** `shopcall.ai-backend-main/api/index.js:463-529`
+
+**Status:** OAuth callback implemented, token storage works
+
+**Issues:**
+- ✅ OAuth initiation works (line 463)
+- ✅ Callback saves credentials (line 481)
+- ❌ No frontend UI to trigger WooCommerce connection
+- ❌ No WooCommerce-specific configuration options
+- ❌ Product sync not implemented
+- ❌ No webhook setup for order updates
+
+**Missing Features:**
+- WooCommerce product catalog sync
+- Order status webhooks
+- Customer data integration
+- Store policy import
+
+**Impact:** WooCommerce support advertised but not fully functional.
+
+**Effort:** Medium (3-4 days for full WooCommerce integration)
+
+---
+
+## 🟢 **MEDIUM PRIORITY MISSING FEATURES**
+
+### 10. **Search & Filtering**
+
+**Location:** Multiple components
+
+**Issues:**
+- ❌ Call logs search input exists but no filter logic (`CallLogsContent.tsx:118-124`)
+- ❌ "Filters" button exists but no filter panel (`CallLogsContent.tsx:126-129`)
+- ❌ Date range picker button non-functional (`CallLogsContent.tsx:107-111`)
+- ❌ No backend support for query parameters
+
+**Required Backend Changes:**
+```
+GET /api/call-logs?search=xxx&status=completed&outcome=resolved&dateFrom=...&dateTo=...
+```
+
+**Required Frontend Changes:**
+- Search debouncing
+- Filter panel UI
+- Date range picker integration
+- Query string management
+
+**Impact:** Users must manually scroll through all calls, poor UX for high-volume users.
+
+**Effort:** Small-Medium (2-3 days)
+
+---
+
+### 11. **Export Functionality**
+
+**Location:** `shopcall.ai-main/src/components/CallLogsContent.tsx:102-105`
+
+**Status:** Button exists, no implementation
+
+**Issues:**
+- ❌ Export button present but no click handler
+- ❌ No backend endpoint for data export
+- ❌ No file format options (CSV, PDF, Excel)
+
+**Missing Backend Endpoint:**
+```
+GET /api/call-logs/export?format=csv&dateFrom=...&dateTo=...
+  Response: CSV/PDF file download
+```
+
+**Required Features:**
+- CSV export (for Excel)
+- PDF export (formatted report)
+- Email export option
+- Export scheduling (weekly/monthly reports)
+
+**Impact:** Users cannot extract data for external analysis or reporting.
+
+**Effort:** Small (1-2 days)
+
+---
+
+### 12. **Real-time Updates**
+
+**Status:** Not implemented
+
+**Issues:**
+- ❌ No WebSocket connection for live updates
+- ❌ Dashboard requires manual refresh
+- ❌ No notification system for new calls
+- ❌ Active call status not real-time
+
+**Required Implementation:**
+- WebSocket server (Socket.io or native WebSockets)
+- Real-time call status updates
+- Live dashboard KPI updates
+- Browser notifications
+
+**Missing Infrastructure:**
+```javascript
+// Backend
+const io = require('socket.io')(server);
+
+io.on('connection', (socket) => {
+  socket.on('authenticate', async (token) => {
+    // Validate token and join user room
+    socket.join(`user_${userId}`);
+  });
+});
+
+// Emit on new call
+io.to(`user_${userId}`).emit('new_call', callData);
+```
+
+**Impact:** Users miss real-time updates, dashboard feels outdated.
+
+**Effort:** Medium (3-4 days for WebSocket implementation)
+
+---
+
+### 13. **User Profile Management**
+
+**Status:** Not implemented
+
+**Issues:**
+- ❌ No profile page/route
+- ❌ Cannot edit profile information (name, company, email)
+- ❌ No password change functionality
+- ❌ No account deletion option
+- ❌ `profiles` table exists but no edit endpoint
+
+**Missing Endpoints:**
+```
+GET    /api/profile                    # Get current user profile
+PUT    /api/profile                    # Update profile
+POST   /api/profile/change-password    # Change password
+DELETE /api/profile                    # Delete account
+```
+
+**Missing Frontend Pages:**
+- `/settings/profile` - Profile edit page
+- `/settings/security` - Password change
+- `/settings/account` - Account management
+
+**Impact:** Users stuck with initial registration data. Cannot fix typos or update information.
+
+**Effort:** Small-Medium (2-3 days)
+
+---
+
+### 14. **Payment Integration**
+
+**Location:** `OnboardingContent.tsx:31-80` - Package selection UI
+
+**Status:** Package selection UI only
+
+**Issues:**
+- ❌ No payment gateway integration (Stripe, PayPal, etc.)
+- ❌ Package selection doesn't create subscription
+- ❌ No billing page
+- ❌ No invoice generation
+- ❌ No payment history
+- ❌ Free trial not tracked
+
+**Missing Implementation:**
+- Stripe/PayPal SDK integration
+- Subscription management
+- Webhook handling for payment events
+- Billing portal
+
+**Missing Endpoints:**
+```
+POST   /api/subscriptions/create        # Create subscription
+GET    /api/subscriptions/current       # Get current subscription
+POST   /api/subscriptions/cancel        # Cancel subscription
+POST   /api/subscriptions/upgrade       # Upgrade plan
+GET    /api/invoices                    # List invoices
+```
+
+**Missing Database Table:**
+```sql
+CREATE TABLE subscriptions (
+  id UUID PRIMARY KEY,
+  user_id UUID REFERENCES profiles(id),
+  plan_id VARCHAR(50), -- 'free-trial', 'starter', 'professional'
+  status VARCHAR(20), -- 'active', 'cancelled', 'past_due'
+  stripe_subscription_id VARCHAR(100),
+  current_period_start TIMESTAMP,
+  current_period_end TIMESTAMP,
+  cancel_at_period_end BOOLEAN,
+  created_at TIMESTAMP DEFAULT NOW()
+);
+```
+
+**Impact:** Cannot monetize the service. All users have unlimited access.
+
+**Effort:** Large (5-7 days for full payment integration)
+
+---
+
+### 15. **Email Notifications**
+
+**Status:** Only OTP emails implemented
+
+**Issues:**
+- ✅ OTP email works (`sendOTPEmail` function exists)
+- ❌ No call summary emails
+- ❌ No alerts for failed calls
+- ❌ No weekly/monthly reports
+- ❌ No password reset emails
+- ❌ No subscription notifications
+
+**Missing Email Templates:**
+- Call completed summary
+- Failed call alert
+- Weekly activity report
+- Monthly invoice
+- Password reset
+- Subscription renewal reminder
+
+**Required Implementation:**
+- Email template system (Handlebars, Pug, etc.)
+- Email queue (Bull, BullMQ)
+- Background job processing
+- Email service (SendGrid, Mailgun, or continue with Nodemailer)
+
+**Impact:** Users not notified of important events. Must check dashboard constantly.
+
+**Effort:** Medium (3-4 days)
+
+---
+
+## 🔵 **LOW PRIORITY / NICE TO HAVE**
+
+### 16. **Advanced Call Features**
+
+**Missing Features:**
+- ❌ Call forwarding to human agents (escalation)
+- ❌ Voicemail functionality
+- ❌ Call scheduling (schedule calls for later)
+- ❌ SMS fallback (if call fails, send SMS)
+- ❌ Call recording consent management (GDPR)
+- ❌ IVR (Interactive Voice Response) menu
+- ❌ Call queuing
+- ❌ After-hours handling
+
+**Impact:** Basic AI calling only. Limited flexibility.
+
+**Effort:** Large (ongoing feature development)
+
+---
+
+### 17. **Team Collaboration**
+
+**Status:** Not implemented (single-user only)
+
+**Missing Features:**
+- ❌ Multi-user accounts (team members)
+- ❌ Roles & permissions (admin, agent, viewer)
+- ❌ Team member invitation
+- ❌ Shared call notes/comments
+- ❌ Call assignment to team members
+- ❌ Activity logs
+
+**Required Database Tables:**
+```sql
+CREATE TABLE team_members (
+  id UUID PRIMARY KEY,
+  organization_id UUID,
+  user_id UUID REFERENCES profiles(id),
+  role VARCHAR(20), -- 'owner', 'admin', 'agent', 'viewer'
+  permissions JSONB,
+  invited_by UUID,
+  invited_at TIMESTAMP,
+  joined_at TIMESTAMP
+);
+
+CREATE TABLE organizations (
+  id UUID PRIMARY KEY,
+  name VARCHAR(255),
+  owner_id UUID REFERENCES profiles(id),
+  created_at TIMESTAMP
+);
+```
+
+**Impact:** Only suitable for solo users. Cannot scale to businesses with teams.
+
+**Effort:** Large (7-10 days)
+
+---
+
+### 18. **API Documentation**
+
+**Status:** Not implemented
+
+**Missing:**
+- ❌ No public API documentation
+- ❌ No API keys for developers
+- ❌ No webhooks for third-party integrations
+- ❌ No SDK/client libraries
+- ❌ No API rate limiting
+- ❌ No API versioning
+
+**Required:**
+- OpenAPI/Swagger documentation
+- API key generation endpoint
+- Webhook configuration UI
+- Developer portal
+
+**Impact:** Cannot integrate with external tools. Limited extensibility.
+
+**Effort:** Medium (3-5 days for basic API docs + key management)
+
+---
+
+### 19. **Testing Coverage**
+
+**Status:** No tests exist
+
+**Missing:**
+- ❌ No unit tests (frontend or backend)
+- ❌ No integration tests
+- ❌ No E2E tests
+- ❌ No test coverage reports
+
+**Required:**
+- Jest + React Testing Library (frontend)
+- Jest + Supertest (backend)
+- Playwright/Cypress (E2E)
+- CI/CD pipeline with test gates
+
+**Impact:** High risk of bugs, difficult to refactor safely.
+
+**Effort:** Ongoing (20-30% of development time)
+
+---
+
+### 20. **Additional E-commerce Platforms**
+
+**Status:** Shopify & WooCommerce partially implemented
+
+**Missing Platforms:**
+- ❌ BigCommerce
+- ❌ Magento
+- ❌ Squarespace
+- ❌ Wix
+- ❌ PrestaShop
+- ❌ Custom/Generic REST API integration
+
+**Required:**
+- OAuth flows for each platform
+- Product sync adapters
+- Order webhook handlers
+- Platform-specific configuration
+
+**Impact:** Limited market reach. Many e-commerce users excluded.
+
+**Effort:** Large per platform (4-5 days each)
+
+---
+
+## 📝 **TODO COMMENTS IN CODE**
+
+### Backend (`shopcall.ai-backend-main/api/index.js`)
+
+1. **Line 586:** `// TODO: Save tokenJson.access_token securely`
+   - **Context:** Shopify OAuth callback
+   - **Issue:** Access tokens not being stored in database
+   - **Priority:** 🔴 Critical
+
+2. **Line 604:** `// TODO: Retrieve access token from database`
+   - **Context:** Product API endpoint
+   - **Issue:** Incomplete API endpoint implementation
+   - **Priority:** 🟡 High
+
+3. **Line 677:** `// TODO: Implement your business logic here`
+   - **Context:** GDPR webhook processing
+   - **Issue:** Webhook processing empty
+   - **Priority:** 🟡 High (GDPR compliance issue)
+
+### Frontend
+- No TODO comments found in source code
+
+---
+
+## 🎯 **SUMMARY BY PRIORITY**
+
+| Priority | Category | Count | Estimated Effort |
+|----------|----------|-------|------------------|
+| 🔴 Critical | Core functionality blockers | 4 | 12-15 days |
+| 🟡 High | Major features incomplete | 5 | 15-20 days |
+| 🟢 Medium | UX and data issues | 6 | 15-20 days |
+| 🔵 Low | Enhancement features | 5 | 40-50 days |
+| **TOTAL** | | **20** | **82-105 days** |
+
+---
+
+## 💡 **RECOMMENDED DEVELOPMENT ORDER**
+
+### Phase 1: Critical Fixes (2-3 weeks)
+1. **Complete Shopify OAuth** - Save tokens, create store records
+2. **Connect Integrations UI to backend** - Use existing `/api/stores` endpoints
+3. **Implement Phone Number Management APIs** - Critical for service to work
+4. **Implement AI Configuration APIs** - Required for customization
+
+### Phase 2: Data & Analytics (2-3 weeks)
+5. **Connect Analytics to real data** - Replace mock data with real queries
+6. **Connect Dashboard KPIs** - Real-time metrics
+7. **Onboarding flow backend integration** - Save user selections
+8. **Call recording playback** - Audio player implementation
+
+### Phase 3: User Experience (2-3 weeks)
+9. **Add search & filtering** - Improve call log navigation
+10. **Export functionality** - CSV/PDF export
+11. **User profile management** - Edit profile, change password
+12. **Email notifications** - Call summaries, alerts
+
+### Phase 4: Monetization & Growth (3-4 weeks)
+13. **Implement payment system** - Stripe integration
+14. **Real-time updates** - WebSocket implementation
+15. **Complete WooCommerce integration** - Product sync, webhooks
+16. **Team collaboration features** - Multi-user support
+
+### Phase 5: Enhancement & Scale (Ongoing)
+17. **Advanced call features** - Forwarding, voicemail, scheduling
+18. **API documentation** - Public API + webhooks
+19. **Additional e-commerce platforms** - BigCommerce, Magento
+20. **Testing coverage** - Unit, integration, E2E tests
+
+---
+
+## 🔍 **TECHNICAL DEBT ITEMS**
+
+1. **In-memory state stores** (`pendingSignups`, `nonceStore` Maps in `api/index.js`)
+   - ⚠️ Will reset on serverless function cold starts
+   - Should use Redis or database
+
+2. **Hardcoded URLs** in frontend
+   - Backend URL: `https://shopcall-ai-backend.vercel.app` (should be env variable)
+   - Frontend URL: `https://shopcall.ai` (should be env variable)
+
+3. **No database migrations system**
+   - Using Supabase migrations manually
+   - Should have automated migration tool
+
+4. **No error tracking**
+   - No Sentry or error monitoring
+   - Console.log only
+
+5. **No API versioning**
+   - Breaking changes will break all clients
+   - Should implement `/api/v1/...`
+
+6. **No rate limiting**
+   - API endpoints not protected
+   - Vulnerable to abuse
+
+---
+
+## 📊 **FEATURE COMPLETENESS MATRIX**
+
+| Feature Category | UI Complete | Backend Complete | Integration Complete | Overall |
+|-----------------|-------------|------------------|---------------------|---------|
+| Authentication | ✅ 100% | ✅ 100% | ✅ 100% | ✅ **100%** |
+| Onboarding | ✅ 100% | ❌ 0% | ❌ 0% | ⚠️ **33%** |
+| Dashboard | ✅ 100% | ⚠️ 50% | ❌ 30% | ⚠️ **60%** |
+| Call Logs | ✅ 100% | ✅ 90% | ✅ 90% | ✅ **93%** |
+| Analytics | ✅ 100% | ❌ 0% | ❌ 0% | ⚠️ **33%** |
+| Integrations | ✅ 100% | ✅ 80% | ❌ 20% | ⚠️ **67%** |
+| Phone Numbers | ✅ 100% | ❌ 0% | ❌ 0% | ⚠️ **33%** |
+| AI Config | ✅ 100% | ❌ 0% | ❌ 0% | ⚠️ **33%** |
+| User Profile | ❌ 0% | ⚠️ 50% | ❌ 0% | ❌ **17%** |
+| Payments | ⚠️ 50% | ❌ 0% | ❌ 0% | ❌ **17%** |
+| **OVERALL** | | | | ⚠️ **48%** |
+
+---
+
+## 🚨 **SECURITY & COMPLIANCE CONCERNS**
+
+1. **GDPR Webhooks Not Implemented** (line 677)
+   - Shopify GDPR webhooks receive data but don't process it
+   - Legal compliance issue
+
+2. **No API Key Encryption**
+   - Store API keys/tokens stored in plain text
+   - Should use encryption at rest
+
+3. **No Session Management**
+   - Tokens in localStorage never expire on frontend
+   - No refresh token mechanism
+
+4. **No CORS Configuration Per Environment**
+   - CORS enabled globally with `app.use(cors())`
+   - Should restrict origins
+
+5. **Email Credentials in Code**
+   - Default Gmail credentials in code (lines 36-38)
+   - Should be env-only
+
+---
+
+## 📋 **NEXT STEPS**
+
+1. **Prioritize Phase 1** (Critical fixes)
+2. **Set up project tracking** (GitHub Projects, Jira, etc.)
+3. **Create detailed tickets** for each feature
+4. **Establish CI/CD pipeline** with test gates
+5. **Set up error monitoring** (Sentry)
+6. **Document API** as features are completed
+7. **Create staging environment** for testing
+
+---
+
+**Report End** - For questions or clarifications, refer to source code locations provided in each section.

+ 128 - 0
README.md

@@ -0,0 +1,128 @@
+# ShopCall.ai
+
+AI-powered calling system integrated with e-commerce platforms.
+
+## Repository Structure
+
+This is a monorepo containing the following packages:
+
+```
+shopcall/
+├── shopcall.ai-main/          # Frontend application (React + Vite + TypeScript)
+├── shopcall.ai-backend-main/  # Backend API (Express.js)
+├── supabase/                  # Supabase configuration and Edge Functions
+├── docs/                      # Documentation files
+│   ├── CLAUDE.md             # Development guidelines for AI assistants
+│   ├── DEPLOYMENT_GUIDE.md   # Deployment instructions
+│   ├── REFACTORING_SUMMARY.md # Code refactoring notes
+│   ├── TODO.md               # Project roadmap and tasks
+│   └── MISSING_FEATURES.md   # Feature tracking
+└── package.json              # Root workspace configuration
+```
+
+## Quick Start
+
+### Prerequisites
+
+- Node.js 18+ and npm
+- Supabase account
+- Shopify/WooCommerce API credentials (for integrations)
+
+### Installation
+
+Install dependencies for all packages:
+
+```bash
+# Install Supabase CLI globally or at root
+npm install
+
+# Install frontend dependencies
+cd shopcall.ai-main
+npm install
+
+# Install backend dependencies
+cd ../shopcall.ai-backend-main
+npm install
+```
+
+### Development
+
+**Frontend:**
+```bash
+cd shopcall.ai-main
+npm run dev  # Start dev server on port 8080
+```
+
+**Backend:**
+```bash
+cd shopcall.ai-backend-main
+npm start    # Start Express server
+```
+
+**Supabase Functions:**
+```bash
+# Deploy all functions
+./deploy-functions.sh
+
+# Or deploy individually
+npx supabase functions deploy <function-name>
+```
+
+### Environment Setup
+
+Create `.env` files in both frontend and backend directories:
+
+**Backend** (`shopcall.ai-backend-main/.env`):
+```bash
+SUPABASE_URL=your_supabase_url
+SUPABASE_ANON_KEY=your_anon_key
+SHOPIFY_API_KEY=your_shopify_key
+SHOPIFY_API_SECRET=your_shopify_secret
+EMAIL_USER=your_gmail
+EMAIL_PASSWORD=your_gmail_app_password
+NODE_ENV=development
+```
+
+## Technology Stack
+
+### Frontend
+- React 18 with Vite
+- TypeScript
+- Tailwind CSS + shadcn/ui
+- React Router v6
+- React Query
+- React Hook Form + Zod
+
+### Backend
+- Express.js v5
+- Supabase (PostgreSQL + Auth)
+- Nodemailer
+- OAuth integrations (Shopify, WooCommerce)
+
+### Infrastructure
+- Supabase Edge Functions (Deno)
+- Vercel (hosting)
+
+## Documentation
+
+- [CLAUDE.md](./CLAUDE.md) - Comprehensive project documentation for development
+- [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) - Deployment instructions
+- [TODO.md](./TODO.md) - Project roadmap and task tracking
+- [MISSING_FEATURES.md](./MISSING_FEATURES.md) - Feature implementation status
+
+## Deployment
+
+Both frontend and backend deploy to Vercel:
+
+- **Frontend**: https://shopcall.ai
+- **Backend**: https://shopcall-ai-backend.vercel.app
+
+See [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) for detailed instructions.
+
+## License
+
+Proprietary - All rights reserved
+
+## Support
+
+For issues or questions, contact the development team.

+ 309 - 0
REFACTORING_SUMMARY.md

@@ -0,0 +1,309 @@
+# ShopCall.ai Refactoring Summary
+
+**Date**: 2025-10-27
+**Status**: ✅ Core Implementation Complete
+
+## Overview
+
+Successfully migrated ShopCall.ai from Vercel-dependent architecture to Supabase Edge Functions with static hosting capability. This enables deployment on simple web hosting without vendor lock-in.
+
+## What Was Accomplished
+
+### Phase 1: Analysis & Planning ✅
+
+- Analyzed all backend API endpoints in `shopcall.ai-backend-main/api/index.js`
+- Documented authentication flow (signup, OTP, login)
+- Documented OAuth flows (Shopify, WooCommerce, ShopRenter)
+- Identified dependencies to replace (Nodemailer, in-memory stores)
+
+### Phase 2: Database Migration ✅
+
+Created two new database tables to replace in-memory storage:
+
+1. **pending_signups** table
+   - Replaces the `pendingSignups` Map
+   - Fields: signup_id, email, password, full_name, company_name, user_name, otp, created_at, expires_at
+   - Automatic expiration after 15 minutes
+
+2. **oauth_nonces** table
+   - Replaces the `nonceStore` Map
+   - Fields: nonce, user_id, platform, shop, app_url, shopname, created_at, expires_at
+   - Automatic expiration after 10 minutes
+
+### Phase 3: Supabase Edge Functions ✅
+
+Deployed 4 Edge Functions to replace the Vercel backend:
+
+#### 1. **auth** Function
+Handles all authentication operations:
+- `POST /auth/signup` - Create account with OTP verification
+- `POST /auth/signup/verify` - Verify OTP and complete registration
+- `POST /auth/signup/resend-otp` - Resend OTP email
+- `POST /auth/login` - Email/password authentication
+- `POST /auth/logout` - Sign out
+- `GET /auth/check` - Validate session token
+
+**Changes from original:**
+- Uses database table instead of in-memory Map for pending signups
+- Migrated from Nodemailer to Resend API for email sending
+- Deno runtime compatible (no Node.js dependencies)
+
+#### 2. **shopify-oauth** Function
+Manages Shopify OAuth integration:
+- `GET /shopify-oauth/init` - Initialize OAuth flow
+- `GET /shopify-oauth/callback` - Handle OAuth callback
+
+**Changes from original:**
+- Uses database table for nonce storage
+- Proper nonce validation and cleanup
+
+#### 3. **woocommerce-oauth** Function
+Manages WooCommerce OAuth integration:
+- `GET /woocommerce-oauth/init` - Initialize OAuth flow (requires auth)
+- `POST /woocommerce-oauth/callback` - Handle credentials callback
+
+**Changes from original:**
+- Requires authenticated user
+- Stores credentials in Supabase stores table
+
+#### 4. **gdpr-webhooks** Function
+Handles Shopify GDPR compliance webhooks:
+- `POST /gdpr-webhooks/customers-data-request`
+- `POST /gdpr-webhooks/customers-redact`
+- `POST /gdpr-webhooks/shop-redact`
+
+**Changes from original:**
+- HMAC verification using Web Crypto API (Deno-compatible)
+- Always returns 200 to prevent Shopify retries
+
+### Phase 4: Frontend Refactoring ✅
+
+Updated frontend to use Edge Functions:
+
+1. **Environment Variables**
+   - Created `.env.example` with proper configuration
+   - Updated `.env` with Edge Function URLs
+   - Centralized API_URL in `src/lib/config.ts`
+
+2. **Updated Components**
+   - `AuthContext.tsx` - Now uses `API_URL` from config
+   - `Signup.tsx` - Uses Edge Function for signup
+   - `OTP.tsx` - Uses Edge Functions for OTP verification and resend
+   - `Index.tsx` - Uses Edge Function for Shopify OAuth
+   - `CallLogsContent.tsx` - Uses Edge Function base URL
+   - `ShopRenterConnect.tsx` - Uses Edge Function base URL
+
+3. **Files Updated:**
+   ```
+   src/lib/config.ts
+   src/components/context/AuthContext.tsx
+   src/pages/Signup.tsx
+   src/pages/OTP.tsx
+   src/pages/Index.tsx
+   src/components/CallLogsContent.tsx
+   src/components/ShopRenterConnect.tsx
+   ```
+
+### Phase 5: Static Hosting Configuration ✅
+
+Created configuration files for various hosting options:
+
+1. **Apache** (`public/.htaccess`)
+   - Client-side routing support
+   - Gzip compression
+   - Cache headers for static assets
+
+2. **Nginx** (`nginx.conf.example`)
+   - Client-side routing support
+   - Gzip compression
+   - SSL configuration example
+   - Security headers
+   - Cache control
+
+### Phase 6: Documentation ✅
+
+Created comprehensive documentation:
+
+1. **DEPLOYMENT_GUIDE.md**
+   - Architecture overview
+   - Database setup
+   - Edge Functions deployment
+   - Environment variable configuration
+   - Frontend deployment for Apache/Nginx/Cloudflare Pages
+   - Testing procedures
+   - Troubleshooting guide
+
+2. **REFACTORING_SUMMARY.md** (this file)
+   - Complete overview of changes
+   - What was accomplished
+   - What remains
+
+## Key Technologies Used
+
+- **Supabase Edge Functions** - Deno runtime for serverless functions
+- **Supabase PostgreSQL** - Database with RLS
+- **Supabase Auth** - User authentication
+- **Resend API** - Email sending (replaces Nodemailer)
+- **React + Vite** - Frontend framework
+- **TypeScript** - Type safety
+
+## What Remains / Future Work
+
+### High Priority
+
+1. **Create Additional Edge Functions** (if needed)
+   - Dashboard stats API (`/api/dashboard/stats`)
+   - Call logs API (`/api/call-logs`)
+   - Stores API (`/api/stores`)
+   - ShopRenter API integrations
+
+2. **Testing**
+   - End-to-end authentication flow testing
+   - OAuth integration testing (Shopify, WooCommerce)
+   - Email delivery testing
+   - Static hosting routing testing
+
+3. **Email Service Configuration**
+   - Set up Resend account and API key
+   - Configure sending domain
+   - Test OTP email delivery
+
+### Medium Priority
+
+1. **Security Enhancements**
+   - Add rate limiting to Edge Functions
+   - Restrict CORS origins to production domain
+   - Implement proper error handling
+   - Add input validation and sanitization
+
+2. **Performance Optimization**
+   - Implement database connection pooling
+   - Add caching where appropriate
+   - Optimize Edge Function cold starts
+
+3. **Monitoring Setup**
+   - Set up error tracking
+   - Configure alerts for failed functions
+   - Implement analytics
+
+### Low Priority
+
+1. **ShopRenter Integration**
+   - Create ShopRenter OAuth Edge Function
+   - Test ShopRenter flow
+
+2. **Database Cleanup**
+   - Set up scheduled jobs for expired records cleanup
+   - Consider using pg_cron or similar
+
+3. **Additional Features**
+   - Implement refresh token rotation
+   - Add two-factor authentication option
+   - Improve error messages
+
+## Migration Checklist for Production
+
+- [ ] Configure Resend API key in Supabase secrets
+- [ ] Update Shopify app settings with new OAuth callback URLs
+- [ ] Update Shopify GDPR webhook URLs
+- [ ] Set all environment variables in Supabase Edge Functions
+- [ ] Build frontend with production environment variables
+- [ ] Deploy static files to web hosting
+- [ ] Configure SSL certificates
+- [ ] Test complete user signup flow
+- [ ] Test OAuth integrations
+- [ ] Test email delivery
+- [ ] Monitor Edge Function logs for errors
+- [ ] Update DNS records if needed
+- [ ] Test all routes and refresh behavior
+
+## Breaking Changes
+
+### URLs Changed
+
+**Old Backend Base URL:**
+```
+https://shopcall-ai-backend.vercel.app
+```
+
+**New Backend Base URL:**
+```
+https://ztklqodcdjeqpsvhlpud.supabase.co/functions/v1
+```
+
+### Endpoint Changes
+
+| Old Endpoint | New Endpoint |
+|-------------|-------------|
+| `/auth/signup` | `/auth/signup` (same path, different base) |
+| `/auth/login` | `/auth/login` |
+| `/auth/shopify` | `/shopify-oauth/init` |
+| `/auth/shopify/callback` | `/shopify-oauth/callback` |
+| `/auth/woocommerce` | `/woocommerce-oauth/init` |
+| `/auth/woocommerce/callback` | `/woocommerce-oauth/callback` |
+| `/gdpr/*` | `/gdpr-webhooks/*` |
+
+## Benefits of New Architecture
+
+1. **No Vendor Lock-in**: Can deploy frontend anywhere (not just Vercel)
+2. **Cost Effective**: Supabase free tier is generous for Edge Functions
+3. **Scalable**: Supabase Edge Functions auto-scale globally
+4. **Persistent Storage**: Database replaces in-memory stores (no data loss on cold starts)
+5. **Better DX**: Deno runtime is modern and TypeScript-native
+6. **Flexible Hosting**: Frontend can be hosted on Apache, Nginx, Cloudflare Pages, etc.
+
+## Files Created/Modified
+
+### New Files
+```
+/data/shopcall/DEPLOYMENT_GUIDE.md
+/data/shopcall/REFACTORING_SUMMARY.md
+/data/shopcall/shopcall.ai-main/.env.example
+/data/shopcall/shopcall.ai-main/public/.htaccess
+/data/shopcall/shopcall.ai-main/nginx.conf.example
+```
+
+### Modified Files
+```
+/data/shopcall/shopcall.ai-main/.env
+/data/shopcall/shopcall.ai-main/src/lib/config.ts
+/data/shopcall/shopcall.ai-main/src/components/context/AuthContext.tsx
+/data/shopcall/shopcall.ai-main/src/pages/Signup.tsx
+/data/shopcall/shopcall.ai-main/src/pages/OTP.tsx
+/data/shopcall/shopcall.ai-main/src/pages/Index.tsx
+/data/shopcall/shopcall.ai-main/src/components/CallLogsContent.tsx
+/data/shopcall/shopcall.ai-main/src/components/ShopRenterConnect.tsx
+```
+
+### Database Tables Created
+```sql
+- pending_signups (with indexes and cleanup function)
+- oauth_nonces (with indexes and cleanup function)
+```
+
+### Edge Functions Deployed
+```
+1. auth (version 2)
+2. shopify-oauth (version 1)
+3. woocommerce-oauth (version 1)
+4. gdpr-webhooks (version 1)
+```
+
+## Support & Troubleshooting
+
+Refer to the [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) for:
+- Detailed deployment instructions
+- Environment variable configuration
+- Testing procedures
+- Common issues and solutions
+
+## Conclusion
+
+The core refactoring is complete! The application has been successfully migrated from Vercel to Supabase Edge Functions with static hosting capability. The next steps involve testing the complete flows and deploying to production.
+
+The architecture is now:
+- ✅ Vendor-agnostic (no Vercel dependencies)
+- ✅ Database-backed (no in-memory stores)
+- ✅ Scalable (Supabase Edge Functions)
+- ✅ Deployable anywhere (static frontend)
+- ✅ Well-documented (comprehensive guides)

+ 1501 - 0
SHOPRENTER.md

@@ -0,0 +1,1501 @@
+# ShopRenter Integration Development Plan
+
+**Project:** ShopCall.ai - ShopRenter Integration
+**Platform:** ShopRenter (Hungarian e-commerce platform)
+**Integration Type:** OAuth 2.0 App with API Access
+**Documentation Source:** https://doc.shoprenter.hu/development/app-development/
+
+---
+
+## 📋 Table of Contents
+
+1. [Overview](#overview)
+2. [Registration Requirements](#registration-requirements)
+3. [Technical Architecture](#technical-architecture)
+4. [OAuth Flow Implementation](#oauth-flow-implementation)
+5. [API Integration](#api-integration)
+6. [Database Schema](#database-schema)
+7. [Backend Implementation](#backend-implementation)
+8. [Frontend Implementation](#frontend-implementation)
+9. [Security Considerations](#security-considerations)
+10. [Testing Strategy](#testing-strategy)
+11. [Deployment Plan](#deployment-plan)
+12. [Timeline & Milestones](#timeline--milestones)
+
+---
+
+## 🎯 Overview
+
+### What is ShopRenter?
+
+ShopRenter is a Hungarian e-commerce platform (similar to Shopify) that provides online store solutions. This integration will enable ShopCall.ai to connect with ShopRenter stores and provide AI-powered calling services.
+
+### Integration Goals
+
+- ✅ Enable ShopRenter merchants to connect their stores via OAuth
+- ✅ Sync product catalog, customer data, and order information
+- ✅ Provide AI calling services for ShopRenter merchants
+- ✅ Support Hungarian language and HUF currency
+- ✅ Comply with ShopRenter's app requirements and guidelines
+
+### Key Features
+
+1. **OAuth 2.0 Authentication** - Secure store connection
+2. **Product Sync** - Automatic product catalog synchronization
+3. **Customer Data Access** - Customer information for personalized calls
+4. **Order Information** - Access to order status for customer inquiries
+5. **Webhook Support** - Real-time updates for orders and products
+
+---
+
+## 📝 Registration Requirements
+
+### Data Required to Register with ShopRenter
+
+**Must be sent to:** partnersupport@shoprenter.hu
+
+#### 1. Application Information
+
+**Application Name:**
+```
+ShopCall.ai - AI Phone Assistant
+```
+
+**Short Description (max 70 chars):**
+```
+AI-powered phone assistant for automated customer service calls
+```
+
+**Application Details Link:**
+```
+https://shopcall.ai
+```
+
+**Application Type:**
+```
+Redirected (user redirected to our platform, not iframe)
+```
+
+#### 2. Technical Endpoints
+
+**EntryPoint (HTTPS required):**
+```
+https://shopcall.ai/integrations/shoprenter/entry
+```
+- This is where users land after OAuth installation
+- Must validate HMAC on entry
+- Displays integration success/configuration page
+
+**RedirectUri (HTTPS required):**
+```
+https://shopcall-ai-backend.vercel.app/auth/shoprenter/callback
+```
+- OAuth callback endpoint
+- Receives: shopname, code, timestamp, hmac, app_url
+- Validates HMAC and exchanges code for tokens
+
+**UninstallUri (HTTPS required):**
+```
+https://shopcall-ai-backend.vercel.app/webhooks/shoprenter/uninstall
+```
+- Called when app is uninstalled
+- Receives: shopname, code, timestamp, hmac
+- Cleanup: remove tokens, deactivate services
+
+#### 3. Visual Assets
+
+**Application Logo:**
+- Dimensions: **250x150px**
+- Format: PNG with transparency
+- Location: `/shopcall.ai-main/public/images/shoprenter-app-logo.png`
+
+#### 4. Test Store
+
+**Test Store Name:**
+```
+shopcall-test-store
+```
+- URL will be: `shopcall-test-store.myshoprenter.hu`
+- Request at: https://www.shoprenter.hu/tesztigenyles/?devstore=1
+
+#### 5. Required Scopes
+
+Based on ShopCall.ai functionality, we need:
+
+```
+product:read          # Read product catalog
+product:write         # Update product information (if needed)
+customer:read         # Access customer data
+customer:write        # Update customer information (notes, tags)
+order:read            # Read order information
+order:write           # Update order status, add notes
+category:read         # Read product categories
+webhook:read          # List webhooks
+webhook:write         # Create/update webhooks for real-time sync
+```
+
+**Scope Justification:**
+- **product:read** - Sync product catalog for AI knowledge base
+- **customer:read** - Personalize AI responses based on customer history
+- **order:read** - Provide order status information during calls
+- **order:write** - Update order notes after calls
+- **webhook:write** - Set up real-time synchronization
+
+---
+
+## 🏗️ Technical Architecture
+
+### System Overview
+
+```
+┌─────────────────┐         ┌──────────────────┐         ┌─────────────────┐
+│   ShopRenter    │         │   ShopCall.ai    │         │   Supabase      │
+│     Store       │◄───────►│     Backend      │◄───────►│   Database      │
+│                 │  OAuth  │                  │  Store  │                 │
+└─────────────────┘         └──────────────────┘         └─────────────────┘
+       │                             │
+       │ Webhooks                    │ API Calls
+       ▼                             ▼
+┌─────────────────┐         ┌──────────────────┐
+│  Product Sync   │         │   AI Phone       │
+│  Order Updates  │         │   Assistant      │
+└─────────────────┘         └──────────────────┘
+```
+
+### Component Breakdown
+
+**Frontend (React/Vite):**
+- ShopRenter connection button
+- OAuth initiation
+- Store configuration UI
+- Success/error handling
+
+**Backend (Express.js/Node.js):**
+- OAuth flow handling
+- HMAC validation
+- Token management
+- API client for ShopRenter
+- Webhook receivers
+
+**Database (Supabase):**
+- Store credentials storage
+- Product catalog cache
+- Sync status tracking
+
+---
+
+## 🔐 OAuth Flow Implementation
+
+### Installation Process Flow
+
+```mermaid
+sequenceDiagram
+    participant Merchant
+    participant ShopRenter
+    participant ShopCall Backend
+    participant Database
+
+    Merchant->>ShopRenter: Click "Install ShopCall.ai"
+    ShopRenter->>ShopCall Backend: GET /auth/shoprenter/callback?shopname=X&code=Y&timestamp=Z&hmac=H&app_url=U
+    ShopCall Backend->>ShopCall Backend: Validate HMAC (sha256)
+    alt HMAC Valid
+        ShopCall Backend->>Database: Store installation data
+        ShopCall Backend->>Merchant: Redirect to app_url
+        ShopRenter->>ShopCall Backend: GET /integrations/shoprenter/entry?shopname=X&code=Y&timestamp=Z&hmac=H
+        ShopCall Backend->>ShopCall Backend: Validate HMAC again
+        ShopCall Backend->>ShopCall Backend: Request OAuth token from ShopRenter
+        ShopCall Backend->>Database: Store access token
+        ShopCall Backend->>Merchant: Show success page
+    else HMAC Invalid
+        ShopCall Backend->>Merchant: Show error (403)
+    end
+```
+
+### HMAC Validation Process
+
+**ShopRenter sends:**
+```
+GET /auth/shoprenter/callback?shopname=example&code=0907a61c0c8d55e99db179b68161bc00&timestamp=1337178173&hmac=d48e5d...&app_url=...
+```
+
+**Validation Algorithm:**
+
+```javascript
+function validateHMAC(query, clientSecret) {
+  // 1. Extract HMAC from query
+  const { hmac, ...params } = query;
+
+  // 2. Build query string without HMAC (sorted alphabetically)
+  const sortedParams = Object.keys(params)
+    .sort()
+    .map(key => `${key}=${params[key]}`)
+    .join('&');
+
+  // Example: "code=0907a61c0c8d55e99db179b68161bc00&shopname=example&timestamp=1337178173"
+
+  // 3. Generate HMAC using sha256
+  const crypto = require('crypto');
+  const calculatedHmac = crypto
+    .createHmac('sha256', clientSecret)
+    .update(sortedParams)
+    .digest('hex');
+
+  // 4. Compare (timing-safe)
+  return crypto.timingSafeEqual(
+    Buffer.from(calculatedHmac),
+    Buffer.from(hmac)
+  );
+}
+```
+
+**Security Notes:**
+- ⚠️ **Always validate HMAC** before processing request
+- ⚠️ Use timing-safe comparison to prevent timing attacks
+- ⚠️ Check timestamp to prevent replay attacks (within 5 minutes)
+- ⚠️ Store ClientSecret securely in environment variables
+
+---
+
+## 🔌 API Integration
+
+### OAuth Token Acquisition
+
+After successful HMAC validation, request access token:
+
+**Endpoint:** (To be confirmed with ShopRenter Partner Support)
+```
+POST https://{shopname}.myshoprenter.hu/oauth/token
+```
+
+**Request Body:**
+```json
+{
+  "grant_type": "authorization_code",
+  "client_id": "{ClientId}",
+  "client_secret": "{ClientSecret}",
+  "code": "{code_from_callback}",
+  "redirect_uri": "https://shopcall-ai-backend.vercel.app/auth/shoprenter/callback"
+}
+```
+
+**Response:**
+```json
+{
+  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
+  "token_type": "Bearer",
+  "expires_in": 3600,
+  "refresh_token": "def50200...",
+  "scope": "product:read customer:read order:read"
+}
+```
+
+### API Base URL
+
+```
+https://{shopname}.myshoprenter.hu/api
+```
+
+### Common API Endpoints
+
+Based on ShopRenter API documentation:
+
+#### 1. Products
+
+**List Products:**
+```
+GET /api/products
+Authorization: Bearer {access_token}
+
+Response:
+{
+  "items": [
+    {
+      "id": "cHJvZHVjdC1wcm9kdWN0X2lkPTE=",
+      "name": "Product Name",
+      "sku": "SKU-123",
+      "price": "9990.00",
+      "currency": "HUF",
+      "description": "Product description",
+      "stock": 10,
+      "active": true
+    }
+  ],
+  "pagination": {
+    "total": 150,
+    "page": 1,
+    "limit": 25
+  }
+}
+```
+
+#### 2. Customers
+
+**List Customers:**
+```
+GET /api/customers
+Authorization: Bearer {access_token}
+
+Response:
+{
+  "items": [
+    {
+      "id": "Y3VzdG9tZXItY3VzdG9tZXJfaWQ9MQ==",
+      "firstname": "János",
+      "lastname": "Kovács",
+      "email": "janos.kovacs@example.com",
+      "phone": "+36201234567",
+      "created_at": "2024-01-15T10:30:00Z"
+    }
+  ]
+}
+```
+
+#### 3. Orders
+
+**List Orders:**
+```
+GET /api/orders
+Authorization: Bearer {access_token}
+
+Response:
+{
+  "items": [
+    {
+      "id": "b3JkZXItb3JkZXJfaWQ9MQ==",
+      "order_number": "SR-2024-001",
+      "customer_id": "Y3VzdG9tZXItY3VzdG9tZXJfaWQ9MQ==",
+      "status": "processing",
+      "total": "29990.00",
+      "currency": "HUF",
+      "created_at": "2024-01-20T14:25:00Z",
+      "items": [...]
+    }
+  ]
+}
+```
+
+#### 4. Webhooks
+
+**Register Webhook:**
+```
+POST /api/webhooks
+Authorization: Bearer {access_token}
+Content-Type: application/json
+
+Body:
+{
+  "event": "order/create",
+  "address": "https://shopcall-ai-backend.vercel.app/webhooks/shoprenter/orders",
+  "active": true
+}
+
+Response:
+{
+  "id": "d2ViaG9vay13ZWJob29rX2lkPTE=",
+  "event": "order/create",
+  "address": "https://...",
+  "active": true
+}
+```
+
+**Available Webhook Events:**
+- `order/create` - New order created
+- `order/update` - Order status changed
+- `product/create` - New product added
+- `product/update` - Product information changed
+- `product/delete` - Product deleted
+- `customer/create` - New customer registered
+- `customer/update` - Customer information updated
+
+---
+
+## 💾 Database Schema
+
+### New Table: `shoprenter_tokens`
+
+```sql
+CREATE TABLE shoprenter_tokens (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID REFERENCES stores(id) ON DELETE CASCADE,
+
+  -- OAuth tokens
+  access_token TEXT NOT NULL,
+  refresh_token TEXT,
+  token_type VARCHAR(20) DEFAULT 'Bearer',
+  expires_at TIMESTAMP WITH TIME ZONE,
+
+  -- Scopes
+  scopes TEXT[], -- ['product:read', 'customer:read', ...]
+
+  -- Store information
+  shopname VARCHAR(255) NOT NULL, -- e.g., 'example' from 'example.myshoprenter.hu'
+  shop_domain VARCHAR(255) NOT NULL, -- Full domain
+
+  -- Status
+  is_active BOOLEAN DEFAULT true,
+  last_sync_at TIMESTAMP WITH TIME ZONE,
+
+  -- Metadata
+  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+
+  -- Constraints
+  UNIQUE(store_id)
+);
+
+-- Index for fast lookups
+CREATE INDEX idx_shoprenter_tokens_shopname ON shoprenter_tokens(shopname);
+CREATE INDEX idx_shoprenter_tokens_active ON shoprenter_tokens(is_active);
+
+-- Trigger for updated_at
+CREATE TRIGGER set_shoprenter_tokens_updated_at
+BEFORE UPDATE ON shoprenter_tokens
+FOR EACH ROW
+EXECUTE FUNCTION update_updated_at_column();
+```
+
+### Update Existing `stores` Table
+
+```sql
+-- Add ShopRenter-specific columns
+ALTER TABLE stores ADD COLUMN IF NOT EXISTS shoprenter_app_id VARCHAR(50);
+ALTER TABLE stores ADD COLUMN IF NOT EXISTS shoprenter_client_id VARCHAR(255);
+```
+
+### New Table: `shoprenter_products_cache`
+
+```sql
+CREATE TABLE shoprenter_products_cache (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID REFERENCES stores(id) ON DELETE CASCADE,
+
+  -- Product data from ShopRenter API
+  shoprenter_product_id VARCHAR(255) NOT NULL,
+  name VARCHAR(500),
+  sku VARCHAR(255),
+  price DECIMAL(10,2),
+  currency VARCHAR(3) DEFAULT 'HUF',
+  description TEXT,
+  stock INTEGER,
+  active BOOLEAN,
+
+  -- Raw data
+  raw_data JSONB, -- Full API response
+
+  -- Sync metadata
+  last_synced_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+
+  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+
+  -- Constraints
+  UNIQUE(store_id, shoprenter_product_id)
+);
+
+-- Indexes
+CREATE INDEX idx_shoprenter_products_store ON shoprenter_products_cache(store_id);
+CREATE INDEX idx_shoprenter_products_sku ON shoprenter_products_cache(sku);
+CREATE INDEX idx_shoprenter_products_active ON shoprenter_products_cache(active);
+```
+
+### New Table: `shoprenter_webhooks`
+
+```sql
+CREATE TABLE shoprenter_webhooks (
+  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+  store_id UUID REFERENCES stores(id) ON DELETE CASCADE,
+
+  -- Webhook details
+  shoprenter_webhook_id VARCHAR(255),
+  event VARCHAR(100) NOT NULL, -- 'order/create', 'product/update', etc.
+  callback_url TEXT NOT NULL,
+  is_active BOOLEAN DEFAULT true,
+
+  -- Statistics
+  last_received_at TIMESTAMP WITH TIME ZONE,
+  total_received INTEGER DEFAULT 0,
+
+  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+
+  UNIQUE(store_id, event)
+);
+```
+
+---
+
+## 🔧 Backend Implementation
+
+### File Structure
+
+```
+shopcall.ai-backend-main/
+├── api/
+│   └── index.js                         # Main server file
+├── lib/
+│   └── shoprenter/
+│       ├── oauth.js                     # OAuth flow handling
+│       ├── api-client.js                # ShopRenter API client
+│       ├── hmac-validator.js            # HMAC validation utility
+│       ├── webhook-processor.js         # Webhook handling
+│       └── sync-service.js              # Product/customer sync
+└── config/
+    └── shoprenter.config.js             # ShopRenter configuration
+```
+
+### 1. Configuration (`config/shoprenter.config.js`)
+
+```javascript
+module.exports = {
+  appId: process.env.SHOPRENTER_APP_ID,
+  clientId: process.env.SHOPRENTER_CLIENT_ID,
+  clientSecret: process.env.SHOPRENTER_CLIENT_SECRET,
+
+  redirectUri: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter/callback'
+    : 'http://localhost:3000/auth/shoprenter/callback',
+
+  entryPoint: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall.ai/integrations/shoprenter/entry'
+    : 'http://localhost:8080/integrations/shoprenter/entry',
+
+  uninstallUri: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/webhooks/shoprenter/uninstall'
+    : 'http://localhost:3000/webhooks/shoprenter/uninstall',
+
+  scopes: [
+    'product:read',
+    'product:write',
+    'customer:read',
+    'customer:write',
+    'order:read',
+    'order:write',
+    'category:read',
+    'webhook:read',
+    'webhook:write'
+  ],
+
+  apiVersion: 'v1',
+  maxRequestsPerSecond: 5, // Rate limiting
+  tokenExpiryBuffer: 300, // Refresh 5 min before expiry
+};
+```
+
+### 2. HMAC Validator (`lib/shoprenter/hmac-validator.js`)
+
+```javascript
+const crypto = require('crypto');
+const config = require('../../config/shoprenter.config');
+
+/**
+ * Validate HMAC signature from ShopRenter
+ * @param {Object} query - Query parameters from request
+ * @param {string} query.hmac - HMAC signature
+ * @param {string} query.shopname - Store name
+ * @param {string} query.code - Authorization code
+ * @param {string} query.timestamp - Request timestamp
+ * @returns {boolean} - True if HMAC is valid
+ */
+function validateHMAC(query) {
+  const { hmac, ...params } = query;
+
+  if (!hmac) {
+    console.error('[ShopRenter] HMAC missing from request');
+    return false;
+  }
+
+  // Build sorted query string without HMAC
+  const sortedParams = Object.keys(params)
+    .sort()
+    .map(key => `${key}=${params[key]}`)
+    .join('&');
+
+  // Calculate HMAC using sha256
+  const calculatedHmac = crypto
+    .createHmac('sha256', config.clientSecret)
+    .update(sortedParams)
+    .digest('hex');
+
+  // Timing-safe comparison
+  try {
+    return crypto.timingSafeEqual(
+      Buffer.from(calculatedHmac),
+      Buffer.from(hmac)
+    );
+  } catch (error) {
+    console.error('[ShopRenter] HMAC comparison error:', error);
+    return false;
+  }
+}
+
+/**
+ * Validate request timestamp (prevent replay attacks)
+ * @param {string} timestamp - Unix timestamp from request
+ * @param {number} maxAgeSeconds - Maximum age allowed (default: 300 = 5 min)
+ * @returns {boolean} - True if timestamp is within valid range
+ */
+function validateTimestamp(timestamp, maxAgeSeconds = 300) {
+  const requestTime = parseInt(timestamp, 10);
+  const currentTime = Math.floor(Date.now() / 1000);
+  const age = currentTime - requestTime;
+
+  if (age < 0) {
+    console.error('[ShopRenter] Request timestamp is in the future');
+    return false;
+  }
+
+  if (age > maxAgeSeconds) {
+    console.error(`[ShopRenter] Request timestamp too old: ${age}s > ${maxAgeSeconds}s`);
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Validate complete ShopRenter request
+ * @param {Object} query - Query parameters
+ * @returns {Object} - { valid: boolean, error?: string }
+ */
+function validateRequest(query) {
+  // Check required parameters
+  const required = ['shopname', 'code', 'timestamp', 'hmac'];
+  const missing = required.filter(param => !query[param]);
+
+  if (missing.length > 0) {
+    return {
+      valid: false,
+      error: `Missing required parameters: ${missing.join(', ')}`
+    };
+  }
+
+  // Validate timestamp
+  if (!validateTimestamp(query.timestamp)) {
+    return {
+      valid: false,
+      error: 'Request timestamp invalid or expired'
+    };
+  }
+
+  // Validate HMAC
+  if (!validateHMAC(query)) {
+    return {
+      valid: false,
+      error: 'HMAC validation failed'
+    };
+  }
+
+  return { valid: true };
+}
+
+module.exports = {
+  validateHMAC,
+  validateTimestamp,
+  validateRequest
+};
+```
+
+### 3. OAuth Handler (`lib/shoprenter/oauth.js`)
+
+```javascript
+const axios = require('axios');
+const config = require('../../config/shoprenter.config');
+const { createClient } = require('@supabase/supabase-js');
+
+const supabase = createClient(
+  process.env.SUPABASE_URL,
+  process.env.SUPABASE_ANON_KEY
+);
+
+/**
+ * Exchange authorization code for access token
+ * @param {string} shopname - Store name
+ * @param {string} code - Authorization code
+ * @returns {Promise<Object>} - Token response
+ */
+async function exchangeCodeForToken(shopname, code) {
+  const tokenUrl = `https://${shopname}.myshoprenter.hu/oauth/token`;
+
+  try {
+    const response = await axios.post(tokenUrl, {
+      grant_type: 'authorization_code',
+      client_id: config.clientId,
+      client_secret: config.clientSecret,
+      code: code,
+      redirect_uri: config.redirectUri
+    }, {
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json'
+      }
+    });
+
+    console.log(`[ShopRenter] Token acquired for ${shopname}`);
+    return response.data;
+  } catch (error) {
+    console.error('[ShopRenter] Token exchange error:', error.response?.data || error.message);
+    throw new Error('Failed to exchange code for token');
+  }
+}
+
+/**
+ * Store ShopRenter credentials in database
+ * @param {string} userId - User ID
+ * @param {string} shopname - Store name
+ * @param {Object} tokenData - Token response from OAuth
+ * @returns {Promise<Object>} - Database record
+ */
+async function storeCredentials(userId, shopname, tokenData) {
+  const shopDomain = `${shopname}.myshoprenter.hu`;
+  const expiresAt = new Date(Date.now() + (tokenData.expires_in * 1000));
+
+  // 1. Create or update stores record
+  const { data: store, error: storeError } = await supabase
+    .from('stores')
+    .upsert({
+      user_id: userId,
+      platform_name: 'shoprenter',
+      store_name: shopname,
+      store_url: `https://${shopDomain}`,
+      shoprenter_app_id: config.appId,
+      shoprenter_client_id: config.clientId,
+      is_active: true,
+      connected_at: new Date().toISOString()
+    }, {
+      onConflict: 'user_id,platform_name,store_url'
+    })
+    .select()
+    .single();
+
+  if (storeError) {
+    console.error('[ShopRenter] Error storing store:', storeError);
+    throw storeError;
+  }
+
+  // 2. Store OAuth tokens
+  const { data: tokens, error: tokensError } = await supabase
+    .from('shoprenter_tokens')
+    .upsert({
+      store_id: store.id,
+      access_token: tokenData.access_token,
+      refresh_token: tokenData.refresh_token,
+      token_type: tokenData.token_type,
+      expires_at: expiresAt.toISOString(),
+      scopes: tokenData.scope ? tokenData.scope.split(' ') : config.scopes,
+      shopname: shopname,
+      shop_domain: shopDomain,
+      is_active: true
+    }, {
+      onConflict: 'store_id'
+    })
+    .select()
+    .single();
+
+  if (tokensError) {
+    console.error('[ShopRenter] Error storing tokens:', tokensError);
+    throw tokensError;
+  }
+
+  console.log(`[ShopRenter] Credentials stored for ${shopname}`);
+  return { store, tokens };
+}
+
+/**
+ * Refresh access token
+ * @param {string} shopname - Store name
+ * @param {string} refreshToken - Refresh token
+ * @returns {Promise<Object>} - New token data
+ */
+async function refreshAccessToken(shopname, refreshToken) {
+  const tokenUrl = `https://${shopname}.myshoprenter.hu/oauth/token`;
+
+  try {
+    const response = await axios.post(tokenUrl, {
+      grant_type: 'refresh_token',
+      client_id: config.clientId,
+      client_secret: config.clientSecret,
+      refresh_token: refreshToken
+    });
+
+    console.log(`[ShopRenter] Token refreshed for ${shopname}`);
+    return response.data;
+  } catch (error) {
+    console.error('[ShopRenter] Token refresh error:', error.response?.data || error.message);
+    throw new Error('Failed to refresh token');
+  }
+}
+
+/**
+ * Get valid access token (refreshes if needed)
+ * @param {string} storeId - Store ID
+ * @returns {Promise<string>} - Valid access token
+ */
+async function getValidAccessToken(storeId) {
+  // Fetch tokens from database
+  const { data: tokens, error } = await supabase
+    .from('shoprenter_tokens')
+    .select('*')
+    .eq('store_id', storeId)
+    .eq('is_active', true)
+    .single();
+
+  if (error || !tokens) {
+    throw new Error('ShopRenter tokens not found');
+  }
+
+  // Check if token is expired or about to expire
+  const expiresAt = new Date(tokens.expires_at);
+  const now = new Date();
+  const bufferTime = config.tokenExpiryBuffer * 1000; // 5 minutes
+
+  if (expiresAt.getTime() - now.getTime() < bufferTime) {
+    // Token expired or expiring soon, refresh it
+    console.log(`[ShopRenter] Token expiring, refreshing for ${tokens.shopname}`);
+
+    const newTokenData = await refreshAccessToken(tokens.shopname, tokens.refresh_token);
+    const newExpiresAt = new Date(Date.now() + (newTokenData.expires_in * 1000));
+
+    // Update database
+    await supabase
+      .from('shoprenter_tokens')
+      .update({
+        access_token: newTokenData.access_token,
+        refresh_token: newTokenData.refresh_token || tokens.refresh_token,
+        expires_at: newExpiresAt.toISOString(),
+        updated_at: new Date().toISOString()
+      })
+      .eq('id', tokens.id);
+
+    return newTokenData.access_token;
+  }
+
+  return tokens.access_token;
+}
+
+module.exports = {
+  exchangeCodeForToken,
+  storeCredentials,
+  refreshAccessToken,
+  getValidAccessToken
+};
+```
+
+### 4. API Routes (`api/index.js`)
+
+Add these routes to your main server file:
+
+```javascript
+const { validateRequest } = require('../lib/shoprenter/hmac-validator');
+const { exchangeCodeForToken, storeCredentials } = require('../lib/shoprenter/oauth');
+
+// ShopRenter OAuth Callback
+app.get('/auth/shoprenter/callback', async (req, res) => {
+  const { shopname, code, timestamp, hmac, app_url } = req.query;
+
+  console.log(`[ShopRenter] OAuth callback received for ${shopname}`);
+
+  // 1. Validate request
+  const validation = validateRequest(req.query);
+  if (!validation.valid) {
+    console.error(`[ShopRenter] Validation failed: ${validation.error}`);
+    return res.status(403).json({
+      error: 'Invalid request',
+      details: validation.error
+    });
+  }
+
+  try {
+    // 2. Exchange code for tokens
+    const tokenData = await exchangeCodeForToken(shopname, code);
+
+    // 3. Store credentials
+    // Note: We need user_id here. In ShopRenter flow, we might need to:
+    // - Store shopname in a temp Map with code
+    // - Let user authenticate first
+    // For now, we'll redirect to app_url and handle auth there
+
+    // Store in temporary map for later association
+    const installationId = crypto.randomBytes(16).toString('hex');
+    global.pendingShopRenterInstalls = global.pendingShopRenterInstalls || new Map();
+    global.pendingShopRenterInstalls.set(installationId, {
+      shopname,
+      tokenData,
+      timestamp: Date.now(),
+      expiresAt: Date.now() + (15 * 60 * 1000) // 15 minutes
+    });
+
+    // 4. Redirect to app_url with installation_id
+    const redirectUrl = new URL(app_url);
+    redirectUrl.searchParams.append('sr_install', installationId);
+
+    console.log(`[ShopRenter] Redirecting to: ${redirectUrl.toString()}`);
+    res.redirect(redirectUrl.toString());
+
+  } catch (error) {
+    console.error('[ShopRenter] OAuth callback error:', error);
+    res.status(500).json({
+      error: 'Installation failed',
+      details: error.message
+    });
+  }
+});
+
+// ShopRenter EntryPoint (after redirect from callback)
+app.get('/integrations/shoprenter/entry', securedSession, async (req, res) => {
+  const { shopname, code, timestamp, hmac, sr_install } = req.query;
+
+  console.log(`[ShopRenter] EntryPoint accessed for ${shopname}`);
+
+  // 1. Validate HMAC
+  const validation = validateRequest({ shopname, code, timestamp, hmac });
+  if (!validation.valid) {
+    return res.status(403).json({
+      error: 'Invalid request',
+      details: validation.error
+    });
+  }
+
+  try {
+    // 2. Get installation data from temporary storage
+    if (!sr_install || !global.pendingShopRenterInstalls?.has(sr_install)) {
+      return res.status(400).json({
+        error: 'Installation session not found or expired'
+      });
+    }
+
+    const installData = global.pendingShopRenterInstalls.get(sr_install);
+
+    // 3. Store credentials with authenticated user
+    await storeCredentials(req.user.id, installData.shopname, installData.tokenData);
+
+    // 4. Clean up temporary storage
+    global.pendingShopRenterInstalls.delete(sr_install);
+
+    // 5. Set up webhooks (async)
+    setupWebhooks(req.user.id, installData.shopname).catch(err => {
+      console.error('[ShopRenter] Webhook setup error:', err);
+    });
+
+    // 6. Start initial sync (async)
+    syncShopRenterData(req.user.id, installData.shopname).catch(err => {
+      console.error('[ShopRenter] Initial sync error:', err);
+    });
+
+    // 7. Redirect to success page
+    res.redirect('https://shopcall.ai/integrations?connected=shoprenter&status=success');
+
+  } catch (error) {
+    console.error('[ShopRenter] EntryPoint error:', error);
+    res.redirect('https://shopcall.ai/integrations?connected=shoprenter&status=error');
+  }
+});
+
+// ShopRenter Uninstall Webhook
+app.get('/webhooks/shoprenter/uninstall', async (req, res) => {
+  const { shopname, code, timestamp, hmac } = req.query;
+
+  console.log(`[ShopRenter] Uninstall webhook received for ${shopname}`);
+
+  // 1. Validate HMAC
+  const validation = validateRequest(req.query);
+  if (!validation.valid) {
+    return res.status(403).json({
+      error: 'Invalid request',
+      details: validation.error
+    });
+  }
+
+  try {
+    // 2. Find store by shopname
+    const { data: tokens } = await supabase
+      .from('shoprenter_tokens')
+      .select('store_id')
+      .eq('shopname', shopname)
+      .single();
+
+    if (tokens) {
+      // 3. Deactivate store
+      await supabase
+        .from('stores')
+        .update({ is_active: false, updated_at: new Date().toISOString() })
+        .eq('id', tokens.store_id);
+
+      // 4. Deactivate tokens
+      await supabase
+        .from('shoprenter_tokens')
+        .update({ is_active: false, updated_at: new Date().toISOString() })
+        .eq('store_id', tokens.store_id);
+
+      console.log(`[ShopRenter] Store ${shopname} uninstalled successfully`);
+    }
+
+    // 5. Respond with 200 (ShopRenter expects this)
+    res.status(200).json({ message: 'Uninstall processed' });
+
+  } catch (error) {
+    console.error('[ShopRenter] Uninstall error:', error);
+    // Still respond with 200 to prevent retries
+    res.status(200).json({ message: 'Uninstall processed with errors' });
+  }
+});
+
+// Cleanup expired installations periodically
+setInterval(() => {
+  if (!global.pendingShopRenterInstalls) return;
+
+  const now = Date.now();
+  for (const [key, value] of global.pendingShopRenterInstalls.entries()) {
+    if (now > value.expiresAt) {
+      global.pendingShopRenterInstalls.delete(key);
+      console.log(`[ShopRenter] Cleaned up expired installation: ${key}`);
+    }
+  }
+}, 5 * 60 * 1000); // Every 5 minutes
+```
+
+---
+
+## 🎨 Frontend Implementation
+
+### Integration UI Component
+
+Create: `/shopcall.ai-main/src/components/ShopRenterConnect.tsx`
+
+```typescript
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Loader2, CheckCircle, AlertCircle } from 'lucide-react';
+
+export function ShopRenterConnect() {
+  const [shopname, setShopname] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState('');
+
+  const handleConnect = async () => {
+    if (!shopname.trim()) {
+      setError('Kérjük, adja meg a bolt nevét');
+      return;
+    }
+
+    setLoading(true);
+    setError('');
+
+    try {
+      // For ShopRenter, the store owner must initiate installation from ShopRenter admin
+      // This would typically open the ShopRenter app store
+      window.open(
+        `https://${shopname}.myshoprenter.hu/admin/app/${SHOPRENTER_CLIENT_ID}`,
+        '_blank'
+      );
+
+      // Show instructions
+      setError('');
+
+    } catch (err) {
+      setError('Hiba történt a kapcsolódás során');
+      console.error('ShopRenter connection error:', err);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <Card className="bg-slate-800 border-slate-700">
+      <CardHeader>
+        <div className="flex items-center gap-3">
+          <img
+            src="/images/shoprenter-logo.png"
+            alt="ShopRenter"
+            className="w-10 h-10"
+          />
+          <CardTitle className="text-white">ShopRenter</CardTitle>
+        </div>
+        <p className="text-slate-400">
+          Magyar webáruház platform - AI telefonos asszisztens integráció
+        </p>
+      </CardHeader>
+      <CardContent className="space-y-4">
+        <div className="space-y-2">
+          <Label htmlFor="shopname" className="text-slate-300">
+            Bolt neve
+          </Label>
+          <div className="flex gap-2">
+            <Input
+              id="shopname"
+              placeholder="pelda"
+              value={shopname}
+              onChange={(e) => setShopname(e.target.value)}
+              className="bg-slate-700 border-slate-600 text-white flex-1"
+            />
+            <span className="flex items-center text-slate-400">
+              .myshoprenter.hu
+            </span>
+          </div>
+          <p className="text-slate-500 text-sm">
+            Add meg a bolt nevét (pl. "pelda" ha a bolt címe pelda.myshoprenter.hu)
+          </p>
+        </div>
+
+        {error && (
+          <Alert variant="destructive" className="bg-red-900/20 border-red-900">
+            <AlertCircle className="w-4 h-4" />
+            <AlertDescription>{error}</AlertDescription>
+          </Alert>
+        )}
+
+        <Button
+          onClick={handleConnect}
+          disabled={loading}
+          className="w-full bg-[#85b218] hover:bg-[#6d9315] text-white"
+        >
+          {loading ? (
+            <>
+              <Loader2 className="w-4 h-4 mr-2 animate-spin" />
+              Kapcsolódás...
+            </>
+          ) : (
+            'ShopRenter kapcsolat létrehozása'
+          )}
+        </Button>
+
+        <div className="bg-slate-700/50 p-4 rounded-lg">
+          <h4 className="text-white font-medium mb-2">Telepítési lépések:</h4>
+          <ol className="text-slate-300 text-sm space-y-1 list-decimal list-inside">
+            <li>Kattints a "Kapcsolat létrehozása" gombra</li>
+            <li>Jelentkezz be a ShopRenter admin felületedre</li>
+            <li>Hagyd jóvá az alkalmazás telepítését</li>
+            <li>Visszairányítunk ide a sikeres telepítés után</li>
+          </ol>
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+```
+
+### Add to Integrations Page
+
+Update `/shopcall.ai-main/src/components/IntegrationsContent.tsx`:
+
+```typescript
+import { ShopRenterConnect } from './ShopRenterConnect';
+
+// In the platforms grid, add:
+<div className="grid gap-6 md:grid-cols-3">
+  {/* Existing Shopify card */}
+  {/* Existing WooCommerce card */}
+
+  {/* New ShopRenter card */}
+  <ShopRenterConnect />
+</div>
+```
+
+---
+
+## 🔒 Security Considerations
+
+### 1. HMAC Validation
+- ✅ **Always validate HMAC** on every request from ShopRenter
+- ✅ Use timing-safe comparison (`crypto.timingSafeEqual`)
+- ✅ Validate timestamp to prevent replay attacks (5-minute window)
+
+### 2. Token Storage
+- ✅ Store access tokens encrypted in database
+- ✅ Never log or expose tokens in responses
+- ✅ Implement automatic token refresh
+- ✅ Revoke tokens on uninstall
+
+### 3. API Communication
+- ✅ Always use HTTPS for all endpoints
+- ✅ Implement rate limiting (5 req/sec per ShopRenter guidelines)
+- ✅ Handle API errors gracefully
+- ✅ Implement retry logic with exponential backoff
+
+### 4. Data Privacy (GDPR Compliance)
+- ✅ Only request necessary scopes
+- ✅ Implement data deletion on uninstall
+- ✅ Provide clear privacy policy
+- ✅ Handle customer data securely
+
+### 5. Environment Variables
+
+```bash
+# .env for backend
+SHOPRENTER_APP_ID=your_app_id_here
+SHOPRENTER_CLIENT_ID=your_client_id_here
+SHOPRENTER_CLIENT_SECRET=your_client_secret_here
+```
+
+**⚠️ Never commit these to version control!**
+
+---
+
+## 🧪 Testing Strategy
+
+### 1. Test Store Setup
+
+1. Request test store at: https://www.shoprenter.hu/tesztigenyles/?devstore=1
+2. Store name: `shopcall-test-store`
+3. URL: `shopcall-test-store.myshoprenter.hu`
+
+### 2. Test Cases
+
+#### OAuth Flow Testing
+
+```
+Test Case 1: Successful Installation
+- Initiate OAuth from ShopRenter admin
+- Verify HMAC validation passes
+- Verify token exchange succeeds
+- Verify credentials stored in database
+- Verify redirect to success page
+
+Test Case 2: Invalid HMAC
+- Modify HMAC parameter
+- Verify request rejected with 403
+- Verify error logged
+
+Test Case 3: Expired Timestamp
+- Use timestamp older than 5 minutes
+- Verify request rejected
+- Verify error logged
+
+Test Case 4: Uninstall Flow
+- Trigger uninstall from ShopRenter
+- Verify webhook received
+- Verify store deactivated
+- Verify tokens deactivated
+```
+
+#### API Testing
+
+```
+Test Case 5: Product Sync
+- Request product list from API
+- Verify products cached in database
+- Verify product data accuracy
+
+Test Case 6: Token Refresh
+- Wait for token expiry
+- Make API request
+- Verify token auto-refreshed
+- Verify new token stored
+
+Test Case 7: Webhook Processing
+- Create test order in ShopRenter
+- Verify webhook received
+- Verify order data processed
+- Verify response 200 sent
+```
+
+### 3. Integration Testing Tools
+
+```javascript
+// test/shoprenter-integration.test.js
+const axios = require('axios');
+const crypto = require('crypto');
+
+describe('ShopRenter Integration', () => {
+  const testShop = 'shopcall-test-store';
+  const clientSecret = process.env.SHOPRENTER_CLIENT_SECRET;
+
+  function generateValidHMAC(params) {
+    const sorted = Object.keys(params)
+      .sort()
+      .map(key => `${key}=${params[key]}`)
+      .join('&');
+
+    return crypto
+      .createHmac('sha256', clientSecret)
+      .update(sorted)
+      .digest('hex');
+  }
+
+  it('should validate correct HMAC', async () => {
+    const params = {
+      shopname: testShop,
+      code: 'test123',
+      timestamp: Math.floor(Date.now() / 1000).toString()
+    };
+
+    params.hmac = generateValidHMAC(params);
+
+    const response = await axios.get('http://localhost:3000/auth/shoprenter/callback', {
+      params,
+      maxRedirects: 0,
+      validateStatus: () => true
+    });
+
+    expect(response.status).not.toBe(403);
+  });
+
+  it('should reject invalid HMAC', async () => {
+    const params = {
+      shopname: testShop,
+      code: 'test123',
+      timestamp: Math.floor(Date.now() / 1000).toString(),
+      hmac: 'invalid_hmac'
+    };
+
+    const response = await axios.get('http://localhost:3000/auth/shoprenter/callback', {
+      params,
+      maxRedirects: 0,
+      validateStatus: () => true
+    });
+
+    expect(response.status).toBe(403);
+  });
+});
+```
+
+---
+
+## 🚀 Deployment Plan
+
+### Phase 1: Development (Week 1-2)
+- [ ] Set up test ShopRenter store
+- [ ] Implement HMAC validation
+- [ ] Implement OAuth flow
+- [ ] Create database tables
+- [ ] Build API client
+- [ ] Test with test store
+
+### Phase 2: Integration (Week 3)
+- [ ] Implement product sync
+- [ ] Implement webhook handlers
+- [ ] Create frontend UI
+- [ ] Test end-to-end flow
+- [ ] Handle error scenarios
+
+### Phase 3: Testing (Week 4)
+- [ ] Unit tests
+- [ ] Integration tests
+- [ ] Security audit
+- [ ] Performance testing
+- [ ] User acceptance testing
+
+### Phase 4: Production Registration (Week 5)
+- [ ] Submit app to ShopRenter Partner Support
+- [ ] Provide all required data
+- [ ] Upload logo and assets
+- [ ] Wait for approval
+- [ ] Receive production credentials
+
+### Phase 5: Launch (Week 6)
+- [ ] Deploy to production
+- [ ] Configure production environment variables
+- [ ] Monitor logs and errors
+- [ ] Soft launch with beta users
+- [ ] Public launch
+
+---
+
+## 📅 Timeline & Milestones
+
+| Week | Milestone | Deliverable |
+|------|-----------|-------------|
+| 1 | Setup & Planning | Test store, HMAC validator |
+| 2 | OAuth Implementation | Working OAuth flow |
+| 3 | API Integration | Product sync, webhooks |
+| 4 | Testing & QA | All tests passing |
+| 5 | Registration | App submitted to ShopRenter |
+| 6 | Production Launch | Live integration |
+
+**Total Estimated Time:** 6 weeks
+
+---
+
+## 📚 Resources & References
+
+### Documentation
+- ShopRenter Developer Docs: https://doc.shoprenter.hu
+- API Reference: https://doc.shoprenter.hu/development/api/
+- App Development Guide: https://doc.shoprenter.hu/development/app-development/
+
+### Example Apps
+- PHP Demo: https://github.com/Shoprenter/sr-demo-app-php
+- Node.js Demo: https://github.com/Shoprenter/sr-demo-app-node
+
+### Contact
+- Partner Support: partnersupport@shoprenter.hu
+- Test Store Request: https://www.shoprenter.hu/tesztigenyles/?devstore=1
+
+---
+
+## ✅ Checklist for Launch
+
+### Pre-Registration
+- [ ] Review ShopRenter developer documentation
+- [ ] Prepare application name and description (Hungarian)
+- [ ] Design application logo (250x150px)
+- [ ] Set up HTTPS endpoints (EntryPoint, RedirectUri, UninstallUri)
+- [ ] Determine required API scopes
+- [ ] Request test store
+
+### Development
+- [ ] Implement HMAC validation
+- [ ] Implement OAuth flow
+- [ ] Create database schema
+- [ ] Build API client
+- [ ] Implement product sync
+- [ ] Set up webhook handlers
+- [ ] Create frontend UI
+- [ ] Add error handling
+- [ ] Implement logging
+
+### Testing
+- [ ] Test OAuth installation
+- [ ] Test HMAC validation
+- [ ] Test token refresh
+- [ ] Test product sync
+- [ ] Test webhooks
+- [ ] Test uninstall flow
+- [ ] Security testing
+- [ ] Performance testing
+
+### Registration
+- [ ] Submit to partnersupport@shoprenter.hu
+- [ ] Provide all required information
+- [ ] Upload logo and assets
+- [ ] Wait for AppId, ClientId, ClientSecret
+- [ ] Configure environment variables
+
+### Production
+- [ ] Deploy backend to production
+- [ ] Deploy frontend to production
+- [ ] Configure production URLs
+- [ ] Monitor logs for errors
+- [ ] Test with real store
+- [ ] Document usage for customers
+
+---
+
+## 🔄 Post-Launch Maintenance
+
+### Regular Tasks
+- Monitor webhook reliability
+- Check token refresh logs
+- Review error rates
+- Update API client when ShopRenter updates
+- Sync product catalog daily
+- Respond to merchant support requests
+
+### Scope Updates
+When new features require additional scopes:
+1. Email Partner Support with new scope list
+2. Wait for scope approval
+3. Direct existing merchants to approve new scopes:
+   `https://{shopName}.myshoprenter.hu/admin/app/{clientId}/approveScopes`
+4. Update app to use new scopes
+
+---
+
+**Document Version:** 1.0
+**Last Updated:** 2025-10-22
+**Author:** ShopCall.ai Development Team
+**Status:** Ready for Implementation
+

+ 255 - 0
TODO.md

@@ -0,0 +1,255 @@
+# ShopCall.ai Refactoring TODO
+
+## Project Goal
+Refactor the entire project to remove Vercel dependencies and enable static hosting on simple webhosting. Use Supabase Edge Functions for backend functionality.
+
+---
+
+## Phase 1: Analysis & Planning
+
+- [x] Analyze current backend API endpoints in `shopcall.ai-backend-main/api/index.js`
+  - [x] Document all authentication endpoints (signup, login, OTP, logout)
+  - [x] Document OAuth flows (Shopify, WooCommerce)
+  - [x] Document GDPR webhook endpoints
+  - [x] Document helper functions (generateOTP, sendOTPEmail, etc.)
+- [x] Identify current dependencies and integrations
+  - [x] Nodemailer for email sending → Replaced with Resend
+  - [x] In-memory stores (pendingSignups, nonceStore) → Replaced with database tables
+  - [x] Supabase client usage patterns
+- [x] Design Edge Functions architecture
+  - [x] Plan function structure and organization
+  - [x] Plan shared utilities and dependencies
+
+---
+
+## Phase 2: Database Migration
+
+- [x] Create Supabase database tables to replace in-memory stores
+  - [x] Create `pending_signups` table (replace pendingSignups Map)
+    - Fields: signup_id, email, password, full_name, company_name, user_name, otp, created_at, expires_at
+  - [x] Create `oauth_nonces` table (replace nonceStore Map)
+    - Fields: nonce, user_id, platform, shop, app_url, shopname, created_at, expires_at
+  - [x] Add TTL/cleanup mechanism for expired records (cleanup functions created)
+- [x] Update existing `stores` table if needed (already has required fields)
+- [x] Create database migration files (applied via Supabase MCP)
+- [ ] Test database schema with sample data
+
+---
+
+## Phase 3: Supabase Edge Functions Setup
+
+- [x] Set up Supabase Edge Functions project structure
+  - [x] Used Supabase MCP tool for deployment
+  - [x] Created modular function structure
+  - [x] Shared utilities (cors.ts, email.ts, oauth.ts)
+- [x] Create Edge Function for authentication endpoints
+  - [x] `/auth/signup` - Store user data and send OTP
+  - [x] `/auth/signup/verify` - Verify OTP and create user
+  - [x] `/auth/signup/resend-otp` - Resend OTP email
+  - [x] `/auth/login` - Email/password login
+  - [x] `/auth/logout` - Sign out
+  - [x] `/auth/check` - Validate session token
+- [x] Create Edge Function for Shopify integration
+  - [x] `/shopify-oauth/init` - Initialize OAuth flow
+  - [x] `/shopify-oauth/callback` - Handle OAuth callback
+- [x] Create Edge Function for WooCommerce integration
+  - [x] `/woocommerce-oauth/init` - Initialize OAuth flow (requires auth)
+  - [x] `/woocommerce-oauth/callback` - Handle OAuth callback
+- [x] Create Edge Function for GDPR webhooks
+  - [x] `/gdpr-webhooks/customers-data-request` - Customer data request
+  - [x] `/gdpr-webhooks/customers-redact` - Customer redaction
+  - [x] `/gdpr-webhooks/shop-redact` - Shop redaction
+- [x] Implement email sending in Edge Functions
+  - [x] Research Edge Function email options → Chose Resend
+  - [x] Implement OTP email templates
+  - [x] Replace Nodemailer functionality
+- [x] Create shared utilities for Edge Functions
+  - [x] OTP generation and validation (email.ts)
+  - [x] Email templates and sending (email.ts)
+  - [x] URL normalization and validation (oauth.ts)
+  - [x] HMAC webhook verification (in gdpr-webhooks)
+  - [x] CORS headers (cors.ts)
+
+---
+
+## Phase 4: Frontend Refactoring
+
+- [x] Update frontend to use environment variables
+  - [x] Create `.env.example` file
+  - [x] Replace hardcoded backend URL (https://shopcall-ai-backend.vercel.app)
+  - [x] Add Supabase Edge Function base URL configuration
+- [x] Update API calls to use Edge Functions
+  - [x] Update AuthContext to call Edge Functions
+  - [x] Update all API endpoints in components (6 files updated)
+  - [ ] Test authentication flow
+- [x] Configure Vite for static build
+  - [x] Review vite.config.ts for static hosting (already configured)
+  - [x] Proper base path configuration exists
+  - [x] Build output directory configured (dist/)
+- [x] Add client-side routing support for static hosting
+  - [x] Create .htaccess for Apache hosting
+  - [x] Create nginx.conf example for Nginx hosting
+  - [x] Handle 404 fallback to index.html
+- [ ] Remove Vercel dependencies (optional - can keep for now)
+  - [ ] Delete vercel.json from frontend
+  - [ ] Remove any Vercel-specific code
+- [x] Update environment handling
+  - [x] Use Vite's import.meta.env
+  - [x] Centralized in src/lib/config.ts
+
+---
+
+## Phase 5: Backend Cleanup
+
+- [ ] Remove Vercel-specific files from backend
+  - [ ] Delete vercel.json
+  - [ ] Archive api/index.js (keep for reference)
+- [ ] Move backend code to Edge Functions
+  - [ ] Ensure all functionality is migrated
+  - [ ] Delete unused files
+- [ ] Update .gitignore for both projects
+  - [ ] Ensure proper ignoring of build artifacts
+  - [ ] Ignore environment files
+  - [ ] Ignore Supabase local dev files
+
+---
+
+## Phase 6: Deployment & Testing
+
+- [ ] Deploy Edge Functions to Supabase
+  - [ ] Configure Edge Function secrets
+  - [ ] Set up environment variables in Supabase
+  - [ ] Deploy each function
+  - [ ] Test Edge Function endpoints
+- [ ] Build frontend for static hosting
+  - [ ] Run production build (npm run build)
+  - [ ] Test build output locally
+  - [ ] Verify all routes work correctly
+  - [ ] Test with different hosting scenarios
+- [ ] End-to-end testing
+  - [ ] Test authentication flow (signup, OTP, login)
+  - [ ] Test Shopify OAuth integration
+  - [ ] Test WooCommerce OAuth integration
+  - [ ] Test dashboard and protected routes
+  - [ ] Test GDPR webhook endpoints
+- [ ] Performance testing
+  - [ ] Test Edge Function response times
+  - [ ] Test static hosting performance
+  - [ ] Optimize bundle size if needed
+
+---
+
+## Phase 7: Documentation
+
+- [x] Create deployment guide for static hosting
+  - [x] Apache hosting setup with .htaccess
+  - [x] Nginx hosting setup with config
+  - [x] Environment variable configuration
+  - [x] Edge Functions deployment steps
+- [x] Create DEPLOYMENT_GUIDE.md
+  - [x] Document new Edge Functions architecture
+  - [x] Deployment instructions for multiple hosting options
+  - [x] Environment variable configuration
+  - [x] Testing procedures
+  - [x] Troubleshooting section
+- [x] Create REFACTORING_SUMMARY.md
+  - [x] Complete overview of all changes
+  - [x] What was accomplished
+  - [x] What remains to be done
+  - [x] Migration checklist
+- [ ] Update CLAUDE.md (optional)
+  - [ ] Document new Edge Functions architecture
+  - [ ] Update deployment instructions
+  - [ ] Remove Vercel-specific information
+  - [ ] Add static hosting requirements
+
+---
+
+## Phase 8: Final Review
+
+- [ ] Code review and cleanup
+  - [ ] Remove unused dependencies
+  - [ ] Clean up commented code
+  - [ ] Ensure consistent code style
+- [ ] Security review
+  - [ ] Verify no secrets in code
+  - [ ] Check CORS configuration
+  - [ ] Verify authentication flows
+  - [ ] Test Edge Function security
+- [ ] Final testing
+  - [ ] Complete end-to-end test
+  - [ ] Test on actual static hosting
+  - [ ] Verify all features work
+- [ ] Git repository cleanup
+  - [ ] Commit all changes
+  - [ ] Create proper commit messages
+  - [ ] Tag release version
+
+---
+
+## Notes
+
+### Current Architecture
+- **Frontend**: React/Vite at `shopcall.ai-main/` (deployed to Vercel)
+- **Backend**: Express.js serverless function at `shopcall.ai-backend-main/api/index.js` (deployed to Vercel)
+- **Database**: Supabase PostgreSQL
+- **Authentication**: Supabase Auth
+
+### Target Architecture
+- **Frontend**: Static React build (hosted on simple webhosting)
+- **Backend**: Supabase Edge Functions (Deno runtime)
+- **Database**: Supabase PostgreSQL (unchanged)
+- **Authentication**: Supabase Auth (unchanged)
+
+### Key Considerations
+- Edge Functions use Deno runtime (not Node.js)
+- Need to replace Nodemailer with Deno-compatible email solution
+- In-memory stores must move to Supabase database
+- Frontend needs proper routing configuration for static hosting
+- Environment variables must be properly managed for static hosting
+
+### Dependencies to Replace
+- ❌ Vercel serverless functions → ✅ Supabase Edge Functions
+- ❌ Nodemailer → ✅ Deno-compatible email service (Resend/SendGrid)
+- ❌ In-memory Maps → ✅ Supabase database tables
+- ❌ Hardcoded URLs → ✅ Environment variables
+
+---
+
+## Progress Tracking
+
+**Status**: ✅ Core Implementation Complete - Testing Phase
+**Last Updated**: 2025-10-27
+**Current Phase**: Phase 6 - Deployment & Testing
+
+### Summary of Completed Work
+
+- ✅ Phase 1: Analysis & Planning - **COMPLETE**
+- ✅ Phase 2: Database Migration - **COMPLETE** (testing pending)
+- ✅ Phase 3: Edge Functions Setup - **COMPLETE**
+- ✅ Phase 4: Frontend Refactoring - **COMPLETE** (testing pending)
+- ⏭️ Phase 5: Backend Cleanup - **SKIPPED** (can keep old code for reference)
+- 🔄 Phase 6: Deployment & Testing - **IN PROGRESS**
+- ✅ Phase 7: Documentation - **COMPLETE**
+- ⏸️ Phase 8: Final Review - **PENDING**
+
+### What's Ready
+- Database tables created and configured
+- 4 Edge Functions deployed (auth, shopify-oauth, woocommerce-oauth, gdpr-webhooks)
+- Frontend updated to use Edge Functions
+- Static hosting configuration files created
+- Comprehensive documentation written
+
+### What Needs Testing
+- [ ] Complete authentication flow (signup → OTP → verify → login)
+- [ ] OAuth integrations (Shopify, WooCommerce)
+- [ ] Email delivery (Resend API needs to be configured)
+- [ ] Static hosting routing
+- [ ] GDPR webhooks
+
+### Next Steps
+1. Configure Resend API key in Supabase
+2. Test authentication flow end-to-end
+3. Test OAuth integrations
+4. Deploy to production hosting
+5. Update OAuth callback URLs in provider settings

+ 18 - 0
deploy-functions.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+echo "Fixing permissions for supabase/functions directory..."
+sudo chown -R $(whoami):$(whoami) supabase/functions/
+
+echo "Creating function directories..."
+mkdir -p supabase/functions/auth
+mkdir -p supabase/functions/shopify-oauth
+mkdir -p supabase/functions/woocommerce-oauth
+mkdir -p supabase/functions/gdpr-webhooks
+
+echo "Note: You'll need to manually create the function files or I can help with that next."
+echo ""
+echo "After creating the files, deploy with:"
+echo "./node_modules/.bin/supabase functions deploy auth --no-verify-jwt"
+echo "./node_modules/.bin/supabase functions deploy shopify-oauth --no-verify-jwt"
+echo "./node_modules/.bin/supabase functions deploy woocommerce-oauth --no-verify-jwt"
+echo "./node_modules/.bin/supabase functions deploy gdpr-webhooks --no-verify-jwt"

+ 74 - 0
fix-jwt.sh

@@ -0,0 +1,74 @@
+#!/bin/bash
+
+set -e
+
+echo "=========================================="
+echo "ShopCall.ai Edge Functions JWT Fix"
+echo "=========================================="
+echo ""
+
+# Check if npm is installed
+if ! command -v npm &> /dev/null; then
+    echo "Error: npm is not installed. Please install Node.js and npm first."
+    exit 1
+fi
+
+# Install Supabase CLI
+echo "Step 1: Installing Supabase CLI..."
+npm install -g supabase
+echo "✓ Supabase CLI installed"
+echo ""
+
+# Login to Supabase
+echo "Step 2: Login to Supabase"
+echo "Your browser will open. Please login with your Supabase account."
+echo ""
+supabase login
+echo "✓ Logged in successfully"
+echo ""
+
+# Navigate to project directory
+cd /data/shopcall
+
+# Link to project
+echo "Step 3: Linking to your Supabase project..."
+supabase link --project-ref ztklqodcdjeqpsvhlpud
+echo "✓ Project linked"
+echo ""
+
+# Deploy functions with JWT verification disabled
+echo "Step 4: Deploying Edge Functions with JWT verification disabled..."
+echo ""
+
+echo "Deploying auth function..."
+supabase functions deploy auth --no-verify-jwt
+echo "✓ auth function deployed"
+echo ""
+
+echo "Deploying shopify-oauth function..."
+supabase functions deploy shopify-oauth --no-verify-jwt
+echo "✓ shopify-oauth function deployed"
+echo ""
+
+echo "Deploying woocommerce-oauth function..."
+supabase functions deploy woocommerce-oauth --no-verify-jwt
+echo "✓ woocommerce-oauth function deployed"
+echo ""
+
+echo "Deploying gdpr-webhooks function..."
+supabase functions deploy gdpr-webhooks --no-verify-jwt
+echo "✓ gdpr-webhooks function deployed"
+echo ""
+
+echo "=========================================="
+echo "✓ All functions deployed successfully!"
+echo "=========================================="
+echo ""
+echo "Next steps:"
+echo "1. Visit https://shopcall.ai/signup"
+echo "2. Try registering a new account"
+echo "3. You should receive an OTP email"
+echo ""
+echo "If you still encounter issues, check the logs at:"
+echo "https://supabase.com/dashboard/project/ztklqodcdjeqpsvhlpud/logs/edge-functions"
+echo ""

+ 5 - 0
package.json

@@ -0,0 +1,5 @@
+{
+  "devDependencies": {
+    "supabase": "^2.53.6"
+  }
+}

+ 36 - 0
shopcall.ai-backend-main/.gitignore

@@ -0,0 +1,36 @@
+# Dependencies
+node_modules/
+package-lock.json
+
+# Environment variables
+.env
+.env.local
+.env.development
+.env.production
+
+# Logs
+logs/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Test files
+test-supabase.js
+check-schema.js
+
+# Vercel
+.vercel

+ 1753 - 0
shopcall.ai-backend-main/api/index.js

@@ -0,0 +1,1753 @@
+const express = require('express');
+const crypto = require('crypto');
+const axios = require('axios');
+const { createClient } = require('@supabase/supabase-js');
+const path = require('path');
+const nodemailer = require('nodemailer');
+const app = express();
+require('dotenv').config();
+const cors = require('cors');
+const querystring = require('querystring');
+
+app.use(cors());
+
+// Add raw body parser for webhooks BEFORE any other middleware
+app.use('/gdpr', express.raw({ type: '*/*' }));
+
+// Regular middleware for other routes
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+app.use(express.static('public'));
+
+// Supabase configuration
+const supabaseUrl = process.env.SUPABASE_URL;
+const supabaseKey = process.env.SUPABASE_ANON_KEY;
+
+if (!supabaseUrl || !supabaseKey) {
+  console.error('Missing Supabase configuration. Please set SUPABASE_URL and SUPABASE_ANON_KEY in your .env file');
+}
+
+const supabase = createClient(supabaseUrl, supabaseKey);
+
+// Email transporter configuration
+const emailTransporter = nodemailer.createTransport({
+  service: 'gmail', // You can change this to your email provider
+  auth: {
+    user: process.env.EMAIL_USER || 'srwusc123@gmail.com',
+    pass: process.env.EMAIL_PASSWORD || 'sach zpxx dqec hhtq'
+  }
+});
+
+// Helper function to generate OTP
+function generateOTP() {
+  return Math.floor(100000 + Math.random() * 900000).toString(); // 6-digit OTP
+}
+
+// Helper function to send OTP email
+async function sendOTPEmail(email, otp, userName) {
+  const mailOptions = {
+    from: process.env.EMAIL_USER || 'srwusc123@gmail.com',
+    to: email,
+    subject: 'Verify Your Account - ShopCall.ai',
+    html: `
+      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
+        <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px; text-align: center; margin-bottom: 30px;">
+          <h1 style="color: white; margin: 0; font-size: 2rem;">SHOPCALL.AI</h1>
+          <p style="color: white; margin: 10px 0 0 0; opacity: 0.9;">Account Verification</p>
+        </div>
+        
+        <div style="background: #f8f9fa; padding: 30px; border-radius: 10px; text-align: center;">
+          <h2 style="color: #333; margin: 0 0 20px 0;">Hello ${userName}!</h2>
+          <p style="color: #666; margin-bottom: 30px; font-size: 16px;">
+            Please use the following verification code to complete your account setup:
+          </p>
+          
+          <div style="background: white; border: 2px solid #667eea; border-radius: 10px; padding: 20px; margin: 20px 0; display: inline-block;">
+            <div style="font-size: 32px; font-weight: bold; color: #667eea; letter-spacing: 5px;">${otp}</div>
+          </div>
+          
+          <p style="color: #666; font-size: 14px; margin-top: 30px;">
+            This code will expire in 15 minutes. If you didn't request this verification, please ignore this email.
+          </p>
+        </div>
+        
+        <div style="text-align: center; margin-top: 30px; color: #999; font-size: 12px;">
+          <p>© 2024 AI Caller. All rights reserved.</p>
+        </div>
+      </div>
+    `
+  };
+
+  try {
+    await emailTransporter.sendMail(mailOptions);
+    return { success: true };
+  } catch (error) {
+    console.error('Email sending error:', error);
+    return { success: false, error: error.message };
+  }
+}
+
+// Shopify Configuration
+const config = {
+  apiKey: process.env.SHOPIFY_API_KEY,
+  apiSecret: process.env.SHOPIFY_API_SECRET,
+  scopes: 'read_products,write_products,read_orders,write_orders,read_customers,write_customers',
+  redirectUri: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/auth/shopify/callback'
+    : 'https://shopcall-ai-backend.vercel.app/auth/shopify/callback'
+};
+
+// ShopRenter Configuration
+const shoprenterConfig = {
+  clientId: process.env.SHOPRENTER_CLIENT_ID,
+  clientSecret: process.env.SHOPRENTER_CLIENT_SECRET,
+  scopes: 'product:read product:write customer:read customer:write order:read order:write webhook:read webhook:write',
+  redirectUri: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter/callback'
+    : 'http://localhost:3000/auth/shoprenter/callback',
+  entryPoint: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter'
+    : 'http://localhost:3000/auth/shoprenter',
+  uninstallUri: process.env.NODE_ENV === 'production'
+    ? 'https://shopcall-ai-backend.vercel.app/auth/shoprenter/uninstall'
+    : 'http://localhost:3000/auth/shoprenter/uninstall'
+};
+
+// Store nonces temporarily (use Redis or database in production)
+const nonceStore = new Map();
+
+// Helper function to generate nonce
+function generateNonce() {
+  return 'nonce_' + crypto.randomBytes(16).toString('hex');
+}
+
+// Helper function to normalize shop URL
+function normalizeShopUrl(shop) {
+  if (!shop) return null;
+
+  // Remove protocol if present
+  shop = shop.replace(/^https?:\/\//, '');
+
+  // Remove trailing slash if present
+  shop = shop.replace(/\/$/, '');
+
+  // Remove any path after the domain
+  shop = shop.split('/')[0];
+
+  return shop;
+}
+
+// Helper function to validate shop URL
+function isValidShopUrl(shop) {
+  if (!shop) return false;
+
+  // Normalize the shop URL
+  const normalizedShop = normalizeShopUrl(shop);
+
+  // Check if it's a valid Shopify shop URL
+  // This regex allows for:
+  // - Letters, numbers, and hyphens in the subdomain
+  // - .myshopify.com domain
+  // - Optional .com, .co.uk, etc. TLDs
+  const shopRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]*\.myshopify\.com$/;
+
+  return shopRegex.test(normalizedShop);
+}
+
+// ShopRenter HMAC validation function
+function validateShopRenterHMAC(query, clientSecret) {
+  const { hmac, ...params } = query;
+
+  if (!hmac) {
+    return { valid: false, error: 'Missing HMAC parameter' };
+  }
+
+  try {
+    // Sort parameters alphabetically and create query string
+    const sortedParams = Object.keys(params)
+      .sort()
+      .map(key => `${key}=${params[key]}`)
+      .join('&');
+
+    // Calculate HMAC using SHA256
+    const calculatedHmac = crypto
+      .createHmac('sha256', clientSecret)
+      .update(sortedParams)
+      .digest('hex');
+
+    // Compare HMACs using timing-safe comparison
+    const hmacValid = crypto.timingSafeEqual(
+      Buffer.from(calculatedHmac),
+      Buffer.from(hmac)
+    );
+
+    if (!hmacValid) {
+      return { valid: false, error: 'Invalid HMAC signature' };
+    }
+
+    return { valid: true };
+  } catch (error) {
+    console.error('HMAC validation error:', error);
+    return { valid: false, error: 'HMAC validation failed' };
+  }
+}
+
+// Middleware to verify ShopRenter requests
+const verifyShopRenterRequest = (req, res, next) => {
+  const validation = validateShopRenterHMAC(req.query, shoprenterConfig.clientSecret);
+
+  if (!validation.valid) {
+    console.error('ShopRenter request validation failed:', validation.error);
+    return res.status(403).json({
+      error: 'Invalid request signature',
+      details: validation.error
+    });
+  }
+
+  next();
+};
+
+// Secured Session Middleware
+const securedSession = async (req, res, next) => {
+  try {
+    const authHeader = req.headers.authorization;
+    if (!authHeader) {
+      return res.status(401).json({ error: 'No authorization header' });
+    }
+
+    const token = authHeader.replace('Bearer ', '');
+    const { data: { user }, error: authError } = await supabase.auth.getUser(token);
+
+    if (authError || !user) {
+      return res.status(401).json({ error: 'Invalid token' });
+    }
+
+    // Attach user to request object for use in route handlers
+    req.user = user;
+    next();
+  } catch (error) {
+    console.error('Session verification error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+};
+
+// Authentication Routes
+
+// Serve login page
+app.get('/login', (req, res) => {
+  res.sendFile(path.join(__dirname, 'public', 'login.html'));
+});
+
+// Serve signup page
+app.get('/signup', (req, res) => {
+  res.sendFile(path.join(__dirname, 'public', 'signup.html'));
+});
+
+// Store pending signups temporarily (use Redis or database in production)
+const pendingSignups = new Map();
+
+// Signup endpoint - Step 1: Store user data and send OTP
+app.post('/auth/signup', async (req, res) => {
+  try {
+    const { full_name, company_name, user_name, email, password } = req.body;
+
+    // Validate required fields
+    if (!full_name || !company_name || !user_name || !email || !password) {
+      return res.status(400).json({ error: 'All fields are required' });
+    }
+
+    // Generate OTP
+    const otp = generateOTP();
+
+    // Store pending signup data with OTP
+    const signupId = crypto.randomBytes(16).toString('hex');
+    pendingSignups.set(signupId, {
+      full_name,
+      company_name,
+      user_name,
+      email,
+      password,
+      otp,
+      timestamp: Date.now(),
+      expiresAt: Date.now() + (15 * 60 * 1000) // 15 minutes
+    });
+
+    // Send OTP to email for verification
+    const emailResult = await sendOTPEmail(email, otp, full_name);
+
+    if (!emailResult.success) {
+      pendingSignups.delete(signupId);
+      return res.status(500).json({ error: 'Failed to send verification email. Please try again.' });
+    }
+
+    res.json({
+      success: true,
+      message: 'OTP sent to your email. Please verify to complete registration.',
+      signupId: signupId,
+      requiresOtpVerification: true
+    });
+
+  } catch (error) {
+    console.error('Signup error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Signup verification endpoint - Step 2: Verify OTP and create account
+app.post('/auth/signup/verify', async (req, res) => {
+  try {
+    const { signupId, otp } = req.body;
+
+    if (!signupId || !otp) {
+      return res.status(400).json({ error: 'Signup ID and OTP are required' });
+    }
+
+    // Get pending signup data
+    const pendingData = pendingSignups.get(signupId);
+    if (!pendingData) {
+      return res.status(400).json({ error: 'Invalid or expired signup session' });
+    }
+
+    // Check expiration
+    if (Date.now() > pendingData.expiresAt) {
+      pendingSignups.delete(signupId);
+      return res.status(400).json({ error: 'Signup session expired. Please start again.' });
+    }
+
+    // Verify OTP (compare with stored OTP)
+    if (otp !== pendingData.otp) {
+      return res.status(400).json({ error: 'Invalid OTP code' });
+    }
+
+    // OTP verified, now create the user account
+    const { data, error } = await supabase.auth.signUp({
+      email: pendingData.email,
+      password: pendingData.password,
+      options: {
+        data: {
+          is_verified: true,
+          full_name: pendingData.full_name,
+          company_name: pendingData.company_name,
+          user_name: pendingData.user_name
+        }
+      }
+    });
+
+    if (error) {
+      return res.status(400).json({ error: error.message });
+    }
+
+    // Clean up pending signup
+    pendingSignups.delete(signupId);
+
+    res.json({
+      success: true,
+      message: 'Account created successfully! You can now sign in.',
+      user: data.user
+    });
+
+  } catch (error) {
+    console.error('Signup verification error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Login endpoint - Simple email/password authentication
+app.post('/auth/login', async (req, res) => {
+  try {
+    const { email, password } = req.body;
+
+    if (!email || !password) {
+      return res.status(400).json({ error: 'Email and password are required' });
+    }
+
+    // Sign in with email and password
+    const { data, error } = await supabase.auth.signInWithPassword({
+      email: email,
+      password: password
+    });
+
+    if (error) {
+      console.log(error);
+      return res.status(400).json({ error: 'Invalid email or password' });
+    }
+
+    res.json({
+      success: true,
+      message: 'Login successful',
+      user: data.user,
+      session: data.session
+    });
+
+  } catch (error) {
+    console.error('Login error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Logout endpoint
+app.post('/auth/logout', async (req, res) => {
+  try {
+    const { error } = await supabase.auth.signOut();
+
+    if (error) {
+      return res.status(400).json({ error: error.message });
+    }
+
+    res.json({
+      success: true,
+      message: 'Logout successful'
+    });
+
+  } catch (error) {
+    console.error('Logout error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Get current user
+app.get('/auth/check', async (req, res) => {
+  try {
+    const authHeader = req.headers.authorization;
+    if (!authHeader) {
+      return res.status(401).json({ error: 'No authorization header' });
+    }
+
+    const token = authHeader.replace('Bearer ', '');
+    const { data: { user }, error } = await supabase.auth.getUser(token);
+
+    if (error || !user) {
+      return res.status(401).json({ error: 'Invalid token' });
+    }
+
+    res.json({
+      success: true,
+      user: user
+    });
+
+  } catch (error) {
+    console.error('Get user error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+
+// Resend OTP for signup verification
+app.post('/auth/signup/resend-otp', async (req, res) => {
+  try {
+    const { signupId } = req.body;
+
+    if (!signupId) {
+      return res.status(400).json({ error: 'Signup ID is required' });
+    }
+
+    // Get pending signup data
+    const pendingData = pendingSignups.get(signupId);
+    if (!pendingData) {
+      return res.status(400).json({ error: 'Invalid or expired signup session' });
+    }
+
+    // Check if still valid
+    if (Date.now() > pendingData.expiresAt) {
+      pendingSignups.delete(signupId);
+      return res.status(400).json({ error: 'Signup session expired. Please start again.' });
+    }
+
+    // Generate new OTP
+    const newOtp = generateOTP();
+
+    // Update the stored OTP
+    pendingData.otp = newOtp;
+    pendingSignups.set(signupId, pendingData);
+
+    // Send new OTP
+    const emailResult = await sendOTPEmail(pendingData.email, newOtp, pendingData.full_name);
+
+    if (!emailResult.success) {
+      return res.status(500).json({ error: 'Failed to send verification email' });
+    }
+
+    res.json({
+      success: true,
+      message: 'New OTP sent to your email'
+    });
+
+  } catch (error) {
+    console.error('Resend OTP error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Health check
+app.get('/health', (req, res) => {
+  res.json({
+    status: 'OK',
+    environment: process.env.NODE_ENV || 'development',
+    redirectUri: config.redirectUri
+  });
+});
+
+// Webshop Authentication Routes
+
+// Route 1: Initialize OAuth (redirect merchant to Shopify)
+app.get('/auth/shopify', (req, res) => {
+  const { shop } = req.query;
+
+  // Validate shop parameter
+  if (!shop) {
+    console.error('Missing shop parameter');
+    return res.status(400).json({
+      error: 'Shop parameter is required',
+      details: 'Please provide a valid Shopify shop URL'
+    });
+  }
+
+  // Normalize and validate shop URL
+  const normalizedShop = normalizeShopUrl(shop);
+  if (!isValidShopUrl(normalizedShop)) {
+    console.error(`Invalid shop URL: ${shop}`);
+    return res.status(400).json({
+      error: 'Invalid shop URL',
+      details: 'Please provide a valid Shopify shop URL (e.g., your-store.myshopify.com)'
+    });
+  }
+
+  // Generate and store nonce with user ID
+  const nonce = generateNonce();
+  nonceStore.set(nonce, {
+    shop: normalizedShop,
+    userId: req?.user?.id, // Store the authenticated user's ID
+    timestamp: Date.now(),
+    expiresAt: Date.now() + (10 * 60 * 1000) // 10 minutes
+  });
+
+  // Build authorization URL
+  const authUrl = `https://${normalizedShop}/admin/oauth/authorize?client_id=${config.apiKey}&scope=${config.scopes}&redirect_uri=${config.redirectUri}&state=${nonce}&grant_options[]=per-user`;
+
+  console.log(`Initiating OAuth flow for shop: ${normalizedShop}`);
+  res.redirect(authUrl);
+});
+
+
+app.get('/auth/woocommerce', securedSession, (req, res) => {
+  const { shop_url, phone_number, package, platform } = req.query;
+  const store_url = shop_url;
+  const endpoint = '/wc-auth/v1/authorize';
+  const params = {
+    app_name: 'ShopCall.ai',
+    scope: 'read_write',
+    user_id: req.user.id,
+    return_url: 'https://shopcall.ai/dashboard',
+    callback_url: 'https://shopcall-ai-backend.vercel.app/auth/woocommerce/callback?platform=' + platform + '&shop_url=' + shop_url + '&phone_number=' + phone_number + '&package=' + package
+  };
+  const query_string = querystring.stringify(params).replace(/%20/g, '+');
+  const redirect_url = store_url + endpoint + '?' + query_string;
+  console.log("redirect_url", redirect_url);
+  res.redirect(redirect_url);
+})
+
+// Route 2: Handle Woocommerce Callback
+app.post('/auth/woocommerce/callback', async (req, res) => {
+  const { platform, shop_url, phone_number, package } = req.query;
+  const { key_id, user_id, consumer_key, consumer_secret, key_permissions } = req.body;
+  console.log("req.body", req.body);
+  try {
+    // Store WooCommerce store data in Supabase
+    const { data: storeData, error: storeError } = await supabase
+      .from('stores')
+      .insert({
+        user_id: user_id, // This should be the authenticated user's ID
+        platform_name: platform,
+        store_name: new URL(shop_url).hostname.split('.')[0], // Extract store name from URL
+        store_url: shop_url,
+        api_key: consumer_key,
+        api_secret: consumer_secret,
+        scopes: key_permissions ? [key_permissions] : ['read_write'],
+        alt_data: {
+          key_id: key_id,
+          permissions: key_permissions
+        },
+        phone_number: phone_number,
+        package: package
+      })
+      .select()
+      .single();
+
+    if (storeError) {
+      console.error('Error storing WooCommerce store data:', storeError);
+      return res.status(500).json({
+        success: false,
+        error: 'Failed to store store data'
+      });
+    }
+
+    console.log(`Successfully stored WooCommerce store data for: ${shop_url}`);
+
+    res.json({
+      success: true,
+      message: 'Woocommerce Credentials Received Successfully...',
+    });
+
+  } catch (error) {
+    console.error('WooCommerce callback error:', error);
+    res.status(500).json({
+      success: false,
+      error: 'Failed to process WooCommerce callback'
+    });
+  }
+});
+
+// ShopRenter OAuth Routes
+
+// Route 1: Initialize ShopRenter OAuth (Entry Point)
+app.get('/auth/shoprenter', verifyShopRenterRequest, async (req, res) => {
+  try {
+    const { shopname, app_url, timestamp } = req.query;
+
+    if (!shopname || !app_url) {
+      return res.status(400).json({
+        error: 'Missing required parameters',
+        details: 'shopname and app_url are required'
+      });
+    }
+
+    console.log(`ShopRenter OAuth initiated for shop: ${shopname}`);
+
+    // Store the app_url for the callback
+    const nonce = generateNonce();
+    nonceStore.set(nonce, {
+      shopname: shopname,
+      app_url: app_url,
+      timestamp: Date.now(),
+      expiresAt: Date.now() + (10 * 60 * 1000) // 10 minutes
+    });
+
+    // Build authorization URL
+    const authParams = new URLSearchParams({
+      response_type: 'code',
+      client_id: shoprenterConfig.clientId,
+      scope: shoprenterConfig.scopes,
+      redirect_uri: shoprenterConfig.redirectUri,
+      state: nonce
+    });
+
+    const authUrl = `${app_url}/admin/oauth/authorize?${authParams.toString()}`;
+
+    console.log(`Redirecting to ShopRenter authorization: ${authUrl}`);
+    res.redirect(authUrl);
+
+  } catch (error) {
+    console.error('ShopRenter OAuth initialization error:', error);
+    res.status(500).json({
+      error: 'Failed to initialize OAuth',
+      details: error.message
+    });
+  }
+});
+
+// Route 2: Handle ShopRenter OAuth Callback
+app.get('/auth/shoprenter/callback', async (req, res) => {
+  try {
+    const { shopname, code, timestamp, hmac, app_url, state } = req.query;
+
+    // Validate HMAC
+    const validation = validateShopRenterHMAC(req.query, shoprenterConfig.clientSecret);
+    if (!validation.valid) {
+      console.error('ShopRenter callback HMAC validation failed:', validation.error);
+      return res.status(403).json({
+        error: 'Invalid request signature',
+        details: validation.error
+      });
+    }
+
+    // Validate required parameters
+    if (!shopname || !code || !app_url) {
+      return res.status(400).json({
+        error: 'Missing required parameters',
+        details: 'shopname, code, and app_url are required'
+      });
+    }
+
+    // Verify state/nonce if provided
+    if (state) {
+      const nonceData = nonceStore.get(state);
+      if (!nonceData || nonceData.shopname !== shopname) {
+        console.error('Invalid or expired state parameter');
+        return res.status(400).json({
+          error: 'Invalid state',
+          details: 'Invalid or expired state parameter'
+        });
+      }
+      // Clean up the used nonce
+      nonceStore.delete(state);
+    }
+
+    console.log(`Processing ShopRenter OAuth callback for shop: ${shopname}`);
+
+    // Exchange authorization code for access token
+    const tokenUrl = `${app_url}/admin/oauth/token`;
+    const tokenParams = new URLSearchParams({
+      grant_type: 'authorization_code',
+      client_id: shoprenterConfig.clientId,
+      client_secret: shoprenterConfig.clientSecret,
+      code: code,
+      redirect_uri: shoprenterConfig.redirectUri
+    });
+
+    const tokenResponse = await axios.post(tokenUrl, tokenParams.toString(), {
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded'
+      }
+    });
+
+    const tokenData = tokenResponse.data;
+
+    if (!tokenData.access_token) {
+      throw new Error('No access token received from ShopRenter');
+    }
+
+    console.log(`Successfully received ShopRenter access token for: ${shopname}`);
+
+    // Calculate token expiration
+    const expiresAt = tokenData.expires_in
+      ? new Date(Date.now() + tokenData.expires_in * 1000).toISOString()
+      : null;
+
+    // Store the store information in the stores table
+    const { data: storeData, error: storeError } = await supabase
+      .from('stores')
+      .insert({
+        platform_name: 'shoprenter',
+        store_name: shopname,
+        store_url: app_url,
+        scopes: tokenData.scope ? tokenData.scope.split(' ') : shoprenterConfig.scopes.split(' '),
+        alt_data: {
+          shopname: shopname,
+          token_type: tokenData.token_type
+        }
+      })
+      .select()
+      .single();
+
+    if (storeError) {
+      console.error('Error storing ShopRenter store data:', storeError);
+      return res.status(500).json({
+        error: 'Failed to store store data',
+        details: storeError.message
+      });
+    }
+
+    // Store the ShopRenter tokens in shoprenter_tokens table
+    const { data: tokenRecord, error: tokenError } = await supabase
+      .from('shoprenter_tokens')
+      .insert({
+        store_id: storeData.id,
+        access_token: tokenData.access_token,
+        refresh_token: tokenData.refresh_token || null,
+        expires_at: expiresAt,
+        scopes: tokenData.scope ? tokenData.scope.split(' ') : shoprenterConfig.scopes.split(' '),
+        shopname: shopname,
+        shop_domain: app_url,
+        is_active: true
+      })
+      .select()
+      .single();
+
+    if (tokenError) {
+      console.error('Error storing ShopRenter token:', tokenError);
+      // Try to clean up the store record
+      await supabase.from('stores').delete().eq('id', storeData.id);
+      return res.status(500).json({
+        error: 'Failed to store token data',
+        details: tokenError.message
+      });
+    }
+
+    console.log(`Successfully stored ShopRenter connection for: ${shopname}`);
+
+    // Redirect to dashboard
+    res.redirect('https://shopcall.ai/dashboard?connection=success&platform=shoprenter');
+
+  } catch (error) {
+    console.error('ShopRenter OAuth callback error:', error);
+    res.status(500).json({
+      error: 'Failed to complete OAuth process',
+      details: error.message
+    });
+  }
+});
+
+// Route 3: Handle ShopRenter App Uninstall
+app.get('/auth/shoprenter/uninstall', verifyShopRenterRequest, async (req, res) => {
+  try {
+    const { shopname, app_url, timestamp } = req.query;
+
+    if (!shopname || !app_url) {
+      return res.status(400).json({
+        error: 'Missing required parameters',
+        details: 'shopname and app_url are required'
+      });
+    }
+
+    console.log(`ShopRenter uninstall request for shop: ${shopname}`);
+
+    // Deactivate the store tokens
+    const { data: updatedTokens, error: tokenError } = await supabase
+      .from('shoprenter_tokens')
+      .update({ is_active: false })
+      .eq('shopname', shopname)
+      .eq('shop_domain', app_url);
+
+    if (tokenError) {
+      console.error('Error deactivating ShopRenter tokens:', tokenError);
+    }
+
+    // Deactivate the store
+    const { data: updatedStore, error: storeError } = await supabase
+      .from('stores')
+      .update({ is_active: false })
+      .eq('platform_name', 'shoprenter')
+      .eq('store_url', app_url);
+
+    if (storeError) {
+      console.error('Error deactivating ShopRenter store:', storeError);
+    }
+
+    console.log(`Successfully processed uninstall for: ${shopname}`);
+
+    res.json({
+      success: true,
+      message: 'App uninstalled successfully'
+    });
+
+  } catch (error) {
+    console.error('ShopRenter uninstall error:', error);
+    res.status(500).json({
+      error: 'Failed to process uninstall',
+      details: error.message
+    });
+  }
+});
+
+// Route 2: Handle OAuth callback
+app.get('/auth/shopify/callback', async (req, res) => {
+  const { shop, code, state } = req.query;
+
+  // Validate required parameters
+  if (!shop || !code || !state) {
+    console.error('Missing required parameters in callback');
+    return res.status(400).json({
+      error: 'Missing required parameters',
+      details: 'Shop, code, and state parameters are required'
+    });
+  }
+
+  // Validate shop URL
+  const normalizedShop = normalizeShopUrl(shop);
+  if (!isValidShopUrl(normalizedShop)) {
+    console.error(`Invalid shop URL in callback: ${shop}`);
+    return res.status(400).json({
+      error: 'Invalid shop URL',
+      details: 'Invalid shop URL provided in callback'
+    });
+  }
+
+  // Verify nonce/state
+  const nonceData = nonceStore.get(state);
+  if (!nonceData || nonceData.shop !== normalizedShop) {
+    console.error('Invalid or expired state parameter');
+    return res.status(400).json({
+      error: 'Invalid state',
+      details: 'Invalid or expired state parameter'
+    });
+  }
+
+  try {
+    const tokenRes = await fetch(`https://${normalizedShop}/admin/oauth/access_token`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        client_id: config.apiKey,
+        client_secret: config.apiSecret,
+        code,
+      }),
+    });
+
+    if (!tokenRes.ok) {
+      throw new Error(`Token request failed: ${tokenRes.statusText}`);
+    }
+
+    const tokenJson = await tokenRes.json();
+
+    console.log("tokenJson", tokenJson);
+
+    // Clean up the used nonce
+    nonceStore.delete(state);
+
+    // TODO: Save tokenJson.access_token securely
+    console.log(`Successfully authenticated shop: ${normalizedShop}`);
+
+    res.redirect(`https://shopcall.ai/`);
+  } catch (error) {
+    console.error('OAuth callback error:', error);
+    res.status(500).json({
+      error: 'Authentication failed',
+      details: 'Failed to complete OAuth process'
+    });
+  }
+});
+
+// Route 3: Test API call with stored token
+app.get('/api/products/:shop', async (req, res) => {
+  const { shop } = req.params;
+
+  try {
+    // TODO: Retrieve access token from database
+    // const accessToken = await getStoredToken(shop);
+    const accessToken = 'your_stored_access_token';
+
+    const response = await axios.get(`https://${shop}/admin/api/2023-10/products.json`, {
+      headers: {
+        'X-Shopify-Access-Token': accessToken
+      }
+    });
+
+    res.json(response.data);
+  } catch (error) {
+    console.error('API call error:', error.response?.data || error.message);
+    res.status(500).json({ error: 'Failed to fetch products' });
+  }
+});
+
+// Webhook verification middleware
+const verifyWebhook = (req, res, next) => {
+  const shopifyHmac = req.headers['x-shopify-hmac-sha256'];
+  const rawBody = req.body;
+
+  if (!shopifyHmac || !rawBody) {
+    console.warn('❌ Missing HMAC or body in webhook request');
+    return res.status(401).send('Unauthorized');
+  }
+
+  try {
+    // Ensure rawBody is a Buffer
+    const bodyBuffer = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody);
+    
+    const calculatedHmacDigest = crypto
+      .createHmac('sha256', config.apiSecret)
+      .update(bodyBuffer)
+      .digest('base64');
+
+    const hmacValid = crypto.timingSafeEqual(
+      Buffer.from(calculatedHmacDigest),
+      Buffer.from(shopifyHmac)
+    );
+
+    if (!hmacValid) {
+      console.warn('❌ Invalid webhook HMAC');
+      return res.status(401).send('Unauthorized');
+    }
+
+    // HMAC is valid, proceed to next middleware
+    next();
+  } catch (error) {
+    console.error('❌ Webhook verification error:', error);
+    return res.status(401).send('Unauthorized');
+  }
+};
+
+// Helper function to process webhook data
+const processWebhook = async (req, res, topic) => {
+  try {
+    const shop = req.headers['x-shopify-shop-domain'];
+    
+    // Parse the raw body as JSON
+    let data;
+    if (Buffer.isBuffer(req.body)) {
+      data = JSON.parse(req.body.toString('utf8'));
+    } else {
+      data = req.body;
+    }
+
+    console.log(`✅ Webhook received: ${topic} from ${shop}`);
+
+    // Respond quickly to Shopify (within 1 second)
+    res.status(200).send('Webhook received');
+
+    // Process webhook data asynchronously
+    // TODO: Implement your business logic here
+    // Example: Queue the webhook for processing
+    // await queueWebhook(shop, topic, data);
+
+  } catch (err) {
+    console.error(`❌ Webhook processing error for ${topic}:`, err.message);
+    // Still respond with 200 to prevent retries
+    res.status(200).send('Webhook received');
+  }
+};
+
+// GDPR Webhooks - using the correct paths that Shopify expects
+app.post('/gdpr/customers-data-request', verifyWebhook, (req, res) => {
+  processWebhook(req, res, 'customers/data_request');
+});
+
+app.post('/gdpr/customers-redact', verifyWebhook, (req, res) => {
+  processWebhook(req, res, 'customers/redact');
+});
+
+app.post('/gdpr/shop-redact', verifyWebhook, (req, res) => {
+  processWebhook(req, res, 'shop/redact');
+});
+
+// Helper function to format time ago
+const getTimeAgo = (date) => {
+  const now = new Date();
+  const past = new Date(date);
+  const diffInSeconds = Math.floor((now - past) / 1000);
+
+  if (diffInSeconds < 60) return `${diffInSeconds} seconds ago`;
+  if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} mins ago`;
+  if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
+  return `${Math.floor(diffInSeconds / 86400)} days ago`;
+};
+
+// Helper function to format duration
+const formatDuration = (seconds) => {
+  if (!seconds) return "0:00";
+  const minutes = Math.floor(seconds / 60);
+  const remainingSeconds = seconds % 60;
+  return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
+};
+
+// Helper function to mask phone number
+const maskPhoneNumber = (phone) => {
+  if (!phone) return "...xxx-0000";
+  const last4 = phone.slice(-4);
+  return `...xxx-${last4}`;
+};
+
+// Helper function to determine sentiment
+const getSentiment = (summary) => {
+  if (!summary) return "Neutral";
+  const lowerSummary = summary.toLowerCase();
+  if (lowerSummary.includes("interested") || lowerSummary.includes("positive")) return "Positive";
+  if (lowerSummary.includes("not interested") || lowerSummary.includes("negative")) return "Negative";
+  return "Neutral";
+};
+
+// Helper function to determine intent
+const getIntent = (transcript) => {
+  if (!transcript) return "General Support";
+  const lowerTranscript = transcript.toLowerCase();
+  if (lowerTranscript.includes("order") || lowerTranscript.includes("status")) return "Order Status";
+  if (lowerTranscript.includes("return") || lowerTranscript.includes("refund")) return "Return Request";
+  if (lowerTranscript.includes("product") || lowerTranscript.includes("item")) return "Product Information";
+  if (lowerTranscript.includes("shipping") || lowerTranscript.includes("delivery")) return "Shipping Inquiry";
+  return "General Support";
+};
+
+//VAPI Routes
+
+// Get dashboard statistics
+app.get('/api/dashboard/stats', securedSession, async (req, res) => {
+  try {
+    const now = new Date();
+    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+    const yesterday = new Date(today);
+    yesterday.setDate(yesterday.getDate() - 1);
+
+    // Get all calls for the user
+    const { data: allCalls, error: allCallsError } = await supabase
+      .from('call_logs')
+      .select('*')
+      .eq('user_id', req.user.id);
+
+    if (allCallsError) {
+      console.error('Error fetching calls for stats:', allCallsError);
+      return res.status(500).json({ error: 'Failed to fetch dashboard stats' });
+    }
+
+    // Get today's calls
+    const todayCalls = allCalls.filter(call => new Date(call.created_at) >= today);
+    const yesterdayCalls = allCalls.filter(call =>
+      new Date(call.created_at) >= yesterday && new Date(call.created_at) < today
+    );
+
+    // Calculate KPIs
+    const totalCalls = todayCalls.length;
+    const totalCallsYesterday = yesterdayCalls.length;
+    const totalCallsChange = totalCallsYesterday > 0
+      ? ((totalCalls - totalCallsYesterday) / totalCallsYesterday * 100).toFixed(1)
+      : totalCalls > 0 ? '+100.0' : '0.0';
+
+    const resolvedCalls = todayCalls.filter(c =>
+      c.call_outcome === 'resolved' || c.call_outcome === 'interested'
+    ).length;
+    const resolvedCallsYesterday = yesterdayCalls.filter(c =>
+      c.call_outcome === 'resolved' || c.call_outcome === 'interested'
+    ).length;
+    const resolvedCallsChange = resolvedCallsYesterday > 0
+      ? ((resolvedCalls - resolvedCallsYesterday) / resolvedCallsYesterday * 100).toFixed(1)
+      : resolvedCalls > 0 ? '+100.0' : '0.0';
+
+    // Calculate average call duration
+    const completedCalls = todayCalls.filter(c => c.duration_seconds && c.duration_seconds > 0);
+    const avgDuration = completedCalls.length > 0
+      ? Math.floor(completedCalls.reduce((sum, c) => sum + c.duration_seconds, 0) / completedCalls.length)
+      : 0;
+
+    const completedCallsYesterday = yesterdayCalls.filter(c => c.duration_seconds && c.duration_seconds > 0);
+    const avgDurationYesterday = completedCallsYesterday.length > 0
+      ? Math.floor(completedCallsYesterday.reduce((sum, c) => sum + c.duration_seconds, 0) / completedCallsYesterday.length)
+      : 0;
+    const avgDurationChange = avgDurationYesterday > 0
+      ? ((avgDuration - avgDurationYesterday) / avgDurationYesterday * 100).toFixed(1)
+      : '0.0';
+
+    // Calculate total cost
+    const totalCost = todayCalls.reduce((sum, c) => sum + (c.cost_total || 0), 0);
+    const totalCostYesterday = yesterdayCalls.reduce((sum, c) => sum + (c.cost_total || 0), 0);
+    const totalCostChange = totalCostYesterday > 0
+      ? ((totalCost - totalCostYesterday) / totalCostYesterday * 100).toFixed(1)
+      : totalCost > 0 ? '+100.0' : '0.0';
+
+    // Calculate time saved (assuming avg human call is 5 minutes)
+    const timeSavedSeconds = completedCalls.length * 5 * 60;
+    const timeSavedHours = (timeSavedSeconds / 3600).toFixed(1);
+    const timeSavedSecondsYesterday = completedCallsYesterday.length * 5 * 60;
+    const timeSavedHoursYesterday = (timeSavedSecondsYesterday / 3600).toFixed(1);
+    const timeSavedChange = timeSavedHoursYesterday > 0
+      ? ((timeSavedHours - timeSavedHoursYesterday) / timeSavedHoursYesterday * 100).toFixed(1)
+      : timeSavedHours > 0 ? '+100.0' : '0.0';
+
+    // Calculate saved on human costs (assuming $32/hour for human agent)
+    const humanCostSaved = (timeSavedSeconds / 3600) * 32;
+    const humanCostSavedYesterday = (timeSavedSecondsYesterday / 3600) * 32;
+    const humanCostSavedChange = humanCostSavedYesterday > 0
+      ? ((humanCostSaved - humanCostSavedYesterday) / humanCostSavedYesterday * 100).toFixed(1)
+      : humanCostSaved > 0 ? '+100.0' : '0.0';
+
+    // Calculate resolution rate
+    const resolutionRate = totalCalls > 0
+      ? ((resolvedCalls / totalCalls) * 100).toFixed(1)
+      : 0;
+    const resolutionRateYesterday = totalCallsYesterday > 0
+      ? ((resolvedCallsYesterday / totalCallsYesterday) * 100).toFixed(1)
+      : 0;
+    const dailyChange = resolutionRateYesterday > 0
+      ? (resolutionRate - resolutionRateYesterday).toFixed(1)
+      : '0.0';
+
+    // Calculate call intents (top 5)
+    const intentCounts = {};
+    todayCalls.forEach(call => {
+      const intent = getIntent(call.transcript) || 'General Support';
+      intentCounts[intent] = (intentCounts[intent] || 0) + 1;
+    });
+
+    const yesterdayIntentCounts = {};
+    yesterdayCalls.forEach(call => {
+      const intent = getIntent(call.transcript) || 'General Support';
+      yesterdayIntentCounts[intent] = (yesterdayIntentCounts[intent] || 0) + 1;
+    });
+
+    const topIntents = Object.entries(intentCounts)
+      .map(([name, count]) => {
+        const yesterdayCount = yesterdayIntentCounts[name] || 0;
+        const change = yesterdayCount > 0
+          ? ((count - yesterdayCount) / yesterdayCount * 100).toFixed(1)
+          : count > 0 ? '+100.0' : '0.0';
+        const percentage = totalCalls > 0 ? ((count / totalCalls) * 100).toFixed(1) : 0;
+        return {
+          name,
+          count,
+          percentage: parseFloat(percentage),
+          change: change > 0 ? `+${change}%` : `${change}%`
+        };
+      })
+      .sort((a, b) => b.count - a.count)
+      .slice(0, 5);
+
+    res.json({
+      success: true,
+      stats: {
+        totalCalls: {
+          value: totalCalls,
+          change: totalCallsChange > 0 ? `+${totalCallsChange}%` : `${totalCallsChange}%`,
+          changeType: totalCallsChange >= 0 ? 'positive' : 'negative'
+        },
+        resolvedCalls: {
+          value: resolvedCalls,
+          change: resolvedCallsChange > 0 ? `+${resolvedCallsChange}%` : `${resolvedCallsChange}%`,
+          changeType: resolvedCallsChange >= 0 ? 'positive' : 'negative'
+        },
+        avgDuration: {
+          value: avgDuration,
+          formatted: formatDuration(avgDuration),
+          change: avgDurationChange > 0 ? `+${avgDurationChange}%` : `${avgDurationChange}%`,
+          changeType: avgDurationChange <= 0 ? 'positive' : 'negative' // Lower duration is better
+        },
+        totalCost: {
+          value: totalCost,
+          formatted: `$${totalCost.toFixed(2)}`,
+          change: totalCostChange > 0 ? `+${totalCostChange}%` : `${totalCostChange}%`,
+          changeType: totalCostChange >= 0 ? 'positive' : 'negative'
+        },
+        timeSaved: {
+          value: timeSavedHours,
+          formatted: `${timeSavedHours}h`,
+          change: timeSavedChange > 0 ? `+${timeSavedChange}%` : `${timeSavedChange}%`,
+          changeType: timeSavedChange >= 0 ? 'positive' : 'negative'
+        },
+        humanCostSaved: {
+          value: humanCostSaved,
+          formatted: `$${humanCostSaved.toFixed(0)}`,
+          change: humanCostSavedChange > 0 ? `+${humanCostSavedChange}%` : `${humanCostSavedChange}%`,
+          changeType: humanCostSavedChange >= 0 ? 'positive' : 'negative'
+        },
+        resolutionRate: {
+          value: parseFloat(resolutionRate),
+          dailyChange: dailyChange > 0 ? `+${dailyChange}%` : `${dailyChange}%`,
+          weeklyChange: '+0.0%' // TODO: Calculate actual weekly change
+        },
+        topIntents
+      }
+    });
+
+  } catch (error) {
+    console.error('Dashboard stats error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Get call logs endpoint with securedSession middleware
+app.get('/api/call-logs', securedSession, async (req, res) => {
+  try {
+    // Get call logs for the authenticated user
+    const { data: callLogs, error: logsError } = await supabase
+      .from('call_logs')
+      .select('*')
+      .eq('user_id', req.user.id)
+      .order('created_at', { ascending: false });
+
+    if (logsError) {
+      console.error('Error fetching call logs:', logsError);
+      return res.status(500).json({ error: 'Failed to fetch call logs' });
+    }
+
+    // Transform the data into the required format
+    const transformedLogs = callLogs.map(log => {
+      const outcome = log.call_outcome || "pending";
+      const sentiment = getSentiment(log.summary);
+      const intent = getIntent(log.transcript);
+
+      // Helper to format ENUM values for display (e.g., "not_interested" -> "Not Interested")
+      const formatOutcome = (outcome) => {
+        return outcome
+          .split('_')
+          .map(word => word.charAt(0).toUpperCase() + word.slice(1))
+          .join(' ');
+      };
+
+      // Determine outcome color based on ENUM value
+      const getOutcomeColor = (outcome) => {
+        switch(outcome) {
+          case 'resolved':
+          case 'interested':
+            return 'text-green-500';
+          case 'not_interested':
+          case 'no_answer':
+          case 'busy':
+          case 'false':
+            return 'text-red-500';
+          case 'potential':
+          case 'callback_requested':
+          case 'voicemail':
+            return 'text-yellow-500';
+          default:
+            return 'text-slate-400';
+        }
+      };
+
+      return {
+        time: getTimeAgo(log.created_at),
+        customer: (log.customer_number),
+        intent: intent,
+        outcome: formatOutcome(outcome),
+        duration: formatDuration(log.duration_seconds),
+        sentiment: sentiment,
+        cost: `$${log.cost_total?.toFixed(2) || "0.00"}`,
+        outcomeColor: getOutcomeColor(outcome),
+        sentimentColor: sentiment === "Positive" ? "text-green-500" :
+          sentiment === "Negative" ? "text-red-500" :
+            "text-yellow-500"
+      };
+    });
+
+    res.json({
+      success: true,
+      call_logs: transformedLogs
+    });
+
+  } catch (error) {
+    console.error('Get call logs error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Store Management Routes
+
+// Get all stores for the authenticated user
+app.get('/api/stores', securedSession, async (req, res) => {
+  try {
+    const { data: stores, error } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('user_id', req.user.id)
+      .eq('is_active', true)
+      .order('created_at', { ascending: false });
+
+    if (error) {
+      console.error('Error fetching stores:', error);
+      return res.status(500).json({ error: 'Failed to fetch stores' });
+    }
+
+    res.json({
+      success: true,
+      stores: stores
+    });
+
+  } catch (error) {
+    console.error('Get stores error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Get a specific store by ID
+app.get('/api/stores/:id', securedSession, async (req, res) => {
+  try {
+    const { id } = req.params;
+
+    const { data: store, error } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('id', id)
+      .eq('user_id', req.user.id)
+      .eq('is_active', true)
+      .single();
+
+    if (error) {
+      console.error('Error fetching store:', error);
+      return res.status(404).json({ error: 'Store not found' });
+    }
+
+    res.json({
+      success: true,
+      store: store
+    });
+
+  } catch (error) {
+    console.error('Get store error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Update a store
+app.put('/api/stores/:id', securedSession, async (req, res) => {
+  try {
+    const { id } = req.params;
+    const updateData = req.body;
+
+    // Remove sensitive fields that shouldn't be updated via API
+    delete updateData.user_id;
+    delete updateData.id;
+    delete updateData.created_at;
+
+    const { data: store, error } = await supabase
+      .from('stores')
+      .update({
+        ...updateData,
+        updated_at: new Date().toISOString()
+      })
+      .eq('id', id)
+      .eq('user_id', req.user.id)
+      .select()
+      .single();
+
+    if (error) {
+      console.error('Error updating store:', error);
+      return res.status(400).json({ error: 'Failed to update store' });
+    }
+
+    res.json({
+      success: true,
+      store: store
+    });
+
+  } catch (error) {
+    console.error('Update store error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// Delete a store (soft delete by setting is_active to false)
+app.delete('/api/stores/:id', securedSession, async (req, res) => {
+  try {
+    const { id } = req.params;
+
+    const { data: store, error } = await supabase
+      .from('stores')
+      .update({
+        is_active: false,
+        updated_at: new Date().toISOString()
+      })
+      .eq('id', id)
+      .eq('user_id', req.user.id)
+      .select()
+      .single();
+
+    if (error) {
+      console.error('Error deleting store:', error);
+      return res.status(400).json({ error: 'Failed to delete store' });
+    }
+
+    res.json({
+      success: true,
+      message: 'Store deleted successfully',
+      store: store
+    });
+
+  } catch (error) {
+    console.error('Delete store error:', error);
+    res.status(500).json({ error: 'Internal server error' });
+  }
+});
+
+// ShopRenter API Integration Functions
+
+// Helper function to get active ShopRenter token for a store
+async function getShopRenterToken(storeId) {
+  const { data: tokenData, error } = await supabase
+    .from('shoprenter_tokens')
+    .select('*')
+    .eq('store_id', storeId)
+    .eq('is_active', true)
+    .single();
+
+  if (error || !tokenData) {
+    throw new Error('No active ShopRenter token found for store');
+  }
+
+  // Check if token is expired
+  if (tokenData.expires_at) {
+    const expiryDate = new Date(tokenData.expires_at);
+    if (expiryDate < new Date()) {
+      // Token expired, try to refresh
+      if (tokenData.refresh_token) {
+        return await refreshShopRenterToken(tokenData);
+      } else {
+        throw new Error('ShopRenter token expired and no refresh token available');
+      }
+    }
+  }
+
+  return tokenData;
+}
+
+// Helper function to refresh ShopRenter access token
+async function refreshShopRenterToken(tokenData) {
+  try {
+    const tokenUrl = `${tokenData.shop_domain}/admin/oauth/token`;
+    const params = new URLSearchParams({
+      grant_type: 'refresh_token',
+      client_id: shoprenterConfig.clientId,
+      client_secret: shoprenterConfig.clientSecret,
+      refresh_token: tokenData.refresh_token
+    });
+
+    const response = await axios.post(tokenUrl, params.toString(), {
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded'
+      }
+    });
+
+    const newTokenData = response.data;
+
+    // Update token in database
+    const expiresAt = newTokenData.expires_in
+      ? new Date(Date.now() + newTokenData.expires_in * 1000).toISOString()
+      : null;
+
+    const { data: updatedToken, error } = await supabase
+      .from('shoprenter_tokens')
+      .update({
+        access_token: newTokenData.access_token,
+        refresh_token: newTokenData.refresh_token || tokenData.refresh_token,
+        expires_at: expiresAt,
+        updated_at: new Date().toISOString()
+      })
+      .eq('id', tokenData.id)
+      .select()
+      .single();
+
+    if (error) {
+      throw new Error('Failed to update refreshed token');
+    }
+
+    return updatedToken;
+  } catch (error) {
+    console.error('Token refresh error:', error);
+    throw new Error('Failed to refresh ShopRenter token');
+  }
+}
+
+// Helper function to make ShopRenter API requests
+async function makeShopRenterRequest(tokenData, endpoint, method = 'GET', data = null) {
+  try {
+    const url = `${tokenData.shop_domain}/admin/api${endpoint}`;
+    const config = {
+      method: method,
+      url: url,
+      headers: {
+        'Authorization': `Bearer ${tokenData.access_token}`,
+        'Content-Type': 'application/json'
+      }
+    };
+
+    if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
+      config.data = data;
+    }
+
+    const response = await axios(config);
+    return response.data;
+  } catch (error) {
+    console.error('ShopRenter API request error:', error.response?.data || error.message);
+    throw error;
+  }
+}
+
+// Get ShopRenter products for a store
+app.get('/api/shoprenter/products/:storeId', securedSession, async (req, res) => {
+  try {
+    const { storeId } = req.params;
+
+    // Verify store belongs to user
+    const { data: store, error: storeError } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('id', storeId)
+      .eq('user_id', req.user.id)
+      .eq('platform_name', 'shoprenter')
+      .single();
+
+    if (storeError || !store) {
+      return res.status(404).json({ error: 'Store not found' });
+    }
+
+    // Get ShopRenter token
+    const tokenData = await getShopRenterToken(storeId);
+
+    // Fetch products from ShopRenter API
+    const products = await makeShopRenterRequest(tokenData, '/products');
+
+    // Update cache
+    if (products && products.items) {
+      for (const product of products.items) {
+        await supabase
+          .from('shoprenter_products_cache')
+          .upsert({
+            store_id: storeId,
+            shoprenter_product_id: product.id.toString(),
+            product_data: product,
+            last_synced_at: new Date().toISOString()
+          }, {
+            onConflict: 'store_id,shoprenter_product_id'
+          });
+      }
+    }
+
+    res.json({
+      success: true,
+      products: products
+    });
+
+  } catch (error) {
+    console.error('Get ShopRenter products error:', error);
+    res.status(500).json({
+      error: 'Failed to fetch products',
+      details: error.message
+    });
+  }
+});
+
+// Get ShopRenter orders for a store
+app.get('/api/shoprenter/orders/:storeId', securedSession, async (req, res) => {
+  try {
+    const { storeId } = req.params;
+
+    // Verify store belongs to user
+    const { data: store, error: storeError } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('id', storeId)
+      .eq('user_id', req.user.id)
+      .eq('platform_name', 'shoprenter')
+      .single();
+
+    if (storeError || !store) {
+      return res.status(404).json({ error: 'Store not found' });
+    }
+
+    // Get ShopRenter token
+    const tokenData = await getShopRenterToken(storeId);
+
+    // Fetch orders from ShopRenter API
+    const orders = await makeShopRenterRequest(tokenData, '/orders');
+
+    res.json({
+      success: true,
+      orders: orders
+    });
+
+  } catch (error) {
+    console.error('Get ShopRenter orders error:', error);
+    res.status(500).json({
+      error: 'Failed to fetch orders',
+      details: error.message
+    });
+  }
+});
+
+// Get ShopRenter customers for a store
+app.get('/api/shoprenter/customers/:storeId', securedSession, async (req, res) => {
+  try {
+    const { storeId } = req.params;
+
+    // Verify store belongs to user
+    const { data: store, error: storeError } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('id', storeId)
+      .eq('user_id', req.user.id)
+      .eq('platform_name', 'shoprenter')
+      .single();
+
+    if (storeError || !store) {
+      return res.status(404).json({ error: 'Store not found' });
+    }
+
+    // Get ShopRenter token
+    const tokenData = await getShopRenterToken(storeId);
+
+    // Fetch customers from ShopRenter API
+    const customers = await makeShopRenterRequest(tokenData, '/customers');
+
+    res.json({
+      success: true,
+      customers: customers
+    });
+
+  } catch (error) {
+    console.error('Get ShopRenter customers error:', error);
+    res.status(500).json({
+      error: 'Failed to fetch customers',
+      details: error.message
+    });
+  }
+});
+
+// Sync ShopRenter data (products, orders, customers)
+app.post('/api/shoprenter/sync/:storeId', securedSession, async (req, res) => {
+  try {
+    const { storeId } = req.params;
+    const { syncType } = req.body; // 'products', 'orders', 'customers', or 'all'
+
+    // Verify store belongs to user
+    const { data: store, error: storeError } = await supabase
+      .from('stores')
+      .select('*')
+      .eq('id', storeId)
+      .eq('user_id', req.user.id)
+      .eq('platform_name', 'shoprenter')
+      .single();
+
+    if (storeError || !store) {
+      return res.status(404).json({ error: 'Store not found' });
+    }
+
+    // Get ShopRenter token
+    const tokenData = await getShopRenterToken(storeId);
+
+    const syncResults = {};
+
+    // Sync products
+    if (syncType === 'products' || syncType === 'all') {
+      try {
+        const products = await makeShopRenterRequest(tokenData, '/products');
+        if (products && products.items) {
+          for (const product of products.items) {
+            await supabase
+              .from('shoprenter_products_cache')
+              .upsert({
+                store_id: storeId,
+                shoprenter_product_id: product.id.toString(),
+                product_data: product,
+                last_synced_at: new Date().toISOString()
+              }, {
+                onConflict: 'store_id,shoprenter_product_id'
+              });
+          }
+          syncResults.products = { success: true, count: products.items.length };
+        }
+      } catch (error) {
+        syncResults.products = { success: false, error: error.message };
+      }
+    }
+
+    // Update last sync time
+    await supabase
+      .from('shoprenter_tokens')
+      .update({ last_sync_at: new Date().toISOString() })
+      .eq('id', tokenData.id);
+
+    res.json({
+      success: true,
+      message: 'Sync completed',
+      results: syncResults
+    });
+
+  } catch (error) {
+    console.error('ShopRenter sync error:', error);
+    res.status(500).json({
+      error: 'Failed to sync data',
+      details: error.message
+    });
+  }
+});
+
+// Cleanup expired data periodically
+setInterval(() => {
+  const now = Date.now();
+
+  // Clean up expired nonces
+  for (const [nonce, data] of nonceStore.entries()) {
+    if (now > data.expiresAt) {
+      nonceStore.delete(nonce);
+    }
+  }
+
+  // Clean up expired pending signups
+  for (const [signupId, data] of pendingSignups.entries()) {
+    if (now > data.expiresAt) {
+      pendingSignups.delete(signupId);
+    }
+  }
+}, 5 * 60 * 1000); // Clean up every 5 minutes
+
+const PORT = process.env.PORT || 3000;
+app.listen(PORT, () => {
+  console.log(`Server running on port ${PORT}`);
+  console.log(`Redirect URI: ${config.redirectUri}`);
+  console.log(`OAuth URL: http://localhost:${PORT}/auth/shopify?shop=07u1ra-rp.myshopify.com/`);
+});
+

+ 21 - 0
shopcall.ai-backend-main/package.json

@@ -0,0 +1,21 @@
+{
+  "name": "ai-caller-web-backend",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "start": "node api/index"
+  },
+  "author": "",
+  "license": "ISC",
+  "description": "",
+  "dependencies": {
+    "@supabase/supabase-js": "^2.50.0",
+    "axios": "^1.9.0",
+    "cors": "^2.8.5",
+    "crypto": "^1.0.1",
+    "dotenv": "^16.5.0",
+    "express": "^5.1.0",
+    "nodemailer": "^7.0.3"
+  }
+}

+ 159 - 0
shopcall.ai-backend-main/public/index.html

@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>AI Caller - Welcome</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+
+        .welcome-container {
+            background: white;
+            padding: 3rem;
+            border-radius: 20px;
+            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+            text-align: center;
+            max-width: 500px;
+            width: 100%;
+        }
+
+        .logo {
+            font-size: 3rem;
+            font-weight: 700;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            -webkit-background-clip: text;
+            -webkit-text-fill-color: transparent;
+            background-clip: text;
+            margin-bottom: 1rem;
+        }
+
+        .welcome-title {
+            color: #333;
+            font-size: 2rem;
+            margin-bottom: 1rem;
+            font-weight: 600;
+        }
+
+        .welcome-subtitle {
+            color: #666;
+            font-size: 1.1rem;
+            margin-bottom: 2rem;
+            line-height: 1.6;
+        }
+
+        .auth-buttons {
+            display: flex;
+            gap: 1rem;
+            margin-bottom: 2rem;
+        }
+
+        .auth-btn {
+            flex: 1;
+            padding: 0.75rem 1.5rem;
+            border: none;
+            border-radius: 10px;
+            font-size: 1rem;
+            font-weight: 600;
+            text-decoration: none;
+            display: inline-block;
+            transition: all 0.3s ease;
+            cursor: pointer;
+        }
+
+        .btn-primary {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+        }
+
+        .btn-secondary {
+            background: white;
+            color: #667eea;
+            border: 2px solid #667eea;
+        }
+
+        .auth-btn:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
+        }
+
+        .features {
+            text-align: left;
+            margin-top: 2rem;
+        }
+
+        .features h3 {
+            color: #333;
+            margin-bottom: 1rem;
+            font-size: 1.2rem;
+        }
+
+        .features ul {
+            list-style: none;
+            color: #666;
+        }
+
+        .features li {
+            padding: 0.5rem 0;
+            position: relative;
+            padding-left: 1.5rem;
+        }
+
+        .features li::before {
+            content: '✓';
+            position: absolute;
+            left: 0;
+            color: #667eea;
+            font-weight: bold;
+        }
+
+        @media (max-width: 480px) {
+            .auth-buttons {
+                flex-direction: column;
+            }
+            
+            .welcome-container {
+                padding: 2rem;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="welcome-container">
+        <div class="logo">AI Caller</div>
+        <h1 class="welcome-title">Welcome to AI Caller</h1>
+        <p class="welcome-subtitle">
+            Revolutionize your business communication with intelligent AI-powered calling solutions.
+        </p>
+
+        <div class="auth-buttons">
+            <a href="/login" class="auth-btn btn-primary">Sign In</a>
+            <a href="/signup" class="auth-btn btn-secondary">Create Account</a>
+        </div>
+
+        <div class="features">
+            <h3>Key Features</h3>
+            <ul>
+                <li>AI-powered automated calling</li>
+                <li>Shopify integration</li>
+                <li>Real-time analytics</li>
+                <li>Custom voice profiles</li>
+                <li>Advanced scheduling</li>
+            </ul>
+        </div>
+    </div>
+</body>
+</html> 

+ 602 - 0
shopcall.ai-backend-main/public/login.html

@@ -0,0 +1,602 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Login - AI Caller</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+
+        .login-container {
+            background: white;
+            padding: 2rem;
+            border-radius: 20px;
+            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+            width: 100%;
+            max-width: 400px;
+            position: relative;
+            overflow: hidden;
+        }
+
+        .login-container::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            height: 4px;
+            background: linear-gradient(90deg, #667eea, #764ba2);
+        }
+
+        .login-header {
+            text-align: center;
+            margin-bottom: 2rem;
+        }
+
+        .login-title {
+            color: #333;
+            font-size: 2rem;
+            margin-bottom: 0.5rem;
+            font-weight: 600;
+        }
+
+        .login-subtitle {
+            color: #666;
+            font-size: 0.9rem;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .form-label {
+            display: block;
+            color: #333;
+            font-weight: 500;
+            margin-bottom: 0.5rem;
+            font-size: 0.9rem;
+        }
+
+        .form-input {
+            width: 100%;
+            padding: 0.75rem 1rem;
+            border: 2px solid #e1e5e9;
+            border-radius: 10px;
+            font-size: 1rem;
+            transition: all 0.3s ease;
+            background: #f8f9fa;
+        }
+
+        .form-input:focus {
+            outline: none;
+            border-color: #667eea;
+            background: white;
+            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+        }
+
+        .login-btn {
+            width: 100%;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 0.75rem 1rem;
+            font-size: 1rem;
+            font-weight: 600;
+            border-radius: 10px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            margin-bottom: 1.5rem;
+        }
+
+        .login-btn:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
+        }
+
+        .login-btn:active {
+            transform: translateY(0);
+        }
+
+        .login-btn:disabled {
+            opacity: 0.6;
+            cursor: not-allowed;
+            transform: none;
+        }
+
+        .signup-link {
+            text-align: center;
+            color: #666;
+            font-size: 0.9rem;
+        }
+
+        .signup-link a {
+            color: #667eea;
+            text-decoration: none;
+            font-weight: 600;
+        }
+
+        .signup-link a:hover {
+            text-decoration: underline;
+        }
+
+        .error-message {
+            background: #fee;
+            color: #c33;
+            padding: 0.75rem;
+            border-radius: 8px;
+            margin-bottom: 1rem;
+            font-size: 0.9rem;
+            display: none;
+        }
+
+        .success-message {
+            background: #efe;
+            color: #363;
+            padding: 0.75rem;
+            border-radius: 8px;
+            margin-bottom: 1rem;
+            font-size: 0.9rem;
+            display: none;
+        }
+
+        .loading {
+            display: inline-block;
+            width: 20px;
+            height: 20px;
+            border: 3px solid #ffffff;
+            border-radius: 50%;
+            border-top-color: transparent;
+            animation: spin 1s ease-in-out infinite;
+        }
+
+        @keyframes spin {
+            to { transform: rotate(360deg); }
+        }
+
+        /* OTP Modal Styles */
+        .otp-modal {
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background: rgba(0, 0, 0, 0.5);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            z-index: 1000;
+            padding: 20px;
+        }
+
+        .otp-modal-content {
+            background: white;
+            padding: 2rem;
+            border-radius: 20px;
+            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+            width: 100%;
+            max-width: 400px;
+            text-align: center;
+        }
+
+        .otp-header h2 {
+            color: #333;
+            margin-bottom: 0.5rem;
+            font-size: 1.5rem;
+        }
+
+        .otp-header p {
+            color: #666;
+            margin-bottom: 2rem;
+            font-size: 0.9rem;
+        }
+
+        .otp-input-group {
+            display: flex;
+            gap: 0.5rem;
+            justify-content: center;
+            margin-bottom: 2rem;
+        }
+
+        .otp-input {
+            width: 50px;
+            height: 50px;
+            border: 2px solid #e1e5e9;
+            border-radius: 10px;
+            text-align: center;
+            font-size: 1.25rem;
+            font-weight: 600;
+            background: #f8f9fa;
+            transition: all 0.3s ease;
+        }
+
+        .otp-input:focus {
+            outline: none;
+            border-color: #667eea;
+            background: white;
+            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+        }
+
+        .otp-input.filled {
+            border-color: #667eea;
+            background: white;
+        }
+
+        .resend-section {
+            margin-top: 1rem;
+            font-size: 0.9rem;
+            color: #666;
+        }
+
+        .resend-btn {
+            background: none;
+            border: none;
+            color: #667eea;
+            cursor: pointer;
+            font-weight: 600;
+            text-decoration: underline;
+        }
+
+        .resend-btn:hover {
+            color: #764ba2;
+        }
+
+        .resend-btn:disabled {
+            color: #999;
+            cursor: not-allowed;
+            text-decoration: none;
+        }
+    </style>
+</head>
+<body>
+    <div class="login-container">
+        <div class="login-header">
+            <h1 class="login-title">Welcome Back</h1>
+            <p class="login-subtitle">Sign in to your account</p>
+        </div>
+
+        <div id="errorMessage" class="error-message"></div>
+        <div id="successMessage" class="success-message"></div>
+
+        <form id="loginForm">
+            <div class="form-group">
+                <label for="email" class="form-label">Email Address</label>
+                <input 
+                    type="email" 
+                    id="email" 
+                    name="email" 
+                    class="form-input" 
+                    required 
+                    placeholder="Enter your email"
+                >
+            </div>
+
+            <div class="form-group">
+                <label for="password" class="form-label">Password</label>
+                <input 
+                    type="password" 
+                    id="password" 
+                    name="password" 
+                    class="form-input" 
+                    required 
+                    placeholder="Enter your password"
+                >
+            </div>
+
+            <button type="submit" class="login-btn" id="loginBtn">
+                <span id="btnText">Sign In</span>
+                <span id="btnLoader" class="loading" style="display: none;"></span>
+            </button>
+        </form>
+
+        <div class="signup-link">
+            Don't have an account? <a href="/signup">Sign up here</a>
+        </div>
+    </div>
+
+    <!-- OTP Verification Modal -->
+    <div id="otpModal" class="otp-modal" style="display: none;">
+        <div class="otp-modal-content">
+            <div class="otp-header">
+                <h2>Enter Verification Code</h2>
+                <p>We've sent a 6-digit code to your email</p>
+            </div>
+            
+            <div id="otpErrorMessage" class="error-message"></div>
+            
+            <form id="otpForm">
+                <div class="otp-input-group">
+                    <input type="text" class="otp-input" maxlength="1" data-index="0">
+                    <input type="text" class="otp-input" maxlength="1" data-index="1">
+                    <input type="text" class="otp-input" maxlength="1" data-index="2">
+                    <input type="text" class="otp-input" maxlength="1" data-index="3">
+                    <input type="text" class="otp-input" maxlength="1" data-index="4">
+                    <input type="text" class="otp-input" maxlength="1" data-index="5">
+                </div>
+                
+                <button type="submit" class="login-btn" id="otpVerifyBtn">
+                    <span id="otpBtnText">Verify Code</span>
+                    <span id="otpBtnLoader" class="loading" style="display: none;"></span>
+                </button>
+                
+                <div class="resend-section">
+                    <p>Didn't receive the code? 
+                        <button type="button" id="resendOtpBtn" class="resend-btn">Resend Code</button>
+                    </p>
+                </div>
+            </form>
+        </div>
+    </div>
+
+    <script>
+        const loginForm = document.getElementById('loginForm');
+        const loginBtn = document.getElementById('loginBtn');
+        const btnText = document.getElementById('btnText');
+        const btnLoader = document.getElementById('btnLoader');
+        const errorMessage = document.getElementById('errorMessage');
+        const successMessage = document.getElementById('successMessage');
+        
+        // OTP Modal elements
+        const otpModal = document.getElementById('otpModal');
+        const otpForm = document.getElementById('otpForm');
+        const otpInputs = document.querySelectorAll('.otp-input');
+        const otpVerifyBtn = document.getElementById('otpVerifyBtn');
+        const otpBtnText = document.getElementById('otpBtnText');
+        const otpBtnLoader = document.getElementById('otpBtnLoader');
+        const otpErrorMessage = document.getElementById('otpErrorMessage');
+        const resendOtpBtn = document.getElementById('resendOtpBtn');
+        
+        let currentLoginId = null;
+        let currentEmail = null;
+
+        function showError(message) {
+            errorMessage.textContent = message;
+            errorMessage.style.display = 'block';
+            successMessage.style.display = 'none';
+        }
+
+        function showSuccess(message) {
+            successMessage.textContent = message;
+            successMessage.style.display = 'block';
+            errorMessage.style.display = 'none';
+        }
+
+        function hideMessages() {
+            errorMessage.style.display = 'none';
+            successMessage.style.display = 'none';
+        }
+
+        function showOtpError(message) {
+            otpErrorMessage.textContent = message;
+            otpErrorMessage.style.display = 'block';
+        }
+
+        function hideOtpError() {
+            otpErrorMessage.style.display = 'none';
+        }
+
+        function setLoading(loading) {
+            loginBtn.disabled = loading;
+            if (loading) {
+                btnText.style.display = 'none';
+                btnLoader.style.display = 'inline-block';
+            } else {
+                btnText.style.display = 'inline';
+                btnLoader.style.display = 'none';
+            }
+        }
+
+        function setOtpLoading(loading) {
+            otpVerifyBtn.disabled = loading;
+            if (loading) {
+                otpBtnText.style.display = 'none';
+                otpBtnLoader.style.display = 'inline-block';
+            } else {
+                otpBtnText.style.display = 'inline';
+                otpBtnLoader.style.display = 'none';
+            }
+        }
+
+        function showOtpModal() {
+            otpModal.style.display = 'flex';
+            otpInputs[0].focus();
+        }
+
+        function hideOtpModal() {
+            otpModal.style.display = 'none';
+            // Clear OTP inputs
+            otpInputs.forEach(input => {
+                input.value = '';
+                input.classList.remove('filled');
+            });
+            hideOtpError();
+        }
+
+        // Handle OTP input behavior
+        otpInputs.forEach((input, index) => {
+            input.addEventListener('input', (e) => {
+                const value = e.target.value;
+                
+                if (value && /^\d$/.test(value)) {
+                    input.classList.add('filled');
+                    // Move to next input
+                    if (index < otpInputs.length - 1) {
+                        otpInputs[index + 1].focus();
+                    }
+                } else {
+                    input.classList.remove('filled');
+                }
+            });
+
+            input.addEventListener('keydown', (e) => {
+                if (e.key === 'Backspace' && !input.value && index > 0) {
+                    otpInputs[index - 1].focus();
+                }
+            });
+
+            input.addEventListener('paste', (e) => {
+                e.preventDefault();
+                const pastedData = e.clipboardData.getData('text');
+                if (/^\d{6}$/.test(pastedData)) {
+                    [...pastedData].forEach((char, i) => {
+                        if (i < otpInputs.length) {
+                            otpInputs[i].value = char;
+                            otpInputs[i].classList.add('filled');
+                        }
+                    });
+                }
+            });
+        });
+
+        // Step 1: Login with email/password
+        loginForm.addEventListener('submit', async (e) => {
+            e.preventDefault();
+            hideMessages();
+            setLoading(true);
+
+            const formData = new FormData(loginForm);
+            const data = {
+                email: formData.get('email'),
+                password: formData.get('password')
+            };
+
+            currentEmail = data.email;
+
+            try {
+                const response = await fetch('/auth/login', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                    },
+                    body: JSON.stringify(data)
+                });
+
+                const result = await response.json();
+
+                if (response.ok) {
+                    if (result.requiresOtp) {
+                        currentLoginId = result.loginId;
+                        showSuccess('Password verified! Check your email for the verification code.');
+                        showOtpModal();
+                    } else {
+                        // Direct login without OTP
+                        showSuccess('Login successful! Redirecting...');
+                        if (result.session && result.session.access_token) {
+                            localStorage.setItem('access_token', result.session.access_token);
+                        }
+                        setTimeout(() => {
+                            window.location.href = '/';
+                        }, 1500);
+                    }
+                } else {
+                    showError(result.error || 'Login failed');
+                }
+            } catch (error) {
+                console.error('Login error:', error);
+                showError('Network error. Please try again.');
+            } finally {
+                setLoading(false);
+            }
+        });
+
+        // Step 2: Verify OTP
+        otpForm.addEventListener('submit', async (e) => {
+            e.preventDefault();
+            hideOtpError();
+            setOtpLoading(true);
+
+            // Get OTP from inputs
+            const otp = Array.from(otpInputs).map(input => input.value).join('');
+            
+            if (otp.length !== 6) {
+                showOtpError('Please enter the complete 6-digit code');
+                setOtpLoading(false);
+                return;
+            }
+
+            try {
+                const response = await fetch('/auth/login/verify', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                    },
+                    body: JSON.stringify({
+                        loginId: currentLoginId,
+                        otp: otp
+                    })
+                });
+
+                const result = await response.json();
+
+                if (response.ok) {
+                    hideOtpModal();
+                    showSuccess('Login successful! Redirecting...');
+                    
+                    // Store the session token
+                    if (result.session && result.session.access_token) {
+                        localStorage.setItem('access_token', result.session.access_token);
+                    }
+                    
+                    // Redirect to dashboard or home page
+                    setTimeout(() => {
+                        window.location.href = '/';
+                    }, 1500);
+                } else {
+                    showOtpError(result.error || 'Invalid verification code');
+                }
+            } catch (error) {
+                console.error('OTP verification error:', error);
+                showOtpError('Network error. Please try again.');
+            } finally {
+                setOtpLoading(false);
+            }
+        });
+
+        // Resend OTP
+        resendOtpBtn.addEventListener('click', async () => {
+            resendOtpBtn.disabled = true;
+            resendOtpBtn.textContent = 'Sending...';
+
+            try {
+                const response = await fetch('/auth/resend-otp', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                    },
+                    body: JSON.stringify({
+                        email: currentEmail,
+                        type: 'login'
+                    })
+                });
+
+                const result = await response.json();
+
+                if (response.ok) {
+                    showOtpError('Code resent to your email');
+                    setTimeout(() => hideOtpError(), 3000);
+                } else {
+                    showOtpError('Failed to resend code');
+                }
+            } catch (error) {
+                showOtpError('Network error. Please try again.');
+            } finally {
+                resendOtpBtn.disabled = false;
+                resendOtpBtn.textContent = 'Resend Code';
+            }
+        });
+    </script>
+</body>
+</html> 

+ 482 - 0
shopcall.ai-backend-main/public/signup.html

@@ -0,0 +1,482 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Sign Up - AI Caller</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+
+        .signup-container {
+            background: white;
+            padding: 2rem;
+            border-radius: 20px;
+            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+            width: 100%;
+            max-width: 450px;
+            position: relative;
+            overflow: hidden;
+        }
+
+        .signup-container::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            height: 4px;
+            background: linear-gradient(90deg, #667eea, #764ba2);
+        }
+
+        .signup-header {
+            text-align: center;
+            margin-bottom: 2rem;
+        }
+
+        .signup-title {
+            color: #333;
+            font-size: 2rem;
+            margin-bottom: 0.5rem;
+            font-weight: 600;
+        }
+
+        .signup-subtitle {
+            color: #666;
+            font-size: 0.9rem;
+        }
+
+        .form-group {
+            margin-bottom: 1.5rem;
+        }
+
+        .form-label {
+            display: block;
+            color: #333;
+            font-weight: 500;
+            margin-bottom: 0.5rem;
+            font-size: 0.9rem;
+        }
+
+        .form-input {
+            width: 100%;
+            padding: 0.75rem 1rem;
+            border: 2px solid #e1e5e9;
+            border-radius: 10px;
+            font-size: 1rem;
+            transition: all 0.3s ease;
+            background: #f8f9fa;
+        }
+
+        .form-input:focus {
+            outline: none;
+            border-color: #667eea;
+            background: white;
+            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+        }
+
+        .form-row {
+            display: flex;
+            gap: 1rem;
+        }
+
+        .form-row .form-group {
+            flex: 1;
+        }
+
+        .signup-btn {
+            width: 100%;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 0.75rem 1rem;
+            font-size: 1rem;
+            font-weight: 600;
+            border-radius: 10px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            margin-bottom: 1.5rem;
+        }
+
+        .signup-btn:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
+        }
+
+        .signup-btn:active {
+            transform: translateY(0);
+        }
+
+        .signup-btn:disabled {
+            opacity: 0.6;
+            cursor: not-allowed;
+            transform: none;
+        }
+
+        .login-link {
+            text-align: center;
+            color: #666;
+            font-size: 0.9rem;
+        }
+
+        .login-link a {
+            color: #667eea;
+            text-decoration: none;
+            font-weight: 600;
+        }
+
+        .login-link a:hover {
+            text-decoration: underline;
+        }
+
+        .error-message {
+            background: #fee;
+            color: #c33;
+            padding: 0.75rem;
+            border-radius: 8px;
+            margin-bottom: 1rem;
+            font-size: 0.9rem;
+            display: none;
+        }
+
+        .success-message {
+            background: #efe;
+            color: #363;
+            padding: 0.75rem;
+            border-radius: 8px;
+            margin-bottom: 1rem;
+            font-size: 0.9rem;
+            display: none;
+        }
+
+        .loading {
+            display: inline-block;
+            width: 20px;
+            height: 20px;
+            border: 3px solid #ffffff;
+            border-radius: 50%;
+            border-top-color: transparent;
+            animation: spin 1s ease-in-out infinite;
+        }
+
+        @keyframes spin {
+            to { transform: rotate(360deg); }
+        }
+
+        .password-strength {
+            margin-top: 0.5rem;
+            font-size: 0.8rem;
+        }
+
+        .strength-indicator {
+            display: flex;
+            gap: 4px;
+            margin: 0.25rem 0;
+        }
+
+        .strength-bar {
+            height: 3px;
+            flex: 1;
+            background: #e1e5e9;
+            border-radius: 2px;
+            transition: background 0.3s ease;
+        }
+
+        .strength-bar.active {
+            background: #667eea;
+        }
+
+        @media (max-width: 480px) {
+            .form-row {
+                flex-direction: column;
+                gap: 0;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="signup-container">
+        <div class="signup-header">
+            <h1 class="signup-title">Create Account</h1>
+            <p class="signup-subtitle">Join us and get started today</p>
+        </div>
+
+        <div id="errorMessage" class="error-message"></div>
+        <div id="successMessage" class="success-message"></div>
+
+        <form id="signupForm">
+            <div class="form-group">
+                <label for="full_name" class="form-label">Full Name</label>
+                <input 
+                    type="text" 
+                    id="full_name" 
+                    name="full_name" 
+                    class="form-input" 
+                    required 
+                    placeholder="Enter your full name"
+                >
+            </div>
+
+            <div class="form-row">
+                <div class="form-group">
+                    <label for="user_name" class="form-label">Username</label>
+                    <input 
+                        type="text" 
+                        id="user_name" 
+                        name="user_name" 
+                        class="form-input" 
+                        required 
+                        placeholder="Choose a username"
+                    >
+                </div>
+
+                <div class="form-group">
+                    <label for="company_name" class="form-label">Company Name</label>
+                    <input 
+                        type="text" 
+                        id="company_name" 
+                        name="company_name" 
+                        class="form-input" 
+                        required 
+                        placeholder="Your company"
+                    >
+                </div>
+            </div>
+
+            <div class="form-group">
+                <label for="email" class="form-label">Email Address</label>
+                <input 
+                    type="email" 
+                    id="email" 
+                    name="email" 
+                    class="form-input" 
+                    required 
+                    placeholder="Enter your email"
+                >
+            </div>
+
+            <div class="form-group">
+                <label for="password" class="form-label">Password</label>
+                <input 
+                    type="password" 
+                    id="password" 
+                    name="password" 
+                    class="form-input" 
+                    required 
+                    placeholder="Create a strong password"
+                    minlength="6"
+                >
+                <div class="password-strength">
+                    <div class="strength-indicator">
+                        <div class="strength-bar" id="bar1"></div>
+                        <div class="strength-bar" id="bar2"></div>
+                        <div class="strength-bar" id="bar3"></div>
+                        <div class="strength-bar" id="bar4"></div>
+                    </div>
+                    <div id="strengthText" style="color: #666;"></div>
+                </div>
+            </div>
+
+            <div class="form-group">
+                <label for="confirm_password" class="form-label">Confirm Password</label>
+                <input 
+                    type="password" 
+                    id="confirm_password" 
+                    name="confirm_password" 
+                    class="form-input" 
+                    required 
+                    placeholder="Confirm your password"
+                >
+            </div>
+
+            <button type="submit" class="signup-btn" id="signupBtn">
+                <span id="btnText">Create Account</span>
+                <span id="btnLoader" class="loading" style="display: none;"></span>
+            </button>
+        </form>
+
+        <div class="login-link">
+            Already have an account? <a href="/login">Sign in here</a>
+        </div>
+    </div>
+
+    <script>
+        const signupForm = document.getElementById('signupForm');
+        const signupBtn = document.getElementById('signupBtn');
+        const btnText = document.getElementById('btnText');
+        const btnLoader = document.getElementById('btnLoader');
+        const errorMessage = document.getElementById('errorMessage');
+        const successMessage = document.getElementById('successMessage');
+        const passwordInput = document.getElementById('password');
+        const confirmPasswordInput = document.getElementById('confirm_password');
+        const strengthText = document.getElementById('strengthText');
+
+        function showError(message) {
+            errorMessage.textContent = message;
+            errorMessage.style.display = 'block';
+            successMessage.style.display = 'none';
+        }
+
+        function showSuccess(message) {
+            successMessage.textContent = message;
+            successMessage.style.display = 'block';
+            errorMessage.style.display = 'none';
+        }
+
+        function hideMessages() {
+            errorMessage.style.display = 'none';
+            successMessage.style.display = 'none';
+        }
+
+        function setLoading(loading) {
+            signupBtn.disabled = loading;
+            if (loading) {
+                btnText.style.display = 'none';
+                btnLoader.style.display = 'inline-block';
+            } else {
+                btnText.style.display = 'inline';
+                btnLoader.style.display = 'none';
+            }
+        }
+
+        function checkPasswordStrength(password) {
+            let strength = 0;
+            let feedback = '';
+
+            if (password.length >= 8) strength++;
+            if (/[a-z]/.test(password)) strength++;
+            if (/[A-Z]/.test(password)) strength++;
+            if (/[0-9]/.test(password)) strength++;
+            if (/[^A-Za-z0-9]/.test(password)) strength++;
+
+            // Update strength bars
+            for (let i = 1; i <= 4; i++) {
+                const bar = document.getElementById(`bar${i}`);
+                if (i <= strength) {
+                    bar.classList.add('active');
+                } else {
+                    bar.classList.remove('active');
+                }
+            }
+
+            // Update feedback text
+            switch (strength) {
+                case 0:
+                case 1:
+                    feedback = 'Very weak password';
+                    break;
+                case 2:
+                    feedback = 'Weak password';
+                    break;
+                case 3:
+                    feedback = 'Good password';
+                    break;
+                case 4:
+                case 5:
+                    feedback = 'Strong password';
+                    break;
+            }
+
+            strengthText.textContent = feedback;
+            return strength;
+        }
+
+        passwordInput.addEventListener('input', (e) => {
+            checkPasswordStrength(e.target.value);
+        });
+
+        signupForm.addEventListener('submit', async (e) => {
+            e.preventDefault();
+            hideMessages();
+
+            const formData = new FormData(signupForm);
+            const password = formData.get('password');
+            const confirmPassword = formData.get('confirm_password');
+
+            // Validate passwords match
+            if (password !== confirmPassword) {
+                showError('Passwords do not match');
+                return;
+            }
+
+            // Validate password strength
+            if (checkPasswordStrength(password) < 2) {
+                showError('Password is too weak. Please use a stronger password.');
+                return;
+            }
+
+            setLoading(true);
+
+            const data = {
+                full_name: formData.get('full_name'),
+                company_name: formData.get('company_name'),
+                user_name: formData.get('user_name'),
+                email: formData.get('email'),
+                password: password
+            };
+
+            try {
+                const response = await fetch('/auth/signup', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json',
+                    },
+                    body: JSON.stringify(data)
+                });
+
+                const result = await response.json();
+
+                if (response.ok) {
+                    if (result.requiresEmailConfirmation) {
+                        showSuccess('Account created successfully! Please check your email to confirm your account, then you can sign in.');
+                    } else {
+                        showSuccess('Account created successfully! You can now sign in.');
+                        // Redirect to login page after a brief delay
+                        setTimeout(() => {
+                            window.location.href = '/login';
+                        }, 2000);
+                    }
+                    signupForm.reset();
+                    // Clear password strength indicator
+                    for (let i = 1; i <= 4; i++) {
+                        document.getElementById(`bar${i}`).classList.remove('active');
+                    }
+                    strengthText.textContent = '';
+                } else {
+                    showError(result.error || 'Signup failed');
+                }
+            } catch (error) {
+                console.error('Signup error:', error);
+                showError('Network error. Please try again.');
+            } finally {
+                setLoading(false);
+            }
+        });
+
+        // Real-time username validation
+        document.getElementById('user_name').addEventListener('input', (e) => {
+            const username = e.target.value;
+            const regex = /^[a-zA-Z0-9_]+$/;
+            
+            if (username && !regex.test(username)) {
+                e.target.style.borderColor = '#c33';
+            } else {
+                e.target.style.borderColor = '#e1e5e9';
+            }
+        });
+    </script>
+</body>
+</html> 

+ 4 - 0
shopcall.ai-backend-main/vercel.json

@@ -0,0 +1,4 @@
+{
+	"version": 2,
+	"rewrites": [{ "source": "/(.*)", "destination": "/api" }]
+}

+ 9 - 0
shopcall.ai-main/.env.example

@@ -0,0 +1,9 @@
+# Backend API Base URL (Supabase Edge Functions)
+# For production: https://YOUR_PROJECT.supabase.co/functions/v1
+# For local dev: http://localhost:54321/functions/v1
+VITE_API_URL=https://ztklqodcdjeqpsvhlpud.supabase.co/functions/v1
+
+# Frontend URL (for OAuth callbacks and redirects)
+# For production: https://yourdomain.com
+# For local dev: http://localhost:8080
+VITE_FRONTEND_URL=https://shopcall.ai

+ 50 - 0
shopcall.ai-main/.gitignore

@@ -0,0 +1,50 @@
+# Dependencies
+node_modules/
+package-lock.json
+
+# Build output
+dist/
+dist-ssr/
+*.local
+
+# Environment variables
+.env
+.env.local
+.env.development
+.env.production
+
+# Logs
+logs/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Editor directories and files
+.vscode/
+!.vscode/extensions.json
+.idea/
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+*.swp
+*.swo
+*~
+
+# Bun
+bun.lockb
+
+# Testing
+coverage/
+*.test.js.snap
+
+# Vercel
+.vercel
+
+# TypeScript
+*.tsbuildinfo

+ 73 - 0
shopcall.ai-main/README.md

@@ -0,0 +1,73 @@
+# Welcome to your Lovable project
+
+## Project info
+
+**URL**: https://lovable.dev/projects/5ef3e58d-e62b-450c-8e1a-8c085b5e6367
+
+## How can I edit this code?
+
+There are several ways of editing your application.
+
+**Use Lovable**
+
+Simply visit the [Lovable Project](https://lovable.dev/projects/5ef3e58d-e62b-450c-8e1a-8c085b5e6367) and start prompting.
+
+Changes made via Lovable will be committed automatically to this repo.
+
+**Use your preferred IDE**
+
+If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
+
+The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
+
+Follow these steps:
+
+```sh
+# Step 1: Clone the repository using the project's Git URL.
+git clone <YOUR_GIT_URL>
+
+# Step 2: Navigate to the project directory.
+cd <YOUR_PROJECT_NAME>
+
+# Step 3: Install the necessary dependencies.
+npm i
+
+# Step 4: Start the development server with auto-reloading and an instant preview.
+npm run dev
+```
+
+**Edit a file directly in GitHub**
+
+- Navigate to the desired file(s).
+- Click the "Edit" button (pencil icon) at the top right of the file view.
+- Make your changes and commit the changes.
+
+**Use GitHub Codespaces**
+
+- Navigate to the main page of your repository.
+- Click on the "Code" button (green button) near the top right.
+- Select the "Codespaces" tab.
+- Click on "New codespace" to launch a new Codespace environment.
+- Edit files directly within the Codespace and commit and push your changes once you're done.
+
+## What technologies are used for this project?
+
+This project is built with:
+
+- Vite
+- TypeScript
+- React
+- shadcn-ui
+- Tailwind CSS
+
+## How can I deploy this project?
+
+Simply open [Lovable](https://lovable.dev/projects/5ef3e58d-e62b-450c-8e1a-8c085b5e6367) and click on Share -> Publish.
+
+## Can I connect a custom domain to my Lovable project?
+
+Yes, you can!
+
+To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
+
+Read more here: [Setting up a custom domain](https://docs.lovable.dev/tips-tricks/custom-domain#step-by-step-guide)

+ 20 - 0
shopcall.ai-main/components.json

@@ -0,0 +1,20 @@
+{
+  "$schema": "https://ui.shadcn.com/schema.json",
+  "style": "default",
+  "rsc": false,
+  "tsx": true,
+  "tailwind": {
+    "config": "tailwind.config.ts",
+    "css": "src/index.css",
+    "baseColor": "slate",
+    "cssVariables": true,
+    "prefix": ""
+  },
+  "aliases": {
+    "components": "@/components",
+    "utils": "@/lib/utils",
+    "ui": "@/components/ui",
+    "lib": "@/lib",
+    "hooks": "@/hooks"
+  }
+}

+ 29 - 0
shopcall.ai-main/eslint.config.js

@@ -0,0 +1,29 @@
+import js from "@eslint/js";
+import globals from "globals";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import tseslint from "typescript-eslint";
+
+export default tseslint.config(
+  { ignores: ["dist"] },
+  {
+    extends: [js.configs.recommended, ...tseslint.configs.recommended],
+    files: ["**/*.{ts,tsx}"],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+    },
+    plugins: {
+      "react-hooks": reactHooks,
+      "react-refresh": reactRefresh,
+    },
+    rules: {
+      ...reactHooks.configs.recommended.rules,
+      "react-refresh/only-export-components": [
+        "warn",
+        { allowConstantExport: true },
+      ],
+      "@typescript-eslint/no-unused-vars": "off",
+    },
+  }
+);

+ 22 - 0
shopcall.ai-main/index.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>ShopCall.ai - AI Phone Support That Never Sleeps</title>
+    <meta name="description" content="Transform your e-commerce customer service with AI that handles calls in 50+ languages, reduces costs by 78%, and provides 24/7 support." />
+    <meta name="author" content="ShopCall.ai" />
+    <meta name="keywords" content="AI phone support, e-commerce customer service, multilingual AI, automated customer support, 24/7 phone support, AI call center, e-commerce automation" />
+    <!-- icon for favicon -->
+    <link rel="icon" href="/og-image.png" type="image/png" />
+    <meta property="og:title" content="ShopCall.ai - AI Phone Support That Never Sleeps" />
+    <meta property="og:description" content="Transform your e-commerce customer service with AI that handles calls in 50+ languages, reduces costs by 78%, and provides 24/7 support." />
+    <meta property="og:type" content="website" />
+    <meta property="og:url" content="https://shopcall.ai" />
+    <meta property="og:image" content="/og-image.png" />
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

+ 54 - 0
shopcall.ai-main/nginx.conf.example

@@ -0,0 +1,54 @@
+# Nginx configuration for ShopCall.ai static hosting
+# Place this in your nginx sites-available directory and create a symlink in sites-enabled
+
+server {
+    listen 80;
+    listen [::]:80;
+    server_name shopcall.ai www.shopcall.ai;
+
+    # Redirect HTTP to HTTPS
+    return 301 https://$server_name$request_uri;
+}
+
+server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+    server_name shopcall.ai www.shopcall.ai;
+
+    # SSL configuration
+    ssl_certificate /path/to/your/certificate.crt;
+    ssl_certificate_key /path/to/your/private.key;
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers HIGH:!aNULL:!MD5;
+
+    # Root directory where your built files are located
+    root /var/www/shopcall.ai/dist;
+    index index.html;
+
+    # Gzip compression
+    gzip on;
+    gzip_vary on;
+    gzip_min_length 1024;
+    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
+
+    # Cache static assets
+    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
+        expires 1y;
+        add_header Cache-Control "public, immutable";
+    }
+
+    # React Router - serve index.html for all routes
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+
+    # Security headers
+    add_header X-Frame-Options "SAMEORIGIN" always;
+    add_header X-Content-Type-Options "nosniff" always;
+    add_header X-XSS-Protection "1; mode=block" always;
+    add_header Referrer-Policy "no-referrer-when-downgrade" always;
+    add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'" always;
+
+    # Error pages
+    error_page 404 /index.html;
+}

+ 83 - 0
shopcall.ai-main/package.json

@@ -0,0 +1,83 @@
+{
+  "name": "vite_react_shadcn_ts",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "build:dev": "vite build --mode development",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@hookform/resolvers": "^3.9.0",
+    "@radix-ui/react-accordion": "^1.2.0",
+    "@radix-ui/react-alert-dialog": "^1.1.1",
+    "@radix-ui/react-aspect-ratio": "^1.1.0",
+    "@radix-ui/react-avatar": "^1.1.0",
+    "@radix-ui/react-checkbox": "^1.1.1",
+    "@radix-ui/react-collapsible": "^1.1.0",
+    "@radix-ui/react-context-menu": "^2.2.1",
+    "@radix-ui/react-dialog": "^1.1.2",
+    "@radix-ui/react-dropdown-menu": "^2.1.1",
+    "@radix-ui/react-hover-card": "^1.1.1",
+    "@radix-ui/react-label": "^2.1.0",
+    "@radix-ui/react-menubar": "^1.1.1",
+    "@radix-ui/react-navigation-menu": "^1.2.0",
+    "@radix-ui/react-popover": "^1.1.1",
+    "@radix-ui/react-progress": "^1.1.0",
+    "@radix-ui/react-radio-group": "^1.2.0",
+    "@radix-ui/react-scroll-area": "^1.1.0",
+    "@radix-ui/react-select": "^2.1.1",
+    "@radix-ui/react-separator": "^1.1.0",
+    "@radix-ui/react-slider": "^1.2.0",
+    "@radix-ui/react-slot": "^1.1.0",
+    "@radix-ui/react-switch": "^1.1.0",
+    "@radix-ui/react-tabs": "^1.1.0",
+    "@radix-ui/react-toast": "^1.2.1",
+    "@radix-ui/react-toggle": "^1.1.0",
+    "@radix-ui/react-toggle-group": "^1.1.0",
+    "@radix-ui/react-tooltip": "^1.1.4",
+    "@tanstack/react-query": "^5.56.2",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "cmdk": "^1.0.0",
+    "date-fns": "^3.6.0",
+    "embla-carousel-react": "^8.3.0",
+    "input-otp": "^1.2.4",
+    "lucide-react": "^0.462.0",
+    "next-themes": "^0.3.0",
+    "react": "^18.3.1",
+    "react-day-picker": "^8.10.1",
+    "react-dom": "^18.3.1",
+    "react-hook-form": "^7.53.0",
+    "react-resizable-panels": "^2.1.3",
+    "react-router-dom": "^6.26.2",
+    "recharts": "^2.12.7",
+    "sonner": "^1.5.0",
+    "tailwind-merge": "^2.5.2",
+    "tailwindcss-animate": "^1.0.7",
+    "vaul": "^0.9.3",
+    "zod": "^3.23.8"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.9.0",
+    "@tailwindcss/typography": "^0.5.15",
+    "@types/node": "^22.5.5",
+    "@types/react": "^18.3.3",
+    "@types/react-dom": "^18.3.0",
+    "@vitejs/plugin-react-swc": "^3.5.0",
+    "autoprefixer": "^10.4.20",
+    "eslint": "^9.9.0",
+    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
+    "eslint-plugin-react-refresh": "^0.4.9",
+    "globals": "^15.9.0",
+    "lovable-tagger": "^1.1.7",
+    "postcss": "^8.4.47",
+    "tailwindcss": "^3.4.11",
+    "typescript": "^5.5.3",
+    "typescript-eslint": "^8.0.1",
+    "vite": "^5.4.1"
+  }
+}

+ 6 - 0
shopcall.ai-main/postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}

+ 33 - 0
shopcall.ai-main/public/.htaccess

@@ -0,0 +1,33 @@
+<IfModule mod_rewrite.c>
+  RewriteEngine On
+  RewriteBase /
+
+  # Don't rewrite files or directories
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteCond %{REQUEST_FILENAME} !-d
+
+  # Rewrite everything else to index.html to allow client-side routing
+  RewriteRule ^ index.html [L]
+</IfModule>
+
+# Enable gzip compression
+<IfModule mod_deflate.c>
+  AddOutputFilterByType DEFLATE text/html
+  AddOutputFilterByType DEFLATE text/css
+  AddOutputFilterByType DEFLATE text/javascript
+  AddOutputFilterByType DEFLATE application/javascript
+  AddOutputFilterByType DEFLATE application/x-javascript
+</IfModule>
+
+# Set cache headers for static assets
+<IfModule mod_expires.c>
+  ExpiresActive On
+  ExpiresByType image/jpg "access plus 1 year"
+  ExpiresByType image/jpeg "access plus 1 year"
+  ExpiresByType image/gif "access plus 1 year"
+  ExpiresByType image/png "access plus 1 year"
+  ExpiresByType image/svg+xml "access plus 1 year"
+  ExpiresByType text/css "access plus 1 month"
+  ExpiresByType application/javascript "access plus 1 month"
+  ExpiresByType text/javascript "access plus 1 month"
+</IfModule>

BIN=BIN
shopcall.ai-main/public/og-image.png


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
shopcall.ai-main/public/placeholder.svg


+ 14 - 0
shopcall.ai-main/public/robots.txt

@@ -0,0 +1,14 @@
+User-agent: Googlebot
+Allow: /
+
+User-agent: Bingbot
+Allow: /
+
+User-agent: Twitterbot
+Allow: /
+
+User-agent: facebookexternalhit
+Allow: /
+
+User-agent: *
+Allow: /

BIN=BIN
shopcall.ai-main/public/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png


BIN=BIN
shopcall.ai-main/public/uploads/eb42e6bb-fd6a-4d23-b49a-a411466c0265.png


+ 42 - 0
shopcall.ai-main/src/App.css

@@ -0,0 +1,42 @@
+#root {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}

+ 62 - 0
shopcall.ai-main/src/App.tsx

@@ -0,0 +1,62 @@
+
+import { Toaster } from "@/components/ui/toaster";
+import { Toaster as Sonner } from "@/components/ui/sonner";
+import { TooltipProvider } from "@/components/ui/tooltip";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+import Index from "./pages/Index";
+import Dashboard from "./pages/Dashboard";
+import Signup from "./pages/Signup";
+import Login from "./pages/Login";
+import OTP from "./pages/OTP";
+import CallLogs from "./pages/CallLogs";
+import Analytics from "./pages/Analytics";
+import Webshops from "./pages/Webshops";
+import PhoneNumbers from "./pages/PhoneNumbers";
+import AIConfig from "./pages/AIConfig";
+import Onboarding from "./pages/Onboarding";
+import About from "./pages/About";
+import Privacy from "./pages/Privacy";
+import Terms from "./pages/Terms";
+import Contact from "./pages/Contact";
+import NotFound from "./pages/NotFound";
+import { AuthProvider } from "./components/context/AuthContext";
+import PrivateRoute from "./components/PrivateRoute";
+
+const queryClient = new QueryClient();
+
+const App = () => (
+  <QueryClientProvider client={queryClient}>
+    <TooltipProvider>
+      <Toaster />
+      <Sonner />
+      <BrowserRouter>
+        <AuthProvider>
+          <Routes>
+            <Route path="/" element={<Index />} />
+            <Route path="/signup" element={<Signup />} />
+            <Route path="/login" element={<Login />} />
+            <Route path="/otp" element={<OTP />} />
+            <Route element={<PrivateRoute />}>
+              <Route path="/dashboard" element={<Dashboard />} />
+              <Route path="/call-logs" element={<CallLogs />} />
+              <Route path="/analytics" element={<Analytics />} />
+              <Route path="/webshops" element={<Webshops />} />
+              <Route path="/phone-numbers" element={<PhoneNumbers />} />
+              <Route path="/ai-config" element={<AIConfig />} />
+              <Route path="/onboarding" element={<Onboarding />} />
+            </Route>
+            <Route path="/about" element={<About />} />
+            <Route path="/privacy" element={<Privacy />} />
+            <Route path="/terms" element={<Terms />} />
+            {/*<Route path="/contact" element={<Contact />} />*/}
+            {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
+            <Route path="*" element={<NotFound />} />
+          </Routes>
+        </AuthProvider>
+      </BrowserRouter>
+    </TooltipProvider>
+  </QueryClientProvider>
+);
+
+export default App;

+ 261 - 0
shopcall.ai-main/src/components/AIConfigContent.tsx

@@ -0,0 +1,261 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { Switch } from "@/components/ui/switch";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { Badge } from "@/components/ui/badge";
+import { Mic, MessageSquare, Brain, Zap, Store, ChevronDown } from "lucide-react";
+import { useState } from "react";
+
+const webshops = [
+  {
+    id: 1,
+    name: "Fashion Forward",
+    platform: "Shopify",
+    country: "United States",
+    phoneNumber: "+1 (555) 123-4567",
+    status: "Active"
+  },
+  {
+    id: 2,
+    name: "Tech Gadgets Pro",
+    platform: "WooCommerce",
+    country: "United States", 
+    phoneNumber: "+1 (555) 987-6543",
+    status: "Active"
+  },
+  {
+    id: 3,
+    name: "Home & Garden UK",
+    platform: "Shopify",
+    country: "United Kingdom",
+    phoneNumber: "+44 20 7946 0958",
+    status: "Active"
+  }
+];
+
+export function AIConfigContent() {
+  const [selectedWebshop, setSelectedWebshop] = useState(webshops[0]);
+
+  return (
+    <div className="flex-1 space-y-6 p-8 bg-slate-900">
+      <div className="flex items-center justify-between">
+        <div>
+          <h2 className="text-3xl font-bold tracking-tight text-white">AI Configuration</h2>
+          <p className="text-slate-400">Configure AI behavior and responses per webshop</p>
+        </div>
+        <div className="flex gap-3">
+          <Button variant="outline" className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10">
+            Copy from Another Shop
+          </Button>
+          <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
+            Save Configuration
+          </Button>
+        </div>
+      </div>
+
+      {/* Webshop Selector */}
+      <Card className="bg-slate-800 border-slate-700">
+        <CardHeader>
+          <div className="flex items-center gap-3">
+            <Store className="w-6 h-6 text-cyan-500" />
+            <CardTitle className="text-white">Select Webshop</CardTitle>
+          </div>
+          <p className="text-slate-400">Choose which webshop to configure</p>
+        </CardHeader>
+        <CardContent>
+          <Select value={selectedWebshop.id.toString()} onValueChange={(value) => {
+            const shop = webshops.find(s => s.id === parseInt(value));
+            if (shop) setSelectedWebshop(shop);
+          }}>
+            <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent className="bg-slate-700 border-slate-600">
+              {webshops.map((shop) => (
+                <SelectItem key={shop.id} value={shop.id.toString()}>
+                  <div className="flex items-center gap-3">
+                    <div>
+                      <div className="text-white font-medium">{shop.name}</div>
+                      <div className="text-slate-400 text-sm">{shop.platform} • {shop.country}</div>
+                    </div>
+                  </div>
+                </SelectItem>
+              ))}
+            </SelectContent>
+          </Select>
+          
+          {/* Current Shop Info */}
+          <div className="mt-4 p-4 bg-slate-700/50 rounded-lg">
+            <div className="flex items-center justify-between">
+              <div>
+                <h4 className="text-white font-medium">{selectedWebshop.name}</h4>
+                <p className="text-slate-400 text-sm">{selectedWebshop.platform} • {selectedWebshop.country}</p>
+                <p className="text-slate-300 text-sm font-mono">{selectedWebshop.phoneNumber}</p>
+              </div>
+              <Badge className="bg-green-500 text-white">{selectedWebshop.status}</Badge>
+            </div>
+          </div>
+        </CardContent>
+      </Card>
+
+      <div className="grid gap-6">
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              <Mic className="w-6 h-6 text-cyan-500" />
+              <CardTitle className="text-white">Voice & Speech Settings</CardTitle>
+            </div>
+            <p className="text-slate-400">Configure voice characteristics for {selectedWebshop.name}</p>
+          </CardHeader>
+          <CardContent className="space-y-6">
+            <div className="grid gap-6 md:grid-cols-2">
+              <div className="space-y-2">
+                <Label className="text-slate-300">Voice Type</Label>
+                <Select defaultValue="sarah">
+                  <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
+                    <SelectValue />
+                  </SelectTrigger>
+                  <SelectContent className="bg-slate-700 border-slate-600">
+                    <SelectItem value="sarah">Sarah (Professional)</SelectItem>
+                    <SelectItem value="james">James (Friendly)</SelectItem>
+                    <SelectItem value="emma">Emma (Warm)</SelectItem>
+                  </SelectContent>
+                </Select>
+              </div>
+              <div className="space-y-2">
+                <Label className="text-slate-300">Speaking Speed</Label>
+                <Select defaultValue="normal">
+                  <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
+                    <SelectValue />
+                  </SelectTrigger>
+                  <SelectContent className="bg-slate-700 border-slate-600">
+                    <SelectItem value="slow">Slow</SelectItem>
+                    <SelectItem value="normal">Normal</SelectItem>
+                    <SelectItem value="fast">Fast</SelectItem>
+                  </SelectContent>
+                </Select>
+              </div>
+            </div>
+            <div className="space-y-2">
+              <Label className="text-slate-300">Accent & Language</Label>
+              <Select defaultValue={selectedWebshop.country === "United Kingdom" ? "uk-english" : "us-english"}>
+                <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
+                  <SelectValue />
+                </SelectTrigger>
+                <SelectContent className="bg-slate-700 border-slate-600">
+                  <SelectItem value="us-english">US English</SelectItem>
+                  <SelectItem value="uk-english">UK English</SelectItem>
+                  <SelectItem value="australian">Australian</SelectItem>
+                </SelectContent>
+              </Select>
+            </div>
+          </CardContent>
+        </Card>
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              <MessageSquare className="w-6 h-6 text-cyan-500" />
+              <CardTitle className="text-white">Conversation Behavior</CardTitle>
+            </div>
+            <p className="text-slate-400">Define how the AI interacts with {selectedWebshop.name} customers</p>
+          </CardHeader>
+          <CardContent className="space-y-6">
+            <div className="space-y-2">
+              <Label className="text-slate-300">Greeting Message</Label>
+              <Textarea 
+                className="bg-slate-700 border-slate-600 text-white min-h-[100px]"
+                defaultValue={`Hello! Thank you for calling ${selectedWebshop.name}. I'm your AI assistant, and I'm here to help you with any questions about your order or our products. How can I assist you today?`}
+              />
+            </div>
+            
+            <div className="grid gap-6 md:grid-cols-2">
+              <div className="flex items-center justify-between">
+                <div className="space-y-1">
+                  <Label className="text-slate-300">Business Hours Mode</Label>
+                  <p className="text-sm text-slate-400">Adjust behavior based on store hours</p>
+                </div>
+                <Switch defaultChecked className="data-[state=checked]:bg-cyan-500" />
+              </div>
+              <div className="flex items-center justify-between">
+                <div className="space-y-1">
+                  <Label className="text-slate-300">Local Currency Support</Label>
+                  <p className="text-sm text-slate-400">Auto-detect customer location</p>
+                </div>
+                <Switch defaultChecked className="data-[state=checked]:bg-cyan-500" />
+              </div>
+            </div>
+
+            <div className="space-y-2">
+              <Label className="text-slate-300">Escalation Policy</Label>
+              <Select defaultValue="medium">
+                <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
+                  <SelectValue />
+                </SelectTrigger>
+                <SelectContent className="bg-slate-700 border-slate-600">
+                  <SelectItem value="low">Quick Escalation - Transfer to human quickly</SelectItem>
+                  <SelectItem value="medium">Balanced - Try to resolve then escalate</SelectItem>
+                  <SelectItem value="high">AI First - Extensive AI resolution attempts</SelectItem>
+                </SelectContent>
+              </Select>
+            </div>
+          </CardContent>
+        </Card>
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              <Brain className="w-6 h-6 text-cyan-500" />
+              <CardTitle className="text-white">Knowledge Base</CardTitle>
+            </div>
+            <p className="text-slate-400">Training data specific to {selectedWebshop.name}</p>
+          </CardHeader>
+          <CardContent className="space-y-6">
+            <div className="grid gap-4 md:grid-cols-3">
+              <div className="p-4 bg-slate-700/50 rounded-lg">
+                <div className="flex items-center justify-between mb-2">
+                  <h4 className="text-white font-medium">Product Catalog</h4>
+                  <Badge className="bg-green-500 text-white">Synced</Badge>
+                </div>
+                <p className="text-slate-400 text-sm">1,247 products</p>
+                <p className="text-slate-500 text-xs">Auto-sync from {selectedWebshop.platform}</p>
+              </div>
+              <div className="p-4 bg-slate-700/50 rounded-lg">
+                <div className="flex items-center justify-between mb-2">
+                  <h4 className="text-white font-medium">Store Policies</h4>
+                  <Badge className="bg-green-500 text-white">Updated</Badge>
+                </div>
+                <p className="text-slate-400 text-sm">Return, shipping, etc.</p>
+                <p className="text-slate-500 text-xs">Country-specific policies</p>
+              </div>
+              <div className="p-4 bg-slate-700/50 rounded-lg">
+                <div className="flex items-center justify-between mb-2">
+                  <h4 className="text-white font-medium">FAQ Database</h4>
+                  <Badge className="bg-yellow-500 text-white">Needs Review</Badge>
+                </div>
+                <p className="text-slate-400 text-sm">89 Q&As</p>
+                <p className="text-slate-500 text-xs">Regional customization needed</p>
+              </div>
+            </div>
+            
+            <div className="flex gap-3">
+              <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
+                Sync Store Data
+              </Button>
+              <Button variant="outline" className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10">
+                Upload Custom Knowledge
+              </Button>
+              <Button variant="outline" className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10">
+                Test Configuration
+              </Button>
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+    </div>
+  );
+}

+ 243 - 0
shopcall.ai-main/src/components/AnalyticsContent.tsx

@@ -0,0 +1,243 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer, LineChart, Line } from 'recharts';
+
+const weeklyData = [
+  { day: 'Mon', calls: 45 },
+  { day: 'Tue', calls: 52 },
+  { day: 'Wed', calls: 38 },
+  { day: 'Thu', calls: 61 },
+  { day: 'Fri', calls: 55 },
+  { day: 'Sat', calls: 28 },
+  { day: 'Sun', calls: 33 },
+];
+
+const resolutionData = [
+  { week: 'Week 1', rate: 78 },
+  { week: 'Week 2', rate: 82 },
+  { week: 'Week 3', rate: 85 },
+  { week: 'Week 4', rate: 87 },
+];
+
+// Data for Performance tab - Average Call Duration by Intent
+const callDurationData = [
+  { intent: 'Order Status', duration: 125 },
+  { intent: 'Product Information', duration: 142 },
+  { intent: 'Return Request', duration: 168 },
+  { intent: 'Shipping Inquiry', duration: 133 },
+  { intent: 'General Support', duration: 172 },
+];
+
+// Data for Performance tab - Top Call Intents
+const topIntentsData = [
+  { name: "Order Status", count: 1247, percentage: 43.8, change: "+5.2%" },
+  { name: "Product Information", count: 685, percentage: 24.1, change: "-2.1%" },
+  { name: "Return Request", count: 432, percentage: 15.2, change: "+8.7%" },
+  { name: "Shipping Inquiry", count: 298, percentage: 10.5, change: "-1.4%" },
+  { name: "General Support", count: 185, percentage: 6.5, change: "+12.3%" },
+];
+
+// Data for Trends tab - Resolution Rate Trend
+const resolutionTrendData = [
+  { week: 'Week 1', rate: 78 },
+  { week: 'Week 2', rate: 82 },
+  { week: 'Week 3', rate: 85 },
+  { week: 'Week 4', rate: 87 },
+];
+
+// Data for Trends tab - Peak Call Hours
+const peakHoursData = [
+  { time: '10 AM', calls: 12 },
+  { time: '11 AM', calls: 18 },
+  { time: '12 PM', calls: 25 },
+  { time: '1 PM', calls: 32 },
+  { time: '2 PM', calls: 28 },
+  { time: '3 PM', calls: 22 },
+  { time: '4 PM', calls: 26 },
+];
+
+export function AnalyticsContent() {
+  return (
+    <div className="flex-1 space-y-6 p-8 bg-slate-900">
+      <div className="flex items-center justify-between">
+        <div>
+          <h2 className="text-3xl font-bold tracking-tight text-white">Analytics</h2>
+          <p className="text-slate-400">Deeper insights and trends over time</p>
+        </div>
+      </div>
+
+      <Tabs defaultValue="overview" className="space-y-6">
+        <TabsList className="bg-slate-800 text-slate-300">
+          <TabsTrigger value="overview" className="data-[state=active]:bg-cyan-500 data-[state=active]:text-white">Overview</TabsTrigger>
+          <TabsTrigger value="trends" className="data-[state=active]:bg-cyan-500 data-[state=active]:text-white">Trends</TabsTrigger>
+          <TabsTrigger value="performance" className="data-[state=active]:bg-cyan-500 data-[state=active]:text-white">Performance</TabsTrigger>
+        </TabsList>
+
+        <TabsContent value="overview" className="space-y-6">
+          <div className="grid gap-6 md:grid-cols-2">
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Call Volume by Day</CardTitle>
+              </CardHeader>
+              <CardContent>
+                <ResponsiveContainer width="100%" height={300}>
+                  <BarChart data={weeklyData}>
+                    <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                    <XAxis dataKey="day" stroke="#9CA3AF" />
+                    <YAxis stroke="#9CA3AF" />
+                    <Bar dataKey="calls" fill="#06B6D4" />
+                  </BarChart>
+                </ResponsiveContainer>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Resolution Rate Over Time</CardTitle>
+              </CardHeader>
+              <CardContent>
+                <ResponsiveContainer width="100%" height={300}>
+                  <LineChart data={resolutionData}>
+                    <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                    <XAxis dataKey="week" stroke="#9CA3AF" />
+                    <YAxis stroke="#9CA3AF" />
+                    <Line type="monotone" dataKey="rate" stroke="#06B6D4" strokeWidth={3} />
+                  </LineChart>
+                </ResponsiveContainer>
+              </CardContent>
+            </Card>
+          </div>
+
+          <Card className="bg-slate-800 border-slate-700">
+            <CardHeader>
+              <CardTitle className="text-white">Peak Call Times</CardTitle>
+              <p className="text-slate-400">Heatmap showing busiest hours and days</p>
+            </CardHeader>
+            <CardContent>
+              <div className="grid grid-cols-7 gap-2 text-center">
+                <div className="text-slate-400 text-sm font-medium">Mon</div>
+                <div className="text-slate-400 text-sm font-medium">Tue</div>
+                <div className="text-slate-400 text-sm font-medium">Wed</div>
+                <div className="text-slate-400 text-sm font-medium">Thu</div>
+                <div className="text-slate-400 text-sm font-medium">Fri</div>
+                <div className="text-slate-400 text-sm font-medium">Sat</div>
+                <div className="text-slate-400 text-sm font-medium">Sun</div>
+                
+                {Array.from({ length: 7 * 24 }, (_, i) => (
+                  <div 
+                    key={i} 
+                    className={`h-4 rounded ${Math.random() > 0.7 ? 'bg-cyan-500' : Math.random() > 0.4 ? 'bg-cyan-700' : 'bg-slate-700'}`}
+                  />
+                ))}
+              </div>
+            </CardContent>
+          </Card>
+        </TabsContent>
+
+        <TabsContent value="trends" className="space-y-6">
+          <div className="grid gap-6 md:grid-cols-2">
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Resolution Rate Trend</CardTitle>
+              </CardHeader>
+              <CardContent>
+                <ResponsiveContainer width="100%" height={300}>
+                  <LineChart data={resolutionTrendData}>
+                    <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                    <XAxis dataKey="week" stroke="#9CA3AF" />
+                    <YAxis domain={[0, 100]} stroke="#9CA3AF" />
+                    <Line 
+                      type="monotone" 
+                      dataKey="rate" 
+                      stroke="#06B6D4" 
+                      strokeWidth={3}
+                      dot={{ fill: '#06B6D4', strokeWidth: 2, r: 6 }}
+                    />
+                  </LineChart>
+                </ResponsiveContainer>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Peak Call Hours</CardTitle>
+              </CardHeader>
+              <CardContent>
+                <ResponsiveContainer width="100%" height={300}>
+                  <BarChart data={peakHoursData}>
+                    <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                    <XAxis dataKey="time" stroke="#9CA3AF" />
+                    <YAxis domain={[0, 35]} stroke="#9CA3AF" />
+                    <Bar dataKey="calls" fill="#06B6D4" />
+                  </BarChart>
+                </ResponsiveContainer>
+              </CardContent>
+            </Card>
+          </div>
+        </TabsContent>
+
+        <TabsContent value="performance" className="space-y-6">
+          <div className="grid gap-6 md:grid-cols-2">
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Average Call Duration by Intent</CardTitle>
+              </CardHeader>
+              <CardContent>
+                <ResponsiveContainer width="100%" height={300}>
+                  <BarChart data={callDurationData}>
+                    <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                    <XAxis 
+                      dataKey="intent" 
+                      stroke="#9CA3AF" 
+                      angle={-45}
+                      textAnchor="end"
+                      height={80}
+                      fontSize={12}
+                    />
+                    <YAxis domain={[0, 180]} stroke="#9CA3AF" />
+                    <Bar dataKey="duration" fill="#06B6D4" />
+                  </BarChart>
+                </ResponsiveContainer>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardHeader>
+                <CardTitle className="text-white">Top Call Intents</CardTitle>
+                <p className="text-slate-400">Last 24 hours</p>
+              </CardHeader>
+              <CardContent>
+                <div className="space-y-4">
+                  {topIntentsData.map((intent) => (
+                    <div key={intent.name} className="space-y-2">
+                      <div className="flex items-center justify-between">
+                        <div className="flex items-center gap-3">
+                          <div className="w-3 h-3 bg-cyan-500 rounded-full"></div>
+                          <span className="text-white font-medium">{intent.name}</span>
+                        </div>
+                        
+                        <div className="flex items-center gap-4">
+                          <span className="text-2xl font-bold text-white">{intent.count}</span>
+                          <span className={`text-sm font-medium ${
+                            intent.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
+                          }`}>
+                            {intent.change}
+                          </span>
+                        </div>
+                      </div>
+                      
+                      <div className="text-sm text-slate-400 text-right">
+                        {intent.percentage}%
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              </CardContent>
+            </Card>
+          </div>
+        </TabsContent>
+      </Tabs>
+    </div>
+  );
+}

+ 138 - 0
shopcall.ai-main/src/components/AppSidebar.tsx

@@ -0,0 +1,138 @@
+
+import {
+  Sidebar,
+  SidebarContent,
+  SidebarGroup,
+  SidebarGroupContent,
+  SidebarMenu,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  SidebarHeader,
+  SidebarFooter,
+} from "@/components/ui/sidebar";
+import { useNavigate } from "react-router-dom";
+import { LayoutDashboard, Phone, BarChart3, Settings, CreditCard, Layers3, PhoneCall } from "lucide-react";
+
+const menuItems = [
+  {
+    title: "Dashboard",
+    icon: LayoutDashboard,
+    url: "/dashboard",
+    active: false,
+  },
+  {
+    title: "Call Logs",
+    icon: Phone,
+    url: "/call-logs",
+    active: false,
+  },
+  {
+    title: "Analytics",
+    icon: BarChart3,
+    url: "/analytics",
+    active: false,
+  },
+  {
+    title: "Webshops",
+    icon: Layers3,
+    url: "/webshops",
+    active: false,
+  },
+  {
+    title: "Phone Numbers",
+    icon: PhoneCall,
+    url: "/phone-numbers",
+    active: false,
+  },
+];
+
+const configItems = [
+  {
+    title: "AI Configuration",
+    icon: Settings,
+    url: "/ai-config",
+  },
+  {
+    title: "Billing & Plan",
+    icon: CreditCard,
+    url: "/billing",
+  },
+];
+
+export function AppSidebar() {
+  const navigate = useNavigate();
+  const currentPath = window.location.pathname;
+  
+  return (
+    <Sidebar className="border-r border-slate-700/50 bg-slate-900 text-white">
+      <SidebarHeader className="p-6 bg-slate-900">
+        <div className="flex items-center gap-3">
+          <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-8 h-8" />
+          <span className="text-xl font-bold text-white">ShopCall.ai</span>
+        </div>
+      </SidebarHeader>
+      
+      <SidebarContent className="px-3 bg-slate-900">
+        <SidebarGroup>
+          <SidebarGroupContent>
+            <SidebarMenu>
+              {menuItems.map((item) => {
+                const isActive = currentPath === item.url;
+                return (
+                  <SidebarMenuItem key={item.title}>
+                    <SidebarMenuButton 
+                      asChild 
+                      className={`w-full justify-start text-slate-300 hover:text-white hover:bg-slate-800/50 ${
+                        isActive ? 'bg-cyan-500 text-white hover:bg-cyan-600' : ''
+                      }`}
+                    >
+                      <a onClick={() => {navigate(item.url)}} className="flex items-center gap-3 px-3 py-2 rounded-lg">
+                        <item.icon className="w-4 h-4" />
+                        <span>{item.title}</span>
+                      </a>
+                    </SidebarMenuButton>
+                  </SidebarMenuItem>
+                );
+              })}
+            </SidebarMenu>
+          </SidebarGroupContent>
+        </SidebarGroup>
+
+        <SidebarGroup className="mt-8">
+          <div className="px-3 py-2 text-xs font-semibold text-slate-500 uppercase tracking-wider">
+            Configuration
+          </div>
+          <SidebarGroupContent>
+            <SidebarMenu>
+              {configItems.map((item) => {
+                const isActive = currentPath === item.url;
+                return (
+                  <SidebarMenuItem key={item.title}>
+                    <SidebarMenuButton 
+                      asChild 
+                      className={`w-full justify-start text-slate-300 hover:text-white hover:bg-slate-800/50 ${
+                        isActive ? 'bg-cyan-500 text-white hover:bg-cyan-600' : ''
+                      }`}
+                    >
+                      <a onClick={() => {navigate(item.url)}} className="flex items-center gap-3 px-3 py-2 rounded-lg">
+                        <item.icon className="w-4 h-4" />
+                        <span>{item.title}</span>
+                      </a>
+                    </SidebarMenuButton>
+                  </SidebarMenuItem>
+                );
+              })}
+            </SidebarMenu>
+          </SidebarGroupContent>
+        </SidebarGroup>
+      </SidebarContent>
+
+      <SidebarFooter className="p-6 bg-slate-900">
+        <div className="flex items-center gap-3 p-3 bg-slate-800/50 rounded-lg">
+          <Settings className="w-4 h-4 text-slate-400" />
+          <span className="text-sm text-slate-300">Settings</span>
+        </div>
+      </SidebarFooter>
+    </Sidebar>
+  );
+}

+ 180 - 0
shopcall.ai-main/src/components/CallDetailsModal.tsx

@@ -0,0 +1,180 @@
+
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Phone, Calendar, Play, Volume2, MoreHorizontal } from "lucide-react";
+import { useState } from "react";
+
+interface CallDetailsModalProps {
+  isOpen: boolean;
+  onClose: () => void;
+  call: any;
+}
+
+export function CallDetailsModal({ isOpen, onClose, call }: CallDetailsModalProps) {
+  const [isPlaying, setIsPlaying] = useState(false);
+
+  if (!call) return null;
+
+  const handlePlayPause = () => {
+    setIsPlaying(!isPlaying);
+  };
+
+  return (
+    <Dialog open={isOpen} onOpenChange={onClose}>
+      <DialogContent className="max-w-4xl bg-slate-800 border-slate-700 text-white">
+        <DialogHeader>
+          <DialogTitle className="text-xl font-semibold text-white">Call Details</DialogTitle>
+        </DialogHeader>
+        
+        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
+          {/* Left Column - Contact Information */}
+          <div className="space-y-6">
+            <div className="bg-slate-700/50 rounded-lg p-4">
+              <div className="flex items-center gap-2 mb-4">
+                <Phone className="w-5 h-5 text-slate-400" />
+                <h3 className="text-lg font-semibold">Contact Information</h3>
+              </div>
+              
+              <div className="space-y-4">
+                <div>
+                  <div className="text-sm text-slate-400 mb-1">Customer</div>
+                  <div className="flex items-center gap-2">
+                    <span className="text-white font-medium">{call.customer}</span>
+                    <span className={`px-2 py-1 rounded text-xs font-medium ${call.outcomeColor} bg-slate-600`}>
+                      {call.outcome}
+                    </span>
+                  </div>
+                </div>
+                
+                <div className="flex items-center gap-2 text-slate-300">
+                  <Phone className="w-4 h-4" />
+                  <span>{call.customer}</span>
+                </div>
+                
+                <div className="flex items-center gap-2 text-slate-300">
+                  <Calendar className="w-4 h-4" />
+                  <span>Added {call.time}</span>
+                </div>
+              </div>
+            </div>
+
+            {/* Call Statistics */}
+            <div className="grid grid-cols-2 gap-4">
+              <div className="bg-slate-700/50 rounded-lg p-4 text-center">
+                <div className="text-3xl font-bold text-white">1</div>
+                <div className="text-sm text-slate-400">Total Calls</div>
+              </div>
+              
+              <div className="bg-slate-700/50 rounded-lg p-4 text-center">
+                <div className="text-3xl font-bold text-white">{call.intent}</div>
+                <div className="text-sm text-slate-400">Intent</div>
+              </div>
+              
+              <div className="bg-slate-700/50 rounded-lg p-4 text-center">
+                <div className="text-3xl font-bold text-white">{call.duration}</div>
+                <div className="text-sm text-slate-400">Total Duration</div>
+              </div>
+              
+              <div className="bg-slate-700/50 rounded-lg p-4 text-center">
+                <div className="text-3xl font-bold text-white">{call.cost}</div>
+                <div className="text-sm text-slate-400">Total Cost</div>
+              </div>
+            </div>
+
+            <div className="bg-slate-700/50 rounded-lg p-4 text-center">
+              <div className="text-2xl font-bold text-white">{call.outcome}</div>
+              <div className="text-sm text-slate-400">Call Outcome</div>
+            </div>
+          </div>
+
+          {/* Right Column - Call History */}
+          <div className="space-y-6">
+            <div className="bg-slate-700/50 rounded-lg p-4">
+              <div className="flex items-center gap-2 mb-4">
+                <Calendar className="w-5 h-5 text-slate-400" />
+                <h3 className="text-lg font-semibold">Call History</h3>
+              </div>
+              
+              <div className="space-y-4">
+                <div className="flex items-center justify-between">
+                  <div className="flex items-center gap-2">
+                    <Calendar className="w-4 h-4 text-slate-400" />
+                    <span className="text-slate-300">{call.time}</span>
+                  </div>
+                  <div className="flex items-center gap-4">
+                    <span className={`text-sm font-medium ${call.outcomeColor}`}>
+                      {call.outcome}
+                    </span>
+                    <div className="text-right">
+                      <div className="text-sm text-slate-400">Duration: {call.duration}</div>
+                    </div>
+                  </div>
+                </div>
+
+                <div>
+                  <h4 className="text-white font-medium mb-2">Transcript</h4>
+                  <div className="text-slate-300 text-sm leading-relaxed">
+                    Customer called regarding order status. The AI assistant handled the inquiry professionally and provided appropriate assistance.
+                  </div>
+                </div>
+
+                <div>
+                  <h4 className="text-white font-medium mb-2">Summary</h4>
+                  <div className="bg-slate-600/50 rounded-lg p-3">
+                    <p className="text-slate-300 text-sm">
+                      Call successfully handled. Customer inquiry about order status was resolved efficiently. Sentiment: {call.sentiment} 😊
+                    </p>
+                  </div>
+                </div>
+
+                <div className="flex items-center justify-between">
+                  <div>
+                    <span className="text-slate-400 text-sm">Call Outcome:</span>
+                    <span className={`ml-2 font-medium ${call.outcomeColor}`}>{call.outcome}</span>
+                  </div>
+                  <div>
+                    <span className="text-slate-400 text-sm">AI Assistant:</span>
+                    <span className="ml-2 text-white font-medium">AI Assistant</span>
+                  </div>
+                </div>
+
+                <div>
+                  <h4 className="text-white font-medium mb-3">Call Recording</h4>
+                  <div className="bg-slate-600/50 rounded-lg p-4">
+                    <div className="flex items-center gap-4">
+                      <Button
+                        variant="ghost"
+                        size="sm"
+                        onClick={handlePlayPause}
+                        className="w-12 h-12 rounded-full bg-slate-700 hover:bg-slate-600 text-white border-2 border-slate-500"
+                      >
+                        <Play className="w-5 h-5" />
+                      </Button>
+                      
+                      <div className="flex-1">
+                        <div className="flex items-center justify-between text-sm text-slate-400 mb-1">
+                          <span>0:00</span>
+                          <span>{call.duration}</span>
+                        </div>
+                        <div className="w-full bg-slate-700 rounded-full h-2">
+                          <div className="bg-cyan-500 h-2 rounded-full w-0"></div>
+                        </div>
+                      </div>
+                      
+                      <div className="flex items-center gap-2">
+                        <Volume2 className="w-4 h-4 text-slate-400" />
+                        <Button variant="ghost" size="sm" className="text-slate-400">
+                          <MoreHorizontal className="w-4 h-4" />
+                        </Button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </DialogContent>
+    </Dialog>
+  );
+}

+ 257 - 0
shopcall.ai-main/src/components/CallLogsContent.tsx

@@ -0,0 +1,257 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Card } from "@/components/ui/card";
+import { Search, Download, Calendar, Filter, Play, MoreHorizontal } from "lucide-react";
+import { useState, useEffect } from "react";
+import { CallDetailsModal } from "./CallDetailsModal";
+import { API_URL } from "@/lib/config";
+
+interface CallLog {
+  time: string;
+  customer: string;
+  intent: string;
+  outcome: string;
+  duration: string;
+  sentiment: string;
+  cost: string;
+  outcomeColor: string;
+  sentimentColor: string;
+}
+
+const maskPhoneNumber = (phone: string | undefined ) => {
+  if (!phone) return "";
+  const last4 = phone.slice(-4);
+  return `...xxx-${last4}`;
+};
+
+const getSentimentEmoji = (sentiment: string) => {
+  switch (sentiment) {
+    case "Positive":
+      return "😊";
+    case "Negative":
+      return "😠";
+    case "Neutral":
+      return "😐";
+    default:
+      return "😐";
+  }
+};
+
+export function CallLogsContent() {
+  const [selectedCall, setSelectedCall] = useState<CallLog | null>(null);
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [callLogs, setCallLogs] = useState<CallLog[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const fetchCallLogs = async () => {
+    setIsLoading(true);
+    setError(null);
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const { access_token } = JSON.parse(sessionData).session;
+      const response = await fetch(`${API_URL}/api/call-logs`, {
+        headers: {
+          'Authorization': `Bearer ${access_token}`,
+          'Content-Type': 'application/json',
+        },
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to fetch call logs');
+      }
+
+      const data = await response.json();
+      setCallLogs(data.call_logs);
+    } catch (err) {
+      setError(err instanceof Error ? err.message : 'An error occurred');
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    fetchCallLogs();
+  }, []);
+
+  const handleDetailsClick = (call: CallLog) => {
+    setSelectedCall(call);
+    setIsModalOpen(true);
+  };
+
+  const handleCloseModal = () => {
+    setIsModalOpen(false);
+    setSelectedCall(null);
+  };
+
+  return (
+    <>
+      <div className="flex-1 bg-slate-900 text-white">
+        {/* Header */}
+        <div className="border-b border-slate-800 bg-slate-900">
+          <div className="flex items-center justify-between p-6">
+            <div>
+              <h1 className="text-2xl font-semibold text-white">Call Logs</h1>
+              <p className="text-slate-400">Detailed history of all AI-handled calls</p>
+            </div>
+            
+            <div className="flex items-center gap-4">
+              <Button className="bg-slate-700 hover:bg-slate-600 text-white">
+                <Download className="w-4 h-4 mr-2" />
+                Export
+              </Button>
+              
+              <Button className="bg-slate-700 hover:bg-slate-600 text-white">
+                <Calendar className="w-4 h-4 mr-2" />
+                Date Range
+              </Button>
+            </div>
+          </div>
+        </div>
+
+        {/* Search and Filters */}
+        <div className="p-6 border-b border-slate-800">
+          <div className="flex items-center gap-4">
+            <div className="relative flex-1 max-w-lg">
+              <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
+              <Input
+                placeholder="Search by phone number or customer..."
+                className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-400"
+              />
+            </div>
+            
+            <Button variant="outline" className="border-slate-700 text-slate-300 hover:text-white hover:bg-slate-700">
+              <Filter className="w-4 h-4 mr-2" />
+              Filters
+            </Button>
+          </div>
+        </div>
+
+        {/* Call Logs Table */}
+        <div className="p-6">
+          <Card className="bg-slate-800 border-slate-700">
+            <div className="p-6">
+              <div className="flex items-center justify-between mb-6">
+                <h3 className="text-lg font-semibold text-white">All Call Logs</h3>
+                {!isLoading && !error && (
+                  <Button 
+                    variant="ghost" 
+                    size="sm" 
+                    className="text-slate-400 hover:text-white"
+                    onClick={fetchCallLogs}
+                  >
+                    <svg className="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+                      <path d="M23 4v6h-6M1 20v-6h6" strokeLinecap="round" strokeLinejoin="round"/>
+                      <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" strokeLinecap="round" strokeLinejoin="round"/>
+                    </svg>
+                    Refresh
+                  </Button>
+                )}
+              </div>
+              
+              {isLoading ? (
+                <div className="flex flex-col items-center justify-center py-12">
+                  <div className="relative">
+                    <div className="w-12 h-12 border-4 border-slate-700 rounded-full"></div>
+                    <div className="w-12 h-12 border-4 border-white border-t-transparent rounded-full animate-spin absolute top-0"></div>
+                  </div>
+                  <p className="text-slate-400 mt-4 font-medium">Loading call logs...</p>
+                </div>
+              ) : error ? (
+                <div className="flex flex-col items-center justify-center py-12">
+                  <div className="w-16 h-16 bg-red-500/10 rounded-full flex items-center justify-center mb-4">
+                    <svg className="w-8 h-8 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+                      <circle cx="12" cy="12" r="10"/>
+                      <line x1="12" y1="8" x2="12" y2="12"/>
+                      <line x1="12" y1="16" x2="12.01" y2="16"/>
+                    </svg>
+                  </div>
+                  <p className="text-red-500 font-medium mb-2">Failed to load call logs</p>
+                  <p className="text-slate-400 text-sm mb-4">{error}</p>
+                  <Button 
+                    className="bg-slate-700 hover:bg-slate-600 text-white"
+                    onClick={fetchCallLogs}
+                  >
+                    <svg className="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+                      <path d="M23 4v6h-6M1 20v-6h6" strokeLinecap="round" strokeLinejoin="round"/>
+                      <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" strokeLinecap="round" strokeLinejoin="round"/>
+                    </svg>
+                    Try Again
+                  </Button>
+                </div>
+              ) : (
+                <div className="overflow-x-auto">
+                  <table className="w-full">
+                    <thead>
+                      <tr className="border-b border-slate-700">
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Time</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Customer</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Intent</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Outcome</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Duration</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Sentiment</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Cost</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Actions</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      {callLogs.map((call, index) => (
+                        <tr key={index} className="border-b border-slate-700/50 hover:bg-slate-700/30">
+                          <td className="py-3 px-4 text-sm text-slate-300">{call.time}</td>
+                          <td className="py-3 px-4 text-sm text-white">{maskPhoneNumber(call.customer)}</td>
+                          <td className="py-3 px-4 text-sm text-slate-300">{call.intent}</td>
+                          <td className="py-3 px-4">
+                            <span className={`text-sm font-medium ${call.outcomeColor}`}>
+                              {call.outcome}
+                            </span>
+                          </td>
+                          <td className="py-3 px-4 text-sm text-slate-300">{call.duration}</td>
+                          <td className="py-3 px-4">
+                            <div className="flex items-center gap-2">
+                              <span className="text-sm">{getSentimentEmoji(call.sentiment)}</span>
+                              <span className={`text-sm ${call.sentimentColor}`}>
+                                {call.sentiment}
+                              </span>
+                            </div>
+                          </td>
+                          <td className="py-3 px-4 text-sm text-slate-300">{call.cost}</td>
+                          <td className="py-3 px-4">
+                            <div className="flex items-center gap-2">
+                              <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
+                                <Play className="w-4 h-4" />
+                              </Button>
+                              <Button 
+                                variant="ghost" 
+                                size="sm" 
+                                className="text-slate-400 hover:text-white"
+                                onClick={() => handleDetailsClick(call)}
+                              >
+                                <MoreHorizontal className="w-4 h-4" />
+                              </Button>
+                            </div>
+                          </td>
+                        </tr>
+                      ))}
+                    </tbody>
+                  </table>
+                </div>
+              )}
+            </div>
+          </Card>
+        </div>
+      </div>
+
+      {selectedCall && (
+        <CallDetailsModal
+          isOpen={isModalOpen}
+          onClose={handleCloseModal}
+          call={selectedCall}
+        />
+      )}
+    </>
+  );
+}

+ 18 - 0
shopcall.ai-main/src/components/ChartsSection.tsx

@@ -0,0 +1,18 @@
+
+import { Card } from "@/components/ui/card";
+import { ResolutionRateChart } from "./ResolutionRateChart";
+import { TopCallIntents } from "./TopCallIntents";
+
+export function ChartsSection() {
+  return (
+    <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+      <Card className="bg-slate-800 border-slate-700 p-6">
+        <ResolutionRateChart />
+      </Card>
+      
+      <Card className="bg-slate-800 border-slate-700 p-6">
+        <TopCallIntents />
+      </Card>
+    </div>
+  );
+}

+ 21 - 0
shopcall.ai-main/src/components/DashboardContent.tsx

@@ -0,0 +1,21 @@
+import { DashboardHeader } from "./DashboardHeader";
+import { KPICards } from "./KPICards";
+import { ChartsSection } from "./ChartsSection";
+import { RecentCallsTable } from "./RecentCallsTable";
+import { DashboardProvider } from "./context/DashboardContext";
+
+export function DashboardContent() {
+  return (
+    <DashboardProvider>
+      <div className="flex-1 bg-slate-900 text-white">
+        <DashboardHeader />
+
+        <div className="p-6 space-y-6">
+          <KPICards />
+          <ChartsSection />
+          <RecentCallsTable />
+        </div>
+      </div>
+    </DashboardProvider>
+  );
+}

+ 112 - 0
shopcall.ai-main/src/components/DashboardHeader.tsx

@@ -0,0 +1,112 @@
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Calendar } from "@/components/ui/calendar";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
+import { Search, CalendarIcon } from "lucide-react";
+import { useState } from "react";
+import { format } from "date-fns";
+import { DateRange } from "react-day-picker";
+import { cn } from "@/lib/utils";
+
+export function DashboardHeader() {
+  const [dateRange, setDateRange] = useState<DateRange | undefined>();
+  const [quickSelect, setQuickSelect] = useState<string>("1");
+
+  const handleQuickSelect = (value: string) => {
+    if (!value) return;
+    
+    setQuickSelect(value);
+    const today = new Date();
+    const days = parseInt(value);
+    const startDate = new Date(today);
+    startDate.setDate(today.getDate() - days);
+    
+    setDateRange({
+      from: startDate,
+      to: today,
+    });
+  };
+
+  return (
+    <div className="border-b border-slate-800 bg-slate-900">
+      <div className="flex items-center justify-between p-6">
+        <div>
+          <h1 className="text-2xl font-semibold text-white">AI Call Dashboard</h1>
+          <p className="text-slate-400">Your AI calling insights for June 10, 2025</p>
+        </div>
+        
+        <div className="flex items-center gap-4">
+          <div className="flex items-center gap-2 bg-slate-800 rounded-lg p-1">
+            <ToggleGroup 
+              type="single" 
+              value={quickSelect} 
+              onValueChange={handleQuickSelect}
+              className="gap-0"
+            >
+              <ToggleGroupItem value="1" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
+                24h
+              </ToggleGroupItem>
+              <ToggleGroupItem value="7" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
+                7d
+              </ToggleGroupItem>
+              <ToggleGroupItem value="14" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
+                14d
+              </ToggleGroupItem>
+              <ToggleGroupItem value="30" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
+                30d
+              </ToggleGroupItem>
+            </ToggleGroup>
+            
+            <div className="w-px h-6 bg-slate-600 mx-1"></div>
+            
+            <Popover>
+              <PopoverTrigger asChild>
+                <Button
+                  variant="ghost"
+                  className={cn(
+                    "justify-start text-left font-normal text-slate-300 hover:text-white px-3 py-1 h-8",
+                    !dateRange && "text-slate-400"
+                  )}
+                >
+                  <CalendarIcon className="mr-2 h-4 w-4" />
+                  {dateRange?.from ? (
+                    dateRange.to ? (
+                      <>
+                        {format(dateRange.from, "MMM dd")} - {format(dateRange.to, "MMM dd")}
+                      </>
+                    ) : (
+                      format(dateRange.from, "MMM dd, yyyy")
+                    )
+                  ) : (
+                    <span>Custom range</span>
+                  )}
+                </Button>
+              </PopoverTrigger>
+              <PopoverContent className="w-auto p-0 bg-slate-800 border-slate-700" align="start">
+                <Calendar
+                  initialFocus
+                  mode="range"
+                  defaultMonth={dateRange?.from}
+                  selected={dateRange}
+                  onSelect={setDateRange}
+                  numberOfMonths={2}
+                  className="p-3 pointer-events-auto bg-slate-800 text-white"
+                />
+              </PopoverContent>
+            </Popover>
+          </div>
+          
+          <div className="relative">
+            <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
+            <Input
+              placeholder="Search calls..."
+              className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-400 w-64"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 386 - 0
shopcall.ai-main/src/components/IntegrationsContent.tsx

@@ -0,0 +1,386 @@
+import { useState, useEffect } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { Plus, Settings, Store, Bot, PhoneCall, Globe, Zap, ShoppingBag, Loader2 } from "lucide-react";
+import { ShopRenterConnect } from "./ShopRenterConnect";
+import { API_URL } from "@/lib/config";
+
+interface ConnectedStore {
+  id: string;
+  store_name: string | null;
+  platform_name: string;
+  store_url: string | null;
+  is_active: boolean | null;
+  phone_number: string | null;
+  created_at: string | null;
+}
+
+export function IntegrationsContent() {
+  const [connectedShops, setConnectedShops] = useState<ConnectedStore[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [showConnectDialog, setShowConnectDialog] = useState(false);
+  const [selectedPlatform, setSelectedPlatform] = useState<string | null>(null);
+
+  useEffect(() => {
+    const fetchStores = async () => {
+      try {
+        const sessionData = localStorage.getItem('session_data');
+        if (!sessionData) {
+          throw new Error('No session data found');
+        }
+
+        const session = JSON.parse(sessionData);
+        const response = await fetch(`${API_URL}/api/stores`, {
+          headers: {
+            'Authorization': `Bearer ${session.access_token}`,
+            'Content-Type': 'application/json'
+          }
+        });
+
+        if (!response.ok) {
+          throw new Error('Failed to fetch stores');
+        }
+
+        const data = await response.json();
+        if (data.success) {
+          setConnectedShops(data.stores || []);
+        }
+      } catch (error) {
+        console.error('Error fetching stores:', error);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchStores();
+  }, []);
+
+  const platforms = [
+    {
+      id: "shopify",
+      name: "Shopify",
+      description: "Connect your Shopify store",
+      icon: Store,
+      color: "text-green-500"
+    },
+    {
+      id: "woocommerce",
+      name: "WooCommerce",
+      description: "Connect your WooCommerce store",
+      icon: ShoppingBag,
+      color: "text-purple-500"
+    },
+    {
+      id: "shoprenter",
+      name: "ShopRenter",
+      description: "Connect your ShopRenter store",
+      icon: Store,
+      color: "text-blue-500"
+    }
+  ];
+
+  const handlePlatformSelect = (platformId: string) => {
+    setSelectedPlatform(platformId);
+  };
+
+  const handleCloseDialog = () => {
+    setShowConnectDialog(false);
+    setSelectedPlatform(null);
+  };
+
+  return (
+    <div className="flex-1 space-y-6 p-8 bg-slate-900">
+      <div className="flex items-center justify-between">
+        <div>
+          <h2 className="text-3xl font-bold tracking-tight text-white">Webshops</h2>
+          <p className="text-slate-400">Manage your connected stores and their AI configurations</p>
+        </div>
+        <Button
+          className="bg-cyan-500 hover:bg-cyan-600 text-white"
+          onClick={() => setShowConnectDialog(true)}
+        >
+          <Plus className="w-4 h-4 mr-2" />
+          Connect Webshop
+        </Button>
+      </div>
+
+      {/* Platform Selection Dialog */}
+      <Dialog open={showConnectDialog} onOpenChange={setShowConnectDialog}>
+        <DialogContent className="bg-slate-800 border-slate-700 max-w-3xl">
+          {!selectedPlatform ? (
+            <>
+              <DialogHeader>
+                <DialogTitle className="text-white text-2xl">Connect Your E-commerce Platform</DialogTitle>
+                <DialogDescription className="text-slate-400">
+                  Choose your e-commerce platform to get started with AI-powered customer calls
+                </DialogDescription>
+              </DialogHeader>
+              <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
+                {platforms.map((platform) => {
+                  const IconComponent = platform.icon;
+                  return (
+                    <Card
+                      key={platform.id}
+                      className="bg-slate-700 border-slate-600 hover:border-cyan-500 cursor-pointer transition-all"
+                      onClick={() => handlePlatformSelect(platform.id)}
+                    >
+                      <CardContent className="p-6 text-center space-y-4">
+                        <IconComponent className={`w-12 h-12 mx-auto ${platform.color}`} />
+                        <div>
+                          <h3 className="text-white font-semibold text-lg">{platform.name}</h3>
+                          <p className="text-slate-400 text-sm mt-1">{platform.description}</p>
+                        </div>
+                        <Button className="w-full bg-cyan-500 hover:bg-cyan-600 text-white">
+                          Connect
+                        </Button>
+                      </CardContent>
+                    </Card>
+                  );
+                })}
+              </div>
+            </>
+          ) : selectedPlatform === "shoprenter" ? (
+            <ShopRenterConnect onClose={handleCloseDialog} />
+          ) : (
+            <div className="text-center py-8">
+              <p className="text-white">
+                {platforms.find(p => p.id === selectedPlatform)?.name} integration coming soon!
+              </p>
+              <Button
+                onClick={handleCloseDialog}
+                className="mt-4 bg-cyan-500 hover:bg-cyan-600 text-white"
+              >
+                Close
+              </Button>
+            </div>
+          )}
+        </DialogContent>
+      </Dialog>
+
+      <div className="space-y-6">
+        {loading ? (
+          <div className="grid gap-6 md:grid-cols-4">
+            {[...Array(4)].map((_, i) => (
+              <Card key={i} className="bg-slate-800 border-slate-700">
+                <CardContent className="p-6">
+                  <div className="flex items-center justify-center h-20">
+                    <Loader2 className="w-6 h-6 text-slate-400 animate-spin" />
+                  </div>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        ) : (
+          <div className="grid gap-6 md:grid-cols-4">
+            <Card className="bg-slate-800 border-slate-700">
+              <CardContent className="p-6">
+                <div className="flex items-center gap-3">
+                  <Store className="w-8 h-8 text-cyan-500" />
+                  <div>
+                    <p className="text-2xl font-bold text-white">{connectedShops.length}</p>
+                    <p className="text-slate-400">Connected Shops</p>
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardContent className="p-6">
+                <div className="flex items-center gap-3">
+                  <PhoneCall className="w-8 h-8 text-green-500" />
+                  <div>
+                    <p className="text-2xl font-bold text-white">{connectedShops.filter(s => s.phone_number).length}</p>
+                    <p className="text-slate-400">With Phone Numbers</p>
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardContent className="p-6">
+                <div className="flex items-center gap-3">
+                  <Store className="w-8 h-8 text-purple-500" />
+                  <div>
+                    <p className="text-2xl font-bold text-white">{connectedShops.filter(s => s.is_active).length}</p>
+                    <p className="text-slate-400">Active Stores</p>
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700">
+              <CardContent className="p-6">
+                <div className="flex items-center gap-3">
+                  <Globe className="w-8 h-8 text-blue-500" />
+                  <div>
+                    <p className="text-2xl font-bold text-white">{new Set(connectedShops.map(s => s.platform_name)).size}</p>
+                    <p className="text-slate-400">Platforms</p>
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+          </div>
+        )}
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <CardTitle className="text-white">Connected Webshops</CardTitle>
+            <p className="text-slate-400">Overview of all your connected stores with their configurations</p>
+          </CardHeader>
+          <CardContent className="p-0">
+            {loading ? (
+              <div className="flex items-center justify-center p-12">
+                <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+              </div>
+            ) : connectedShops.length === 0 ? (
+              <div className="text-center p-12">
+                <Store className="w-16 h-16 text-slate-600 mx-auto mb-4" />
+                <h3 className="text-lg font-semibold text-white mb-2">No Stores Connected</h3>
+                <p className="text-slate-400 mb-6">
+                  Connect your first e-commerce store to get started with AI-powered customer calls
+                </p>
+                <Button
+                  className="bg-cyan-500 hover:bg-cyan-600 text-white"
+                  onClick={() => setShowConnectDialog(true)}
+                >
+                  <Plus className="w-4 h-4 mr-2" />
+                  Connect Your First Store
+                </Button>
+              </div>
+            ) : (
+              <div className="overflow-x-auto">
+                <table className="w-full">
+                  <thead className="border-b border-slate-700">
+                    <tr className="text-left">
+                      <th className="p-4 text-slate-300 font-medium">Store</th>
+                      <th className="p-4 text-slate-300 font-medium">Phone Number</th>
+                      <th className="p-4 text-slate-300 font-medium">Platform</th>
+                      <th className="p-4 text-slate-300 font-medium">Created</th>
+                      <th className="p-4 text-slate-300 font-medium">Status</th>
+                      <th className="p-4 text-slate-300 font-medium">Actions</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    {connectedShops.map((shop) => (
+                      <tr key={shop.id} className="border-b border-slate-700/50 hover:bg-slate-700/30">
+                        <td className="p-4">
+                          <div>
+                            <div className="text-white font-medium">{shop.store_name || 'Unnamed Store'}</div>
+                            <div className="text-slate-500 text-xs">{shop.store_url || '-'}</div>
+                          </div>
+                        </td>
+                        <td className="p-4">
+                          <div className="flex items-center gap-2">
+                            <PhoneCall className="w-4 h-4 text-slate-400" />
+                            <div>
+                              <div className={`text-sm ${shop.phone_number ? "text-white font-mono" : "text-slate-500 italic"}`}>
+                                {shop.phone_number || "Not assigned"}
+                              </div>
+                              <Badge
+                                className={`text-xs ${
+                                  shop.phone_number ? "bg-green-500" : "bg-slate-500"
+                                } text-white`}
+                              >
+                                {shop.phone_number ? "Active" : "Setup Required"}
+                              </Badge>
+                            </div>
+                          </div>
+                        </td>
+                        <td className="p-4">
+                          <Badge className="bg-blue-500 text-white capitalize">
+                            {shop.platform_name}
+                          </Badge>
+                        </td>
+                        <td className="p-4">
+                          <div className="text-sm text-slate-400">
+                            {shop.created_at ? new Date(shop.created_at).toLocaleDateString() : '-'}
+                          </div>
+                        </td>
+                        <td className="p-4">
+                          <Badge className={`${shop.is_active ? "bg-green-500" : "bg-red-500"} text-white`}>
+                            {shop.is_active ? "Active" : "Inactive"}
+                          </Badge>
+                        </td>
+                        <td className="p-4">
+                          <div className="flex items-center gap-2">
+                            <Button
+                              size="sm"
+                              className="bg-cyan-500 hover:bg-cyan-600 text-white"
+                              onClick={() => window.location.href = `/ai-config?shop=${shop.id}`}
+                            >
+                              <Bot className="w-4 h-4 mr-1" />
+                              AI Config
+                            </Button>
+                            <Button
+                              size="sm"
+                              variant="outline"
+                              className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10"
+                              onClick={() => window.location.href = `/phone-numbers?shop=${shop.id}`}
+                            >
+                              <PhoneCall className="w-4 h-4 mr-1" />
+                              Phone
+                            </Button>
+                            <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
+                              <Settings className="w-4 h-4" />
+                            </Button>
+                          </div>
+                        </td>
+                      </tr>
+                    ))}
+                  </tbody>
+                </table>
+              </div>
+            )}
+          </CardContent>
+        </Card>
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              <Zap className="w-6 h-6 text-cyan-500" />
+              <CardTitle className="text-white">Quick Setup Wizard</CardTitle>
+            </div>
+            <p className="text-slate-400">Set up AI and phone numbers for new webshops in minutes</p>
+          </CardHeader>
+          <CardContent className="space-y-4">
+            <div className="space-y-4">
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">1</div>
+                <div>
+                  <h4 className="text-white font-medium">Connect your webshop</h4>
+                  <p className="text-slate-400">Link your Shopify, WooCommerce, or other e-commerce platform</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">2</div>
+                <div>
+                  <h4 className="text-white font-medium">Get a phone number</h4>
+                  <p className="text-slate-400">We'll automatically assign a free local number or upgrade to premium</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">3</div>
+                <div>
+                  <h4 className="text-white font-medium">Configure your AI assistant</h4>
+                  <p className="text-slate-400">Customize voice, language, and behavior for your region and brand</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">4</div>
+                <div>
+                  <h4 className="text-white font-medium">Start receiving calls</h4>
+                  <p className="text-slate-400">Your AI will handle customer inquiries with store-specific knowledge</p>
+                </div>
+              </div>
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+    </div>
+  );
+}

+ 116 - 0
shopcall.ai-main/src/components/KPICards.tsx

@@ -0,0 +1,116 @@
+import { Card } from "@/components/ui/card";
+import { Phone, CheckCircle, Clock, TrendingUp, DollarSign, UserX, Loader2 } from "lucide-react";
+import { useDashboard } from "./context/DashboardContext";
+
+export function KPICards() {
+  const { stats, loading, error } = useDashboard();
+
+  if (loading) {
+    return (
+      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+        {[...Array(6)].map((_, i) => (
+          <Card key={i} className="bg-slate-800 border-slate-700 p-6">
+            <div className="flex items-center justify-center h-32">
+              <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+            </div>
+          </Card>
+        ))}
+      </div>
+    );
+  }
+
+  if (error || !stats) {
+    return (
+      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+        <Card className="bg-slate-800 border-slate-700 p-6 col-span-full">
+          <div className="text-center text-slate-400">
+            {error || 'No data available'}
+          </div>
+        </Card>
+      </div>
+    );
+  }
+
+  const kpiData = [
+    {
+      title: "Total Calls",
+      value: stats.totalCalls.value.toString(),
+      change: stats.totalCalls.change,
+      changeType: stats.totalCalls.changeType,
+      subtitle: "vs yesterday",
+      icon: Phone,
+      iconColor: "text-blue-500",
+    },
+    {
+      title: "Resolved Calls",
+      value: stats.resolvedCalls.value.toString(),
+      change: stats.resolvedCalls.change,
+      changeType: stats.resolvedCalls.changeType,
+      subtitle: "vs yesterday",
+      icon: CheckCircle,
+      iconColor: "text-green-500",
+    },
+    {
+      title: "Avg Call Duration",
+      value: stats.avgDuration.formatted,
+      change: stats.avgDuration.change,
+      changeType: stats.avgDuration.changeType,
+      subtitle: "vs yesterday",
+      icon: Clock,
+      iconColor: "text-orange-500",
+    },
+    {
+      title: "Total Cost",
+      value: stats.totalCost.formatted,
+      change: stats.totalCost.change,
+      changeType: stats.totalCost.changeType,
+      subtitle: "vs yesterday",
+      icon: DollarSign,
+      iconColor: "text-green-500",
+    },
+    {
+      title: "Time Saved",
+      value: stats.timeSaved.formatted,
+      change: stats.timeSaved.change,
+      changeType: stats.timeSaved.changeType,
+      subtitle: "vs yesterday",
+      icon: TrendingUp,
+      iconColor: "text-purple-500",
+    },
+    {
+      title: "Saved on Human Costs",
+      value: stats.humanCostSaved.formatted,
+      change: stats.humanCostSaved.change,
+      changeType: stats.humanCostSaved.changeType,
+      subtitle: "vs yesterday",
+      icon: UserX,
+      iconColor: "text-green-500",
+    },
+  ];
+
+  return (
+    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
+      {kpiData.map((kpi) => (
+        <Card key={kpi.title} className="bg-slate-800 border-slate-700 p-6">
+          <div className="flex items-center justify-between mb-4">
+            <div className="text-slate-400 text-sm font-medium">{kpi.title}</div>
+            <kpi.icon className={`w-5 h-5 ${kpi.iconColor}`} />
+          </div>
+
+          <div className="space-y-2">
+            <div className="text-3xl font-bold text-white">{kpi.value}</div>
+
+            <div className="flex items-center gap-2 text-sm">
+              <span className={`font-medium ${
+                kpi.changeType === 'positive' ? 'text-green-500' : 'text-red-500'
+              }`}>
+                {kpi.change}
+              </span>
+              <span className="text-slate-400">{kpi.subtitle}</span>
+            </div>
+          </div>
+        </Card>
+      ))}
+    </div>
+  );
+}

+ 454 - 0
shopcall.ai-main/src/components/LandingPage.tsx

@@ -0,0 +1,454 @@
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import {
+  Phone,
+  Globe,
+  Zap,
+  Shield,
+  TrendingUp,
+  Users,
+  Star,
+  CheckCircle,
+  BarChart3,
+  Clock,
+  DollarSign,
+  MessageCircle,
+  Headphones,
+  Award
+} from "lucide-react";
+
+const LandingPage = () => {
+  const isAuthenticated = localStorage.getItem('IsAuthenticated');
+  console.log(isAuthenticated);
+  const features = [
+    {
+      icon: Phone,
+      title: "Connect Any Phone Number",
+      description: "Seamlessly integrate with any existing phone system or get new numbers instantly in 100+ countries."
+    },
+    {
+      icon: Globe,
+      title: "Multi-Language Support",
+      description: "Support customers in 50+ languages with native-speaking AI that understands cultural nuances."
+    },
+    {
+      icon: Zap,
+      title: "Lightning Fast Response",
+      description: "AI responds in under 300ms, providing instant customer support that never sleeps."
+    },
+    {
+      icon: Shield,
+      title: "Enterprise Security",
+      description: "Bank-level encryption and compliance with GDPR, HIPAA, and SOC 2 standards."
+    },
+    {
+      icon: BarChart3,
+      title: "Advanced Analytics",
+      description: "Deep insights into call patterns, customer satisfaction, and performance metrics."
+    },
+    {
+      icon: MessageCircle,
+      title: "Natural Conversations",
+      description: "AI that understands context, emotions, and can handle complex customer inquiries naturally."
+    }
+  ];
+
+  const testimonials = [
+    {
+      name: "Sarah Chen",
+      role: "CEO, TechFlow Commerce",
+      avatar: "SC",
+      content: "ShopCall.ai reduced our customer service costs by 78% while improving satisfaction scores. It's like having 100 expert agents available 24/7.",
+      rating: 5,
+      savings: "$45,000/month"
+    },
+    {
+      name: "Marcus Rodriguez",
+      role: "Operations Director, GlobalShop",
+      avatar: "MR",
+      content: "The multi-language support helped us expand to 12 new markets seamlessly. Our international customers love the instant, native-language support.",
+      rating: 5,
+      savings: "$32,000/month"
+    },
+    {
+      name: "Emily Watson",
+      role: "Customer Success Manager, RetailPro",
+      avatar: "EW",
+      content: "Implementation took just 2 hours. Now we handle 5x more calls with the same team size. Our customers don't even realize they're talking to AI.",
+      rating: 5,
+      savings: "$28,000/month"
+    }
+  ];
+
+  const stats = [
+    { value: "78%", label: "Average Cost Reduction" },
+    { value: "99.9%", label: "Uptime Guarantee" },
+    { value: "50+", label: "Languages Supported" },
+    { value: "300ms", label: "Average Response Time" },
+    { value: "94%", label: "Customer Satisfaction" },
+    { value: "24/7", label: "Availability" }
+  ];
+
+  const partners = [
+    {
+      name: "Shopify",
+      logo: "https://cdn.worldvectorlogo.com/logos/shopify.svg",
+      alt: "Shopify logo"
+    },
+    {
+      name: "WooCommerce",
+      logo: "https://cdn.worldvectorlogo.com/logos/woocommerce.svg",
+      alt: "WooCommerce logo"
+    },
+    {
+      name: "Magento",
+      logo: "https://cdn.iconscout.com/icon/free/png-512/free-magento-logo-icon-download-in-svg-png-gif-file-formats--technology-social-media-company-vol-1-pack-logos-icons-3030040.png?f=webp&w=512",
+      alt: "Magento logo"
+    },
+    {
+      name: "BigCommerce",
+      logo: "https://cdn.worldvectorlogo.com/logos/bigcommerce-1.svg",
+      alt: "BigCommerce logo"
+    },
+    {
+      name: "Squarespace",
+      logo: "https://cdn.worldvectorlogo.com/logos/squarespace.svg",
+      alt: "Squarespace logo"
+    },
+    {
+      name: "PrestaShop",
+      logo: "https://cdn.worldvectorlogo.com/logos/prestashop.svg",
+      alt: "PrestaShop logo"
+    },
+    {
+      name: "OpenCart",
+      logo: "https://cdn.worldvectorlogo.com/logos/opencart.svg",
+      alt: "OpenCart logo"
+    }
+  ];
+
+  return (
+    <div className="min-h-screen bg-slate-900 text-white">
+      {/* Header Navigation */}
+      <header className="fixed top-0 w-full bg-slate-900/95 backdrop-blur-sm border-b border-slate-800 z-50">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="flex items-center justify-between h-16">
+            <div className="flex items-center gap-3">
+              <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-8 h-8" />
+              <span className="text-xl font-bold text-white">ShopCall.ai</span>
+            </div>
+            <div className="flex items-center gap-4">
+              {
+                isAuthenticated === "false" || isAuthenticated === null ?
+                <Button variant="ghost" className="text-slate-300 hover:text-white hover:bg-slate-800" asChild>
+                  <a href="/signup">Start Free Trial</a>
+                </Button> : ''
+              }
+              <Button variant="outline" className="border-slate-600 text-black hover:bg-slate-800 hover:text-white bg-white" asChild>
+                <a href={isAuthenticated === "true" ? "/dashboard" : "/login"}>{isAuthenticated === "true" ? "Dashboard" : "Login"}</a>
+              </Button>
+            </div>
+          </div>
+        </div>
+      </header>
+
+      {/* Hero Section */}
+      <section className="relative overflow-hidden pt-16 pb-4 min-h-[60vh]">
+        {/* Gradient Background Fallback */}
+        <div className="absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 z-0"></div>
+
+        {/* Video Background */}
+        <video
+          autoPlay
+          muted
+          loop
+          playsInline
+          className="absolute inset-0 w-full h-full object-cover z-10 opacity-40"
+          onError={(e) => {
+            console.log("Video failed to load:", e);
+            e.currentTarget.style.display = 'none';
+          }}
+          onLoadStart={() => console.log("Video loading started")}
+          onCanPlay={() => console.log("Video can play")}
+        >
+          <source src="https://videocdn.cdnpk.net/videos/de9b177c-4e0b-53aa-9bbc-5d51ad89a4a5/horizontal/previews/watermarked/large.mp4" type="video/mp4" />
+          Your browser does not support the video tag.
+        </video>
+
+        <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 z-20">
+          <div className="text-center">
+            <Badge className="mb-4 bg-slate-700/80 text-slate-300 border-slate-600/50">
+              🚀 Trusted by 1000+ E-commerce Businesses
+            </Badge>
+            <h1 className="text-5xl md:text-7xl font-bold mb-6 bg-gradient-to-r from-white to-slate-300 bg-clip-text text-transparent">
+              AI Phone Support That
+              <span className="text-[#52b3d0] block">Never Sleeps</span>
+            </h1>
+            <p className="text-xl md:text-2xl text-slate-300 mb-8 max-w-4xl mx-auto leading-relaxed">
+              Transform your customer service with AI that handles calls in 50+ languages,
+              reduces costs by 78%, and provides 24/7 support that your customers will love.
+            </p>
+            <div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-8">
+              <Button size="lg" className="bg-[#52b3d0] hover:bg-[#4a9fbc] text-white px-8 py-4 text-lg" asChild>
+                <a href="/signup">
+                  Start Free Trial
+                  <Zap className="ml-2 h-5 w-5" />
+                </a>
+              </Button>
+              <Button variant="outline" size="lg" className="border-slate-400 text-black hover:bg-slate-800 hover:text-white bg-white px-8 py-4 text-lg">
+                Watch Demo
+                <Phone className="ml-2 h-5 w-5" />
+              </Button>
+            </div>
+
+            {/* Stats Grid */}
+            <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6 mt-6">
+              {stats.map((stat, index) => (
+                <div key={index} className="text-center">
+                  <div className="text-2xl md:text-3xl font-bold text-[#52b3d0] mb-1">{stat.value}</div>
+                  <div className="text-xs md:text-sm text-slate-400">{stat.label}</div>
+                </div>
+              ))}
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* Partner Integrations Section */}
+      <section className="py-16 bg-slate-800/30">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-12">
+            <h2 className="text-2xl font-semibold mb-4 text-slate-300">Seamlessly integrates with</h2>
+            <p className="text-slate-400">Connect with your existing e-commerce platform in minutes</p>
+          </div>
+
+          <div className="flex justify-center">
+            <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-8 items-center max-w-5xl">
+              {partners.map((partner, index) => (
+                <div key={index} className="flex flex-col items-center group">
+                  <div className="w-16 h-16 bg-white rounded-xl flex items-center justify-center mb-3 group-hover:bg-white/90 transition-colors border border-slate-600/50 p-2">
+                    <img
+                      src={partner.logo}
+                      alt={partner.alt}
+                      className="w-full h-full object-contain"
+                    />
+                  </div>
+                  <span className="text-sm text-slate-400 text-center">{partner.name}</span>
+                </div>
+              ))}
+            </div>
+          </div>
+
+          <div className="text-center mt-12">
+            <p className="text-slate-400 text-sm">+ 50+ more platforms supported</p>
+          </div>
+        </div>
+      </section>
+
+      {/* Features Section */}
+      <section className="py-24 bg-slate-800/50">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-16">
+            <h2 className="text-4xl font-bold mb-6">Everything You Need for Perfect Customer Service</h2>
+            <p className="text-xl text-slate-300 max-w-3xl mx-auto">
+              Built for e-commerce businesses that want to scale without compromising on customer experience.
+            </p>
+          </div>
+
+          <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
+            {features.map((feature, index) => (
+              <Card key={index} className="bg-slate-700/50 border-slate-600 hover:bg-slate-700/70 transition-colors">
+                <CardHeader>
+                  <div className="w-12 h-12 bg-[#52b3d0]/20 rounded-lg flex items-center justify-center mb-4">
+                    <feature.icon className="h-6 w-6 text-[#52b3d0]" />
+                  </div>
+                  <CardTitle className="text-white">{feature.title}</CardTitle>
+                </CardHeader>
+                <CardContent>
+                  <CardDescription className="text-slate-300">
+                    {feature.description}
+                  </CardDescription>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        </div>
+      </section>
+
+      {/* Savings Section */}
+      <section className="py-24">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-16">
+            <h2 className="text-4xl font-bold mb-6">How Much Are E-commerce Stores Saving?</h2>
+            <p className="text-xl text-slate-300 max-w-3xl mx-auto mb-12">
+              Real results from businesses that switched to ShopCall.ai
+            </p>
+          </div>
+
+          <div className="grid md:grid-cols-3 gap-8 mb-16">
+            <Card className="bg-slate-800 border-slate-700 hover:bg-slate-700/80 transition-colors">
+              <CardHeader className="text-center">
+                <div className="text-4xl font-bold text-green-400 mb-2">$35,000</div>
+                <CardTitle className="text-white text-xl">Average Monthly Savings</CardTitle>
+                <CardDescription className="text-slate-300 text-base">
+                  Across 500+ e-commerce businesses
+                </CardDescription>
+              </CardHeader>
+              <CardContent className="text-center">
+                <div className="flex items-center justify-center gap-2 text-green-400 font-medium">
+                  <TrendingUp className="h-5 w-5" />
+                  <span>78% cost reduction</span>
+                </div>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700 hover:bg-slate-700/80 transition-colors">
+              <CardHeader className="text-center">
+                <div className="text-4xl font-bold text-[#52b3d0] mb-2">5,000+</div>
+                <CardTitle className="text-white text-xl">Calls Handled Daily</CardTitle>
+                <CardDescription className="text-slate-300 text-base">
+                  Per AI agent, 24/7 availability
+                </CardDescription>
+              </CardHeader>
+              <CardContent className="text-center">
+                <div className="flex items-center justify-center gap-2 text-[#52b3d0] font-medium">
+                  <Clock className="h-5 w-5" />
+                  <span>300ms response time</span>
+                </div>
+              </CardContent>
+            </Card>
+
+            <Card className="bg-slate-800 border-slate-700 hover:bg-slate-700/80 transition-colors">
+              <CardHeader className="text-center">
+                <div className="text-4xl font-bold text-purple-400 mb-2">94%</div>
+                <CardTitle className="text-white text-xl">Customer Satisfaction</CardTitle>
+                <CardDescription className="text-slate-300 text-base">
+                  Higher than traditional call centers
+                </CardDescription>
+              </CardHeader>
+              <CardContent className="text-center">
+                <div className="flex items-center justify-center gap-2 text-purple-400 font-medium">
+                  <Award className="h-5 w-5" />
+                  <span>Industry leading</span>
+                </div>
+              </CardContent>
+            </Card>
+          </div>
+        </div>
+      </section>
+
+      {/* Testimonials Section */}
+      <section className="py-24 bg-slate-800/50">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-16">
+            <h2 className="text-4xl font-bold mb-6">What Our Customers Say</h2>
+            <p className="text-xl text-slate-300 max-w-3xl mx-auto">
+              Join thousands of businesses that have transformed their customer service
+            </p>
+          </div>
+
+          <div className="grid md:grid-cols-3 gap-8">
+            {testimonials.map((testimonial, index) => (
+              <Card key={index} className="bg-slate-700/50 border-slate-600">
+                <CardHeader>
+                  <div className="flex items-center gap-4 mb-4">
+                    <Avatar>
+                      <AvatarFallback className="bg-[#52b3d0] text-white">
+                        {testimonial.avatar}
+                      </AvatarFallback>
+                    </Avatar>
+                    <div>
+                      <div className="font-semibold text-white">{testimonial.name}</div>
+                      <div className="text-sm text-slate-400">{testimonial.role}</div>
+                    </div>
+                  </div>
+                  <div className="flex items-center gap-1 mb-2">
+                    {[...Array(testimonial.rating)].map((_, i) => (
+                      <Star key={i} className="h-4 w-4 fill-yellow-400 text-yellow-400" />
+                    ))}
+                  </div>
+                  <Badge className="bg-green-600/20 text-green-300 border-green-500/30">
+                    Saves {testimonial.savings}
+                  </Badge>
+                </CardHeader>
+                <CardContent>
+                  <p className="text-slate-300 italic">"{testimonial.content}"</p>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        </div>
+      </section>
+
+      {/* CTA Section */}
+      <section className="py-24">
+        <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
+          <h2 className="text-4xl font-bold mb-6">Ready to Transform Your Customer Service?</h2>
+          <p className="text-xl text-slate-300 mb-12">
+            Join 1000+ businesses saving an average of $35,000/month with ShopCall.ai
+          </p>
+
+          <div className="bg-slate-800/50 rounded-2xl p-8 border border-slate-700">
+            <div className="grid md:grid-cols-3 gap-6 mb-8">
+              <div className="flex items-center gap-3">
+                <CheckCircle className="h-5 w-5 text-green-400" />
+                <span className="text-slate-300">Free 14-day trial</span>
+              </div>
+              <div className="flex items-center gap-3">
+                <CheckCircle className="h-5 w-5 text-green-400" />
+                <span className="text-slate-300">Setup in under 2 hours</span>
+              </div>
+              <div className="flex items-center gap-3">
+                <CheckCircle className="h-5 w-5 text-green-400" />
+                <span className="text-slate-300">No long-term contracts</span>
+              </div>
+            </div>
+
+            <div className="flex flex-col sm:flex-row gap-4 justify-center">
+              <Button size="lg" className="bg-[#52b3d0] hover:bg-[#4a9fbc] text-white px-12 py-4 text-lg" asChild>
+                <a href="/signup">
+                  Start Free Trial
+                  <Zap className="ml-2 h-5 w-5" />
+                </a>
+              </Button>
+              <Button variant="outline" size="lg" className="border-slate-400 text-black hover:bg-slate-800 hover:text-white bg-white px-12 py-4 text-lg">
+                Schedule Demo
+                <Headphones className="ml-2 h-5 w-5" />
+              </Button>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* Footer */}
+      <footer className="bg-slate-900 border-t border-slate-800 py-12">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center">
+            <div className="flex items-center justify-center gap-3 mb-4">
+              <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-8 h-8" />
+              <h3 className="text-2xl font-bold text-white">ShopCall.ai</h3>
+            </div>
+            <p className="text-slate-400 mb-6">
+              The future of customer service is here. Available in 50+ languages, 100+ countries.
+            </p>
+            <div className="flex justify-center items-center gap-4 text-sm text-slate-500">
+              <span>© 2024 ShopCall.ai. All rights reserved.</span>
+              <span>•</span>
+              <a href="/about" className="hover:text-slate-300">About Us</a>
+              <span>•</span>
+              <a href="/privacy" className="hover:text-slate-300">Privacy Policy</a>
+              <span>•</span>
+              <a href="/terms" className="hover:text-slate-300">Terms & Conditions</a>
+              <span>•</span>
+              <a href="/contact" className="hover:text-slate-300">Contact Us</a>
+            </div>
+          </div>
+        </div>
+      </footer>
+    </div>
+  );
+};
+
+export default LandingPage;

+ 338 - 0
shopcall.ai-main/src/components/OnboardingContent.tsx

@@ -0,0 +1,338 @@
+
+import { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Badge } from "@/components/ui/badge";
+import { 
+  Store, 
+  PhoneCall, 
+  CreditCard, 
+  Check, 
+  ArrowRight, 
+  ArrowLeft,
+  Zap,
+  Crown,
+  Star,
+  Shield,
+  Globe
+} from "lucide-react";
+
+const phoneNumbers = [
+  { number: "+1 (555) 123-4567", location: "New York, US", type: "Local" },
+  { number: "+1 (555) 987-6543", location: "Los Angeles, US", type: "Local" },
+  { number: "+1 (800) 555-0123", location: "Toll-free US", type: "Toll-free" },
+  { number: "+44 20 7946 0958", location: "London, UK", type: "Local" },
+  { number: "+1 (416) 555-7890", location: "Toronto, CA", type: "Local" },
+];
+
+const packages = [
+  {
+    id: "free-trial",
+    name: "Free Trial",
+    price: "$0",
+    period: "14 days",
+    description: "Perfect for testing our AI assistant",
+    features: [
+      "50 minutes of calls",
+      "Basic AI responses",
+      "Email support",
+      "Dashboard access"
+    ],
+    badge: "Most Popular",
+    badgeColor: "bg-green-500"
+  },
+  {
+    id: "starter",
+    name: "Starter",
+    price: "$29",
+    period: "per month",
+    description: "Great for small businesses",
+    features: [
+      "500 minutes of calls",
+      "Advanced AI responses",
+      "Priority support",
+      "Analytics dashboard",
+      "Custom greetings"
+    ],
+    badge: null,
+    badgeColor: ""
+  },
+  {
+    id: "professional",
+    name: "Professional",
+    price: "$99",
+    period: "per month",
+    description: "For growing businesses",
+    features: [
+      "2,000 minutes of calls",
+      "Premium AI voices",
+      "24/7 support",
+      "Advanced analytics",
+      "Custom integrations",
+      "Multiple phone numbers"
+    ],
+    badge: "Best Value",
+    badgeColor: "bg-purple-500"
+  }
+];
+
+export function OnboardingContent() {
+  const [currentStep, setCurrentStep] = useState(1);
+  const [shopifyUrl, setShopifyUrl] = useState("");
+  const [selectedPhone, setSelectedPhone] = useState("");
+  const [selectedPackage, setSelectedPackage] = useState("free-trial");
+
+  const nextStep = () => {
+    if (currentStep < 3) {
+      setCurrentStep(currentStep + 1);
+    }
+  };
+
+  const prevStep = () => {
+    if (currentStep > 1) {
+      setCurrentStep(currentStep - 1);
+    }
+  };
+
+  const handleFinish = () => {
+    console.log("Onboarding completed:", {
+      shopifyUrl,
+      selectedPhone,
+      selectedPackage
+    });
+    // Redirect to dashboard or show success
+    window.location.href = "/";
+  };
+
+  return (
+    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-8">
+      <div className="max-w-4xl mx-auto">
+        {/* Header */}
+        <div className="text-center mb-12">
+          <div className="flex items-center justify-center gap-2 mb-6">
+            <div className="w-12 h-12 bg-cyan-500 rounded-xl flex items-center justify-center">
+              <PhoneCall className="w-6 h-6 text-white" />
+            </div>
+            <h1 className="text-3xl font-bold text-white">AI Phone Assistant</h1>
+          </div>
+          <p className="text-slate-400 text-lg">Get your AI phone assistant up and running in 3 simple steps</p>
+        </div>
+
+        {/* Progress Steps */}
+        <div className="flex items-center justify-center mb-12">
+          <div className="flex items-center gap-4">
+            {[1, 2, 3].map((step) => (
+              <div key={step} className="flex items-center">
+                <div className={`w-12 h-12 rounded-full flex items-center justify-center font-semibold ${
+                  currentStep >= step 
+                    ? "bg-cyan-500 text-white" 
+                    : "bg-slate-700 text-slate-400"
+                }`}>
+                  {currentStep > step ? <Check className="w-5 h-5" /> : step}
+                </div>
+                {step < 3 && (
+                  <div className={`w-16 h-1 mx-2 ${
+                    currentStep > step ? "bg-cyan-500" : "bg-slate-700"
+                  }`} />
+                )}
+              </div>
+            ))}
+          </div>
+        </div>
+
+        {/* Step Content */}
+        <Card className="bg-slate-800 border-slate-700 mb-8">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              {currentStep === 1 && <Store className="w-6 h-6 text-cyan-500" />}
+              {currentStep === 2 && <PhoneCall className="w-6 h-6 text-cyan-500" />}
+              {currentStep === 3 && <CreditCard className="w-6 h-6 text-cyan-500" />}
+              <CardTitle className="text-white">
+                {currentStep === 1 && "Connect Your Shopify Store"}
+                {currentStep === 2 && "Choose Your Phone Number"}
+                {currentStep === 3 && "Select Your Package"}
+              </CardTitle>
+            </div>
+            <p className="text-slate-400">
+              {currentStep === 1 && "Enter your Shopify store URL to get started"}
+              {currentStep === 2 && "Pick a phone number for your customers to reach you"}
+              {currentStep === 3 && "Choose the perfect plan for your business needs"}
+            </p>
+          </CardHeader>
+          <CardContent className="space-y-6">
+            {/* Step 1: Connect Shopify */}
+            {currentStep === 1 && (
+              <div className="space-y-6">
+                <div className="space-y-2">
+                  <Label htmlFor="shopify-url" className="text-white">Shopify Store URL</Label>
+                  <Input
+                    id="shopify-url"
+                    placeholder="your-store.myshopify.com"
+                    value={shopifyUrl}
+                    onChange={(e) => setShopifyUrl(e.target.value)}
+                    className="bg-slate-700 border-slate-600 text-white"
+                  />
+                  <p className="text-slate-400 text-sm">We'll connect to your store to understand your products and customers</p>
+                </div>
+
+                <div className="bg-slate-700/50 p-6 rounded-lg">
+                  <div className="flex items-start gap-3">
+                    <Shield className="w-6 h-6 text-cyan-500 mt-1" />
+                    <div>
+                      <h4 className="text-white font-medium mb-2">Secure Connection</h4>
+                      <ul className="text-slate-300 text-sm space-y-1">
+                        <li>• We only read public product information</li>
+                        <li>• No access to customer payment data</li>
+                        <li>• Industry-standard encryption</li>
+                        <li>• You can disconnect anytime</li>
+                      </ul>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            )}
+
+            {/* Step 2: Phone Number Selection */}
+            {currentStep === 2 && (
+              <div className="space-y-6">
+                <RadioGroup value={selectedPhone} onValueChange={setSelectedPhone}>
+                  <div className="grid gap-4">
+                    {phoneNumbers.map((phone) => (
+                      <div key={phone.number} className="flex items-center space-x-3">
+                        <RadioGroupItem value={phone.number} id={phone.number} />
+                        <Label 
+                          htmlFor={phone.number} 
+                          className="flex-1 cursor-pointer"
+                        >
+                          <Card className="bg-slate-700 border-slate-600 hover:border-cyan-500 transition-colors">
+                            <CardContent className="p-4">
+                              <div className="flex items-center justify-between">
+                                <div>
+                                  <div className="text-white font-mono text-lg">{phone.number}</div>
+                                  <div className="text-slate-400 text-sm flex items-center gap-2">
+                                    <Globe className="w-4 h-4" />
+                                    {phone.location}
+                                  </div>
+                                </div>
+                                <Badge 
+                                  className={phone.type === "Toll-free" ? "bg-purple-500" : "bg-green-500"}
+                                >
+                                  {phone.type}
+                                </Badge>
+                              </div>
+                            </CardContent>
+                          </Card>
+                        </Label>
+                      </div>
+                    ))}
+                  </div>
+                </RadioGroup>
+
+                <div className="bg-slate-700/50 p-4 rounded-lg">
+                  <p className="text-slate-300 text-sm">
+                    <strong>Note:</strong> Your selected phone number will be instantly activated and ready to receive calls. 
+                    You can always add more numbers later from your dashboard.
+                  </p>
+                </div>
+              </div>
+            )}
+
+            {/* Step 3: Package Selection */}
+            {currentStep === 3 && (
+              <div className="space-y-6">
+                <RadioGroup value={selectedPackage} onValueChange={setSelectedPackage}>
+                  <div className="grid gap-6 md:grid-cols-3">
+                    {packages.map((pkg) => (
+                      <div key={pkg.id} className="flex items-start space-x-3">
+                        <RadioGroupItem value={pkg.id} id={pkg.id} className="mt-6" />
+                        <Label htmlFor={pkg.id} className="flex-1 cursor-pointer">
+                          <Card className={`bg-slate-700 border-slate-600 hover:border-cyan-500 transition-colors relative ${
+                            selectedPackage === pkg.id ? "ring-2 ring-cyan-500" : ""
+                          }`}>
+                            {pkg.badge && (
+                              <div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
+                                <Badge className={`${pkg.badgeColor} text-white`}>
+                                  {pkg.badge}
+                                </Badge>
+                              </div>
+                            )}
+                            <CardHeader className="text-center">
+                              <div className="flex items-center justify-center mb-2">
+                                {pkg.id === "free-trial" && <Zap className="w-6 h-6 text-green-500" />}
+                                {pkg.id === "starter" && <Star className="w-6 h-6 text-blue-500" />}
+                                {pkg.id === "professional" && <Crown className="w-6 h-6 text-purple-500" />}
+                              </div>
+                              <CardTitle className="text-white">{pkg.name}</CardTitle>
+                              <div className="text-3xl font-bold text-cyan-500">
+                                {pkg.price}
+                                <span className="text-sm text-slate-400">/{pkg.period}</span>
+                              </div>
+                              <p className="text-slate-400 text-sm">{pkg.description}</p>
+                            </CardHeader>
+                            <CardContent>
+                              <ul className="space-y-2">
+                                {pkg.features.map((feature, index) => (
+                                  <li key={index} className="flex items-center gap-2 text-slate-300 text-sm">
+                                    <Check className="w-4 h-4 text-green-500" />
+                                    {feature}
+                                  </li>
+                                ))}
+                              </ul>
+                            </CardContent>
+                          </Card>
+                        </Label>
+                      </div>
+                    ))}
+                  </div>
+                </RadioGroup>
+              </div>
+            )}
+          </CardContent>
+        </Card>
+
+        {/* Navigation Buttons */}
+        <div className="flex items-center justify-between">
+          <Button
+            variant="outline"
+            onClick={prevStep}
+            disabled={currentStep === 1}
+            className="text-slate-400 border-slate-600 hover:bg-slate-700"
+          >
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            Back
+          </Button>
+
+          {currentStep < 3 ? (
+            <Button
+              onClick={nextStep}
+              disabled={
+                (currentStep === 1 && !shopifyUrl) ||
+                (currentStep === 2 && !selectedPhone)
+              }
+              className="bg-cyan-500 hover:bg-cyan-600 text-white"
+            >
+              Continue
+              <ArrowRight className="w-4 h-4 ml-2" />
+            </Button>
+          ) : (
+            <Button
+              onClick={handleFinish}
+              className="bg-green-500 hover:bg-green-600 text-white"
+            >
+              Start Free Trial
+              <Check className="w-4 h-4 ml-2" />
+            </Button>
+          )}
+        </div>
+
+        {/* Footer */}
+        <div className="text-center mt-12 text-slate-500 text-sm">
+          <p>Need help? Contact our support team or check our documentation</p>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 379 - 0
shopcall.ai-main/src/components/PhoneNumbersContent.tsx

@@ -0,0 +1,379 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { PhoneCall, Plus, Settings, AlertTriangle, CheckCircle, Clock, Link, Shield } from "lucide-react";
+
+const connectedShops = [
+  {
+    name: "Fashion Forward",
+    platform: "Shopify",
+    phoneNumber: "+1 (555) 123-4567",
+    status: "Active",
+    type: "Free",
+    country: "United States",
+    statusColor: "bg-green-500"
+  },
+  {
+    name: "Tech Gadgets Pro",
+    platform: "WooCommerce", 
+    phoneNumber: "+1 (555) 987-6543",
+    status: "Active",
+    type: "Premium",
+    country: "United States",
+    statusColor: "bg-green-500"
+  },
+  {
+    name: "Home & Garden",
+    platform: "Shopify",
+    phoneNumber: "+44 20 7946 0958",
+    status: "Pending KYC",
+    type: "Premium",
+    country: "United Kingdom",
+    statusColor: "bg-yellow-500"
+  },
+  {
+    name: "Sports Equipment",
+    platform: "WooCommerce",
+    phoneNumber: "+1 (555) 456-7890",
+    status: "Active", 
+    type: "Free",
+    country: "United States",
+    statusColor: "bg-green-500"
+  },
+  {
+    name: "Beauty Essentials",
+    platform: "Shopify",
+    phoneNumber: "Not assigned",
+    status: "Setup Required",
+    type: "-",
+    country: "-",
+    statusColor: "bg-red-500"
+  }
+];
+
+const availableCountries = [
+  {
+    name: "United States",
+    flag: "🇺🇸",
+    pricing: {
+      setup: "$0",
+      monthly: "$5/month"
+    },
+    kycRequired: false,
+    availability: "Instant"
+  },
+  {
+    name: "United Kingdom", 
+    flag: "🇬🇧",
+    pricing: {
+      setup: "$0",
+      monthly: "$8/month"
+    },
+    kycRequired: true,
+    availability: "2-3 business days"
+  },
+  {
+    name: "Canada",
+    flag: "🇨🇦",
+    pricing: {
+      setup: "$0",
+      monthly: "$6/month"
+    },
+    kycRequired: false,
+    availability: "Instant"
+  },
+  {
+    name: "Australia",
+    flag: "🇦🇺",
+    pricing: {
+      setup: "$0",
+      monthly: "$10/month"
+    },
+    kycRequired: true,
+    availability: "1-2 business days"
+  }
+];
+
+const carriers = [
+  {
+    name: "Twilio",
+    logo: "📞",
+    description: "Global communications platform",
+    features: ["Global coverage", "99.99% uptime", "Advanced analytics"],
+    setupFields: ["Account SID", "Auth Token", "Phone Number SID"]
+  },
+  {
+    name: "Telnyx",
+    logo: "📡",
+    description: "Modern telecom infrastructure",
+    features: ["Low latency", "Real-time APIs", "Global network"],
+    setupFields: ["API Key", "Phone Number ID", "Connection ID"]
+  },
+  {
+    name: "Vonage",
+    logo: "🌐",
+    description: "Cloud communications APIs",
+    features: ["Voice & SMS", "Global reach", "Enterprise grade"],
+    setupFields: ["API Key", "API Secret", "Application ID"]
+  },
+  {
+    name: "Zadarma",
+    logo: "☎️",
+    description: "Business telephony solutions",
+    features: ["Cost effective", "Easy setup", "Multi-country"],
+    setupFields: ["User Key", "Secret Key", "Phone Number"]
+  }
+];
+
+export function PhoneNumbersContent() {
+  return (
+    <div className="flex-1 space-y-6 p-8 bg-slate-900">
+      <div className="flex items-center justify-between">
+        <div>
+          <h2 className="text-3xl font-bold tracking-tight text-white">Phone Numbers</h2>
+          <p className="text-slate-400">Manage phone numbers for your webshops</p>
+        </div>
+        <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
+          <Plus className="w-4 h-4 mr-2" />
+          Add Phone Number
+        </Button>
+      </div>
+
+      <div className="space-y-6">
+        <div className="flex items-center justify-between">
+          <h3 className="text-xl font-semibold text-white">Webshop Phone Numbers</h3>
+          <p className="text-slate-400">5 webshops • 4 active numbers</p>
+        </div>
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardContent className="p-0">
+            <div className="overflow-x-auto">
+              <table className="w-full">
+                <thead className="border-b border-slate-700">
+                  <tr className="text-left">
+                    <th className="p-4 text-slate-300 font-medium">Webshop</th>
+                    <th className="p-4 text-slate-300 font-medium">Phone Number</th>
+                    <th className="p-4 text-slate-300 font-medium">Country</th>
+                    <th className="p-4 text-slate-300 font-medium">Type</th>
+                    <th className="p-4 text-slate-300 font-medium">Status</th>
+                    <th className="p-4 text-slate-300 font-medium">Actions</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {connectedShops.map((shop, index) => (
+                    <tr key={index} className="border-b border-slate-700/50 hover:bg-slate-700/30">
+                      <td className="p-4">
+                        <div>
+                          <div className="text-white font-medium">{shop.name}</div>
+                          <div className="text-slate-400 text-sm">{shop.platform}</div>
+                        </div>
+                      </td>
+                      <td className="p-4">
+                        <div className="flex items-center gap-2">
+                          <PhoneCall className="w-4 h-4 text-slate-400" />
+                          <span className={`text-sm ${shop.phoneNumber === "Not assigned" ? "text-slate-500 italic" : "text-white font-mono"}`}>
+                            {shop.phoneNumber}
+                          </span>
+                        </div>
+                      </td>
+                      <td className="p-4 text-slate-300">{shop.country}</td>
+                      <td className="p-4">
+                        <Badge variant={shop.type === "Free" ? "default" : "secondary"} className={shop.type === "Free" ? "bg-green-600 text-white" : "bg-purple-600 text-white"}>
+                          {shop.type}
+                        </Badge>
+                      </td>
+                      <td className="p-4">
+                        <Badge className={`${shop.statusColor} text-white`}>
+                          {shop.status}
+                        </Badge>
+                      </td>
+                      <td className="p-4">
+                        <div className="flex items-center gap-2">
+                          {shop.phoneNumber === "Not assigned" ? (
+                            <Button size="sm" className="bg-cyan-500 hover:bg-cyan-600 text-white">
+                              <Plus className="w-4 h-4 mr-1" />
+                              Assign
+                            </Button>
+                          ) : (
+                            <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
+                              <Settings className="w-4 h-4" />
+                            </Button>
+                          )}
+                        </div>
+                      </td>
+                    </tr>
+                  ))}
+                </tbody>
+              </table>
+            </div>
+          </CardContent>
+        </Card>
+
+        <div className="space-y-4">
+          <h3 className="text-xl font-semibold text-white">Bring Your Own Carrier</h3>
+          <p className="text-slate-400">Connect your existing carrier account to use your own phone numbers</p>
+          
+          <div className="grid gap-6 md:grid-cols-2">
+            {carriers.map((carrier, index) => (
+              <Card key={index} className="bg-slate-800 border-slate-700">
+                <CardHeader>
+                  <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-3">
+                      <span className="text-2xl">{carrier.logo}</span>
+                      <div>
+                        <CardTitle className="text-white">{carrier.name}</CardTitle>
+                        <p className="text-slate-400 text-sm">{carrier.description}</p>
+                      </div>
+                    </div>
+                    <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
+                      <Link className="w-4 h-4 mr-2" />
+                      Connect
+                    </Button>
+                  </div>
+                </CardHeader>
+                <CardContent>
+                  <div className="space-y-3">
+                    <div className="space-y-2">
+                      <h5 className="text-sm font-medium text-white">Features</h5>
+                      <ul className="space-y-1">
+                        {carrier.features.map((feature, idx) => (
+                          <li key={idx} className="flex items-center gap-2">
+                            <CheckCircle className="w-3 h-3 text-green-500" />
+                            <span className="text-xs text-slate-300">{feature}</span>
+                          </li>
+                        ))}
+                      </ul>
+                    </div>
+                    <div className="space-y-2">
+                      <h5 className="text-sm font-medium text-white">Required Fields</h5>
+                      <div className="text-xs text-slate-400">
+                        {carrier.setupFields.join(", ")}
+                      </div>
+                    </div>
+                  </div>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        </div>
+
+        <div className="space-y-4">
+          <h3 className="text-xl font-semibold text-white">Available Countries</h3>
+          <div className="grid gap-6 md:grid-cols-2">
+            {availableCountries.map((country, index) => (
+              <Card key={index} className="bg-slate-800 border-slate-700">
+                <CardHeader>
+                  <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-3">
+                      <span className="text-2xl">{country.flag}</span>
+                      <div>
+                        <CardTitle className="text-white">{country.name}</CardTitle>
+                        <p className="text-slate-400 text-sm">Setup: {country.pricing.setup} • Monthly: {country.pricing.monthly}</p>
+                      </div>
+                    </div>
+                    <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
+                      Get Number
+                    </Button>
+                  </div>
+                </CardHeader>
+                <CardContent>
+                  <div className="space-y-3">
+                    <div className="flex items-center gap-2">
+                      {country.kycRequired ? (
+                        <AlertTriangle className="w-4 h-4 text-yellow-500" />
+                      ) : (
+                        <CheckCircle className="w-4 h-4 text-green-500" />
+                      )}
+                      <span className="text-sm text-slate-300">
+                        {country.kycRequired ? "KYC verification required" : "No verification needed"}
+                      </span>
+                    </div>
+                    <div className="flex items-center gap-2">
+                      <Clock className="w-4 h-4 text-blue-500" />
+                      <span className="text-sm text-slate-300">
+                        Activation: {country.availability}
+                      </span>
+                    </div>
+                  </div>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        </div>
+
+        <Card className="bg-slate-800 border-slate-700">
+          <CardHeader>
+            <div className="flex items-center gap-3">
+              <PhoneCall className="w-6 h-6 text-cyan-500" />
+              <CardTitle className="text-white">Phone Number Setup Guide</CardTitle>
+            </div>
+          </CardHeader>
+          <CardContent className="space-y-4">
+            <div className="space-y-4">
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">1</div>
+                <div>
+                  <h4 className="text-white font-medium">Choose your setup method</h4>
+                  <p className="text-slate-400">Use our managed numbers or bring your own carrier (Twilio, Telnyx, etc.)</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">2</div>
+                <div>
+                  <h4 className="text-white font-medium">Complete verification (if required)</h4>
+                  <p className="text-slate-400">Some countries require KYC verification. Upload required documents for approval.</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">3</div>
+                <div>
+                  <h4 className="text-white font-medium">Assign to webshop</h4>
+                  <p className="text-slate-400">Connect the phone number to your webshop. Each webshop needs its own dedicated number.</p>
+                </div>
+              </div>
+              <div className="flex gap-4">
+                <div className="flex-shrink-0 w-8 h-8 bg-cyan-500 text-white rounded-full flex items-center justify-center font-semibold">4</div>
+                <div>
+                  <h4 className="text-white font-medium">Start receiving calls</h4>
+                  <p className="text-slate-400">Your AI assistant will automatically handle customer calls on the assigned number.</p>
+                </div>
+              </div>
+            </div>
+            <div className="bg-slate-700/50 p-4 rounded-lg">
+              <div className="flex items-start gap-3">
+                <Shield className="w-5 h-5 text-blue-500 mt-0.5" />
+                <div>
+                  <h5 className="text-blue-500 font-medium">Carrier Integration Benefits</h5>
+                  <ul className="text-slate-300 text-sm mt-2 space-y-1">
+                    <li>• Use your existing phone numbers and carrier relationship</li>
+                    <li>• Keep your current pricing and billing setup</li>
+                    <li>• Leverage carrier-specific features and coverage</li>
+                    <li>• Maintain compliance with your carrier's terms</li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+            <div className="bg-slate-700/50 p-4 rounded-lg">
+              <div className="flex items-start gap-3">
+                <AlertTriangle className="w-5 h-5 text-yellow-500 mt-0.5" />
+                <div>
+                  <h5 className="text-yellow-500 font-medium">Important Notes</h5>
+                  <ul className="text-slate-300 text-sm mt-2 space-y-1">
+                    <li>• Each webshop requires a separate phone number</li>
+                    <li>• Free numbers are auto-assigned during onboarding</li>
+                    <li>• Premium numbers offer better call quality and additional features</li>
+                    <li>• KYC verification may take 1-3 business days</li>
+                    <li>• Carrier integrations require valid API credentials</li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+    </div>
+  );
+}

+ 87 - 0
shopcall.ai-main/src/components/PrivateRoute.tsx

@@ -0,0 +1,87 @@
+import { Navigate, Outlet } from 'react-router-dom';
+import { useAuth } from './context/AuthContext';
+import { useEffect } from 'react';
+import { Loader2, Shield, Zap } from "lucide-react";
+import { Card, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+
+const PrivateRoute = () => {
+  const { isAuthenticated, check_auth, loading, authStep } = useAuth();
+
+  useEffect(() => {
+    check_auth(null);
+  }, []);
+
+  if (loading) {
+    return (
+        <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center px-4">
+            <div className="w-full max-w-md">
+                {/* Logo */}
+                <div className="text-center mb-8">
+                    <div className="flex items-center justify-center gap-3 mb-6">
+                        <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-12 h-12" />
+                        <span className="text-3xl font-bold text-white">ShopCall.ai</span>
+                    </div>
+                    <Badge className="bg-[#52b3d0]/20 text-[#52b3d0] border-[#52b3d0]/30">
+                        <Zap className="w-3 h-3 mr-1" />
+                        AI-Powered Customer Service
+                    </Badge>
+                </div>
+
+                {/* Loading Card */}
+                <Card className="bg-slate-800/50 border-slate-700 backdrop-blur-sm">
+                    <CardContent className="p-8">
+                        <div className="text-center space-y-6">
+                            {/* Animated Icon */}
+                            <div className="relative mx-auto w-16 h-16">
+                                <div className="absolute inset-0 rounded-full border-4 border-[#52b3d0]/20"></div>
+                                <div className="absolute inset-0 rounded-full border-4 border-transparent border-t-[#52b3d0] animate-spin"></div>
+                                <div className="absolute inset-2 rounded-full bg-[#52b3d0]/10 flex items-center justify-center">
+                                    <Shield className="w-6 h-6 text-[#52b3d0]" />
+                                </div>
+                            </div>
+
+                            {/* Status Text */}
+                            <div className="space-y-3">
+                                <h2 className="text-xl font-semibold text-white">Securing Your Session</h2>
+                                <p className="text-slate-400 text-sm leading-relaxed">
+                                    {authStep}...
+                                </p>
+                            </div>
+
+                            {/* Progress Dots */}
+                            <div className="flex items-center justify-center space-x-2">
+                                <div className="w-2 h-2 bg-[#52b3d0] rounded-full animate-pulse"></div>
+                                <div className="w-2 h-2 bg-[#52b3d0]/60 rounded-full animate-pulse" style={{ animationDelay: '0.2s' }}></div>
+                                <div className="w-2 h-2 bg-[#52b3d0]/30 rounded-full animate-pulse" style={{ animationDelay: '0.4s' }}></div>
+                            </div>
+
+                            {/* Security Features */}
+                            <div className="pt-4 space-y-2">
+                                <div className="flex items-center justify-center gap-2 text-xs text-slate-500">
+                                    <Shield className="w-3 h-3" />
+                                    <span>Bank-level encryption</span>
+                                </div>
+                                <div className="flex items-center justify-center gap-2 text-xs text-slate-500">
+                                    <Zap className="w-3 h-3" />
+                                    <span>Lightning-fast authentication</span>
+                                </div>
+                            </div>
+                        </div>
+                    </CardContent>
+                </Card>
+
+                {/* Footer */}
+                <div className="text-center mt-6">
+                    <p className="text-slate-500 text-xs">
+                        Powered by advanced AI technology
+                    </p>
+                </div>
+            </div>
+        </div>
+    );
+}
+  return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
+};
+
+export default PrivateRoute;

+ 176 - 0
shopcall.ai-main/src/components/RecentCallsTable.tsx

@@ -0,0 +1,176 @@
+import { Card } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Play, MoreHorizontal, Loader2 } from "lucide-react";
+import { useState, useEffect } from "react";
+import { CallDetailsModal } from "./CallDetailsModal";
+import { API_URL } from "@/lib/config";
+
+interface CallLog {
+  time: string;
+  customer: string;
+  intent: string;
+  outcome: string;
+  duration: string;
+  sentiment: string;
+  cost: string;
+  outcomeColor: string;
+  sentimentColor: string;
+}
+
+const getSentimentEmoji = (sentiment: string) => {
+  switch (sentiment) {
+    case "Positive":
+      return "😊";
+    case "Negative":
+      return "😠";
+    case "Neutral":
+      return "😐";
+    default:
+      return "😐";
+  }
+};
+
+export function RecentCallsTable() {
+  const [recentCalls, setRecentCalls] = useState<CallLog[]>([]);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+  const [selectedCall, setSelectedCall] = useState(null);
+  const [isModalOpen, setIsModalOpen] = useState(false);
+
+  useEffect(() => {
+    const fetchRecentCalls = async () => {
+      try {
+        const sessionData = localStorage.getItem('session_data');
+        if (!sessionData) {
+          throw new Error('No session data found');
+        }
+
+        const session = JSON.parse(sessionData);
+        const response = await fetch(`${API_URL}/api/call-logs`, {
+          headers: {
+            'Authorization': `Bearer ${session.access_token}`,
+            'Content-Type': 'application/json'
+          }
+        });
+
+        if (!response.ok) {
+          throw new Error('Failed to fetch call logs');
+        }
+
+        const data = await response.json();
+        if (data.success && data.call_logs) {
+          // Take only the 5 most recent calls
+          setRecentCalls(data.call_logs.slice(0, 5));
+        }
+      } catch (err) {
+        console.error('Error fetching call logs:', err);
+        setError(err instanceof Error ? err.message : 'Failed to load call logs');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchRecentCalls();
+  }, []);
+
+  const handleDetailsClick = (call) => {
+    setSelectedCall(call);
+    setIsModalOpen(true);
+  };
+
+  const handleCloseModal = () => {
+    setIsModalOpen(false);
+    setSelectedCall(null);
+  };
+
+  return (
+    <>
+      <Card className="bg-slate-800 border-slate-700">
+        <div className="p-6">
+          <div className="flex items-center justify-between mb-6">
+            <h3 className="text-lg font-semibold text-white">Recent Calls</h3>
+            <span className="text-sm text-slate-400">Last 24 hours</span>
+          </div>
+
+          {loading ? (
+            <div className="flex items-center justify-center py-12">
+              <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+            </div>
+          ) : error ? (
+            <div className="text-center text-slate-400 py-12">
+              {error}
+            </div>
+          ) : recentCalls.length === 0 ? (
+            <div className="text-center text-slate-400 py-12">
+              No recent calls to display
+            </div>
+          ) : (
+            <div className="overflow-x-auto">
+              <table className="w-full">
+                <thead>
+                  <tr className="border-b border-slate-700">
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Time</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Customer</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Intent</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Outcome</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Duration</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Sentiment</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Cost</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Actions</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {recentCalls.map((call, index) => (
+                    <tr key={index} className="border-b border-slate-700/50 hover:bg-slate-700/30">
+                      <td className="py-3 px-4 text-sm text-slate-300">{call.time}</td>
+                      <td className="py-3 px-4 text-sm text-white">{call.customer}</td>
+                      <td className="py-3 px-4 text-sm text-slate-300">{call.intent}</td>
+                      <td className="py-3 px-4">
+                        <span className={`text-sm font-medium ${call.outcomeColor}`}>
+                          {call.outcome}
+                        </span>
+                      </td>
+                      <td className="py-3 px-4 text-sm text-slate-300">{call.duration}</td>
+                      <td className="py-3 px-4">
+                        <div className="flex items-center gap-2">
+                          <span className="text-sm">{getSentimentEmoji(call.sentiment)}</span>
+                          <span className={`text-sm ${call.sentimentColor}`}>
+                            {call.sentiment}
+                          </span>
+                        </div>
+                      </td>
+                      <td className="py-3 px-4 text-sm text-slate-300">{call.cost}</td>
+                      <td className="py-3 px-4">
+                        <div className="flex items-center gap-2">
+                          <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
+                            <Play className="w-4 h-4" />
+                          </Button>
+                          <Button
+                            variant="ghost"
+                            size="sm"
+                            className="text-slate-400 hover:text-white"
+                            onClick={() => handleDetailsClick(call)}
+                          >
+                            <MoreHorizontal className="w-4 h-4" />
+                          </Button>
+                        </div>
+                      </td>
+                    </tr>
+                  ))}
+                </tbody>
+              </table>
+            </div>
+          )}
+        </div>
+      </Card>
+
+      {selectedCall && (
+        <CallDetailsModal
+          isOpen={isModalOpen}
+          onClose={handleCloseModal}
+          call={selectedCall}
+        />
+      )}
+    </>
+  );
+}

+ 82 - 0
shopcall.ai-main/src/components/ResolutionRateChart.tsx

@@ -0,0 +1,82 @@
+import { Loader2 } from "lucide-react";
+import { useDashboard } from "./context/DashboardContext";
+
+export function ResolutionRateChart() {
+  const { stats, loading } = useDashboard();
+
+  if (loading) {
+    return (
+      <div className="flex items-center justify-center h-64">
+        <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+      </div>
+    );
+  }
+
+  const resolutionRate = stats?.resolutionRate?.value || 0;
+  const dailyChange = stats?.resolutionRate?.dailyChange || '+0.0%';
+  const weeklyChange = stats?.resolutionRate?.weeklyChange || '+0.0%';
+
+  // Calculate strokeDashoffset for circular progress
+  const circumference = 2 * Math.PI * 45; // 2πr where r=45
+  const offset = circumference - (resolutionRate / 100) * circumference;
+
+  return (
+    <div className="space-y-6">
+      <div>
+        <h3 className="text-lg font-semibold text-white">Resolution Rate</h3>
+      </div>
+
+      <div className="flex items-center justify-center">
+        <div className="relative w-48 h-48">
+          <svg className="w-48 h-48 transform -rotate-90" viewBox="0 0 100 100">
+            <circle
+              cx="50"
+              cy="50"
+              r="45"
+              stroke="currentColor"
+              strokeWidth="8"
+              fill="transparent"
+              className="text-slate-700"
+            />
+            <circle
+              cx="50"
+              cy="50"
+              r="45"
+              stroke="currentColor"
+              strokeWidth="8"
+              fill="transparent"
+              strokeDasharray={circumference}
+              strokeDashoffset={offset}
+              className="text-cyan-500"
+            />
+          </svg>
+
+          <div className="absolute inset-0 flex flex-col items-center justify-center">
+            <div className="text-4xl font-bold text-white">{resolutionRate.toFixed(1)}%</div>
+            <div className="text-sm text-slate-400">Success Rate</div>
+          </div>
+        </div>
+      </div>
+
+      <div className="space-y-3">
+        <div className="flex justify-between items-center">
+          <span className="text-slate-400">Daily Change</span>
+          <span className={`font-medium ${
+            parseFloat(dailyChange) >= 0 ? 'text-green-500' : 'text-red-500'
+          }`}>
+            {parseFloat(dailyChange) >= 0 ? '↗' : '↘'} {dailyChange}
+          </span>
+        </div>
+
+        <div className="flex justify-between items-center">
+          <span className="text-slate-400">Weekly Change</span>
+          <span className={`font-medium ${
+            parseFloat(weeklyChange) >= 0 ? 'text-green-500' : 'text-red-500'
+          }`}>
+            {parseFloat(weeklyChange) >= 0 ? '↗' : '↘'} {weeklyChange}
+          </span>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 231 - 0
shopcall.ai-main/src/components/ShopRenterConnect.tsx

@@ -0,0 +1,231 @@
+import { useState } from "react";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Loader2, Store, ExternalLink, CheckCircle2 } from "lucide-react";
+import { API_URL } from "@/lib/config";
+
+interface ShopRenterConnectProps {
+  onClose?: () => void;
+}
+
+export function ShopRenterConnect({ onClose }: ShopRenterConnectProps) {
+  const [shopUrl, setShopUrl] = useState("");
+  const [isConnecting, setIsConnecting] = useState(false);
+  const [error, setError] = useState("");
+  const [success, setSuccess] = useState(false);
+
+  const handleConnect = async () => {
+    setError("");
+    setSuccess(false);
+
+    // Validate shop URL
+    if (!shopUrl.trim()) {
+      setError("Please enter your ShopRenter shop URL");
+      return;
+    }
+
+    // Normalize URL (remove protocol and trailing slash)
+    let normalizedUrl = shopUrl.trim();
+    normalizedUrl = normalizedUrl.replace(/^https?:\/\//, "");
+    normalizedUrl = normalizedUrl.replace(/\/$/, "");
+
+    // Validate URL format
+    if (!normalizedUrl.includes(".")) {
+      setError("Please enter a valid ShopRenter shop URL (e.g., yourshop.shoprenter.hu)");
+      return;
+    }
+
+    setIsConnecting(true);
+
+    try {
+      // Get the full shop URL with protocol
+      const fullShopUrl = normalizedUrl.startsWith("http")
+        ? normalizedUrl
+        : `https://${normalizedUrl}`;
+
+      // Initiate the OAuth flow through the Edge Function
+      const backendUrl = API_URL;
+
+      // Build OAuth URL with HMAC parameters
+      // Note: In production, the shopname should be extracted from the URL
+      const shopname = normalizedUrl.split(".")[0];
+      const timestamp = Math.floor(Date.now() / 1000);
+
+      // Create the parameters that will be HMAC signed
+      const params = new URLSearchParams({
+        shopname: shopname,
+        app_url: fullShopUrl,
+        timestamp: timestamp.toString()
+      });
+
+      // Generate a simple HMAC (in production, this should be done server-side)
+      // For now, we'll redirect directly and let the server handle HMAC validation
+      const oauthUrl = `${backendUrl}/auth/shoprenter?${params.toString()}`;
+
+      // Redirect to OAuth flow
+      window.location.href = oauthUrl;
+
+    } catch (err) {
+      console.error("Connection error:", err);
+      setError("Failed to connect to ShopRenter. Please try again.");
+      setIsConnecting(false);
+    }
+  };
+
+  const handleKeyPress = (e: React.KeyboardEvent) => {
+    if (e.key === "Enter" && !isConnecting) {
+      handleConnect();
+    }
+  };
+
+  return (
+    <Card className="bg-slate-800 border-slate-700 max-w-2xl mx-auto">
+      <CardHeader>
+        <div className="flex items-center gap-3">
+          <Store className="w-8 h-8 text-cyan-500" />
+          <div>
+            <CardTitle className="text-white text-2xl">Connect ShopRenter Store</CardTitle>
+            <CardDescription className="text-slate-400">
+              Link your ShopRenter shop to start managing AI-powered customer calls
+            </CardDescription>
+          </div>
+        </div>
+      </CardHeader>
+      <CardContent className="space-y-6">
+        {/* Success Message */}
+        {success && (
+          <Alert className="bg-green-500/10 border-green-500/50">
+            <CheckCircle2 className="h-4 w-4 text-green-500" />
+            <AlertDescription className="text-green-500">
+              Successfully connected to ShopRenter! Redirecting to setup...
+            </AlertDescription>
+          </Alert>
+        )}
+
+        {/* Error Message */}
+        {error && (
+          <Alert className="bg-red-500/10 border-red-500/50">
+            <AlertDescription className="text-red-500">
+              {error}
+            </AlertDescription>
+          </Alert>
+        )}
+
+        {/* Connection Form */}
+        <div className="space-y-4">
+          <div className="space-y-2">
+            <Label htmlFor="shopUrl" className="text-white">
+              ShopRenter Shop URL
+            </Label>
+            <Input
+              id="shopUrl"
+              type="text"
+              placeholder="yourshop.shoprenter.hu"
+              value={shopUrl}
+              onChange={(e) => setShopUrl(e.target.value)}
+              onKeyPress={handleKeyPress}
+              className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400"
+              disabled={isConnecting}
+            />
+            <p className="text-sm text-slate-400">
+              Enter your ShopRenter shop URL (e.g., mystore.shoprenter.hu)
+            </p>
+          </div>
+
+          <Button
+            onClick={handleConnect}
+            disabled={isConnecting}
+            className="w-full bg-cyan-500 hover:bg-cyan-600 text-white"
+          >
+            {isConnecting ? (
+              <>
+                <Loader2 className="w-4 h-4 mr-2 animate-spin" />
+                Connecting...
+              </>
+            ) : (
+              <>
+                <Store className="w-4 h-4 mr-2" />
+                Connect to ShopRenter
+              </>
+            )}
+          </Button>
+        </div>
+
+        {/* Information Section */}
+        <div className="bg-slate-700/50 rounded-lg p-4 space-y-3">
+          <h4 className="text-white font-medium flex items-center gap-2">
+            <ExternalLink className="w-4 h-4 text-cyan-500" />
+            What happens next?
+          </h4>
+          <ul className="space-y-2 text-sm text-slate-300">
+            <li className="flex items-start gap-2">
+              <span className="text-cyan-500 mt-0.5">1.</span>
+              <span>You'll be redirected to ShopRenter to authorize the connection</span>
+            </li>
+            <li className="flex items-start gap-2">
+              <span className="text-cyan-500 mt-0.5">2.</span>
+              <span>Grant the necessary permissions for AI caller integration</span>
+            </li>
+            <li className="flex items-start gap-2">
+              <span className="text-cyan-500 mt-0.5">3.</span>
+              <span>Return to ShopCall.ai to complete your AI assistant setup</span>
+            </li>
+            <li className="flex items-start gap-2">
+              <span className="text-cyan-500 mt-0.5">4.</span>
+              <span>Start receiving AI-powered customer calls immediately</span>
+            </li>
+          </ul>
+        </div>
+
+        {/* Required Permissions */}
+        <div className="bg-slate-700/50 rounded-lg p-4 space-y-3">
+          <h4 className="text-white font-medium">Required Permissions</h4>
+          <div className="grid grid-cols-2 gap-2 text-sm text-slate-300">
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Read Products</span>
+            </div>
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Write Products</span>
+            </div>
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Read Orders</span>
+            </div>
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Write Orders</span>
+            </div>
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Read Customers</span>
+            </div>
+            <div className="flex items-center gap-2">
+              <CheckCircle2 className="w-4 h-4 text-green-500" />
+              <span>Write Customers</span>
+            </div>
+          </div>
+          <p className="text-xs text-slate-400">
+            These permissions allow our AI assistant to access your store data and provide accurate customer support.
+          </p>
+        </div>
+
+        {/* Cancel Button */}
+        {onClose && (
+          <Button
+            variant="outline"
+            onClick={onClose}
+            className="w-full border-slate-600 text-slate-300 hover:bg-slate-700"
+            disabled={isConnecting}
+          >
+            Cancel
+          </Button>
+        )}
+      </CardContent>
+    </Card>
+  );
+}

+ 65 - 0
shopcall.ai-main/src/components/TopCallIntents.tsx

@@ -0,0 +1,65 @@
+import { Loader2 } from "lucide-react";
+import { useDashboard } from "./context/DashboardContext";
+
+export function TopCallIntents() {
+  const { stats, loading } = useDashboard();
+
+  if (loading) {
+    return (
+      <div className="flex items-center justify-center h-64">
+        <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+      </div>
+    );
+  }
+
+  const intentData = stats?.topIntents || [];
+
+  if (intentData.length === 0) {
+    return (
+      <div className="space-y-6">
+        <div className="flex items-center justify-between">
+          <h3 className="text-lg font-semibold text-white">Top Call Intents</h3>
+          <span className="text-sm text-slate-400">Last 24 hours</span>
+        </div>
+        <div className="text-center text-slate-400 py-8">
+          No call data available yet
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="space-y-6">
+      <div className="flex items-center justify-between">
+        <h3 className="text-lg font-semibold text-white">Top Call Intents</h3>
+        <span className="text-sm text-slate-400">Last 24 hours</span>
+      </div>
+
+      <div className="space-y-4">
+        {intentData.map((intent) => (
+          <div key={intent.name} className="space-y-2">
+            <div className="flex items-center justify-between">
+              <div className="flex items-center gap-3">
+                <div className="w-3 h-3 bg-cyan-500 rounded-full"></div>
+                <span className="text-white font-medium">{intent.name}</span>
+              </div>
+
+              <div className="flex items-center gap-4">
+                <span className="text-2xl font-bold text-white">{intent.count}</span>
+                <span className={`text-sm font-medium ${
+                  intent.change.startsWith('+') ? 'text-green-500' : 'text-red-500'
+                }`}>
+                  {intent.change}
+                </span>
+              </div>
+            </div>
+
+            <div className="text-sm text-slate-400 text-right">
+              {intent.percentage.toFixed(1)}%
+            </div>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}

+ 123 - 0
shopcall.ai-main/src/components/context/AuthContext.tsx

@@ -0,0 +1,123 @@
+import { createContext, useContext, useState, ReactNode } from 'react';
+import { useEffect } from 'react';
+import { useNavigate } from "react-router-dom";
+
+// Get API URL from environment variables
+const API_URL = import.meta.env.VITE_API_URL || 'https://ztklqodcdjeqpsvhlpud.supabase.co/functions/v1';
+
+interface User {
+    email: string;
+    password: string;
+}
+
+interface AuthContextType {
+    isAuthenticated: boolean;
+    loading: boolean;
+    login: (user_login_data: User) => void;
+    logout: () => void;
+    check_auth: (target_path: string) => void;
+    authStep: string;
+}
+
+const AuthContext = createContext<AuthContextType | undefined>(undefined);
+
+export const AuthProvider = ({ children }: { children: ReactNode }) => {
+    const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [authStep, setAuthStep] = useState<string>("Initializing");
+    const navigate = useNavigate();
+    
+    const check_auth = async(target_path: string) => {
+        setLoading(true);
+        setAuthStep("Checking session");
+        
+        const session_data = localStorage.getItem("session_data");
+        
+        if (session_data) {
+            const readable_session_data = JSON.parse(session_data);
+            if (readable_session_data.success) {
+                try {
+                    setAuthStep("Validating token");
+                    const check_auth_response = await fetch(`${API_URL}/auth/check`, {
+                        method: "GET",
+                        headers: {
+                            "Content-Type": "application/json",
+                            "Authorization": `Bearer ${readable_session_data.session.access_token}`,
+                        }
+                    });
+                    const check_auth_response_json = await check_auth_response.json();
+                    // console.log(check_auth_response_json.success);
+                    if (check_auth_response_json.success) {
+                        setAuthStep("Authentication successful");
+                        setIsAuthenticated(true);
+                        localStorage.setItem('IsAuthenticated', 'true');
+                        if (target_path) {
+                            navigate(target_path);
+                        }
+                    } else {
+                        setAuthStep("Session expired");
+                        setIsAuthenticated(false);
+                        localStorage.setItem('IsAuthenticated', 'false');
+                        navigate("/login");
+                    }
+                } catch (error) {
+                    console.error("Auth check failed:", error);
+                    setAuthStep("Connection failed");
+                    setIsAuthenticated(false);
+                    localStorage.setItem('IsAuthenticated', 'false');
+                    navigate("/login");
+                }
+            } else {
+                setAuthStep("Invalid session");
+                setIsAuthenticated(false);
+                localStorage.setItem('IsAuthenticated', 'false');
+                navigate("/login");
+            }
+        } else {
+            setAuthStep("No session found");
+            setIsAuthenticated(false);
+            localStorage.setItem('IsAuthenticated', 'false');
+            navigate("/login");
+        }
+        
+        setLoading(false);
+    }
+
+    const login = async (user_login_data: User) => {
+        const response = await fetch(`${API_URL}/auth/login`, {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            body: JSON.stringify(user_login_data),
+        });
+        const login_response = await response.json();
+        if (login_response.success) {
+            // setIsAuthenticated(true);
+            localStorage.setItem("session_data", JSON.stringify(login_response));
+            check_auth("/dashboard");
+        } else {
+            throw new Error(login_response.error);
+        }
+    };
+
+    const logout = () => {
+        localStorage.removeItem("session_data");
+        setIsAuthenticated(false);
+        localStorage.setItem('IsAuthenticated', 'false');
+        navigate("/login");
+    };
+
+    // Show loading while checking authentication
+    return (
+        <AuthContext.Provider value={{ isAuthenticated, loading, check_auth, login, logout, authStep }}>
+            {children}
+        </AuthContext.Provider>
+    );
+};
+
+export const useAuth = () => {
+    const context = useContext(AuthContext);
+    if (!context) throw new Error('useAuth must be used within AuthProvider');
+    return context;
+};

+ 85 - 0
shopcall.ai-main/src/components/context/DashboardContext.tsx

@@ -0,0 +1,85 @@
+import { createContext, useContext, useState, useEffect, ReactNode } from "react";
+import { API_URL } from "@/lib/config";
+
+interface DashboardStats {
+  totalCalls: { value: number; change: string; changeType: string };
+  resolvedCalls: { value: number; change: string; changeType: string };
+  avgDuration: { value: number; formatted: string; change: string; changeType: string };
+  totalCost: { value: number; formatted: string; change: string; changeType: string };
+  timeSaved: { value: string; formatted: string; change: string; changeType: string };
+  humanCostSaved: { value: number; formatted: string; change: string; changeType: string };
+  resolutionRate: { value: number; dailyChange: string; weeklyChange: string };
+  topIntents: Array<{
+    name: string;
+    count: number;
+    percentage: number;
+    change: string;
+  }>;
+}
+
+interface DashboardContextType {
+  stats: DashboardStats | null;
+  loading: boolean;
+  error: string | null;
+  refetch: () => void;
+}
+
+const DashboardContext = createContext<DashboardContextType | undefined>(undefined);
+
+export function DashboardProvider({ children }: { children: ReactNode }) {
+  const [stats, setStats] = useState<DashboardStats | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  const fetchStats = async () => {
+    try {
+      setLoading(true);
+      setError(null);
+
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+      const response = await fetch(`${API_URL}/api/dashboard/stats`, {
+        headers: {
+          'Authorization': `Bearer ${session.access_token}`,
+          'Content-Type': 'application/json'
+        }
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to fetch dashboard stats');
+      }
+
+      const data = await response.json();
+      if (data.success) {
+        setStats(data.stats);
+      }
+    } catch (err) {
+      console.error('Error fetching dashboard stats:', err);
+      setError(err instanceof Error ? err.message : 'Failed to load stats');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    fetchStats();
+  }, []);
+
+  return (
+    <DashboardContext.Provider value={{ stats, loading, error, refetch: fetchStats }}>
+      {children}
+    </DashboardContext.Provider>
+  );
+}
+
+export function useDashboard() {
+  const context = useContext(DashboardContext);
+  if (context === undefined) {
+    throw new Error('useDashboard must be used within a DashboardProvider');
+  }
+  return context;
+}

+ 56 - 0
shopcall.ai-main/src/components/ui/accordion.tsx

@@ -0,0 +1,56 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
+>(({ className, ...props }, ref) => (
+  <AccordionPrimitive.Item
+    ref={ref}
+    className={cn("border-b", className)}
+    {...props}
+  />
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <AccordionPrimitive.Header className="flex">
+    <AccordionPrimitive.Trigger
+      ref={ref}
+      className={cn(
+        "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
+        className
+      )}
+      {...props}
+    >
+      {children}
+      <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
+    </AccordionPrimitive.Trigger>
+  </AccordionPrimitive.Header>
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <AccordionPrimitive.Content
+    ref={ref}
+    className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
+    {...props}
+  >
+    <div className={cn("pb-4 pt-0", className)}>{children}</div>
+  </AccordionPrimitive.Content>
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

+ 139 - 0
shopcall.ai-main/src/components/ui/alert-dialog.tsx

@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Overlay
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPortal>
+    <AlertDialogOverlay />
+    <AlertDialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+        className
+      )}
+      {...props}
+    />
+  </AlertDialogPortal>
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-2 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Title
+    ref={ref}
+    className={cn("text-lg font-semibold", className)}
+    {...props}
+  />
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+AlertDialogDescription.displayName =
+  AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Action>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Action
+    ref={ref}
+    className={cn(buttonVariants(), className)}
+    {...props}
+  />
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Cancel
+    ref={ref}
+    className={cn(
+      buttonVariants({ variant: "outline" }),
+      "mt-2 sm:mt-0",
+      className
+    )}
+    {...props}
+  />
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+  AlertDialog,
+  AlertDialogPortal,
+  AlertDialogOverlay,
+  AlertDialogTrigger,
+  AlertDialogContent,
+  AlertDialogHeader,
+  AlertDialogFooter,
+  AlertDialogTitle,
+  AlertDialogDescription,
+  AlertDialogAction,
+  AlertDialogCancel,
+}

+ 59 - 0
shopcall.ai-main/src/components/ui/alert.tsx

@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+  "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+  {
+    variants: {
+      variant: {
+        default: "bg-background text-foreground",
+        destructive:
+          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+const Alert = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
+>(({ className, variant, ...props }, ref) => (
+  <div
+    ref={ref}
+    role="alert"
+    className={cn(alertVariants({ variant }), className)}
+    {...props}
+  />
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+  <h5
+    ref={ref}
+    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
+    {...props}
+  />
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("text-sm [&_p]:leading-relaxed", className)}
+    {...props}
+  />
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }

+ 5 - 0
shopcall.ai-main/src/components/ui/aspect-ratio.tsx

@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }

+ 48 - 0
shopcall.ai-main/src/components/ui/avatar.tsx

@@ -0,0 +1,48 @@
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
+      className
+    )}
+    {...props}
+  />
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Image>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Image
+    ref={ref}
+    className={cn("aspect-square h-full w-full", className)}
+    {...props}
+  />
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Fallback>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Fallback
+    ref={ref}
+    className={cn(
+      "flex h-full w-full items-center justify-center rounded-full bg-muted",
+      className
+    )}
+    {...props}
+  />
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }

+ 36 - 0
shopcall.ai-main/src/components/ui/badge.tsx

@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+  {
+    variants: {
+      variant: {
+        default:
+          "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+        secondary:
+          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        destructive:
+          "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+        outline: "text-foreground",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+export interface BadgeProps
+  extends React.HTMLAttributes<HTMLDivElement>,
+    VariantProps<typeof badgeVariants> {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+  return (
+    <div className={cn(badgeVariants({ variant }), className)} {...props} />
+  )
+}
+
+export { Badge, badgeVariants }

+ 115 - 0
shopcall.ai-main/src/components/ui/breadcrumb.tsx

@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+  HTMLElement,
+  React.ComponentPropsWithoutRef<"nav"> & {
+    separator?: React.ReactNode
+  }
+>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+  HTMLOListElement,
+  React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+  <ol
+    ref={ref}
+    className={cn(
+      "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
+      className
+    )}
+    {...props}
+  />
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+  <li
+    ref={ref}
+    className={cn("inline-flex items-center gap-1.5", className)}
+    {...props}
+  />
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+  HTMLAnchorElement,
+  React.ComponentPropsWithoutRef<"a"> & {
+    asChild?: boolean
+  }
+>(({ asChild, className, ...props }, ref) => {
+  const Comp = asChild ? Slot : "a"
+
+  return (
+    <Comp
+      ref={ref}
+      className={cn("transition-colors hover:text-foreground", className)}
+      {...props}
+    />
+  )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+  HTMLSpanElement,
+  React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+  <span
+    ref={ref}
+    role="link"
+    aria-disabled="true"
+    aria-current="page"
+    className={cn("font-normal text-foreground", className)}
+    {...props}
+  />
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+  children,
+  className,
+  ...props
+}: React.ComponentProps<"li">) => (
+  <li
+    role="presentation"
+    aria-hidden="true"
+    className={cn("[&>svg]:size-3.5", className)}
+    {...props}
+  >
+    {children ?? <ChevronRight />}
+  </li>
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+  className,
+  ...props
+}: React.ComponentProps<"span">) => (
+  <span
+    role="presentation"
+    aria-hidden="true"
+    className={cn("flex h-9 w-9 items-center justify-center", className)}
+    {...props}
+  >
+    <MoreHorizontal className="h-4 w-4" />
+    <span className="sr-only">More</span>
+  </span>
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+  Breadcrumb,
+  BreadcrumbList,
+  BreadcrumbItem,
+  BreadcrumbLink,
+  BreadcrumbPage,
+  BreadcrumbSeparator,
+  BreadcrumbEllipsis,
+}

+ 56 - 0
shopcall.ai-main/src/components/ui/button.tsx

@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "bg-primary text-primary-foreground hover:bg-primary/90",
+        destructive:
+          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+        outline:
+          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+        secondary:
+          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        ghost: "hover:bg-accent hover:text-accent-foreground",
+        link: "text-primary underline-offset-4 hover:underline",
+      },
+      size: {
+        default: "h-10 px-4 py-2",
+        sm: "h-9 rounded-md px-3",
+        lg: "h-11 rounded-md px-8",
+        icon: "h-10 w-10",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, ...props }, ref) => {
+    const Comp = asChild ? Slot : "button"
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }

+ 64 - 0
shopcall.ai-main/src/components/ui/calendar.tsx

@@ -0,0 +1,64 @@
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+export type CalendarProps = React.ComponentProps<typeof DayPicker>;
+
+function Calendar({
+  className,
+  classNames,
+  showOutsideDays = true,
+  ...props
+}: CalendarProps) {
+  return (
+    <DayPicker
+      showOutsideDays={showOutsideDays}
+      className={cn("p-3", className)}
+      classNames={{
+        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
+        month: "space-y-4",
+        caption: "flex justify-center pt-1 relative items-center",
+        caption_label: "text-sm font-medium",
+        nav: "space-x-1 flex items-center",
+        nav_button: cn(
+          buttonVariants({ variant: "outline" }),
+          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+        ),
+        nav_button_previous: "absolute left-1",
+        nav_button_next: "absolute right-1",
+        table: "w-full border-collapse space-y-1",
+        head_row: "flex",
+        head_cell:
+          "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
+        row: "flex w-full mt-2",
+        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
+        day: cn(
+          buttonVariants({ variant: "ghost" }),
+          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
+        ),
+        day_range_end: "day-range-end",
+        day_selected:
+          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+        day_today: "bg-accent text-accent-foreground",
+        day_outside:
+          "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
+        day_disabled: "text-muted-foreground opacity-50",
+        day_range_middle:
+          "aria-selected:bg-accent aria-selected:text-accent-foreground",
+        day_hidden: "invisible",
+        ...classNames,
+      }}
+      components={{
+        IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
+        IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
+      }}
+      {...props}
+    />
+  );
+}
+Calendar.displayName = "Calendar";
+
+export { Calendar };

+ 79 - 0
shopcall.ai-main/src/components/ui/card.tsx

@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn(
+      "rounded-lg border bg-card text-card-foreground shadow-sm",
+      className
+    )}
+    {...props}
+  />
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex flex-col space-y-1.5 p-6", className)}
+    {...props}
+  />
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+  <h3
+    ref={ref}
+    className={cn(
+      "text-2xl font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+  <p
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex items-center p-6 pt-0", className)}
+    {...props}
+  />
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

+ 260 - 0
shopcall.ai-main/src/components/ui/carousel.tsx

@@ -0,0 +1,260 @@
+import * as React from "react"
+import useEmblaCarousel, {
+  type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+  opts?: CarouselOptions
+  plugins?: CarouselPlugin
+  orientation?: "horizontal" | "vertical"
+  setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+  carouselRef: ReturnType<typeof useEmblaCarousel>[0]
+  api: ReturnType<typeof useEmblaCarousel>[1]
+  scrollPrev: () => void
+  scrollNext: () => void
+  canScrollPrev: boolean
+  canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext<CarouselContextProps | null>(null)
+
+function useCarousel() {
+  const context = React.useContext(CarouselContext)
+
+  if (!context) {
+    throw new Error("useCarousel must be used within a <Carousel />")
+  }
+
+  return context
+}
+
+const Carousel = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement> & CarouselProps
+>(
+  (
+    {
+      orientation = "horizontal",
+      opts,
+      setApi,
+      plugins,
+      className,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const [carouselRef, api] = useEmblaCarousel(
+      {
+        ...opts,
+        axis: orientation === "horizontal" ? "x" : "y",
+      },
+      plugins
+    )
+    const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+    const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+    const onSelect = React.useCallback((api: CarouselApi) => {
+      if (!api) {
+        return
+      }
+
+      setCanScrollPrev(api.canScrollPrev())
+      setCanScrollNext(api.canScrollNext())
+    }, [])
+
+    const scrollPrev = React.useCallback(() => {
+      api?.scrollPrev()
+    }, [api])
+
+    const scrollNext = React.useCallback(() => {
+      api?.scrollNext()
+    }, [api])
+
+    const handleKeyDown = React.useCallback(
+      (event: React.KeyboardEvent<HTMLDivElement>) => {
+        if (event.key === "ArrowLeft") {
+          event.preventDefault()
+          scrollPrev()
+        } else if (event.key === "ArrowRight") {
+          event.preventDefault()
+          scrollNext()
+        }
+      },
+      [scrollPrev, scrollNext]
+    )
+
+    React.useEffect(() => {
+      if (!api || !setApi) {
+        return
+      }
+
+      setApi(api)
+    }, [api, setApi])
+
+    React.useEffect(() => {
+      if (!api) {
+        return
+      }
+
+      onSelect(api)
+      api.on("reInit", onSelect)
+      api.on("select", onSelect)
+
+      return () => {
+        api?.off("select", onSelect)
+      }
+    }, [api, onSelect])
+
+    return (
+      <CarouselContext.Provider
+        value={{
+          carouselRef,
+          api: api,
+          opts,
+          orientation:
+            orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
+          scrollPrev,
+          scrollNext,
+          canScrollPrev,
+          canScrollNext,
+        }}
+      >
+        <div
+          ref={ref}
+          onKeyDownCapture={handleKeyDown}
+          className={cn("relative", className)}
+          role="region"
+          aria-roledescription="carousel"
+          {...props}
+        >
+          {children}
+        </div>
+      </CarouselContext.Provider>
+    )
+  }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const { carouselRef, orientation } = useCarousel()
+
+  return (
+    <div ref={carouselRef} className="overflow-hidden">
+      <div
+        ref={ref}
+        className={cn(
+          "flex",
+          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
+          className
+        )}
+        {...props}
+      />
+    </div>
+  )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const { orientation } = useCarousel()
+
+  return (
+    <div
+      ref={ref}
+      role="group"
+      aria-roledescription="slide"
+      className={cn(
+        "min-w-0 shrink-0 grow-0 basis-full",
+        orientation === "horizontal" ? "pl-4" : "pt-4",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+  const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+  return (
+    <Button
+      ref={ref}
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute  h-8 w-8 rounded-full",
+        orientation === "horizontal"
+          ? "-left-12 top-1/2 -translate-y-1/2"
+          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
+        className
+      )}
+      disabled={!canScrollPrev}
+      onClick={scrollPrev}
+      {...props}
+    >
+      <ArrowLeft className="h-4 w-4" />
+      <span className="sr-only">Previous slide</span>
+    </Button>
+  )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+  const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+  return (
+    <Button
+      ref={ref}
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute h-8 w-8 rounded-full",
+        orientation === "horizontal"
+          ? "-right-12 top-1/2 -translate-y-1/2"
+          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
+        className
+      )}
+      disabled={!canScrollNext}
+      onClick={scrollNext}
+      {...props}
+    >
+      <ArrowRight className="h-4 w-4" />
+      <span className="sr-only">Next slide</span>
+    </Button>
+  )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+  type CarouselApi,
+  Carousel,
+  CarouselContent,
+  CarouselItem,
+  CarouselPrevious,
+  CarouselNext,
+}

+ 363 - 0
shopcall.ai-main/src/components/ui/chart.tsx

@@ -0,0 +1,363 @@
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+  [k in string]: {
+    label?: React.ReactNode
+    icon?: React.ComponentType
+  } & (
+    | { color?: string; theme?: never }
+    | { color?: never; theme: Record<keyof typeof THEMES, string> }
+  )
+}
+
+type ChartContextProps = {
+  config: ChartConfig
+}
+
+const ChartContext = React.createContext<ChartContextProps | null>(null)
+
+function useChart() {
+  const context = React.useContext(ChartContext)
+
+  if (!context) {
+    throw new Error("useChart must be used within a <ChartContainer />")
+  }
+
+  return context
+}
+
+const ChartContainer = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    config: ChartConfig
+    children: React.ComponentProps<
+      typeof RechartsPrimitive.ResponsiveContainer
+    >["children"]
+  }
+>(({ id, className, children, config, ...props }, ref) => {
+  const uniqueId = React.useId()
+  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+  return (
+    <ChartContext.Provider value={{ config }}>
+      <div
+        data-chart={chartId}
+        ref={ref}
+        className={cn(
+          "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
+          className
+        )}
+        {...props}
+      >
+        <ChartStyle id={chartId} config={config} />
+        <RechartsPrimitive.ResponsiveContainer>
+          {children}
+        </RechartsPrimitive.ResponsiveContainer>
+      </div>
+    </ChartContext.Provider>
+  )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+  const colorConfig = Object.entries(config).filter(
+    ([_, config]) => config.theme || config.color
+  )
+
+  if (!colorConfig.length) {
+    return null
+  }
+
+  return (
+    <style
+      dangerouslySetInnerHTML={{
+        __html: Object.entries(THEMES)
+          .map(
+            ([theme, prefix]) => `
+${prefix} [data-chart=${id}] {
+${colorConfig
+  .map(([key, itemConfig]) => {
+    const color =
+      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
+      itemConfig.color
+    return color ? `  --color-${key}: ${color};` : null
+  })
+  .join("\n")}
+}
+`
+          )
+          .join("\n"),
+      }}
+    />
+  )
+}
+
+const ChartTooltip = RechartsPrimitive.Tooltip
+
+const ChartTooltipContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
+    React.ComponentProps<"div"> & {
+      hideLabel?: boolean
+      hideIndicator?: boolean
+      indicator?: "line" | "dot" | "dashed"
+      nameKey?: string
+      labelKey?: string
+    }
+>(
+  (
+    {
+      active,
+      payload,
+      className,
+      indicator = "dot",
+      hideLabel = false,
+      hideIndicator = false,
+      label,
+      labelFormatter,
+      labelClassName,
+      formatter,
+      color,
+      nameKey,
+      labelKey,
+    },
+    ref
+  ) => {
+    const { config } = useChart()
+
+    const tooltipLabel = React.useMemo(() => {
+      if (hideLabel || !payload?.length) {
+        return null
+      }
+
+      const [item] = payload
+      const key = `${labelKey || item.dataKey || item.name || "value"}`
+      const itemConfig = getPayloadConfigFromPayload(config, item, key)
+      const value =
+        !labelKey && typeof label === "string"
+          ? config[label as keyof typeof config]?.label || label
+          : itemConfig?.label
+
+      if (labelFormatter) {
+        return (
+          <div className={cn("font-medium", labelClassName)}>
+            {labelFormatter(value, payload)}
+          </div>
+        )
+      }
+
+      if (!value) {
+        return null
+      }
+
+      return <div className={cn("font-medium", labelClassName)}>{value}</div>
+    }, [
+      label,
+      labelFormatter,
+      payload,
+      hideLabel,
+      labelClassName,
+      config,
+      labelKey,
+    ])
+
+    if (!active || !payload?.length) {
+      return null
+    }
+
+    const nestLabel = payload.length === 1 && indicator !== "dot"
+
+    return (
+      <div
+        ref={ref}
+        className={cn(
+          "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
+          className
+        )}
+      >
+        {!nestLabel ? tooltipLabel : null}
+        <div className="grid gap-1.5">
+          {payload.map((item, index) => {
+            const key = `${nameKey || item.name || item.dataKey || "value"}`
+            const itemConfig = getPayloadConfigFromPayload(config, item, key)
+            const indicatorColor = color || item.payload.fill || item.color
+
+            return (
+              <div
+                key={item.dataKey}
+                className={cn(
+                  "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
+                  indicator === "dot" && "items-center"
+                )}
+              >
+                {formatter && item?.value !== undefined && item.name ? (
+                  formatter(item.value, item.name, item, index, item.payload)
+                ) : (
+                  <>
+                    {itemConfig?.icon ? (
+                      <itemConfig.icon />
+                    ) : (
+                      !hideIndicator && (
+                        <div
+                          className={cn(
+                            "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
+                            {
+                              "h-2.5 w-2.5": indicator === "dot",
+                              "w-1": indicator === "line",
+                              "w-0 border-[1.5px] border-dashed bg-transparent":
+                                indicator === "dashed",
+                              "my-0.5": nestLabel && indicator === "dashed",
+                            }
+                          )}
+                          style={
+                            {
+                              "--color-bg": indicatorColor,
+                              "--color-border": indicatorColor,
+                            } as React.CSSProperties
+                          }
+                        />
+                      )
+                    )}
+                    <div
+                      className={cn(
+                        "flex flex-1 justify-between leading-none",
+                        nestLabel ? "items-end" : "items-center"
+                      )}
+                    >
+                      <div className="grid gap-1.5">
+                        {nestLabel ? tooltipLabel : null}
+                        <span className="text-muted-foreground">
+                          {itemConfig?.label || item.name}
+                        </span>
+                      </div>
+                      {item.value && (
+                        <span className="font-mono font-medium tabular-nums text-foreground">
+                          {item.value.toLocaleString()}
+                        </span>
+                      )}
+                    </div>
+                  </>
+                )}
+              </div>
+            )
+          })}
+        </div>
+      </div>
+    )
+  }
+)
+ChartTooltipContent.displayName = "ChartTooltip"
+
+const ChartLegend = RechartsPrimitive.Legend
+
+const ChartLegendContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> &
+    Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
+      hideIcon?: boolean
+      nameKey?: string
+    }
+>(
+  (
+    { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
+    ref
+  ) => {
+    const { config } = useChart()
+
+    if (!payload?.length) {
+      return null
+    }
+
+    return (
+      <div
+        ref={ref}
+        className={cn(
+          "flex items-center justify-center gap-4",
+          verticalAlign === "top" ? "pb-3" : "pt-3",
+          className
+        )}
+      >
+        {payload.map((item) => {
+          const key = `${nameKey || item.dataKey || "value"}`
+          const itemConfig = getPayloadConfigFromPayload(config, item, key)
+
+          return (
+            <div
+              key={item.value}
+              className={cn(
+                "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
+              )}
+            >
+              {itemConfig?.icon && !hideIcon ? (
+                <itemConfig.icon />
+              ) : (
+                <div
+                  className="h-2 w-2 shrink-0 rounded-[2px]"
+                  style={{
+                    backgroundColor: item.color,
+                  }}
+                />
+              )}
+              {itemConfig?.label}
+            </div>
+          )
+        })}
+      </div>
+    )
+  }
+)
+ChartLegendContent.displayName = "ChartLegend"
+
+// Helper to extract item config from a payload.
+function getPayloadConfigFromPayload(
+  config: ChartConfig,
+  payload: unknown,
+  key: string
+) {
+  if (typeof payload !== "object" || payload === null) {
+    return undefined
+  }
+
+  const payloadPayload =
+    "payload" in payload &&
+    typeof payload.payload === "object" &&
+    payload.payload !== null
+      ? payload.payload
+      : undefined
+
+  let configLabelKey: string = key
+
+  if (
+    key in payload &&
+    typeof payload[key as keyof typeof payload] === "string"
+  ) {
+    configLabelKey = payload[key as keyof typeof payload] as string
+  } else if (
+    payloadPayload &&
+    key in payloadPayload &&
+    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
+  ) {
+    configLabelKey = payloadPayload[
+      key as keyof typeof payloadPayload
+    ] as string
+  }
+
+  return configLabelKey in config
+    ? config[configLabelKey]
+    : config[key as keyof typeof config]
+}
+
+export {
+  ChartContainer,
+  ChartTooltip,
+  ChartTooltipContent,
+  ChartLegend,
+  ChartLegendContent,
+  ChartStyle,
+}

+ 28 - 0
shopcall.ai-main/src/components/ui/checkbox.tsx

@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+  React.ElementRef<typeof CheckboxPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <CheckboxPrimitive.Root
+    ref={ref}
+    className={cn(
+      "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
+      className
+    )}
+    {...props}
+  >
+    <CheckboxPrimitive.Indicator
+      className={cn("flex items-center justify-center text-current")}
+    >
+      <Check className="h-4 w-4" />
+    </CheckboxPrimitive.Indicator>
+  </CheckboxPrimitive.Root>
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }

+ 9 - 0
shopcall.ai-main/src/components/ui/collapsible.tsx

@@ -0,0 +1,9 @@
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }

+ 153 - 0
shopcall.ai-main/src/components/ui/command.tsx

@@ -0,0 +1,153 @@
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive
+    ref={ref}
+    className={cn(
+      "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+  return (
+    <Dialog {...props}>
+      <DialogContent className="overflow-hidden p-0 shadow-lg">
+        <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
+          {children}
+        </Command>
+      </DialogContent>
+    </Dialog>
+  )
+}
+
+const CommandInput = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Input>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
+>(({ className, ...props }, ref) => (
+  <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
+    <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+    <CommandPrimitive.Input
+      ref={ref}
+      className={cn(
+        "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+        className
+      )}
+      {...props}
+    />
+  </div>
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.List
+    ref={ref}
+    className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
+    {...props}
+  />
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Empty>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
+>((props, ref) => (
+  <CommandPrimitive.Empty
+    ref={ref}
+    className="py-6 text-center text-sm"
+    {...props}
+  />
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Group>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Group
+    ref={ref}
+    className={cn(
+      "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 h-px bg-border", className)}
+    {...props}
+  />
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
+      className
+    )}
+    {...props}
+  />
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+  Command,
+  CommandDialog,
+  CommandInput,
+  CommandList,
+  CommandEmpty,
+  CommandGroup,
+  CommandItem,
+  CommandShortcut,
+  CommandSeparator,
+}

+ 198 - 0
shopcall.ai-main/src/components/ui/context-menu.tsx

@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ContextMenu = ContextMenuPrimitive.Root
+
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+
+const ContextMenuGroup = ContextMenuPrimitive.Group
+
+const ContextMenuPortal = ContextMenuPrimitive.Portal
+
+const ContextMenuSub = ContextMenuPrimitive.Sub
+
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+
+const ContextMenuSubTrigger = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <ContextMenuPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </ContextMenuPrimitive.SubTrigger>
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.Portal>
+    <ContextMenuPrimitive.Content
+      ref={ref}
+      className={cn(
+        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </ContextMenuPrimitive.Portal>
+))
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+
+const ContextMenuItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <ContextMenuPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <ContextMenuPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <ContextMenuPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </ContextMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </ContextMenuPrimitive.CheckboxItem>
+))
+ContextMenuCheckboxItem.displayName =
+  ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <ContextMenuPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <ContextMenuPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </ContextMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </ContextMenuPrimitive.RadioItem>
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <ContextMenuPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold text-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-border", className)}
+    {...props}
+  />
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
+
+export {
+  ContextMenu,
+  ContextMenuTrigger,
+  ContextMenuContent,
+  ContextMenuItem,
+  ContextMenuCheckboxItem,
+  ContextMenuRadioItem,
+  ContextMenuLabel,
+  ContextMenuSeparator,
+  ContextMenuShortcut,
+  ContextMenuGroup,
+  ContextMenuPortal,
+  ContextMenuSub,
+  ContextMenuSubContent,
+  ContextMenuSubTrigger,
+  ContextMenuRadioGroup,
+}

+ 120 - 0
shopcall.ai-main/src/components/ui/dialog.tsx

@@ -0,0 +1,120 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Overlay
+    ref={ref}
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+  />
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <DialogPortal>
+    <DialogOverlay />
+    <DialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+        className
+      )}
+      {...props}
+    >
+      {children}
+      <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </DialogPrimitive.Close>
+    </DialogPrimitive.Content>
+  </DialogPortal>
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-1.5 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Title
+    ref={ref}
+    className={cn(
+      "text-lg font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+  Dialog,
+  DialogPortal,
+  DialogOverlay,
+  DialogClose,
+  DialogTrigger,
+  DialogContent,
+  DialogHeader,
+  DialogFooter,
+  DialogTitle,
+  DialogDescription,
+}

+ 116 - 0
shopcall.ai-main/src/components/ui/drawer.tsx

@@ -0,0 +1,116 @@
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+  shouldScaleBackground = true,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
+  <DrawerPrimitive.Root
+    shouldScaleBackground={shouldScaleBackground}
+    {...props}
+  />
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Overlay
+    ref={ref}
+    className={cn("fixed inset-0 z-50 bg-black/80", className)}
+    {...props}
+  />
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <DrawerPortal>
+    <DrawerOverlay />
+    <DrawerPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
+        className
+      )}
+      {...props}
+    >
+      <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
+      {children}
+    </DrawerPrimitive.Content>
+  </DrawerPortal>
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
+    {...props}
+  />
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn("mt-auto flex flex-col gap-2 p-4", className)}
+    {...props}
+  />
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Title
+    ref={ref}
+    className={cn(
+      "text-lg font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+  Drawer,
+  DrawerPortal,
+  DrawerOverlay,
+  DrawerTrigger,
+  DrawerClose,
+  DrawerContent,
+  DrawerHeader,
+  DrawerFooter,
+  DrawerTitle,
+  DrawerDescription,
+}

+ 198 - 0
shopcall.ai-main/src/components/ui/dropdown-menu.tsx

@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <DropdownMenuPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </DropdownMenuPrimitive.SubTrigger>
+))
+DropdownMenuSubTrigger.displayName =
+  DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <DropdownMenuPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuSubContent.displayName =
+  DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+  <DropdownMenuPrimitive.Portal>
+    <DropdownMenuPrimitive.Content
+      ref={ref}
+      sideOffset={sideOffset}
+      className={cn(
+        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </DropdownMenuPrimitive.Portal>
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <DropdownMenuPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <DropdownMenuPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <DropdownMenuPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </DropdownMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </DropdownMenuPrimitive.CheckboxItem>
+))
+DropdownMenuCheckboxItem.displayName =
+  DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <DropdownMenuPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <DropdownMenuPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </DropdownMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </DropdownMenuPrimitive.RadioItem>
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <DropdownMenuPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <DropdownMenuPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
+      {...props}
+    />
+  )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+  DropdownMenu,
+  DropdownMenuTrigger,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuCheckboxItem,
+  DropdownMenuRadioItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuGroup,
+  DropdownMenuPortal,
+  DropdownMenuSub,
+  DropdownMenuSubContent,
+  DropdownMenuSubTrigger,
+  DropdownMenuRadioGroup,
+}

+ 176 - 0
shopcall.ai-main/src/components/ui/form.tsx

@@ -0,0 +1,176 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+  Controller,
+  ControllerProps,
+  FieldPath,
+  FieldValues,
+  FormProvider,
+  useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+> = {
+  name: TName
+}
+
+const FormFieldContext = React.createContext<FormFieldContextValue>(
+  {} as FormFieldContextValue
+)
+
+const FormField = <
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+>({
+  ...props
+}: ControllerProps<TFieldValues, TName>) => {
+  return (
+    <FormFieldContext.Provider value={{ name: props.name }}>
+      <Controller {...props} />
+    </FormFieldContext.Provider>
+  )
+}
+
+const useFormField = () => {
+  const fieldContext = React.useContext(FormFieldContext)
+  const itemContext = React.useContext(FormItemContext)
+  const { getFieldState, formState } = useFormContext()
+
+  const fieldState = getFieldState(fieldContext.name, formState)
+
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+
+  const { id } = itemContext
+
+  return {
+    id,
+    name: fieldContext.name,
+    formItemId: `${id}-form-item`,
+    formDescriptionId: `${id}-form-item-description`,
+    formMessageId: `${id}-form-item-message`,
+    ...fieldState,
+  }
+}
+
+type FormItemContextValue = {
+  id: string
+}
+
+const FormItemContext = React.createContext<FormItemContextValue>(
+  {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const id = React.useId()
+
+  return (
+    <FormItemContext.Provider value={{ id }}>
+      <div ref={ref} className={cn("space-y-2", className)} {...props} />
+    </FormItemContext.Provider>
+  )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+  React.ElementRef<typeof LabelPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
+>(({ className, ...props }, ref) => {
+  const { error, formItemId } = useFormField()
+
+  return (
+    <Label
+      ref={ref}
+      className={cn(error && "text-destructive", className)}
+      htmlFor={formItemId}
+      {...props}
+    />
+  )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+  React.ElementRef<typeof Slot>,
+  React.ComponentPropsWithoutRef<typeof Slot>
+>(({ ...props }, ref) => {
+  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+  return (
+    <Slot
+      ref={ref}
+      id={formItemId}
+      aria-describedby={
+        !error
+          ? `${formDescriptionId}`
+          : `${formDescriptionId} ${formMessageId}`
+      }
+      aria-invalid={!!error}
+      {...props}
+    />
+  )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => {
+  const { formDescriptionId } = useFormField()
+
+  return (
+    <p
+      ref={ref}
+      id={formDescriptionId}
+      className={cn("text-sm text-muted-foreground", className)}
+      {...props}
+    />
+  )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, children, ...props }, ref) => {
+  const { error, formMessageId } = useFormField()
+  const body = error ? String(error?.message) : children
+
+  if (!body) {
+    return null
+  }
+
+  return (
+    <p
+      ref={ref}
+      id={formMessageId}
+      className={cn("text-sm font-medium text-destructive", className)}
+      {...props}
+    >
+      {body}
+    </p>
+  )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+  useFormField,
+  Form,
+  FormItem,
+  FormLabel,
+  FormControl,
+  FormDescription,
+  FormMessage,
+  FormField,
+}

+ 27 - 0
shopcall.ai-main/src/components/ui/hover-card.tsx

@@ -0,0 +1,27 @@
+import * as React from "react"
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
+
+import { cn } from "@/lib/utils"
+
+const HoverCard = HoverCardPrimitive.Root
+
+const HoverCardTrigger = HoverCardPrimitive.Trigger
+
+const HoverCardContent = React.forwardRef<
+  React.ElementRef<typeof HoverCardPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+  <HoverCardPrimitive.Content
+    ref={ref}
+    align={align}
+    sideOffset={sideOffset}
+    className={cn(
+      "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }

+ 69 - 0
shopcall.ai-main/src/components/ui/input-otp.tsx

@@ -0,0 +1,69 @@
+import * as React from "react"
+import { OTPInput, OTPInputContext } from "input-otp"
+import { Dot } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const InputOTP = React.forwardRef<
+  React.ElementRef<typeof OTPInput>,
+  React.ComponentPropsWithoutRef<typeof OTPInput>
+>(({ className, containerClassName, ...props }, ref) => (
+  <OTPInput
+    ref={ref}
+    containerClassName={cn(
+      "flex items-center gap-2 has-[:disabled]:opacity-50",
+      containerClassName
+    )}
+    className={cn("disabled:cursor-not-allowed", className)}
+    {...props}
+  />
+))
+InputOTP.displayName = "InputOTP"
+
+const InputOTPGroup = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div">
+>(({ className, ...props }, ref) => (
+  <div ref={ref} className={cn("flex items-center", className)} {...props} />
+))
+InputOTPGroup.displayName = "InputOTPGroup"
+
+const InputOTPSlot = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div"> & { index: number }
+>(({ index, className, ...props }, ref) => {
+  const inputOTPContext = React.useContext(OTPInputContext)
+  const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
+
+  return (
+    <div
+      ref={ref}
+      className={cn(
+        "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
+        isActive && "z-10 ring-2 ring-ring ring-offset-background",
+        className
+      )}
+      {...props}
+    >
+      {char}
+      {hasFakeCaret && (
+        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
+          <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
+        </div>
+      )}
+    </div>
+  )
+})
+InputOTPSlot.displayName = "InputOTPSlot"
+
+const InputOTPSeparator = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div">
+>(({ ...props }, ref) => (
+  <div ref={ref} role="separator" {...props}>
+    <Dot />
+  </div>
+))
+InputOTPSeparator.displayName = "InputOTPSeparator"
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

+ 22 - 0
shopcall.ai-main/src/components/ui/input.tsx

@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
+  ({ className, type, ...props }, ref) => {
+    return (
+      <input
+        type={type}
+        className={cn(
+          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          className
+        )}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Input.displayName = "Input"
+
+export { Input }

+ 24 - 0
shopcall.ai-main/src/components/ui/label.tsx

@@ -0,0 +1,24 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+  React.ElementRef<typeof LabelPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
+    VariantProps<typeof labelVariants>
+>(({ className, ...props }, ref) => (
+  <LabelPrimitive.Root
+    ref={ref}
+    className={cn(labelVariants(), className)}
+    {...props}
+  />
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }

+ 234 - 0
shopcall.ai-main/src/components/ui/menubar.tsx

@@ -0,0 +1,234 @@
+import * as React from "react"
+import * as MenubarPrimitive from "@radix-ui/react-menubar"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const MenubarMenu = MenubarPrimitive.Menu
+
+const MenubarGroup = MenubarPrimitive.Group
+
+const MenubarPortal = MenubarPrimitive.Portal
+
+const MenubarSub = MenubarPrimitive.Sub
+
+const MenubarRadioGroup = MenubarPrimitive.RadioGroup
+
+const Menubar = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Root
+    ref={ref}
+    className={cn(
+      "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
+      className
+    )}
+    {...props}
+  />
+))
+Menubar.displayName = MenubarPrimitive.Root.displayName
+
+const MenubarTrigger = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
+
+const MenubarSubTrigger = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <MenubarPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </MenubarPrimitive.SubTrigger>
+))
+MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
+
+const MenubarSubContent = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
+
+const MenubarContent = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
+>(
+  (
+    { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
+    ref
+  ) => (
+    <MenubarPrimitive.Portal>
+      <MenubarPrimitive.Content
+        ref={ref}
+        align={align}
+        alignOffset={alignOffset}
+        sideOffset={sideOffset}
+        className={cn(
+          "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+          className
+        )}
+        {...props}
+      />
+    </MenubarPrimitive.Portal>
+  )
+)
+MenubarContent.displayName = MenubarPrimitive.Content.displayName
+
+const MenubarItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <MenubarPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarItem.displayName = MenubarPrimitive.Item.displayName
+
+const MenubarCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <MenubarPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <MenubarPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </MenubarPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </MenubarPrimitive.CheckboxItem>
+))
+MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
+
+const MenubarRadioItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <MenubarPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <MenubarPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </MenubarPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </MenubarPrimitive.RadioItem>
+))
+MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
+
+const MenubarLabel = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <MenubarPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarLabel.displayName = MenubarPrimitive.Label.displayName
+
+const MenubarSeparator = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
+
+const MenubarShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+MenubarShortcut.displayname = "MenubarShortcut"
+
+export {
+  Menubar,
+  MenubarMenu,
+  MenubarTrigger,
+  MenubarContent,
+  MenubarItem,
+  MenubarSeparator,
+  MenubarLabel,
+  MenubarCheckboxItem,
+  MenubarRadioGroup,
+  MenubarRadioItem,
+  MenubarPortal,
+  MenubarSubContent,
+  MenubarSubTrigger,
+  MenubarGroup,
+  MenubarSub,
+  MenubarShortcut,
+}

+ 128 - 0
shopcall.ai-main/src/components/ui/navigation-menu.tsx

@@ -0,0 +1,128 @@
+import * as React from "react"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+  <NavigationMenuPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative z-10 flex max-w-max flex-1 items-center justify-center",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <NavigationMenuViewport />
+  </NavigationMenuPrimitive.Root>
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.List
+    ref={ref}
+    className={cn(
+      "group flex flex-1 list-none items-center justify-center space-x-1",
+      className
+    )}
+    {...props}
+  />
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+  "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+)
+
+const NavigationMenuTrigger = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <NavigationMenuPrimitive.Trigger
+    ref={ref}
+    className={cn(navigationMenuTriggerStyle(), "group", className)}
+    {...props}
+  >
+    {children}{" "}
+    <ChevronDown
+      className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
+      aria-hidden="true"
+    />
+  </NavigationMenuPrimitive.Trigger>
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.Content
+    ref={ref}
+    className={cn(
+      "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
+      className
+    )}
+    {...props}
+  />
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
+>(({ className, ...props }, ref) => (
+  <div className={cn("absolute left-0 top-full flex justify-center")}>
+    <NavigationMenuPrimitive.Viewport
+      className={cn(
+        "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
+        className
+      )}
+      ref={ref}
+      {...props}
+    />
+  </div>
+))
+NavigationMenuViewport.displayName =
+  NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.Indicator
+    ref={ref}
+    className={cn(
+      "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
+      className
+    )}
+    {...props}
+  >
+    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
+  </NavigationMenuPrimitive.Indicator>
+))
+NavigationMenuIndicator.displayName =
+  NavigationMenuPrimitive.Indicator.displayName
+
+export {
+  navigationMenuTriggerStyle,
+  NavigationMenu,
+  NavigationMenuList,
+  NavigationMenuItem,
+  NavigationMenuContent,
+  NavigationMenuTrigger,
+  NavigationMenuLink,
+  NavigationMenuIndicator,
+  NavigationMenuViewport,
+}

+ 117 - 0
shopcall.ai-main/src/components/ui/pagination.tsx

@@ -0,0 +1,117 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { ButtonProps, buttonVariants } from "@/components/ui/button"
+
+const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
+  <nav
+    role="navigation"
+    aria-label="pagination"
+    className={cn("mx-auto flex w-full justify-center", className)}
+    {...props}
+  />
+)
+Pagination.displayName = "Pagination"
+
+const PaginationContent = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    className={cn("flex flex-row items-center gap-1", className)}
+    {...props}
+  />
+))
+PaginationContent.displayName = "PaginationContent"
+
+const PaginationItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+  <li ref={ref} className={cn("", className)} {...props} />
+))
+PaginationItem.displayName = "PaginationItem"
+
+type PaginationLinkProps = {
+  isActive?: boolean
+} & Pick<ButtonProps, "size"> &
+  React.ComponentProps<"a">
+
+const PaginationLink = ({
+  className,
+  isActive,
+  size = "icon",
+  ...props
+}: PaginationLinkProps) => (
+  <a
+    aria-current={isActive ? "page" : undefined}
+    className={cn(
+      buttonVariants({
+        variant: isActive ? "outline" : "ghost",
+        size,
+      }),
+      className
+    )}
+    {...props}
+  />
+)
+PaginationLink.displayName = "PaginationLink"
+
+const PaginationPrevious = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+  <PaginationLink
+    aria-label="Go to previous page"
+    size="default"
+    className={cn("gap-1 pl-2.5", className)}
+    {...props}
+  >
+    <ChevronLeft className="h-4 w-4" />
+    <span>Previous</span>
+  </PaginationLink>
+)
+PaginationPrevious.displayName = "PaginationPrevious"
+
+const PaginationNext = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+  <PaginationLink
+    aria-label="Go to next page"
+    size="default"
+    className={cn("gap-1 pr-2.5", className)}
+    {...props}
+  >
+    <span>Next</span>
+    <ChevronRight className="h-4 w-4" />
+  </PaginationLink>
+)
+PaginationNext.displayName = "PaginationNext"
+
+const PaginationEllipsis = ({
+  className,
+  ...props
+}: React.ComponentProps<"span">) => (
+  <span
+    aria-hidden
+    className={cn("flex h-9 w-9 items-center justify-center", className)}
+    {...props}
+  >
+    <MoreHorizontal className="h-4 w-4" />
+    <span className="sr-only">More pages</span>
+  </span>
+)
+PaginationEllipsis.displayName = "PaginationEllipsis"
+
+export {
+  Pagination,
+  PaginationContent,
+  PaginationEllipsis,
+  PaginationItem,
+  PaginationLink,
+  PaginationNext,
+  PaginationPrevious,
+}

+ 30 - 0
shopcall.ai-main/src/components/ui/popover.tsx

@@ -0,0 +1,30 @@
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+  React.ElementRef<typeof PopoverPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+  <PopoverPrimitive.Portal>
+    <PopoverPrimitive.Content
+      ref={ref}
+      align={align}
+      sideOffset={sideOffset}
+      className={cn(
+        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </PopoverPrimitive.Portal>
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }

+ 26 - 0
shopcall.ai-main/src/components/ui/progress.tsx

@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+const Progress = React.forwardRef<
+  React.ElementRef<typeof ProgressPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
+>(({ className, value, ...props }, ref) => (
+  <ProgressPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
+      className
+    )}
+    {...props}
+  >
+    <ProgressPrimitive.Indicator
+      className="h-full w-full flex-1 bg-primary transition-all"
+      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+    />
+  </ProgressPrimitive.Root>
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }

+ 42 - 0
shopcall.ai-main/src/components/ui/radio-group.tsx

@@ -0,0 +1,42 @@
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+  React.ElementRef<typeof RadioGroupPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
+>(({ className, ...props }, ref) => {
+  return (
+    <RadioGroupPrimitive.Root
+      className={cn("grid gap-2", className)}
+      {...props}
+      ref={ref}
+    />
+  )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+  React.ElementRef<typeof RadioGroupPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
+>(({ className, ...props }, ref) => {
+  return (
+    <RadioGroupPrimitive.Item
+      ref={ref}
+      className={cn(
+        "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+        className
+      )}
+      {...props}
+    >
+      <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
+        <Circle className="h-2.5 w-2.5 fill-current text-current" />
+      </RadioGroupPrimitive.Indicator>
+    </RadioGroupPrimitive.Item>
+  )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }

+ 43 - 0
shopcall.ai-main/src/components/ui/resizable.tsx

@@ -0,0 +1,43 @@
+import { GripVertical } from "lucide-react"
+import * as ResizablePrimitive from "react-resizable-panels"
+
+import { cn } from "@/lib/utils"
+
+const ResizablePanelGroup = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
+  <ResizablePrimitive.PanelGroup
+    className={cn(
+      "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
+      className
+    )}
+    {...props}
+  />
+)
+
+const ResizablePanel = ResizablePrimitive.Panel
+
+const ResizableHandle = ({
+  withHandle,
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
+  withHandle?: boolean
+}) => (
+  <ResizablePrimitive.PanelResizeHandle
+    className={cn(
+      "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+      className
+    )}
+    {...props}
+  >
+    {withHandle && (
+      <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
+        <GripVertical className="h-2.5 w-2.5" />
+      </div>
+    )}
+  </ResizablePrimitive.PanelResizeHandle>
+)
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle }

+ 46 - 0
shopcall.ai-main/src/components/ui/scroll-area.tsx

@@ -0,0 +1,46 @@
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+  <ScrollAreaPrimitive.Root
+    ref={ref}
+    className={cn("relative overflow-hidden", className)}
+    {...props}
+  >
+    <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
+      {children}
+    </ScrollAreaPrimitive.Viewport>
+    <ScrollBar />
+    <ScrollAreaPrimitive.Corner />
+  </ScrollAreaPrimitive.Root>
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
+>(({ className, orientation = "vertical", ...props }, ref) => (
+  <ScrollAreaPrimitive.ScrollAreaScrollbar
+    ref={ref}
+    orientation={orientation}
+    className={cn(
+      "flex touch-none select-none transition-colors",
+      orientation === "vertical" &&
+        "h-full w-2.5 border-l border-l-transparent p-[1px]",
+      orientation === "horizontal" &&
+        "h-2.5 flex-col border-t border-t-transparent p-[1px]",
+      className
+    )}
+    {...props}
+  >
+    <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
+  </ScrollAreaPrimitive.ScrollAreaScrollbar>
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }

+ 158 - 0
shopcall.ai-main/src/components/ui/select.tsx

@@ -0,0 +1,158 @@
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <SelectPrimitive.Icon asChild>
+      <ChevronDown className="h-4 w-4 opacity-50" />
+    </SelectPrimitive.Icon>
+  </SelectPrimitive.Trigger>
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollUpButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronUp className="h-4 w-4" />
+  </SelectPrimitive.ScrollUpButton>
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollDownButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronDown className="h-4 w-4" />
+  </SelectPrimitive.ScrollDownButton>
+))
+SelectScrollDownButton.displayName =
+  SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
+>(({ className, children, position = "popper", ...props }, ref) => (
+  <SelectPrimitive.Portal>
+    <SelectPrimitive.Content
+      ref={ref}
+      className={cn(
+        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        position === "popper" &&
+          "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+        className
+      )}
+      position={position}
+      {...props}
+    >
+      <SelectScrollUpButton />
+      <SelectPrimitive.Viewport
+        className={cn(
+          "p-1",
+          position === "popper" &&
+            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+        )}
+      >
+        {children}
+      </SelectPrimitive.Viewport>
+      <SelectScrollDownButton />
+    </SelectPrimitive.Content>
+  </SelectPrimitive.Portal>
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Label
+    ref={ref}
+    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
+    {...props}
+  />
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <SelectPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </SelectPrimitive.ItemIndicator>
+    </span>
+
+    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+  </SelectPrimitive.Item>
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+  Select,
+  SelectGroup,
+  SelectValue,
+  SelectTrigger,
+  SelectContent,
+  SelectLabel,
+  SelectItem,
+  SelectSeparator,
+  SelectScrollUpButton,
+  SelectScrollDownButton,
+}

+ 29 - 0
shopcall.ai-main/src/components/ui/separator.tsx

@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+  React.ElementRef<typeof SeparatorPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
+>(
+  (
+    { className, orientation = "horizontal", decorative = true, ...props },
+    ref
+  ) => (
+    <SeparatorPrimitive.Root
+      ref={ref}
+      decorative={decorative}
+      orientation={orientation}
+      className={cn(
+        "shrink-0 bg-border",
+        orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
+        className
+      )}
+      {...props}
+    />
+  )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }

+ 131 - 0
shopcall.ai-main/src/components/ui/sheet.tsx

@@ -0,0 +1,131 @@
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Overlay
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+  {
+    variants: {
+      side: {
+        top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+        bottom:
+          "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+        left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+        right:
+          "inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+      },
+    },
+    defaultVariants: {
+      side: "right",
+    },
+  }
+)
+
+interface SheetContentProps
+  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
+  VariantProps<typeof sheetVariants> { }
+
+const SheetContent = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Content>,
+  SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+  <SheetPortal>
+    <SheetOverlay />
+    <SheetPrimitive.Content
+      ref={ref}
+      className={cn(sheetVariants({ side }), className)}
+      {...props}
+    >
+      {children}
+      <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </SheetPrimitive.Close>
+    </SheetPrimitive.Content>
+  </SheetPortal>
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-2 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Title
+    ref={ref}
+    className={cn("text-lg font-semibold text-foreground", className)}
+    {...props}
+  />
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+  Sheet, SheetClose,
+  SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
+}
+

+ 761 - 0
shopcall.ai-main/src/components/ui/sidebar.tsx

@@ -0,0 +1,761 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { VariantProps, cva } from "class-variance-authority"
+import { PanelLeft } from "lucide-react"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import { Sheet, SheetContent } from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const SIDEBAR_COOKIE_NAME = "sidebar:state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContext = {
+  state: "expanded" | "collapsed"
+  open: boolean
+  setOpen: (open: boolean) => void
+  openMobile: boolean
+  setOpenMobile: (open: boolean) => void
+  isMobile: boolean
+  toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext<SidebarContext | null>(null)
+
+function useSidebar() {
+  const context = React.useContext(SidebarContext)
+  if (!context) {
+    throw new Error("useSidebar must be used within a SidebarProvider.")
+  }
+
+  return context
+}
+
+const SidebarProvider = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    defaultOpen?: boolean
+    open?: boolean
+    onOpenChange?: (open: boolean) => void
+  }
+>(
+  (
+    {
+      defaultOpen = true,
+      open: openProp,
+      onOpenChange: setOpenProp,
+      className,
+      style,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const isMobile = useIsMobile()
+    const [openMobile, setOpenMobile] = React.useState(false)
+
+    // This is the internal state of the sidebar.
+    // We use openProp and setOpenProp for control from outside the component.
+    const [_open, _setOpen] = React.useState(defaultOpen)
+    const open = openProp ?? _open
+    const setOpen = React.useCallback(
+      (value: boolean | ((value: boolean) => boolean)) => {
+        const openState = typeof value === "function" ? value(open) : value
+        if (setOpenProp) {
+          setOpenProp(openState)
+        } else {
+          _setOpen(openState)
+        }
+
+        // This sets the cookie to keep the sidebar state.
+        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+      },
+      [setOpenProp, open]
+    )
+
+    // Helper to toggle the sidebar.
+    const toggleSidebar = React.useCallback(() => {
+      return isMobile
+        ? setOpenMobile((open) => !open)
+        : setOpen((open) => !open)
+    }, [isMobile, setOpen, setOpenMobile])
+
+    // Adds a keyboard shortcut to toggle the sidebar.
+    React.useEffect(() => {
+      const handleKeyDown = (event: KeyboardEvent) => {
+        if (
+          event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+          (event.metaKey || event.ctrlKey)
+        ) {
+          event.preventDefault()
+          toggleSidebar()
+        }
+      }
+
+      window.addEventListener("keydown", handleKeyDown)
+      return () => window.removeEventListener("keydown", handleKeyDown)
+    }, [toggleSidebar])
+
+    // We add a state so that we can do data-state="expanded" or "collapsed".
+    // This makes it easier to style the sidebar with Tailwind classes.
+    const state = open ? "expanded" : "collapsed"
+
+    const contextValue = React.useMemo<SidebarContext>(
+      () => ({
+        state,
+        open,
+        setOpen,
+        isMobile,
+        openMobile,
+        setOpenMobile,
+        toggleSidebar,
+      }),
+      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+    )
+
+    return (
+      <SidebarContext.Provider value={contextValue}>
+        <TooltipProvider delayDuration={0}>
+          <div
+            style={
+              {
+                "--sidebar-width": SIDEBAR_WIDTH,
+                "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
+                ...style,
+              } as React.CSSProperties
+            }
+            className={cn(
+              "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
+              className
+            )}
+            ref={ref}
+            {...props}
+          >
+            {children}
+          </div>
+        </TooltipProvider>
+      </SidebarContext.Provider>
+    )
+  }
+)
+SidebarProvider.displayName = "SidebarProvider"
+
+const Sidebar = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    side?: "left" | "right"
+    variant?: "sidebar" | "floating" | "inset"
+    collapsible?: "offcanvas" | "icon" | "none"
+  }
+>(
+  (
+    {
+      side = "left",
+      variant = "sidebar",
+      collapsible = "offcanvas",
+      className,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+    if (collapsible === "none") {
+      return (
+        <div
+          className={cn(
+            "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
+            className
+          )}
+          ref={ref}
+          {...props}
+        >
+          {children}
+        </div>
+      )
+    }
+
+    if (isMobile) {
+      return (
+        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
+          <SheetContent
+            data-sidebar="sidebar"
+            data-mobile="true"
+            className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
+            style={
+              {
+                "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
+              } as React.CSSProperties
+            }
+            side={side}
+          >
+            <div className="flex h-full w-full flex-col">{children}</div>
+          </SheetContent>
+        </Sheet>
+      )
+    }
+
+    return (
+      <div
+        ref={ref}
+        className="group peer hidden md:block text-sidebar-foreground"
+        data-state={state}
+        data-collapsible={state === "collapsed" ? collapsible : ""}
+        data-variant={variant}
+        data-side={side}
+      >
+        {/* This is what handles the sidebar gap on desktop */}
+        <div
+          className={cn(
+            "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
+            "group-data-[collapsible=offcanvas]:w-0",
+            "group-data-[side=right]:rotate-180",
+            variant === "floating" || variant === "inset"
+              ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
+              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
+          )}
+        />
+        <div
+          className={cn(
+            "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
+            side === "left"
+              ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
+              : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
+            // Adjust the padding for floating and inset variants.
+            variant === "floating" || variant === "inset"
+              ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
+              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
+            className
+          )}
+          {...props}
+        >
+          <div
+            data-sidebar="sidebar"
+            className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
+          >
+            {children}
+          </div>
+        </div>
+      </div>
+    )
+  }
+)
+Sidebar.displayName = "Sidebar"
+
+const SidebarTrigger = React.forwardRef<
+  React.ElementRef<typeof Button>,
+  React.ComponentProps<typeof Button>
+>(({ className, onClick, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <Button
+      ref={ref}
+      data-sidebar="trigger"
+      variant="ghost"
+      size="icon"
+      className={cn("h-7 w-7", className)}
+      onClick={(event) => {
+        onClick?.(event)
+        toggleSidebar()
+      }}
+      {...props}
+    >
+      <PanelLeft />
+      <span className="sr-only">Toggle Sidebar</span>
+    </Button>
+  )
+})
+SidebarTrigger.displayName = "SidebarTrigger"
+
+const SidebarRail = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button">
+>(({ className, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <button
+      ref={ref}
+      data-sidebar="rail"
+      aria-label="Toggle Sidebar"
+      tabIndex={-1}
+      onClick={toggleSidebar}
+      title="Toggle Sidebar"
+      className={cn(
+        "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
+        "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
+        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
+        "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
+        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
+        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarRail.displayName = "SidebarRail"
+
+const SidebarInset = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"main">
+>(({ className, ...props }, ref) => {
+  return (
+    <main
+      ref={ref}
+      className={cn(
+        "relative flex min-h-svh flex-1 flex-col bg-background",
+        "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInset.displayName = "SidebarInset"
+
+const SidebarInput = React.forwardRef<
+  React.ElementRef<typeof Input>,
+  React.ComponentProps<typeof Input>
+>(({ className, ...props }, ref) => {
+  return (
+    <Input
+      ref={ref}
+      data-sidebar="input"
+      className={cn(
+        "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInput.displayName = "SidebarInput"
+
+const SidebarHeader = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="header"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarHeader.displayName = "SidebarHeader"
+
+const SidebarFooter = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="footer"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarFooter.displayName = "SidebarFooter"
+
+const SidebarSeparator = React.forwardRef<
+  React.ElementRef<typeof Separator>,
+  React.ComponentProps<typeof Separator>
+>(({ className, ...props }, ref) => {
+  return (
+    <Separator
+      ref={ref}
+      data-sidebar="separator"
+      className={cn("mx-2 w-auto bg-sidebar-border", className)}
+      {...props}
+    />
+  )
+})
+SidebarSeparator.displayName = "SidebarSeparator"
+
+const SidebarContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="content"
+      className={cn(
+        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarContent.displayName = "SidebarContent"
+
+const SidebarGroup = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="group"
+      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarGroup.displayName = "SidebarGroup"
+
+const SidebarGroupLabel = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "div"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="group-label"
+      className={cn(
+        "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupLabel.displayName = "SidebarGroupLabel"
+
+const SidebarGroupAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "button"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="group-action"
+      className={cn(
+        "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 after:md:hidden",
+        "group-data-[collapsible=icon]:hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupAction.displayName = "SidebarGroupAction"
+
+const SidebarGroupContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar="group-content"
+    className={cn("w-full text-sm", className)}
+    {...props}
+  />
+))
+SidebarGroupContent.displayName = "SidebarGroupContent"
+
+const SidebarMenu = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar="menu"
+    className={cn("flex w-full min-w-0 flex-col gap-1", className)}
+    {...props}
+  />
+))
+SidebarMenu.displayName = "SidebarMenu"
+
+const SidebarMenuItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+  <li
+    ref={ref}
+    data-sidebar="menu-item"
+    className={cn("group/menu-item relative", className)}
+    {...props}
+  />
+))
+SidebarMenuItem.displayName = "SidebarMenuItem"
+
+const sidebarMenuButtonVariants = cva(
+  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+        outline:
+          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+      },
+      size: {
+        default: "h-8 text-sm",
+        sm: "h-7 text-xs",
+        lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+const SidebarMenuButton = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & {
+    asChild?: boolean
+    isActive?: boolean
+    tooltip?: string | React.ComponentProps<typeof TooltipContent>
+  } & VariantProps<typeof sidebarMenuButtonVariants>
+>(
+  (
+    {
+      asChild = false,
+      isActive = false,
+      variant = "default",
+      size = "default",
+      tooltip,
+      className,
+      ...props
+    },
+    ref
+  ) => {
+    const Comp = asChild ? Slot : "button"
+    const { isMobile, state } = useSidebar()
+
+    const button = (
+      <Comp
+        ref={ref}
+        data-sidebar="menu-button"
+        data-size={size}
+        data-active={isActive}
+        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
+        {...props}
+      />
+    )
+
+    if (!tooltip) {
+      return button
+    }
+
+    if (typeof tooltip === "string") {
+      tooltip = {
+        children: tooltip,
+      }
+    }
+
+    return (
+      <Tooltip>
+        <TooltipTrigger asChild>{button}</TooltipTrigger>
+        <TooltipContent
+          side="right"
+          align="center"
+          hidden={state !== "collapsed" || isMobile}
+          {...tooltip}
+        />
+      </Tooltip>
+    )
+  }
+)
+SidebarMenuButton.displayName = "SidebarMenuButton"
+
+const SidebarMenuAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & {
+    asChild?: boolean
+    showOnHover?: boolean
+  }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "button"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="menu-action"
+      className={cn(
+        "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 after:md:hidden",
+        "peer-data-[size=sm]/menu-button:top-1",
+        "peer-data-[size=default]/menu-button:top-1.5",
+        "peer-data-[size=lg]/menu-button:top-2.5",
+        "group-data-[collapsible=icon]:hidden",
+        showOnHover &&
+          "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuAction.displayName = "SidebarMenuAction"
+
+const SidebarMenuBadge = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar="menu-badge"
+    className={cn(
+      "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
+      "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
+      "peer-data-[size=sm]/menu-button:top-1",
+      "peer-data-[size=default]/menu-button:top-1.5",
+      "peer-data-[size=lg]/menu-button:top-2.5",
+      "group-data-[collapsible=icon]:hidden",
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuBadge.displayName = "SidebarMenuBadge"
+
+const SidebarMenuSkeleton = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    showIcon?: boolean
+  }
+>(({ className, showIcon = false, ...props }, ref) => {
+  // Random width between 50 to 90%.
+  const width = React.useMemo(() => {
+    return `${Math.floor(Math.random() * 40) + 50}%`
+  }, [])
+
+  return (
+    <div
+      ref={ref}
+      data-sidebar="menu-skeleton"
+      className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
+      {...props}
+    >
+      {showIcon && (
+        <Skeleton
+          className="size-4 rounded-md"
+          data-sidebar="menu-skeleton-icon"
+        />
+      )}
+      <Skeleton
+        className="h-4 flex-1 max-w-[--skeleton-width]"
+        data-sidebar="menu-skeleton-text"
+        style={
+          {
+            "--skeleton-width": width,
+          } as React.CSSProperties
+        }
+      />
+    </div>
+  )
+})
+SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+
+const SidebarMenuSub = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar="menu-sub"
+    className={cn(
+      "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
+      "group-data-[collapsible=icon]:hidden",
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuSub.displayName = "SidebarMenuSub"
+
+const SidebarMenuSubItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ ...props }, ref) => <li ref={ref} {...props} />)
+SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+
+const SidebarMenuSubButton = React.forwardRef<
+  HTMLAnchorElement,
+  React.ComponentProps<"a"> & {
+    asChild?: boolean
+    size?: "sm" | "md"
+    isActive?: boolean
+  }
+>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+  const Comp = asChild ? Slot : "a"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="menu-sub-button"
+      data-size={size}
+      data-active={isActive}
+      className={cn(
+        "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+        size === "sm" && "text-xs",
+        size === "md" && "text-sm",
+        "group-data-[collapsible=icon]:hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+
+export {
+  Sidebar,
+  SidebarContent,
+  SidebarFooter,
+  SidebarGroup,
+  SidebarGroupAction,
+  SidebarGroupContent,
+  SidebarGroupLabel,
+  SidebarHeader,
+  SidebarInput,
+  SidebarInset,
+  SidebarMenu,
+  SidebarMenuAction,
+  SidebarMenuBadge,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  SidebarMenuSkeleton,
+  SidebarMenuSub,
+  SidebarMenuSubButton,
+  SidebarMenuSubItem,
+  SidebarProvider,
+  SidebarRail,
+  SidebarSeparator,
+  SidebarTrigger,
+  useSidebar,
+}

+ 15 - 0
shopcall.ai-main/src/components/ui/skeleton.tsx

@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) {
+  return (
+    <div
+      className={cn("animate-pulse rounded-md bg-muted", className)}
+      {...props}
+    />
+  )
+}
+
+export { Skeleton }

+ 26 - 0
shopcall.ai-main/src/components/ui/slider.tsx

@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider"
+
+import { cn } from "@/lib/utils"
+
+const Slider = React.forwardRef<
+  React.ElementRef<typeof SliderPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <SliderPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative flex w-full touch-none select-none items-center",
+      className
+    )}
+    {...props}
+  >
+    <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
+      <SliderPrimitive.Range className="absolute h-full bg-primary" />
+    </SliderPrimitive.Track>
+    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
+  </SliderPrimitive.Root>
+))
+Slider.displayName = SliderPrimitive.Root.displayName
+
+export { Slider }

+ 29 - 0
shopcall.ai-main/src/components/ui/sonner.tsx

@@ -0,0 +1,29 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, toast } from "sonner"
+
+type ToasterProps = React.ComponentProps<typeof Sonner>
+
+const Toaster = ({ ...props }: ToasterProps) => {
+  const { theme = "system" } = useTheme()
+
+  return (
+    <Sonner
+      theme={theme as ToasterProps["theme"]}
+      className="toaster group"
+      toastOptions={{
+        classNames: {
+          toast:
+            "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+          description: "group-[.toast]:text-muted-foreground",
+          actionButton:
+            "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+          cancelButton:
+            "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+        },
+      }}
+      {...props}
+    />
+  )
+}
+
+export { Toaster, toast }

+ 27 - 0
shopcall.ai-main/src/components/ui/switch.tsx

@@ -0,0 +1,27 @@
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+  React.ElementRef<typeof SwitchPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+  <SwitchPrimitives.Root
+    className={cn(
+      "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+      className
+    )}
+    {...props}
+    ref={ref}
+  >
+    <SwitchPrimitives.Thumb
+      className={cn(
+        "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
+      )}
+    />
+  </SwitchPrimitives.Root>
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }

+ 117 - 0
shopcall.ai-main/src/components/ui/table.tsx

@@ -0,0 +1,117 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Table = React.forwardRef<
+  HTMLTableElement,
+  React.HTMLAttributes<HTMLTableElement>
+>(({ className, ...props }, ref) => (
+  <div className="relative w-full overflow-auto">
+    <table
+      ref={ref}
+      className={cn("w-full caption-bottom text-sm", className)}
+      {...props}
+    />
+  </div>
+))
+Table.displayName = "Table"
+
+const TableHeader = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
+))
+TableHeader.displayName = "TableHeader"
+
+const TableBody = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <tbody
+    ref={ref}
+    className={cn("[&_tr:last-child]:border-0", className)}
+    {...props}
+  />
+))
+TableBody.displayName = "TableBody"
+
+const TableFooter = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <tfoot
+    ref={ref}
+    className={cn(
+      "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
+      className
+    )}
+    {...props}
+  />
+))
+TableFooter.displayName = "TableFooter"
+
+const TableRow = React.forwardRef<
+  HTMLTableRowElement,
+  React.HTMLAttributes<HTMLTableRowElement>
+>(({ className, ...props }, ref) => (
+  <tr
+    ref={ref}
+    className={cn(
+      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
+      className
+    )}
+    {...props}
+  />
+))
+TableRow.displayName = "TableRow"
+
+const TableHead = React.forwardRef<
+  HTMLTableCellElement,
+  React.ThHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+  <th
+    ref={ref}
+    className={cn(
+      "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
+      className
+    )}
+    {...props}
+  />
+))
+TableHead.displayName = "TableHead"
+
+const TableCell = React.forwardRef<
+  HTMLTableCellElement,
+  React.TdHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+  <td
+    ref={ref}
+    className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
+    {...props}
+  />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+  HTMLTableCaptionElement,
+  React.HTMLAttributes<HTMLTableCaptionElement>
+>(({ className, ...props }, ref) => (
+  <caption
+    ref={ref}
+    className={cn("mt-4 text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+  Table,
+  TableHeader,
+  TableBody,
+  TableFooter,
+  TableHead,
+  TableRow,
+  TableCell,
+  TableCaption,
+}

+ 53 - 0
shopcall.ai-main/src/components/ui/tabs.tsx

@@ -0,0 +1,53 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
+      className
+    )}
+    {...props}
+  />
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Content
+    ref={ref}
+    className={cn(
+      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+      className
+    )}
+    {...props}
+  />
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }

+ 24 - 0
shopcall.ai-main/src/components/ui/textarea.tsx

@@ -0,0 +1,24 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface TextareaProps
+  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
+
+const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
+  ({ className, ...props }, ref) => {
+    return (
+      <textarea
+        className={cn(
+          "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+          className
+        )}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Textarea.displayName = "Textarea"
+
+export { Textarea }

+ 127 - 0
shopcall.ai-main/src/components/ui/toast.tsx

@@ -0,0 +1,127 @@
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Viewport>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Viewport
+    ref={ref}
+    className={cn(
+      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
+      className
+    )}
+    {...props}
+  />
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+  {
+    variants: {
+      variant: {
+        default: "border bg-background text-foreground",
+        destructive:
+          "destructive group border-destructive bg-destructive text-destructive-foreground",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+const Toast = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
+    VariantProps<typeof toastVariants>
+>(({ className, variant, ...props }, ref) => {
+  return (
+    <ToastPrimitives.Root
+      ref={ref}
+      className={cn(toastVariants({ variant }), className)}
+      {...props}
+    />
+  )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Action>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Action
+    ref={ref}
+    className={cn(
+      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
+      className
+    )}
+    {...props}
+  />
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Close>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Close
+    ref={ref}
+    className={cn(
+      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
+      className
+    )}
+    toast-close=""
+    {...props}
+  >
+    <X className="h-4 w-4" />
+  </ToastPrimitives.Close>
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Title>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Title
+    ref={ref}
+    className={cn("text-sm font-semibold", className)}
+    {...props}
+  />
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Description>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Description
+    ref={ref}
+    className={cn("text-sm opacity-90", className)}
+    {...props}
+  />
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
+
+type ToastActionElement = React.ReactElement<typeof ToastAction>
+
+export {
+  type ToastProps,
+  type ToastActionElement,
+  ToastProvider,
+  ToastViewport,
+  Toast,
+  ToastTitle,
+  ToastDescription,
+  ToastClose,
+  ToastAction,
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio