2026-06-28 04:50:29 -04:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > AppForge Documentation - Website to Native Mobile App Platform</ title >
2026-06-29 02:40:49 -04:00
< meta name = "description" content = "Complete documentation for AppForge — convert websites into native iOS and Android apps with built-in SaaS features, admin panel, and credit system." >
2026-06-28 04:50:29 -04:00
< link rel = "stylesheet" href = "styles.css" >
< link rel = "icon" type = "image/svg+xml" href = "../public/favicon.svg" >
< link rel = "icon" type = "image/png" href = "../public/favicon.png" >
< script src = "script.js" ></ script >
</ head >
< body >
<!-- Reading Progress Bar -->
< div class = "reading-progress" ></ div >
< nav class = "nav" >
< div class = "nav-container" >
< a href = "#top" class = "nav-logo" >
< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" width = "26" height = "26" >< rect x = "5" y = "2" width = "14" height = "20" rx = "2" ry = "2" />< line x1 = "12" y1 = "18" x2 = "12.01" y2 = "18" /></ svg >
AppForge< span class = "logo-dot" > Docs</ span >
</ a >
< div class = "nav-center" >
< a href = "#quick-start" > Quick Start</ a >
< a href = "#getting-started" > Setup Guide</ a >
< a href = "#codemagic-pipeline" > Build Pipeline</ a >
< a href = "#api-reference" > API Reference</ a >
< a href = "#deployment" > Deployment</ a >
< a href = "#changelog" > Changelog</ a >
</ div >
< div class = "nav-right" >
< button class = "theme-toggle" onclick = "toggleTheme()" title = "Toggle theme" >
< svg class = "sun-icon" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></ svg >
< svg class = "moon-icon" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></ svg >
</ button >
</ div >
</ div >
</ nav >
< div class = "docs-layout" >
< aside class = "sidebar" id = "sidebar" >
<!-- Sidebar Search -->
< div style = "padding: 12px 16px 8px;" >
< input type = "text" id = "sidebarSearch" placeholder = "Search docs..." oninput = "filterSidebar()" style = "width: 100%; padding: 8px 12px; font-size: 13px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-elevated); color: var(--text); outline: none; font-family: inherit;" />
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Introduction</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#top" class = "sidebar-link active" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />< polyline points = "9,22 9,12 15,12 15,22" /></ svg > Overview</ a ></ li >
< li >< a href = "#quick-start" class = "sidebar-link sidebar-link-sub" > ⚡ Quick Start (5 Phases)</ a ></ li >
< li >< a href = "#what-is-appforge" class = "sidebar-link sidebar-link-sub" > What is AppForge</ a ></ li >
< li >< a href = "#roadmap" class = "sidebar-link sidebar-link-sub" > Workflow Roadmap</ a ></ li >
< li >< a href = "#architecture" class = "sidebar-link sidebar-link-sub" > Architecture</ a ></ li >
< li >< a href = "#tech-stack" class = "sidebar-link sidebar-link-sub" > Tech Stack</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Getting Started</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#getting-started" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< polygon points = "13 2 3 14 12 14 11 22 21 10 12 10 13 2" /></ svg > Setup Guide</ a ></ li >
< li >< a href = "#prerequisites" class = "sidebar-link sidebar-link-sub" > Prerequisites</ a ></ li >
< li >< a href = "#install" class = "sidebar-link sidebar-link-sub" > 1. Install</ a ></ li >
< li >< a href = "#database-setup-step" class = "sidebar-link sidebar-link-sub" > 2. Database Setup</ a ></ li >
< li >< a href = "#env-config" class = "sidebar-link sidebar-link-sub" > 3. Environment</ a ></ li >
< li >< a href = "#db-setup" class = "sidebar-link sidebar-link-sub" > 4. Database Setup</ a ></ li >
< li >< a href = "#start-dev" class = "sidebar-link sidebar-link-sub" > 5. Start Dev Server</ a ></ li >
< li >< a href = "#setup-wizard" class = "sidebar-link sidebar-link-sub" > 6. Setup Wizard</ a ></ li >
< li >< a href = "#admin-config" class = "sidebar-link sidebar-link-sub" > 7. Admin Config</ a ></ li >
< li >< a href = "#first-build" class = "sidebar-link sidebar-link-sub" > 8. First Build</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Reference</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#api-reference" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />< polyline points = "14,2 14,8 20,8" /></ svg > API Reference</ a ></ li >
< li >< a href = "#native-hooks" class = "sidebar-link sidebar-link-sub" > Native Hooks</ a ></ li >
< li >< a href = "#edge-functions" class = "sidebar-link sidebar-link-sub" > Edge Functions</ a ></ li >
< li >< a href = "#edge-functions-detail" class = "sidebar-link sidebar-link-sub" > Edge Functions Detail</ a ></ li >
< li >< a href = "#credits-api" class = "sidebar-link sidebar-link-sub" > Credits API</ a ></ li >
< li >< a href = "#admin-api" class = "sidebar-link sidebar-link-sub" > Admin API</ a ></ li >
< li >< a href = "#auth-context" class = "sidebar-link sidebar-link-sub" > Auth Context</ a ></ li >
< li >< a href = "#native-features" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< rect x = "5" y = "2" width = "14" height = "20" rx = "2" ry = "2" />< line x1 = "12" y1 = "18" x2 = "12.01" y2 = "18" /></ svg > Native Features</ a ></ li >
< li >< a href = "#features-overview" class = "sidebar-link sidebar-link-sub" > Features Overview</ a ></ li >
< li >< a href = "#database-schema" class = "sidebar-link sidebar-link-sub" > Database Schema</ a ></ li >
< li >< a href = "#env-reference" class = "sidebar-link sidebar-link-sub" > Environment Variables</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Build Pipeline</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#codemagic-pipeline" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M22 12h-4l-3 9L9 3l-3 9H2" /></ svg > Codemagic CI/CD</ a ></ li >
< li >< a href = "#cm-architecture" class = "sidebar-link sidebar-link-sub" > Build Architecture</ a ></ li >
< li >< a href = "#cm-yaml" class = "sidebar-link sidebar-link-sub" > codemagic.yaml</ a ></ li >
< li >< a href = "#cm-android" class = "sidebar-link sidebar-link-sub" > Android Pipeline</ a ></ li >
< li >< a href = "#cm-ios" class = "sidebar-link sidebar-link-sub" > iOS Pipeline</ a ></ li >
< li >< a href = "#cm-troubleshooting" class = "sidebar-link sidebar-link-sub" > Troubleshooting</ a ></ li >
< li >< a href = "#cm-e2e-setup" class = "sidebar-link sidebar-link-sub" > End-to-End Setup</ a ></ li >
< li >< a href = "#cm-webhook-flow" class = "sidebar-link sidebar-link-sub" > Webhook Flow</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Admin & Config</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#storage-admin" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /></ svg > Storage & Files</ a ></ li >
< li >< a href = "#storage-buckets" class = "sidebar-link sidebar-link-sub" > Bucket Management</ a ></ li >
< li >< a href = "#storage-optimization" class = "sidebar-link sidebar-link-sub" > Image Optimization</ a ></ li >
< li >< a href = "#storage-quotas" class = "sidebar-link sidebar-link-sub" > Tiered Quotas</ a ></ li >
< li >< a href = "#storage-api-usage" class = "sidebar-link sidebar-link-sub" > Storage API Usage</ a ></ li >
< li >< a href = "#integrations-admin" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< circle cx = "12" cy = "12" r = "3" />< path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /></ svg > Integrations</ a ></ li >
< li >< a href = "#int-resend" class = "sidebar-link sidebar-link-sub" > Resend (Email)</ a ></ li >
< li >< a href = "#int-appetize" class = "sidebar-link sidebar-link-sub" > Appetize.io</ a ></ li >
< li >< a href = "#int-ai" class = "sidebar-link sidebar-link-sub" > AI Providers</ a ></ li >
< li >< a href = "#int-codemagic-wizard" class = "sidebar-link sidebar-link-sub" > Codemagic Wizard</ a ></ li >
< li >< a href = "#payment-gateways" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< rect width = "20" height = "14" x = "2" y = "5" rx = "2" />< line x1 = "2" x2 = "22" y1 = "10" y2 = "10" /></ svg > Payment Gateways</ a ></ li >
< li >< a href = "#pg-stripe" class = "sidebar-link sidebar-link-sub" > Stripe</ a ></ li >
< li >< a href = "#pg-paypal" class = "sidebar-link sidebar-link-sub" > PayPal</ a ></ li >
< li >< a href = "#pg-coinbase" class = "sidebar-link sidebar-link-sub" > Coinbase</ a ></ li >
< li >< a href = "#pg-bank" class = "sidebar-link sidebar-link-sub" > Bank Transfer</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Systems</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#project-structure" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /></ svg > Project Structure</ a ></ li >
< li >< a href = "#app-routes" class = "sidebar-link sidebar-link-sub" > Application Routes</ a ></ li >
< li >< a href = "#keyboard-shortcuts" class = "sidebar-link sidebar-link-sub" > Keyboard Shortcuts</ a ></ li >
< li >< a href = "#gdpr-consent" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></ svg > GDPR & Cookie Consent</ a ></ li >
< li >< a href = "#invoice-system" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />< polyline points = "14,2 14,8 20,8" /></ svg > Invoice System</ a ></ li >
< li >< a href = "#webhook-logs" class = "sidebar-link sidebar-link-sub" > Webhook Logs</ a ></ li >
< li >< a href = "#automation-system" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M22 12h-4l-3 9L9 3l-3 9H2" /></ svg > Automation System</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Deployment</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#deployment" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< rect x = "2" y = "3" width = "20" height = "14" rx = "2" ry = "2" /></ svg > Deploy to Production</ a ></ li >
< li >< a href = "#nginx-config" class = "sidebar-link sidebar-link-sub" > Nginx Configuration</ a ></ li >
< li >< a href = "#updating" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< polyline points = "23 4 23 10 17 10" />< path d = "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" /></ svg > Updating & Redeployment</ a ></ li >
< li >< a href = "#production-guide" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></ svg > Production Guide</ a ></ li >
</ ul >
</ div >
< div class = "sidebar-section" >
< div class = "sidebar-title" > Resources</ div >
< ul class = "sidebar-nav" >
< li >< a href = "#changelog" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< path d = "M12 8v4l3 3" />< circle cx = "12" cy = "12" r = "10" /></ svg > Changelog</ a ></ li >
< li >< a href = "#help-center" class = "sidebar-link" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" width = "15" height = "15" >< circle cx = "12" cy = "12" r = "10" />< path d = "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />< line x1 = "12" y1 = "17" x2 = "12.01" y2 = "17" /></ svg > Help Center</ a ></ li >
</ ul >
</ div >
</ aside >
< div class = "sidebar-overlay" id = "sidebarOverlay" onclick = "toggleSidebar()" ></ div >
< button class = "sidebar-toggle" onclick = "toggleSidebar()" title = "Toggle navigation" >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M4 6h16M4 12h16M4 18h16" /></ svg >
</ button >
< div class = "docs-content" >
< main class = "main-content" id = "top" >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: OVERVIEW / HERO -->
<!-- ═══════════════════════════════════════════════════════════ -->
< header class = "hero" >
< div class = "version-badge" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" />< line x1 = "7" y1 = "7" x2 = "7.01" y2 = "7" /></ svg >
v2.0.4
</ div >
< h1 > AppForge Documentation</ h1 >
2026-06-29 02:40:49 -04:00
< p > The complete self-hosted SaaS platform for converting websites into native iOS & Android apps.< br > Built with React, TypeScript, Express, Postgres, and Capacitor.</ p >
2026-06-28 04:50:29 -04:00
< div class = "flex gap-3 justify-center flex-wrap" >
< a href = "#quick-start" class = "btn btn-primary" > Quick Start →</ a >
< a href = "#api-reference" class = "btn btn-outline" > API Reference</ a >
</ div >
</ header >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- QUICK START: Linear process overview -->
<!-- ═══════════════════════════════════════════════════════════ -->
< section id = "quick-start" class = "mb-16" >
< h2 class = "font-bold mb-2" > Quick Start — From Zero to Production</ h2 >
< p class = "text-muted mb-6" > Follow these 5 phases in order. Each phase links to its detailed section.</ p >
< div style = "display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem;" >
<!-- Phase 1 -->
< a href = "#getting-started" class = "card card-flat" style = "text-decoration: none; border-left: 3px solid var(--accent);" >
< div style = "font-size: 2rem; font-weight: 800; color: var(--accent); line-height: 1;" > 1</ div >
< h4 class = "font-semibold mb-1" style = "margin-top: 0.5rem;" > Install & Configure</ h4 >
< p class = "text-xs text-muted" > Clone repo → Install deps → Set up Postgres → Configure < code > .env</ code > → Run migrations → Start dev server</ p >
< span class = "text-xs" style = "color: var(--accent);" > → Setup Guide</ span >
</ a >
<!-- Phase 2 -->
< a href = "#setup-wizard" class = "card card-flat" style = "text-decoration: none; border-left: 3px solid var(--success);" >
< div style = "font-size: 2rem; font-weight: 800; color: var(--success); line-height: 1;" > 2</ div >
< h4 class = "font-semibold mb-1" style = "margin-top: 0.5rem;" > Setup Wizard</ h4 >
< p class = "text-xs text-muted" > Create admin account → Configure app name & branding → Set pricing plans → Launch</ p >
< span class = "text-xs" style = "color: var(--success);" > → Wizard Guide</ span >
</ a >
<!-- Phase 3 -->
< a href = "#integrations-admin" class = "card card-flat" style = "text-decoration: none; border-left: 3px solid var(--warning);" >
< div style = "font-size: 2rem; font-weight: 800; color: var(--warning); line-height: 1;" > 3</ div >
< h4 class = "font-semibold mb-1" style = "margin-top: 0.5rem;" > Connect Services</ h4 >
< p class = "text-xs text-muted" > Codemagic API token → Resend email → Payment gateways (Stripe/PayPal) → Storage</ p >
< span class = "text-xs" style = "color: var(--warning);" > → Integrations</ span >
</ a >
<!-- Phase 4 -->
< a href = "#codemagic-pipeline" class = "card card-flat" style = "text-decoration: none; border-left: 3px solid var(--info);" >
< div style = "font-size: 2rem; font-weight: 800; color: var(--info); line-height: 1;" > 4</ div >
< h4 class = "font-semibold mb-1" style = "margin-top: 0.5rem;" > Build Apps</ h4 >
< p class = "text-xs text-muted" > Export to GitHub → Configure Codemagic → Build Android APK or iOS IPA → Download</ p >
< span class = "text-xs" style = "color: var(--info);" > → Build Pipeline</ span >
</ a >
<!-- Phase 5 -->
< a href = "#deployment" class = "card card-flat" style = "text-decoration: none; border-left: 3px solid var(--primary);" >
< div style = "font-size: 2rem; font-weight: 800; color: var(--primary); line-height: 1;" > 5</ div >
< h4 class = "font-semibold mb-1" style = "margin-top: 0.5rem;" > Deploy to Production</ h4 >
< p class = "text-xs text-muted" > Build for production → Configure Nginx/reverse proxy → SSL → Domain → Go live</ p >
< span class = "text-xs" style = "color: var(--primary);" > → Deployment</ span >
</ a >
</ div >
< div class = "callout callout-warning" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > Follow the order!</ strong >< p class = "text-sm text-muted" > Each phase depends on the previous one. Don't skip ahead — e.g. Codemagic builds won't work without a GitHub export, and the setup wizard requires a running dev server with database migrations applied.</ p ></ div >
</ div >
</ section >
<!-- What is AppForge -->
< section id = "what-is-appforge" class = "mb-16" >
< h2 class = "font-bold mb-4" > What is AppForge?</ h2 >
< p class = "text-muted mb-6" > AppForge is a self-hosted platform that lets you convert any website into a native mobile app. It includes everything you need to run a SaaS business: user authentication, subscription billing, admin panel, credit system, and cloud build pipeline — all out of the box.</ p >
< div class = "grid grid-3" >
< div class = "card card-flat" >
< div class = "icon-box icon-box-primary" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< rect x = "5" y = "2" width = "14" height = "20" rx = "2" ry = "2" />< line x1 = "12" y1 = "18" x2 = "12.01" y2 = "18" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > App Builder</ h4 >
< p class = "text-xs text-muted" > 4-step wizard: Enter URL → Configure → Preview → Build APK/IPA via Codemagic</ p >
</ div >
< div class = "card card-flat" >
< div class = "icon-box icon-box-info" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > Admin Panel</ h4 >
< p class = "text-xs text-muted" > User management, analytics, payments, system settings, and more</ p >
</ div >
< div class = "card card-flat" >
< div class = "icon-box icon-box-success" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< rect width = "20" height = "14" x = "2" y = "5" rx = "2" />< line x1 = "2" x2 = "22" y1 = "10" y2 = "10" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > Billing & Credits</ h4 >
< p class = "text-xs text-muted" > Stripe, PayPal, Coinbase, bank transfers with subscription tiers</ p >
</ div >
</ div >
</ section >
<!-- Workflow Roadmap -->
< section id = "roadmap" class = "mb-16" >
< h2 class = "font-bold mb-4" > Workflow Roadmap</ h2 >
< p class = "text-muted mb-6" > Follow these steps in order to go from a fresh download to a production-ready deployment:</ p >
< div class = "card mb-4" >< div class = "flex items-start gap-3" >< span class = "step-number" > 1</ span >< div >< h3 class = "font-semibold mb-1" > Install & Configure Environment</ h3 >< p class = "text-sm text-muted mb-2" > Clone the repo, install dependencies, create a Postgres database, configure your < code > .env</ code > file, apply database migrations, and start the dev server.</ p >< a href = "#install" class = "text-sm" > → Installation Guide</ a ></ div ></ div ></ div >
< div class = "card mb-4" >< div class = "flex items-start gap-3" >< span class = "step-number" > 2</ span >< div >< h3 class = "font-semibold mb-1" > Run the Setup Wizard</ h3 >< p class = "text-sm text-muted mb-2" > On first launch, the 4-step setup wizard guides you through environment checks, admin account creation, and app configuration.</ p >< a href = "#setup-wizard" class = "text-sm" > → Setup Wizard Guide</ a ></ div ></ div ></ div >
< div class = "card mb-4" >< div class = "flex items-start gap-3" >< span class = "step-number" > 3</ span >< div >< h3 class = "font-semibold mb-1" > Configure Admin Panel</ h3 >< p class = "text-sm text-muted mb-2" > Set up payment gateways, pricing plans, credit packs, email templates, integrations, and system settings from < code > /admin</ code > .</ p >< a href = "#admin-config" class = "text-sm" > → Admin Configuration</ a ></ div ></ div ></ div >
< div class = "card mb-4" >< div class = "flex items-start gap-3" >< span class = "step-number" > 4</ span >< div >< h3 class = "font-semibold mb-1" > Build Your First App</ h3 >< p class = "text-sm text-muted mb-2" > Navigate to the App Builder, enter a website URL, customize configuration, preview on device mockups, and generate your first APK/IPA.</ p >< a href = "#first-build" class = "text-sm" > → First Build Guide</ a ></ div ></ div ></ div >
< div class = "card mb-4" >< div class = "flex items-start gap-3" >< span class = "step-number" > 5</ span >< div >< h3 class = "font-semibold mb-1" > Deploy to Production</ h3 >< p class = "text-sm text-muted mb-2" > Build for production, upload to your hosting (cPanel, VPS, or cloud), configure SSL, set up edge function secrets, and verify.</ p >< a href = "#deployment" class = "text-sm" > → Deployment Guide</ a > · < a href = "#production-guide" class = "text-sm" > → Production Checklist</ a ></ div ></ div ></ div >
</ section >
<!-- Architecture Overview -->
< section id = "architecture" class = "mb-16" >
< h2 class = "font-bold mb-4" > Architecture Overview</ h2 >
< p class = "text-muted mb-6" > AppForge is a client-side React application backed by a self-hosted Express + Postgres backend for auth, database, storage, and functions. Builds are compiled via Codemagic CI/CD.</ p >
< div class = "card card-flat" >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2" > Frontend (Client)</ h3 >
< ul class = "text-sm text-muted" >
< li >< strong > React 18</ strong > — UI framework with TypeScript</ li >
< li >< strong > Vite</ strong > — Build tool with code splitting</ li >
< li >< strong > Tailwind CSS + shadcn/ui</ strong > — Styling & components</ li >
< li >< strong > Capacitor 8</ strong > — Native device API bridge</ li >
< li >< strong > TanStack Query</ strong > — Server state management</ li >
< li >< strong > Zustand</ strong > — Client state management</ li >
< li >< strong > Framer Motion</ strong > — Animations</ li >
< li >< strong > React Router</ strong > — Client-side routing</ li >
</ ul >
</ div >
< div >
< h3 class = "font-semibold mb-2" > Backend (Express + Postgres)</ h3 >
< ul class = "text-sm text-muted" >
< li >< strong > PostgreSQL</ strong > — Database with RLS policies</ li >
< li >< strong > Better Auth</ strong > — Email/password, Google OAuth, magic links</ li >
< li >< strong > Local file storage</ strong > — File storage (builds, icons, avatars)</ li >
< li >< strong > Edge Functions</ strong > — Deno serverless functions (auto-deployed)</ li >
< li >< strong > Realtime</ strong > — Live database subscriptions</ li >
< li >< strong > Database Functions</ strong > — < code > use_credits</ code > , < code > add_credits</ code > , < code > has_role</ code > , < code > get_user_role</ code ></ li >
</ ul >
</ div >
</ div >
</ div >
</ section >
<!-- Features Overview -->
< section id = "features-overview" class = "mb-16" >
< h2 class = "font-bold mb-4" > Features Overview</ h2 >
< h3 class = "font-semibold mb-3 mt-6" > App Builder</ h3 >
< p class = "text-muted text-sm mb-4" > 4-step wizard that converts any website into a native mobile app:</ p >
< div class = "grid grid-2 mb-6" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Step 1: Enter URL</ h4 >< p class = "text-xs text-muted" > AI analyzes your site — extracts metadata, colors, favicon, and recommends features automatically.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Step 2: Configure</ h4 >< p class = "text-xs text-muted" > Set app name, colors, navigation style (tabs/drawer/bottom-nav), icon, splash screen, and toggle native features.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Step 3: Preview</ h4 >< p class = "text-xs text-muted" > Live device mockup preview with Appetize.io integration. Test on multiple device frames.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Step 4: Build & Download</ h4 >< p class = "text-xs text-muted" > Generate Android APK/AAB or iOS IPA via Codemagic cloud build. Real-time progress tracking.</ p ></ div >
</ div >
< h3 class = "font-semibold mb-3" > Admin Panel</ h3 >
< p class = "text-muted text-sm mb-4" > Full administration dashboard at < code > /admin</ code > (requires admin role):</ p >
< div class = "grid grid-3 mb-6" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > User Management</ h4 >< p class = "text-xs text-muted" > View, edit, assign roles (admin/moderator/user), manage credits.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Analytics</ h4 >< p class = "text-xs text-muted" > Build stats, revenue tracking, user growth, credit usage charts.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Build Monitoring</ h4 >< p class = "text-xs text-muted" > Real-time build status, error logs, queue management.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > System Settings</ h4 >< p class = "text-xs text-muted" > App name, maintenance mode, demo mode, custom CSS, theme.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Payment Gateways</ h4 >< p class = "text-xs text-muted" > Configure Stripe, PayPal, Coinbase, bank transfers (sandbox/live).</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Pricing Plans</ h4 >< p class = "text-xs text-muted" > Create/edit Free, Pro, Enterprise tiers with feature lists.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Credit Packs</ h4 >< p class = "text-xs text-muted" > Purchasable credit bundles with custom pricing.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Email Templates</ h4 >< p class = "text-xs text-muted" > Customize transactional emails (welcome, reset, build done).</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Integrations</ h4 >< p class = "text-xs text-muted" > Resend, Appetize.io, AI providers, Codemagic CI/CD setup.</ p ></ div >
</ div >
< h3 class = "font-semibold mb-3" > Credit & Subscription System</ h3 >
< p class = "text-muted text-sm mb-4" > Credit-based billing model. Each build consumes credits (configurable).</ p >
< div class = "card card-flat mb-4" >
< table class = "env-table" >
< thead >< tr >< th > Setting</ th >< th > Default</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td > default_signup_credits</ td >< td > 5</ td >< td > Free credits given to every new user on signup</ td ></ tr >
< tr >< td > credits_per_build</ td >< td > 1</ td >< td > Credits consumed per app build</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-3 mt-6" > Authentication & Roles</ h3 >
< p class = "text-muted text-sm mb-4" > Better Auth with email/password, Google OAuth, and magic links. Role-based access control via < code > user_roles</ code > table.</ p >
< div class = "card card-flat mb-4" >
< table class = "env-table" >
< thead >< tr >< th > Role</ th >< th > Access</ th ></ tr ></ thead >
< tbody >
< tr >< td > admin</ td >< td > Full access — admin panel, user management, system settings, all CRUD</ td ></ tr >
< tr >< td > moderator</ td >< td > Limited admin — analytics, build management (no system settings)</ td ></ tr >
< tr >< td > user</ td >< td > Standard — app builder, own builds, subscription management, settings</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-3 mt-6" > Dynamic Branding</ h3 >
< p class = "text-muted text-sm mb-4" > The application name, tagline, logo, and colors are all configurable from < code > Admin → System Settings</ code > . Changes propagate automatically to the navbar, auth pages, footer, help, and legal pages.</ p >
< h3 class = "font-semibold mb-3 mt-6" > Demo Mode</ h3 >
< p class = "text-muted text-sm mb-4" > Test environment with special behavior for demo accounts (< code > admin@demo.com</ code > and < code > user@demo.com</ code > ).</ p >
< div class = "callout callout-info mb-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" />< path d = "M12 16v-4" />< path d = "M12 8h.01" /></ svg >
< div >
< strong class = "text-sm" > Priority Order</ strong >
< p class = "text-sm text-muted" >< code > VITE_DEMO_MODE</ code > env var takes priority over the database < code > demo_mode</ code > system setting. If not set, database value is used.</ p >
</ div >
</ div >
< h3 class = "font-semibold mb-3 mt-6" > Progressive Web App (PWA)</ h3 >
< p class = "text-muted text-sm mb-2" > AppForge itself is a PWA with offline support and install prompts via < code > vite-plugin-pwa</ code > .</ p >
< ul class = "text-sm text-muted" >
< li > Auto-update service worker</ li >
< li > Precache static assets, runtime caching for fonts</ li >
< li > Install prompt via < code > usePWAInstall</ code > hook</ li >
< li > Responsive manifest with 192px, 512px, and maskable icons</ li >
</ ul >
</ section >
<!-- Tech Stack -->
< section id = "tech-stack" class = "card card-flat mb-16" >
< h2 class = "font-bold mb-4" > Technology Stack</ h2 >
< div class = "grid grid-4" >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > React 18</ p >< p class = "text-xs text-muted" > UI Framework</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > Vite</ p >< p class = "text-xs text-muted" > Build Tool</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > Tailwind CSS</ p >< p class = "text-xs text-muted" > Styling</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > Capacitor 8</ p >< p class = "text-xs text-muted" > Native Bridge</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > TypeScript</ p >< p class = "text-xs text-muted" > Type Safety</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > PostgreSQL</ p >< p class = "text-xs text-muted" > Database</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > TanStack Query</ p >< p class = "text-xs text-muted" > Data Fetching</ p ></ div >
< div class = "text-center p-4" >< p class = "font-medium text-sm" > Zustand</ p >< p class = "text-xs text-muted" > State Mgmt</ p ></ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: GETTING STARTED -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "getting-started" class = "mb-16" >
< h1 class = "mb-4" > Getting Started</ h1 >
< p class = "text-xl text-muted mb-8" > Complete step-by-step guide from fresh download to your first working app build.</ p >
<!-- Prerequisites -->
< div id = "prerequisites" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Prerequisites</ h2 >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2" > Required</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > Node.js 18+</ strong > and npm (or bun) — < a href = "https://github.com/nvm-sh/nvm" target = "_blank" > install via nvm</ a ></ li >
< li >< strong > Git</ strong > — to clone the repository</ li >
< li >< strong > PostgreSQL database</ strong > — any managed or self-hosted Postgres instance</ li >
< li >< strong > Node.js 18+</ strong > — runs the Express backend and applies the schema via < code > npm run db:push</ code ></ li >
< li >< strong > Modern browser</ strong > — Chrome, Firefox, Safari, or Edge</ li >
</ ul >
</ div >
< div >
< h3 class = "font-semibold mb-2" > For Cloud Builds</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > Codemagic account</ strong > — for Android/iOS cloud builds</ li >
< li >< strong > GitHub repository</ strong > — connected to Codemagic for source code</ li >
< li > Google Play Developer account ($25 one-time fee) for Android publishing</ li >
< li > Apple Developer account ($99/year) for iOS publishing</ li >
</ ul >
</ div >
</ div >
</ div >
<!-- Step 1: Install -->
< div id = "install" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 1: Clone & Install Dependencies</ h2 >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 1</ span > Clone the repository</ h3 >
< pre >< code > git clone < YOUR_GIT_URL>
cd appforge</ code ></ pre >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 2</ span > Install dependencies</ h3 >
< pre >< code > npm install
< span class = "syntax-comment" > # or: bun install</ span ></ code ></ pre >
</ div >
</ div >
<!-- Step 2: Database Setup -->
< div id = "database-setup-step" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 2: Provision a PostgreSQL Database</ h2 >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 1</ span > Create a new project</ h3 >
< p class = "text-muted text-sm mb-2" > Provision a PostgreSQL database (managed service or self-hosted) and note its connection details:</ p >
< ul class = "text-muted text-sm" >
< li >< strong > Connection string</ strong > — e.g. < code > postgres://user:pass@host:5432/postgres</ code ></ li >
< li >< strong > Anon/Public Key</ strong > — starts with < code > eyJ...</ code ></ li >
< li >< strong > Project Reference ID</ strong > — the < code > abcdefgh</ code > part of the URL</ li >
< li >< strong > Service Role Key</ strong > — for edge function secrets (keep this private!)</ li >
</ ul >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 2</ span > Enable Email Auth</ h3 >
< p class = "text-muted text-sm" > In database admin → Authentication → Providers → Email: ensure email auth is enabled. Optionally enable Google OAuth.</ p >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 3</ span > Create Storage Buckets</ h3 >
< p class = "text-muted text-sm mb-2" > In database admin → Storage, create these buckets:</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Bucket</ th >< th > Public</ th >< th > Purpose</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > avatars</ code ></ td >< td > Yes</ td >< td > User profile pictures</ td ></ tr >
< tr >< td >< code > app-icons</ code ></ td >< td > Yes</ td >< td > Generated app icons</ td ></ tr >
< tr >< td >< code > splash-screens</ code ></ td >< td > Yes</ td >< td > Splash screen images</ td ></ tr >
< tr >< td >< code > apk-builds</ code ></ td >< td > Yes</ td >< td > APK/IPA build artifacts</ td ></ tr >
< tr >< td >< code > project-assets</ code ></ td >< td > No</ td >< td > Private project assets</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
</ div >
<!-- Step 3: Environment -->
< div id = "env-config" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 3: Configure Environment Variables</ h2 >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 1</ span > Copy the environment template</ h3 >
< pre >< code > cp .env.example .env</ code ></ pre >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 2</ span > Fill in your environment variables</ h3 >
< pre >< code >< span class = "syntax-comment" > # Required — Your Postgres database URL</ span >
DATABASE_URL=postgres://user:password@host:5432/postgres
< span class = "syntax-comment" > # Required — Better Auth secret (long random string)</ span >
BETTER_AUTH_SECRET=your-long-random-secret
< span class = "syntax-comment" > # Required — Your Postgres database ID (the reference part of the URL)</ span >
BETTER_AUTH_URL=http://localhost:8080</ code ></ pre >
</ div >
< div class = "callout callout-warning" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > Security: Never put secret keys in < code > .env</ code ></ strong >< p class = "text-sm text-muted" > Only < code > VITE_</ code > -prefixed variables are bundled into client JavaScript. Server-side keys (Stripe secret, Resend API key, etc.) must be configured as server environment variables in the .env file.</ p ></ div >
</ div >
</ div >
<!-- Step 4: Database Setup -->
< div id = "db-setup" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 4: Apply Database Migrations</ h2 >
< p class = "text-muted mb-4" > The < code > server/db/</ code > directory contains the SQL schema (< code > schema.sql</ code > + < code > auth-schema.sql</ code > ) for all tables, functions, and triggers.</ p >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 1</ span > Link your Postgres database</ h3 >
< pre >< code >
(set DATABASE_URL in your .env)</ code ></ pre >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 2</ span > Push all migrations</ h3 >
< pre >< code > npm run db:push</ code ></ pre >
< p class = "text-muted text-sm mt-2" > This creates all tables (< code > profiles</ code > , < code > user_roles</ code > , < code > app_builds</ code > , < code > subscription_plans</ code > , etc.), database functions (< code > has_role</ code > , < code > use_credits</ code > , < code > add_credits</ code > , etc.), triggers, and RLS policies.</ p >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 3</ span > Deploy Edge Functions</ h3 >
< pre >< code > npm run dev (backend functions run in the Express server)</ code ></ pre >
< p class = "text-muted text-sm mt-2" > Deploys all edge functions (stripe-checkout, paypal-checkout, send-email, analyze-website, ai-assistant, cloud-build, etc.).</ p >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 4</ span > Set Edge Function Secrets</ h3 >
< p class = "text-muted text-sm mb-2" > In database admin → Settings → Secrets, add the keys your features need:</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Secret</ th >< th > When Needed</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > STRIPE_SECRET_KEY</ code ></ td >< td > Stripe payments</ td ></ tr >
< tr >< td >< code > STRIPE_WEBHOOK_SECRET</ code ></ td >< td > Stripe webhooks</ td ></ tr >
< tr >< td >< code > RESEND_API_KEY</ code ></ td >< td > Transactional emails</ td ></ tr >
< tr >< td >< code > CODEMAGIC_API_TOKEN</ code ></ td >< td > Cloud builds via Codemagic</ td ></ tr >
< tr >< td >< code > CODEMAGIC_APP_ID</ code ></ td >< td > Codemagic app identifier</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
< div class = "callout callout-tip" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M22 11.08V12a10 10 0 1 1-5.93-9.14" />< polyline points = "22 4 12 14.01 9 11.01" /></ svg >
< div >< strong class = "text-sm" > Verify your setup</ strong >< p class = "text-sm text-muted" > Check the database admin → Table Editor to confirm all tables were created. You should see < code > profiles</ code > , < code > user_roles</ code > , < code > app_builds</ code > , < code > system_settings</ code > , and more.</ p ></ div >
</ div >
</ div >
<!-- Step 5: Start Dev Server -->
< div id = "start-dev" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 5: Start the Development Server</ h2 >
< div class = "card mb-4" >
< pre >< code > npm run dev</ code ></ pre >
< p class = "text-muted text-sm mt-2" > The app starts at < code > http://localhost:8080</ code > . Since no admin exists yet, it automatically redirects to < code > /setup</ code > .</ p >
</ div >
</ div >
<!-- Step 6: Setup Wizard -->
< div id = "setup-wizard" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 6: Complete the Setup Wizard</ h2 >
< p class = "text-muted mb-6" > The streamlined 4-step setup wizard runs automatically on first launch when no admin account exists:</ p >
< div class = "card mb-4" >
< div class = "grid grid-2 mb-4" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 1</ span > Welcome & Environment Check</ h4 >< p class = "text-xs text-muted" > Validates database connection, auth service, storage buckets, and system settings. Blocks progression on critical failures.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 2</ span > Super Admin Account</ h4 >< p class = "text-xs text-muted" > Create or sign in with the super admin account. Admin role is assigned automatically via RLS policy.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 3</ span > App Configuration</ h4 >< p class = "text-xs text-muted" > Set app name, tagline, support email, and default credits. Optionally seed demo data.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 4</ span > Launch</ h4 >< p class = "text-xs text-muted" > Success confirmation with link to launch the Admin Panel.</ p ></ div >
</ div >
</ div >
< div class = "callout callout-info" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" />< path d = "M12 16v-4" />< path d = "M12 8h.01" /></ svg >
< div >< strong class = "text-sm" > Advanced settings deferred</ strong >< p class = "text-sm text-muted" > Email, security, and payment configuration are handled in the Admin Panel post-setup to keep the wizard fast and focused.</ p ></ div >
</ div >
</ div >
<!-- Step 7: Admin Config -->
< div id = "admin-config" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 7: Configure the Admin Panel</ h2 >
< p class = "text-muted mb-6" > Log in and navigate to < code > /admin</ code > to configure production settings:</ p >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > A</ span > System Settings</ h3 >
< p class = "text-muted text-sm" > Admin → System Settings: General (app name, tagline, maintenance mode), Appearance (theme, logo, custom CSS), Notifications, Builds (credits per build, max daily builds), and Storage.</ p >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > B</ span > Payment Gateways</ h3 >
< p class = "text-muted text-sm mb-2" > Admin → Payment Gateways. Configure one or more:</ p >
< ul class = "text-muted text-sm" >
< li >< strong > Stripe</ strong > — Publishable key, secret key, webhook secret. Toggle sandbox/live.</ li >
< li >< strong > PayPal</ strong > — Client ID and secret. Create billing plans for subscriptions.</ li >
< li >< strong > Coinbase Commerce</ strong > — API key and webhook secret for crypto payments.</ li >
< li >< strong > Bank Transfer</ strong > — Manual transfers with admin approval workflow.</ li >
</ ul >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > C</ span > Subscription Plans & Credit Packs</ h3 >
< p class = "text-muted text-sm" > Admin → Pricing Plans: Create/edit Free, Pro, Enterprise tiers. Admin → Credit Packs: Create purchasable credit bundles.</ p >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > D</ span > Integrations</ h3 >
< p class = "text-muted text-sm mb-2" > Admin → Integrations. Configure third-party services:</ p >
< ul class = "text-muted text-sm" >
< li >< strong > Resend</ strong > — API key for transactional emails (welcome, password reset, build complete)</ li >
< li >< strong > Appetize.io</ strong > — API key for interactive device previews</ li >
< li >< strong > AI Providers</ strong > — OpenAI/Gemini keys for website analysis and AI assistant</ li >
< li >< strong > Codemagic</ strong > — API token and app ID for cloud build pipeline</ li >
</ ul >
</ div >
< div class = "card mb-4" >
< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > E</ span > Email Templates</ h3 >
< p class = "text-muted text-sm" > Admin → Email Templates. Customize transactional emails with HTML and variable substitution.</ p >
</ div >
</ div >
<!-- Step 8: First Build -->
< div id = "first-build" class = "mb-12" >
< h2 class = "font-bold mb-6" > Step 8: Build Your First App</ h2 >
< div class = "card mb-4" >< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 1</ span > Navigate to App Builder</ h3 >< p class = "text-muted text-sm" > Click "New App" from Dashboard or go to < code > /builder</ code > .</ p ></ div >
< div class = "card mb-4" >< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 2</ span > Enter a Website URL</ h3 >< p class = "text-muted text-sm" > AI analyzes the page and extracts metadata (title, description, colors, favicon) automatically.</ p ></ div >
< div class = "card mb-4" >< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 3</ span > Customize Configuration</ h3 >< p class = "text-muted text-sm" > Configure app name, colors, navigation style, icon style, splash screen, and toggle native features.</ p ></ div >
< div class = "card mb-4" >< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 4</ span > Preview</ h3 >< p class = "text-muted text-sm" > Preview on device mockups. Interactive preview with Appetize.io if configured.</ p ></ div >
< div class = "card mb-4" >< h3 class = "font-semibold mb-3 flex items-center" >< span class = "step-number" > 5</ span > Build & Download</ h3 >< p class = "text-muted text-sm" > Select platform (Android APK or iOS IPA). Codemagic builds in the cloud; real-time progress in the Build Progress Panel. Download when complete.</ p ></ div >
< div class = "callout callout-info mt-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" />< path d = "M12 16v-4" />< path d = "M12 8h.01" /></ svg >
< div >< strong class = "text-sm" > Build Requirements</ strong >< p class = "text-sm text-muted" > Cloud builds require: Codemagic integration configured in Admin → Integrations, project exported to GitHub, and sufficient build credits.</ p ></ div >
</ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: API REFERENCE -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "api-reference" class = "mb-16" >
< h1 class = "mb-4" > API Reference</ h1 >
< p class = "text-xl text-muted mb-8" > Hooks, functions, and services available in the codebase.</ p >
<!-- Native Hooks -->
< div id = "native-hooks" class = "mb-12" >
< h2 class = "font-bold mb-6" > Native Hooks</ h2 >
< p class = "text-muted mb-6" > React hooks wrapping Capacitor plugins. All have web fallbacks.</ p >
< div id = "useCamera" class = "card mb-8" >
< h2 class = "font-bold mb-4" > useCamera</ h2 >
< p class = "text-muted mb-4" > Capture photos or pick from gallery.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useCamera } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useCamera'</ span > ;
< span class = "syntax-keyword" > const</ span > { takePhoto, pickFromGallery, photo, isAvailable } = < span class = "syntax-function" > useCamera</ span > ();
< span class = "syntax-keyword" > const</ span > result = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > takePhoto</ span > (); < span class = "syntax-comment" > // Returns base64 data</ span ></ code ></ pre >
</ div >
< div id = "useBiometricAuth" class = "card mb-8" >
< h2 class = "font-bold mb-4" > useBiometricAuth</ h2 >
< p class = "text-muted mb-4" > Authenticate with fingerprint or Face ID.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useBiometricAuth } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useBiometricAuth'</ span > ;
< span class = "syntax-keyword" > const</ span > { authenticate, isAvailable, biometryType } = < span class = "syntax-function" > useBiometricAuth</ span > ();
< span class = "syntax-keyword" > const</ span > success = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > authenticate</ span > (< span class = "syntax-string" > 'Verify your identity'</ span > );</ code ></ pre >
</ div >
< div id = "useHaptics" class = "card mb-8" >
< h2 class = "font-bold mb-4" > useHaptics</ h2 >
< p class = "text-muted mb-4" > Trigger haptic feedback for tactile responses.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useHaptics } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useHaptics'</ span > ;
< span class = "syntax-keyword" > const</ span > { impact, notification, vibrate } = < span class = "syntax-function" > useHaptics</ span > ();
< span class = "syntax-function" > impact</ span > (< span class = "syntax-string" > 'light'</ span > ); < span class = "syntax-function" > impact</ span > (< span class = "syntax-string" > 'medium'</ span > ); < span class = "syntax-function" > impact</ span > (< span class = "syntax-string" > 'heavy'</ span > );
< span class = "syntax-function" > notification</ span > (< span class = "syntax-string" > 'success'</ span > ); < span class = "syntax-function" > notification</ span > (< span class = "syntax-string" > 'warning'</ span > ); < span class = "syntax-function" > notification</ span > (< span class = "syntax-string" > 'error'</ span > );</ code ></ pre >
</ div >
< div id = "usePushNotifications" class = "card mb-8" >
< h2 class = "font-bold mb-4" > usePushNotifications</ h2 >
< p class = "text-muted mb-4" > Register for push notifications (FCM/APNs).</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { usePushNotifications } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/usePushNotifications'</ span > ;
< span class = "syntax-keyword" > const</ span > { register, token, permission } = < span class = "syntax-function" > usePushNotifications</ span > ();
< span class = "syntax-keyword" > const</ span > result = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > register</ span > ();</ code ></ pre >
</ div >
<!-- Additional Hooks -->
< div class = "mb-12" >
< h2 class = "font-bold mb-6" > Application & Utility Hooks</ h2 >
< p class = "text-muted mb-6" > React hooks for app-wide functionality, payments, and UI state.</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > usePWAInstall</ h2 >
< p class = "text-muted mb-4" > Manages Progressive Web App install prompts and service worker registration.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { usePWAInstall } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/usePWAInstall'</ span > ;
< span class = "syntax-keyword" > const</ span > { isInstallable, isInstalled, promptInstall } = < span class = "syntax-function" > usePWAInstall</ span > ();
< span class = "syntax-keyword" > if</ span > (isInstallable) < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > promptInstall</ span > ();</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useAdminData & useAdminExists</ h2 >
< p class = "text-muted mb-4" > Admin data fetching and admin existence detection for setup flow.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useAdminData } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useAdminData'</ span > ;
< span class = "syntax-keyword" > import</ span > { useAdminExists } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useAdminExists'</ span > ;
< span class = "syntax-comment" > // Fetch admin panel data (users, builds, payments, settings)</ span >
< span class = "syntax-keyword" > const</ span > { data, loading, refresh } = < span class = "syntax-function" > useAdminData</ span > ();
< span class = "syntax-comment" > // Check if any admin account exists (for setup wizard routing)</ span >
< span class = "syntax-keyword" > const</ span > { adminExists, loading } = < span class = "syntax-function" > useAdminExists</ span > ();
< span class = "syntax-comment" > // Uses no_admin_exists() SECURITY DEFINER RPC for guest access</ span ></ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useSetupCheck</ h2 >
< p class = "text-muted mb-4" > Validates system setup status. Redirects to < code > /setup</ code > if no admin exists, or blocks setup if already configured.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useSetupCheck } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useSetupCheck'</ span > ;
< span class = "syntax-keyword" > const</ span > { isSetupComplete, isChecking } = < span class = "syntax-function" > useSetupCheck</ span > ();
< span class = "syntax-comment" > // Used by PublicRoute and SetupRoute guards</ span ></ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useMobile & useToast</ h2 >
< p class = "text-muted mb-4" > Responsive breakpoint detection and toast notification system.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useIsMobile } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/use-mobile'</ span > ;
< span class = "syntax-keyword" > import</ span > { useToast } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/use-toast'</ span > ;
< span class = "syntax-keyword" > const</ span > isMobile = < span class = "syntax-function" > useIsMobile</ span > (); < span class = "syntax-comment" > // true when viewport < 768px</ span >
< span class = "syntax-keyword" > const</ span > { toast } = < span class = "syntax-function" > useToast</ span > ();
< span class = "syntax-function" > toast</ span > ({ title: < span class = "syntax-string" > 'Success'</ span > , description: < span class = "syntax-string" > 'Operation completed'</ span > });</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useBuildQueue</ h2 >
< p class = "text-muted mb-4" > Manages build queue with concurrent limit enforcement and queue position tracking.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useBuildQueue } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useBuildQueue'</ span > ;
< span class = "syntax-keyword" > const</ span > { queue, addToQueue, position, isProcessing } = < span class = "syntax-function" > useBuildQueue</ span > ();</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useBuilderKeyboardShortcuts</ h2 >
< p class = "text-muted mb-4" > Keyboard shortcuts for the app builder. Navigation, refresh, screenshot, rotate, and comparison mode.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useBuilderKeyboardShortcuts } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useBuilderKeyboardShortcuts'</ span > ;
< span class = "syntax-function" > useBuilderKeyboardShortcuts</ span > ({
currentStep, onNextStep, onPrevStep,
onRefresh, onScreenshot, onRotate, onToggleComparison
});</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useRealtime</ h2 >
< p class = "text-muted mb-4" > Subscribes to backend polling channels for live database change events.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useRealtime } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useRealtime'</ span > ;
< span class = "syntax-function" > useRealtime</ span > (< span class = "syntax-string" > 'app_builds'</ span > , (payload) => {
console.< span class = "syntax-function" > log</ span > (< span class = "syntax-string" > 'Build updated:'</ span > , payload);
});</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > usePayPalCheckout</ h2 >
< p class = "text-muted mb-4" > Initiates PayPal checkout for subscriptions, credit packs, or invoices.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { usePayPalCheckout } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/usePayPalCheckout'</ span > ;
< span class = "syntax-keyword" > const</ span > { initiatePayPalCheckout, loading } = < span class = "syntax-function" > usePayPalCheckout</ span > ();
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > initiatePayPalCheckout</ span > ({ type: < span class = "syntax-string" > 'credits'</ span > , creditPackId: < span class = "syntax-string" > '...'</ span > });</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useCoinbaseCheckout</ h2 >
< p class = "text-muted mb-4" > Initiates Coinbase Commerce crypto checkout. Redirects to hosted checkout page.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useCoinbaseCheckout } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useCoinbaseCheckout'</ span > ;
< span class = "syntax-keyword" > const</ span > { initiateCoinbaseCheckout, loading } = < span class = "syntax-function" > useCoinbaseCheckout</ span > ();
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > initiateCoinbaseCheckout</ span > ({ type: < span class = "syntax-string" > 'subscription'</ span > , planId: < span class = "syntax-string" > '...'</ span > });</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useSubscriptionPlans</ h2 >
< p class = "text-muted mb-4" > Fetches active subscription plans with pricing and feature lists.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useSubscriptionPlans } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useSubscriptionPlans'</ span > ;
< span class = "syntax-keyword" > const</ span > { plans, loading } = < span class = "syntax-function" > useSubscriptionPlans</ span > ();</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useBrowserNotifications & useNotificationSounds</ h2 >
< p class = "text-muted mb-4" > Browser notification permission management and audio feedback for events.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useBrowserNotifications } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useBrowserNotifications'</ span > ;
< span class = "syntax-keyword" > import</ span > { useNotificationSounds } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useNotificationSounds'</ span > ;
< span class = "syntax-keyword" > const</ span > { requestPermission, sendNotification } = < span class = "syntax-function" > useBrowserNotifications</ span > ();
< span class = "syntax-keyword" > const</ span > { playSound } = < span class = "syntax-function" > useNotificationSounds</ span > ();</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useStorage & useNavBadges</ h2 >
< p class = "text-muted mb-4" > Storage quota tracking and navigation badge counts (unread notifications, pending builds).</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useStorage } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useStorage'</ span > ;
< span class = "syntax-keyword" > import</ span > { useNavBadges } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useNavBadges'</ span > ;
< span class = "syntax-keyword" > const</ span > { usage, quota, percentUsed } = < span class = "syntax-function" > useStorage</ span > ();
< span class = "syntax-keyword" > const</ span > { buildCount, notificationCount } = < span class = "syntax-function" > useNavBadges</ span > ();</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > useDemoGuard & useAdminAuth</ h2 >
< p class = "text-muted mb-4" > Demo mode mutation blocking and admin role verification hooks.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useDemoGuard } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useDemoGuard'</ span > ;
< span class = "syntax-keyword" > import</ span > { useAdminAuth } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useAdminAuth'</ span > ;
< span class = "syntax-keyword" > const</ span > { isDemoMode, guardAction } = < span class = "syntax-function" > useDemoGuard</ span > ();
< span class = "syntax-keyword" > const</ span > { isAdmin, isLoading } = < span class = "syntax-function" > useAdminAuth</ span > ();</ code ></ pre >
</ div >
< div id = "useSystemSettings" class = "card mb-8" >
< h2 class = "font-bold mb-4" > useSystemSettings</ h2 >
< p class = "text-muted mb-4" > Access global app settings (name, theme, maintenance mode, etc.) from the < code > system_settings</ code > table. Cached globally.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useSystemSettings } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useSystemSettings'</ span > ;
< span class = "syntax-keyword" > const</ span > { settings, loaded, refresh } = < span class = "syntax-function" > useSystemSettings</ span > ();
< span class = "syntax-comment" > // settings.app_name, settings.primary_color, settings.maintenance_mode, etc.</ span ></ code ></ pre >
</ div >
</ div >
<!-- Edge Functions -->
< div id = "edge-functions" class = "mb-12" >
< h2 class = "font-bold mb-6" > Edge Functions</ h2 >
< p class = "text-muted mb-6" > Server-side functions in the Express backend. All support < code > ?health=1</ code > for health checking.</ p >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > cloud-build</ code ></ div >< p class = "text-muted text-sm" > Triggers a Codemagic cloud build for Android APK or iOS IPA. Requires < code > CODEMAGIC_API_TOKEN</ code > and < code > CODEMAGIC_APP_ID</ code > .</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-get" > GET</ span >< code > cloud-build-status</ code ></ div >< p class = "text-muted text-sm" > Checks the status of a running Codemagic build. Returns progress, status, and artifact URLs.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > send-email</ code ></ div >< p class = "text-muted text-sm" > Sends transactional emails via Resend with HTML templates and variable substitution. Requires < code > RESEND_API_KEY</ code > .</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > analyze-website</ code ></ div >< p class = "text-muted text-sm" > Fetches a URL and extracts metadata (title, description, colors, favicon, OG data). Uses AI for enhanced analysis.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > ai-assistant</ code ></ div >< p class = "text-muted text-sm" > AI chat assistant for app configuration guidance. Streams responses using OpenAI/Gemini.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > stripe-checkout</ code > / < code > stripe-webhook</ code > / < code > stripe-portal</ code ></ div >< p class = "text-muted text-sm" > Stripe payments: checkout sessions, webhook processing, customer portal. Requires < code > STRIPE_SECRET_KEY</ code > .</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > paypal-checkout</ code > / < code > paypal-webhook</ code > / < code > paypal-billing</ code ></ div >< p class = "text-muted text-sm" > PayPal: orders, webhooks, billing plan management. Brand name pulled dynamically from system settings.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > coinbase-checkout</ code > / < code > coinbase-webhook</ code ></ div >< p class = "text-muted text-sm" > Coinbase Commerce for cryptocurrency payments.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > reset-demo-data</ code ></ div >< p class = "text-muted text-sm" > Resets demo data (admin-only). Cleans and repopulates sample data for demo users.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > appetize-upload</ code ></ div >< p class = "text-muted text-sm" > Uploads APK/IPA to Appetize.io for interactive device preview. Requires < code > APPETIZE_API_KEY</ code > .</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > storage-admin</ code ></ div >< p class = "text-muted text-sm" > Admin-only bucket management: list buckets with file counts/sizes, create new buckets.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > test-storage-connection</ code ></ div >< p class = "text-muted text-sm" > Validates storage provider credentials (S3, GCS, R2) before saving configuration.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > codemagic-webhook</ code ></ div >< p class = "text-muted text-sm" > Receives Codemagic build completion webhooks. Updates build status and artifact URLs in database.</ p ></ div >
< div class = "card mb-4" >< div class = "flex items-center gap-2 mb-2" >< span class = "method-badge method-post" > POST</ span >< code > retry-webhook</ code ></ div >< p class = "text-muted text-sm" > Admin utility to re-trigger failed webhook events with original payload and < code > X-Webhook-Retry</ code > header.</ p ></ div >
</ div >
<!-- Detailed Edge Functions Reference -->
< div id = "edge-functions-detail" class = "mb-12" >
< h2 class = "font-bold mb-6" > Edge Functions — Detailed Reference</ h2 >
< p class = "text-muted mb-6" > Request/response formats, required secrets, and error codes for each function.</ p >
<!-- cloud-build -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > cloud-build</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Triggers a Codemagic CI/CD build. Authenticates the user, validates credentials, waits for GitHub sync, then fires the build.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request Body</ h4 >
< pre >< code > {
"buildId": "uuid", < span class = "syntax-comment" > // Required — app_builds record ID</ span >
"websiteUrl": "https://...", < span class = "syntax-comment" > // Required — target site URL</ span >
"appName": "My App", < span class = "syntax-comment" > // Required — display name</ span >
"platform": "android", < span class = "syntax-comment" > // "android" | "ios" (default: "android")</ span >
"packageName": "com.app.my", < span class = "syntax-comment" > // Optional — auto-sanitized if invalid</ span >
"config": { ... } < span class = "syntax-comment" > // Optional — colors, features, workflowId</ span >
}</ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Response (200)</ h4 >
< pre >< code > { "success": true, "buildId": "uuid", "cloudBuildId": "cm-id", "message": "..." }</ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Errors</ h4 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Code</ th >< th > Reason</ th ></ tr ></ thead >
< tbody >
< tr >< td > 400</ td >< td > Missing required fields, or Codemagic token/app ID not configured</ td ></ tr >
< tr >< td > 401</ td >< td > User not authenticated, or Codemagic token expired</ td ></ tr >
< tr >< td > 403</ td >< td > Codemagic token has read-only scope (needs Owner or Builds)</ td ></ tr >
< tr >< td > 404</ td >< td > Codemagic app or workflow not found</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-xs mt-2" > Secrets: < code > CODEMAGIC_API_TOKEN</ code > , < code > CODEMAGIC_APP_ID</ code ></ p >
</ div >
<!-- cloud-build-status -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > cloud-build-status</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Polls Codemagic API for current build status. Maps Codemagic states to internal statuses. Extracts artifact URLs on completion.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request Body</ h4 >
< pre >< code > { "buildId": "uuid" } < span class = "syntax-comment" > // app_builds record ID</ span ></ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Response (200)</ h4 >
< pre >< code > {
"id": "uuid", "status": "building", "progress": 45,
"download_url": null, "error_message": null,
"file_size_bytes": null, ...
}</ code ></ pre >
< p class = "text-muted text-xs mt-2" > On completion, < code > download_url</ code > contains the artifact link. On failure, < code > error_message</ code > includes step-level diagnostics.</ p >
</ div >
<!-- codemagic-webhook -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > codemagic-webhook</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Receives POST callbacks from Codemagic with build status updates. < strong > No auth required</ strong > — Codemagic sends directly.</ p >
< h4 class = "font-semibold text-sm mb-1" > Payload (from Codemagic)</ h4 >
< pre >< code > {
"build": {
"_id": "cm-build-id", "status": "finished",
"artefacts": [{ "type": "apk", "url": "https://...", "size": 12345678 }],
"steps": [{ "name": "Build", "status": "success" }]
}
}</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Automatically updates < code > app_builds</ code > with status, progress, download URL, and failure diagnostics.</ p >
</ div >
<!-- analyze-website -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > analyze-website</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Uses AI (Gemini → OpenAI fallback chain) to analyze a URL and suggest app configuration.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request / Response</ h4 >
< pre >< code >< span class = "syntax-comment" > // Request</ span >
{ "websiteUrl": "https://example.com" }
< span class = "syntax-comment" > // Response (200)</ span >
{ "config": {
"app_name": "Example", "primary_color": "#3B82F6",
"accent_color": "#8B5CF6", "navigation_style": "bottom-nav",
"features": ["offline_mode", "push_notifications"],
"app_category": "utility", "icon_style": "modern",
"splash_screen_style": "centered-logo"
}}</ code ></ pre >
< p class = "text-muted text-xs mt-2" > AI provider priority: 1) AI Gateway (< code > AI_API_KEY</ code > ), 2) Admin-configured keys (< code > api_configurations</ code > ), 3) < code > GEMINI_API_KEY</ code > env secret.</ p >
</ div >
<!-- ai-assistant -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > ai-assistant</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Streaming AI chat assistant for app builder guidance. Returns Server-Sent Events (SSE).</ p >
< h4 class = "font-semibold text-sm mb-1" > Request Body</ h4 >
< pre >< code > {
"message": "What navigation style works best for e-commerce?",
"currentStep": 2,
"config": { "appName": "ShopApp", "websiteUrl": "..." },
"conversationHistory": [{ "role": "user", "content": "..." }],
"aiProvider": "gemini" < span class = "syntax-comment" > // optional: "openai" | "gemini"</ span >
}</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Response: < code > text/event-stream</ code > with OpenAI-compatible SSE chunks. Same AI fallback chain as < code > analyze-website</ code > .</ p >
</ div >
<!-- send-email -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > send-email</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Sends transactional emails via Resend. Loads templates from < code > email_templates</ code > table with < code > {{variable}}</ code > substitution.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request / Health Check</ h4 >
< pre >< code >< span class = "syntax-comment" > // Send email</ span >
{ "to": "user@example.com", "templateName": "welcome", "variables": { "app_name": "MyApp", "user_name": "John" } }
< span class = "syntax-comment" > // Health check</ span >
GET /functions/v1/send-email?health=1
< span class = "syntax-comment" > // → { "status": "ok", "env": { "RESEND_API_KEY": true, ... } }</ span ></ code ></ pre >
< p class = "text-muted text-xs mt-2" > Config priority: 1) < code > RESEND_API_KEY</ code > env, 2) < code > api_configurations</ code > table, 3) Legacy < code > system_settings</ code > .</ p >
</ div >
<!-- storage-admin -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-get" > GET</ span >< span class = "method-badge method-post" > POST</ span > < code > storage-admin</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Admin-only bucket management. Requires admin role verification.</ p >
< pre >< code >< span class = "syntax-comment" > // List buckets with stats</ span >
GET /functions/v1/storage-admin?action=list
< span class = "syntax-comment" > // → [{ "name": "avatars", "public": true, "fileCount": 12, "totalSize": 4567890 }]</ span >
< span class = "syntax-comment" > // Create bucket</ span >
POST /functions/v1/storage-admin?action=create
{ "name": "my-bucket", "isPublic": true, "fileSizeLimit": 10485760 }</ code ></ pre >
</ div >
<!-- test-storage-connection -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > test-storage-connection</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Validates storage provider credentials. Admin-only. Supports: < code > local</ code > , < code > s3</ code > , < code > gcs</ code > , < code > cloudflare_r2</ code > .</ p >
< pre >< code >< span class = "syntax-comment" > // Test AWS S3</ span >
{ "provider": "s3", "config": { "s3_access_key": "AKIA...", "s3_secret_key": "...", "s3_bucket": "my-bucket", "s3_region": "us-east-1" } }
< span class = "syntax-comment" > // → { "success": true, "message": "Configuration validated for bucket..." }</ span ></ code ></ pre >
</ div >
<!-- appetize-upload -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > appetize-upload</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Uploads APK/IPA to Appetize.io for interactive previews. Supports configurable retry (up to 10 attempts) and per-request timeout.</ p >
< pre >< code >< span class = "syntax-comment" > // Request</ span >
{ "buildId": "uuid", "downloadUrl": "https://...apk", "platform": "android" }
< span class = "syntax-comment" > // Response (200)</ span >
{ "success": true, "publicKey": "abc123", "previewUrl": "https://appetize.io/app/abc123" }</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Reads < code > APPETIZE_API_KEY</ code > from < code > api_configurations</ code > . Updates < code > app_builds.config</ code > with the preview URL.</ p >
</ div >
<!-- reset-demo-data -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > reset-demo-data</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Admin-only. Resets all data for < code > admin@demo.com</ code > and < code > user@demo.com</ code > : deletes projects, builds, automations, chat messages, and templates. Resets credits (50 monthly + 10 bonus) and re-seeds sample projects.</ p >
</ div >
<!-- retry-webhook -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > retry-webhook</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Admin-only. Re-sends a failed webhook event to the original gateway handler with the < code > X-Webhook-Retry: true</ code > header. Logs the retry attempt in < code > webhook_event_logs</ code > .</ p >
< pre >< code > { "webhook_log_id": "uuid" }
< span class = "syntax-comment" > // → { "success": true, "response_status": 200, "processing_time_ms": 234 }</ span ></ code ></ pre >
< p class = "text-muted text-xs mt-2" > Gateway mapping: < code > stripe</ code > →< code > stripe-webhook</ code > , < code > paypal</ code > →< code > paypal-webhook</ code > , < code > coinbase</ code > →< code > coinbase-webhook</ code > .</ p >
</ div >
<!-- stripe-checkout -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > stripe-checkout</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Creates a Stripe Checkout session for subscriptions or credit pack purchases. Auto-creates/retrieves a Stripe customer.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request Body</ h4 >
< pre >< code > {
"type": "subscription" | "credits", < span class = "syntax-comment" > // Required — purchase type</ span >
"itemId": "uuid", < span class = "syntax-comment" > // plan_id or credit_pack_id</ span >
"billingCycle": "monthly" | "yearly",< span class = "syntax-comment" > // For subscriptions only</ span >
"successUrl": "https://...", < span class = "syntax-comment" > // Redirect after success</ span >
"cancelUrl": "https://..." < span class = "syntax-comment" > // Redirect on cancel</ span >
}</ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Response (200)</ h4 >
< pre >< code > { "url": "https://checkout.stripe.com/c/pay/..." }</ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Errors</ h4 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Code</ th >< th > Reason</ th ></ tr ></ thead >
< tbody >
< tr >< td > 400</ td >< td > Invalid type, missing itemId, or plan/pack not found</ td ></ tr >
< tr >< td > 401</ td >< td > Not authenticated</ td ></ tr >
< tr >< td > 500</ td >< td >< code > STRIPE_SECRET_KEY</ code > not configured</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-xs mt-2" > Secrets: < code > STRIPE_SECRET_KEY</ code ></ p >
</ div >
<!-- stripe-portal -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > stripe-portal</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Creates a Stripe Billing Portal session for self-service subscription management (upgrades, downgrades, cancellations, payment method updates).</ p >
< h4 class = "font-semibold text-sm mb-1" > Request / Response</ h4 >
< pre >< code >< span class = "syntax-comment" > // Request</ span >
{ "returnUrl": "https://your-app.com/subscription" }
< span class = "syntax-comment" > // Response (200)</ span >
{ "url": "https://billing.stripe.com/p/session/..." }</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Requires user to have an existing < code > stripe_customer_id</ code > in their profile. Returns 400 if no customer found.</ p >
</ div >
<!-- stripe-webhook -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > stripe-webhook</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Processes Stripe webhook events. Validates signatures, handles payment completions, subscription lifecycle, and credit allocation.</ p >
< h4 class = "font-semibold text-sm mb-1" > Handled Events</ h4 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Event</ th >< th > Action</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > checkout.session.completed</ code ></ td >< td > Creates subscription record or adds credits depending on session metadata</ td ></ tr >
< tr >< td >< code > customer.subscription.updated</ code ></ td >< td > Updates subscription status, plan, and billing cycle</ td ></ tr >
< tr >< td >< code > customer.subscription.deleted</ code ></ td >< td > Marks subscription as canceled</ td ></ tr >
< tr >< td >< code > invoice.payment_succeeded</ code ></ td >< td > Records payment transaction, resets monthly credits on renewal</ td ></ tr >
< tr >< td >< code > invoice.payment_failed</ code ></ td >< td > Records failed payment, updates subscription status</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-xs mt-2" > Secrets: < code > STRIPE_SECRET_KEY</ code > , < code > STRIPE_WEBHOOK_SECRET</ code > . Logs all events to < code > webhook_event_logs</ code > .</ p >
</ div >
<!-- paypal-checkout -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > paypal-checkout</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Creates PayPal orders for credit pack purchases, subscription signups, and invoice payments. Supports sandbox and live modes via < code > payment_gateway_configs</ code > .</ p >
< h4 class = "font-semibold text-sm mb-1" > Request Body</ h4 >
< pre >< code > {
"type": "subscription" | "credits" | "invoice",
"plan_id": "uuid", < span class = "syntax-comment" > // For subscriptions</ span >
"billing_cycle": "monthly", < span class = "syntax-comment" > // "monthly" | "yearly"</ span >
"credit_pack_id": "uuid", < span class = "syntax-comment" > // For credit purchases</ span >
"invoice_id": "uuid", < span class = "syntax-comment" > // For invoice payments</ span >
"success_url": "https://...",
"cancel_url": "https://..."
}</ code ></ pre >
< h4 class = "font-semibold text-sm mt-2 mb-1" > Response (200)</ h4 >
< pre >< code > { "orderId": "PAY-...", "approvalUrl": "https://paypal.com/checkoutnow?token=..." }</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Reads credentials from < code > payment_gateway_configs</ code > (gateway = "paypal"). Brand name from < code > system_settings</ code > .</ p >
</ div >
<!-- paypal-billing -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > paypal-billing</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Manages PayPal billing plans and products. Creates products, plans with pricing intervals, and subscription links.</ p >
< h4 class = "font-semibold text-sm mb-1" > Actions</ h4 >
< pre >< code >< span class = "syntax-comment" > // Create a billing plan</ span >
{ "action": "create_plan", "plan_id": "uuid", "billing_cycle": "monthly" }
< span class = "syntax-comment" > // Create a subscription</ span >
{ "action": "create_subscription", "plan_id": "uuid", "billing_cycle": "monthly",
"success_url": "...", "cancel_url": "..." }
< span class = "syntax-comment" > // → { "subscriptionId": "I-...", "approvalUrl": "https://..." }</ span ></ code ></ pre >
</ div >
<!-- paypal-webhook -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > paypal-webhook</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Processes PayPal webhook notifications. Handles payment captures, subscription activations, and cancellations.</ p >
< h4 class = "font-semibold text-sm mb-1" > Handled Events</ h4 >
< ul class = "text-muted text-sm" >
< li >< code > PAYMENT.CAPTURE.COMPLETED</ code > — Adds credits or activates subscription</ li >
< li >< code > BILLING.SUBSCRIPTION.ACTIVATED</ code > — Creates subscription record</ li >
< li >< code > BILLING.SUBSCRIPTION.CANCELLED</ code > — Marks subscription as canceled</ li >
< li >< code > BILLING.SUBSCRIPTION.SUSPENDED</ code > — Suspends subscription</ li >
</ ul >
< p class = "text-muted text-xs mt-2" > All events logged to < code > webhook_event_logs</ code > with processing time.</ p >
</ div >
<!-- coinbase-checkout -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > coinbase-checkout</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Creates Coinbase Commerce charges for cryptocurrency payments. Supports credit packs, subscriptions, and invoice payments.</ p >
< h4 class = "font-semibold text-sm mb-1" > Request / Response</ h4 >
< pre >< code >< span class = "syntax-comment" > // Request (same structure as paypal-checkout)</ span >
{ "type": "credits", "credit_pack_id": "uuid", "success_url": "...", "cancel_url": "..." }
< span class = "syntax-comment" > // Response (200)</ span >
{ "chargeId": "...", "hostedUrl": "https://commerce.coinbase.com/charges/..." }</ code ></ pre >
< p class = "text-muted text-xs mt-2" > Reads API key from < code > payment_gateway_configs</ code > (gateway = "coinbase").</ p >
</ div >
<!-- coinbase-webhook -->
< div class = "card mb-4" >
< h3 class = "font-semibold mb-2" >< span class = "method-badge method-post" > POST</ span > < code > coinbase-webhook</ code ></ h3 >
< p class = "text-muted text-sm mb-2" > Processes Coinbase Commerce webhook events for charge completions and failures.</ p >
< h4 class = "font-semibold text-sm mb-1" > Handled Events</ h4 >
< ul class = "text-muted text-sm" >
< li >< code > charge:completed</ code > — Adds credits or activates subscription based on charge metadata</ li >
< li >< code > charge:failed</ code > — Records failed payment transaction</ li >
< li >< code > charge:pending</ code > — Updates transaction status to pending</ li >
</ ul >
< p class = "text-muted text-xs mt-2" > Validates webhook signature using < code > webhook_secret</ code > from gateway config. Logs to < code > webhook_event_logs</ code > .</ p >
</ div >
</ div >
<!-- Credits API -->
< div id = "credits-api" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Credits System</ h2 >
< p class = "text-muted mb-4" > Credits via database RPCs. Users have monthly credits (reset on cycle) and bonus credits (permanent).</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Function</ th >< th > Parameters</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > use_credits</ code ></ td >< td >< code > p_user_id</ code > , < code > p_amount</ code > , < code > p_action_type</ code > , < code > p_description</ code ></ td >< td > Deducts credits (bonus first, then monthly). Returns < code > false</ code > if insufficient.</ td ></ tr >
< tr >< td >< code > add_credits</ code ></ td >< td >< code > p_user_id</ code > , < code > p_amount</ code > , < code > p_credit_type</ code > , < code > p_action_type</ code > , < code > p_description</ code ></ td >< td > Adds credits. < code > p_credit_type</ code > : < code > 'monthly'</ code > or < code > 'bonus'</ code > .</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mt-6 mb-2" > Frontend Hooks</ h3 >
< ul class = "text-muted text-sm" >
< li >< code > useCredits()</ code > — Returns < code > { credits, totalCredits, useCredits, isLoading }</ code ></ li >
< li >< code > useCreditHistory()</ code > — Paginated credit usage history</ li >
< li >< code > useCreditPacks()</ code > — Available credit pack products</ li >
< li >< code > useLowCreditsWarning()</ code > — Toast when credits drop below threshold</ li >
</ ul >
</ div >
<!-- Admin API -->
< div id = "admin-api" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Admin API</ h2 >
< p class = "text-muted mb-4" > Admin operations guarded by RLS policies requiring < code > admin</ code > role.</ p >
< h3 class = "font-semibold mb-2" > Role Verification (Database Functions)</ h3 >
< pre >< code >< span class = "syntax-comment" > -- Check a user's role</ span >
< span class = "syntax-keyword" > SELECT</ span > get_user_role(< span class = "syntax-string" > 'user-uuid'</ span > ); < span class = "syntax-comment" > -- Returns 'admin', 'moderator', or 'user'</ span >
< span class = "syntax-comment" > -- Check if user has specific role</ span >
< span class = "syntax-keyword" > SELECT</ span > has_role(< span class = "syntax-string" > 'user-uuid'</ span > , < span class = "syntax-string" > 'admin'</ span > ); < span class = "syntax-comment" > -- Returns boolean</ span >
< span class = "syntax-comment" > -- Check if any admin exists (used by setup wizard)</ span >
< span class = "syntax-keyword" > SELECT</ span > no_admin_exists(); < span class = "syntax-comment" > -- Returns boolean</ span ></ code ></ pre >
< h3 class = "font-semibold mt-6 mb-2" > Admin-Only Tables</ h3 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Table</ th >< th > Admin Can</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > system_settings</ code ></ td >< td > Read/Write</ td >< td > App-wide configuration (name, theme, maintenance mode)</ td ></ tr >
< tr >< td >< code > email_templates</ code ></ td >< td > CRUD</ td >< td > HTML email templates with variable substitution</ td ></ tr >
< tr >< td >< code > subscription_plans</ code ></ td >< td > CRUD</ td >< td > Plan definitions and pricing tiers</ td ></ tr >
< tr >< td >< code > credit_packs</ code ></ td >< td > CRUD</ td >< td > Credit bundle products</ td ></ tr >
< tr >< td >< code > payment_gateway_configs</ code ></ td >< td > CRUD</ td >< td > Payment provider credentials (encrypted)</ td ></ tr >
< tr >< td >< code > user_roles</ code ></ td >< td > CRUD</ td >< td > Role assignments</ td ></ tr >
< tr >< td >< code > plugins</ code ></ td >< td > CRUD</ td >< td > Feature plugin registry</ td ></ tr >
< tr >< td >< code > api_configurations</ code ></ td >< td > CRUD</ td >< td > Integration API keys</ td ></ tr >
< tr >< td >< code > settings_audit_log</ code ></ td >< td > Read</ td >< td > Change tracking for all settings</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Auth Context -->
< div id = "auth-context" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Authentication Context</ h2 >
< p class = "text-muted mb-4" > Auth via < code > src/contexts/AuthContext.tsx</ code > . Supports email/password, Google OAuth, and magic links.</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useAuth } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/contexts/AuthContext'</ span > ;
< span class = "syntax-keyword" > const</ span > { user, session, signUp, signIn, signInWithGoogle, signInWithMagicLink, signOut, loading } = < span class = "syntax-function" > useAuth</ span > ();
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > signUp</ span > (email, password, displayName);
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > signIn</ span > (email, password, rememberMe);
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > signOut</ span > ();</ code ></ pre >
< h3 class = "font-semibold mt-6 mb-2" > Route Protection</ h3 >
< ul class = "text-muted text-sm" >
< li >< code > ProtectedRoute</ code > — Requires auth. Redirects to < code > /auth</ code > . Also checks setup status.</ li >
< li >< code > PublicRoute</ code > — Accessible by all. Redirects to < code > /setup</ code > if no admin exists.</ li >
< li >< code > SetupRoute</ code > — Only when < code > no_admin_exists()</ code > is true. Blocks re-entry after setup.</ li >
</ ul >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: NATIVE FEATURES -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "native-features" class = "mb-16" >
< h1 class = "mb-4" > Native Features</ h1 >
< p class = "text-xl text-muted mb-8" > Access powerful device capabilities through Capacitor plugins and web APIs.</ p >
< div id = "capacitor-overview" class = "card mb-8" >
< h2 class = "font-bold mb-3" > Capacitor Overview</ h2 >
< p class = "text-muted text-sm mb-4" > AppForge uses < strong > Capacitor 8</ strong > to bridge web code with native device APIs. Features toggled in the builder are included in the generated native project.</ p >
< div class = "grid grid-3" >
< div class = "text-center" >< div class = "icon-box icon-box-info" style = "margin: 0 auto 8px;" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< rect x = "5" y = "2" width = "14" height = "20" rx = "2" ry = "2" /></ svg ></ div >< p class = "font-semibold text-sm" > iOS</ p >< p class = "text-xs text-muted" > Swift/Obj-C bridge</ p ></ div >
< div class = "text-center" >< div class = "icon-box icon-box-success" style = "margin: 0 auto 8px;" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< rect x = "5" y = "2" width = "14" height = "20" rx = "2" ry = "2" /></ svg ></ div >< p class = "font-semibold text-sm" > Android</ p >< p class = "text-xs text-muted" > Java/Kotlin bridge</ p ></ div >
< div class = "text-center" >< div class = "icon-box icon-box-warning" style = "margin: 0 auto 8px;" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" /></ svg ></ div >< p class = "font-semibold text-sm" > Web / PWA</ p >< p class = "text-xs text-muted" > Graceful fallback</ p ></ div >
</ div >
</ div >
<!-- Feature Cards -->
< div class = "grid grid-2 mb-12" >
< div class = "card" >< h3 class = "font-bold mb-2" > 📷 Camera</ h3 >< p class = "text-muted text-sm mb-2" > Photo capture, gallery picker, front/rear camera, base64 output.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > iOS</ span >< span class = "badge badge-android" > Android</ span >< span class = "badge badge-primary" > Web</ span ></ div ></ div >
< div class = "card" >< h3 class = "font-bold mb-2" > 🔐 Biometrics</ h3 >< p class = "text-muted text-sm mb-2" > Touch ID / Face ID / Fingerprint with passcode fallback.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > Face ID</ span >< span class = "badge badge-android" > Fingerprint</ span ></ div ></ div >
< div class = "card" >< h3 class = "font-bold mb-2" > 📳 Haptics</ h3 >< p class = "text-muted text-sm mb-2" > Impact (light/medium/heavy), notification (success/warning/error), custom vibration.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > iOS</ span >< span class = "badge badge-android" > Android</ span ></ div ></ div >
< div class = "card" >< h3 class = "font-bold mb-2" > 🔔 Push Notifications</ h3 >< p class = "text-muted text-sm mb-2" > FCM (Android) / APNs (iOS). Rich notifications, deep linking, token management.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > APNs</ span >< span class = "badge badge-android" > FCM</ span >< span class = "badge badge-primary" > Web Push</ span ></ div ></ div >
< div class = "card" >< h3 class = "font-bold mb-2" > 📍 Geolocation</ h3 >< p class = "text-muted text-sm mb-2" > Current position, continuous tracking, background updates.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > iOS</ span >< span class = "badge badge-android" > Android</ span >< span class = "badge badge-primary" > Web</ span ></ div ></ div >
< div class = "card" >< h3 class = "font-bold mb-2" > 📁 File System</ h3 >< p class = "text-muted text-sm mb-2" > Read/write files, directory management, sharing via native share sheet.</ p >< div class = "flex gap-2" >< span class = "badge badge-ios" > iOS</ span >< span class = "badge badge-android" > Android</ span ></ div ></ div >
</ div >
<!-- AdMob -->
< div id = "admob" class = "card mb-8" >
< h2 class = "font-bold mb-4" > AdMob Advertising</ h2 >
< p class = "text-muted mb-4" > Monetize apps with Google AdMob. Configured via Admin → System Settings.</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Type</ th >< th > Setting Key</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td > Banner</ td >< td >< code > admob_banner_id</ code ></ td >< td > Rectangular ad at top/bottom</ td ></ tr >
< tr >< td > Interstitial</ td >< td >< code > admob_interstitial_id</ code ></ td >< td > Full-screen ad between transitions</ td ></ tr >
< tr >< td > Rewarded</ td >< td >< code > admob_rewarded_id</ code ></ td >< td > User watches ad for reward</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Database Schema -->
< div id = "database-schema" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Database Schema</ h2 >
< p class = "text-muted mb-4" > Complete list of database tables with RLS enabled on all:</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Table</ th >< th > Description</ th >< th > Key Columns</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > profiles</ code ></ td >< td > User profiles (synced from auth.users)</ td >< td > id, email, display_name, avatar_url</ td ></ tr >
< tr >< td >< code > user_roles</ code ></ td >< td > Role assignments</ td >< td > user_id, role (admin/moderator/user)</ td ></ tr >
< tr >< td >< code > user_credits</ code ></ td >< td > Credit balances</ td >< td > user_id, monthly_credits, bonus_credits</ td ></ tr >
< tr >< td >< code > credit_usage_history</ code ></ td >< td > Credit transaction log</ td >< td > user_id, amount, action_type, description</ td ></ tr >
< tr >< td >< code > app_projects</ code ></ td >< td > App project configurations</ td >< td > user_id, website_url, app_name, features</ td ></ tr >
< tr >< td >< code > app_builds</ code ></ td >< td > Build records and artifacts</ td >< td > user_id, status, download_url, cloud_build_id</ td ></ tr >
< tr >< td >< code > app_templates</ code ></ td >< td > Saved project templates</ td >< td > user_id, name, config (JSON)</ td ></ tr >
< tr >< td >< code > chat_messages</ code ></ td >< td > AI assistant chat history</ td >< td > user_id, project_id, role, content</ td ></ tr >
< tr >< td >< code > automation_configs</ code ></ td >< td > Automation workflow definitions</ td >< td > project_id, workflow_type, is_enabled, config</ td ></ tr >
< tr >< td >< code > automation_logs</ code ></ td >< td > Automation execution history</ td >< td > automation_id, status, started_at, completed_at</ td ></ tr >
< tr >< td >< code > subscription_plans</ code ></ td >< td > Plan tier definitions</ td >< td > tier, price_monthly, monthly_credits</ td ></ tr >
< tr >< td >< code > user_subscriptions</ code ></ td >< td > Active user subscriptions</ td >< td > user_id, plan_id, status, billing_cycle</ td ></ tr >
< tr >< td >< code > payment_transactions</ code ></ td >< td > Payment history</ td >< td > user_id, amount, payment_method, status</ td ></ tr >
< tr >< td >< code > bank_transfer_requests</ code ></ td >< td > Manual bank transfer requests</ td >< td > user_id, amount, status, proof_of_payment_url</ td ></ tr >
< tr >< td >< code > invoices</ code ></ td >< td > User invoices</ td >< td > user_id, invoice_number, amount, status, items</ td ></ tr >
< tr >< td >< code > webhook_event_logs</ code ></ td >< td > Payment webhook event log</ td >< td > gateway, event_type, status, processing_time_ms</ td ></ tr >
< tr >< td >< code > consent_records</ code ></ td >< td > GDPR cookie consent records</ td >< td > user_id, consent_type, consented, ip_address</ td ></ tr >
< tr >< td >< code > system_settings</ code ></ td >< td > App-wide configuration</ td >< td > key, value, category</ td ></ tr >
< tr >< td >< code > payment_gateway_configs</ code ></ td >< td > Payment provider credentials</ td >< td > gateway, sandbox_config, live_config</ td ></ tr >
< tr >< td >< code > api_configurations</ code ></ td >< td > Integration API keys</ td >< td > provider, config, is_active</ td ></ tr >
< tr >< td >< code > credit_packs</ code ></ td >< td > Purchasable credit bundles</ td >< td > credits, price, is_active</ td ></ tr >
< tr >< td >< code > email_templates</ code ></ td >< td > Email HTML templates</ td >< td > name, subject, html_content</ td ></ tr >
< tr >< td >< code > plugins</ code ></ td >< td > Feature plugin registry</ td >< td > slug, type, is_active, version</ td ></ tr >
< tr >< td >< code > settings_audit_log</ code ></ td >< td > Settings change history</ td >< td > setting_key, old_value, new_value</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Environment Variables -->
< div id = "env-reference" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Environment Variables Reference</ h2 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Variable</ th >< th > Required</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > VITE_API_URL</ code ></ td >< td > Yes</ td >< td > Your Postgres database URL</ td ></ tr >
< tr >< td >< code > DATABASE_URL</ code ></ td >< td > Yes</ td >< td > Postgres connection string</ td ></ tr >
< tr >< td >< code > BETTER_AUTH_SECRET</ code ></ td >< td > Yes</ td >< td > Secret used to sign auth sessions</ td ></ tr >
< tr >< td >< code > VITE_DEMO_MODE</ code ></ td >< td > No</ td >< td > Enable demo mode (< code > true</ code > /< code > false</ code > )</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mt-6 mb-2" > Edge Function Secrets (database admin)</ h3 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Secret</ th >< th > Used By</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > STRIPE_SECRET_KEY</ code ></ td >< td > stripe-checkout, stripe-webhook, stripe-portal</ td ></ tr >
< tr >< td >< code > STRIPE_WEBHOOK_SECRET</ code ></ td >< td > stripe-webhook</ td ></ tr >
< tr >< td >< code > RESEND_API_KEY</ code ></ td >< td > send-email</ td ></ tr >
< tr >< td >< code > CODEMAGIC_API_TOKEN</ code ></ td >< td > cloud-build, cloud-build-status</ td ></ tr >
< tr >< td >< code > CODEMAGIC_APP_ID</ code ></ td >< td > cloud-build</ td ></ tr >
< tr >< td >< code > PUBLIC_SITE_URL</ code ></ td >< td > stripe-webhook (for email links)</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: CODEMAGIC BUILD PIPELINE -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "codemagic-pipeline" class = "mb-16" >
< h1 class = "mb-4" > Codemagic Build Pipeline</ h1 >
< p class = "text-xl text-muted mb-8" > Cloud-based CI/CD for compiling native Android APKs and iOS IPAs from your web application.</ p >
<!-- Build Architecture -->
< div id = "cm-architecture" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Build Architecture</ h2 >
< p class = "text-muted text-sm mb-4" > AppForge converts websites into native apps by wrapping the target URL in a < strong > Capacitor WebView shell</ strong > . The build pipeline works as follows:</ p >
< div class = "grid grid-2 mb-4" >
< div class = "card card-flat" >
< div class = "icon-box icon-box-info" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< rect x = "2" y = "3" width = "20" height = "14" rx = "2" ry = "2" />< line x1 = "8" x2 = "16" y1 = "21" y2 = "21" />< line x1 = "12" x2 = "12" y1 = "17" y2 = "21" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > 1. Configuration Generation</ h4 >
< p class = "text-xs text-muted" > The edge function (< code > cloud-build</ code > ) dynamically generates < code > capacitor.config.ts</ code > , < code > package.json</ code > , and platform-specific configs based on the user's builder selections.</ p >
</ div >
< div class = "card card-flat" >
< div class = "icon-box icon-box-success" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< polyline points = "16 18 22 12 16 6" />< polyline points = "8 6 2 12 8 18" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > 2. Codemagic CI/CD</ h4 >
< p class = "text-xs text-muted" > Codemagic pulls from the < code > main</ code > branch, installs dependencies, adds the native platform, and compiles the binary on Mac Mini M2 instances.</ p >
</ div >
< div class = "card card-flat" >
< div class = "icon-box icon-box-warning" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > 3. Artifact Output</ h4 >
< p class = "text-xs text-muted" > Android: APK (direct install) or AAB (Play Store). iOS: unsigned IPA for TestFlight distribution. Artifacts uploaded to Local file storage.</ p >
</ div >
< div class = "card card-flat" >
< div class = "icon-box icon-box-primary" >< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M22 12h-4l-3 9L9 3l-3 9H2" /></ svg ></ div >
< h4 class = "font-semibold mb-1" > 4. Real-Time Progress</ h4 >
< p class = "text-xs text-muted" > The < code > cloud-build-status</ code > edge function polls Codemagic for build status. The UI shows live progress with step-by-step updates.</ p >
</ div >
</ div >
< div class = "callout callout-info" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" />< path d = "M12 16v-4" />< path d = "M12 8h.01" /></ svg >
< div >< strong class = "text-sm" > Branch Pinning</ strong >< p class = "text-sm text-muted" > The < code > cloud-build</ code > edge function is hardcoded to trigger builds from the < code > main</ code > branch. This ensures the pipeline always uses the latest configuration and sanitization logic synced from your development environment to GitHub.</ p ></ div >
</ div >
</ div >
<!-- Prerequisites -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Prerequisites</ h2 >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2" > Required Accounts</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > Codemagic account</ strong > — Sign up at < a href = "https://codemagic.io" target = "_blank" > codemagic.io</ a ></ li >
< li >< strong > GitHub repository</ strong > — Your AppForge project must be pushed to GitHub</ li >
< li >< strong > Codemagic API Token</ strong > — Settings → Integrations → API tokens (requires < strong > Owner</ strong > or < strong > Builds</ strong > permission)</ li >
</ ul >
</ div >
< div >
< h3 class = "font-semibold mb-2" > For App Store Publishing</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > Google Play Developer</ strong > — $25 one-time fee for Android publishing</ li >
< li >< strong > Apple Developer Program</ strong > — $99/year for iOS App Store publishing</ li >
< li > Code signing certificates (Android keystore / iOS provisioning profile)</ li >
</ ul >
</ div >
</ div >
</ div >
<!-- API Token Permissions -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > API Token Permissions</ h2 >
< p class = "text-muted text-sm mb-4" > Codemagic API tokens require specific permission scopes. Restricted scopes return < code > 403 Forbidden</ code > errors.</ p >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Permission</ th >< th > Required</ th >< th > Purpose</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > Owner</ code ></ td >< td > ✓ Recommended</ td >< td > Full access to trigger builds, read status, manage apps</ td ></ tr >
< tr >< td >< code > Builds</ code ></ td >< td > ✓ Minimum</ td >< td > Trigger new builds and read build status</ td ></ tr >
< tr >< td >< code > Read-only</ code ></ td >< td > ✗ Insufficient</ td >< td > Cannot trigger builds — will return 403</ td ></ tr >
</ tbody >
</ table >
</ div >
< div class = "callout callout-warning mt-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > Secret Priority</ strong >< p class = "text-sm text-muted" > The < code > cloud-build</ code > edge function prioritizes < strong > environment-level secrets</ strong > (< code > CODEMAGIC_API_TOKEN</ code > , < code > CODEMAGIC_APP_ID</ code > ) over values stored in the < code > api_configurations</ code > database table. This prevents stale or swapped credentials from causing build failures.</ p ></ div >
</ div >
</ div >
<!-- codemagic.yaml -->
< div id = "cm-yaml" class = "card mb-8" >
< h2 class = "font-bold mb-4" > codemagic.yaml Configuration</ h2 >
< p class = "text-muted text-sm mb-4" > The < code > codemagic.yaml</ code > file in the project root defines build pipelines. Runs on < strong > Mac Mini M2</ strong > instances.</ p >
< h3 class = "font-semibold mb-2" > Key Properties</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Property</ th >< th > Value</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > instance_type</ code ></ td >< td >< code > mac_mini_m2</ code ></ td >< td > Apple Silicon build machines</ td ></ tr >
< tr >< td >< code > max_build_duration</ code ></ td >< td >< code > 30</ code > min</ td >< td > Build timeout</ td ></ tr >
< tr >< td >< code > node</ code ></ td >< td >< code > 18.x</ code > +</ td >< td > Node.js for npm install</ td ></ tr >
< tr >< td >< code > java</ code ></ td >< td >< code > 17</ code ></ td >< td > JDK for Android Gradle</ td ></ tr >
< tr >< td >< code > xcode</ code ></ td >< td >< code > latest</ code ></ td >< td > Xcode for iOS builds</ td ></ tr >
< tr >< td >< code > cocoapods</ code ></ td >< td >< code > default</ code ></ td >< td > CocoaPods for iOS dependencies</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Pipeline Structure</ h3 >
< pre >< code >< span class = "syntax-comment" > # codemagic.yaml — simplified structure</ span >
< span class = "syntax-property" > workflows</ span > :
< span class = "syntax-property" > android-build</ span > :
< span class = "syntax-property" > name</ span > : < span class = "syntax-string" > Android Build</ span >
< span class = "syntax-property" > instance_type</ span > : < span class = "syntax-string" > mac_mini_m2</ span >
< span class = "syntax-property" > scripts</ span > :
- < span class = "syntax-string" > npm install</ span >
- < span class = "syntax-string" > npx cap add android</ span >
- < span class = "syntax-string" > npx cap sync android</ span >
- < span class = "syntax-string" > cd android && ./gradlew assembleDebug</ span >
< span class = "syntax-property" > artifacts</ span > :
- < span class = "syntax-string" > android/app/build/outputs/**/*.apk</ span >
< span class = "syntax-property" > ios-build</ span > :
< span class = "syntax-property" > name</ span > : < span class = "syntax-string" > iOS Build</ span >
< span class = "syntax-property" > scripts</ span > :
- < span class = "syntax-string" > npm install</ span >
- < span class = "syntax-string" > npx cap add ios --packagemanager Cocoapods</ span >
- < span class = "syntax-string" > npx cap sync ios</ span >
- < span class = "syntax-string" > xcodebuild build ...</ span >
< span class = "syntax-property" > artifacts</ span > :
- < span class = "syntax-string" > build/ios/ipa/*.ipa</ span ></ code ></ pre >
</ div >
<!-- Android Pipeline -->
< div id = "cm-android" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Android Build Pipeline</ h2 >
< p class = "text-muted text-sm mb-4" > Generates APK files for direct installation or AAB bundles for Google Play Store.</ p >
< h3 class = "font-semibold mb-2" > Build Steps</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li >< strong > Install dependencies</ strong > — < code > npm install</ code ></ li >
< li >< strong > Generate config</ strong > — Dynamically creates < code > capacitor.config.ts</ code > with target URL, app name, package ID</ li >
< li >< strong > Validate appId</ strong > — Ensures Java-style package name (e.g., < code > com.example.myapp</ code > )</ li >
< li >< strong > Add Android</ strong > — < code > npx cap add android</ code ></ li >
< li >< strong > Sync assets</ strong > — < code > npx cap sync android</ code ></ li >
< li >< strong > Gradle build</ strong > — < code > cd android && ./gradlew assembleDebug</ code ></ li >
< li >< strong > Collect artifacts</ strong > — APK from < code > android/app/build/outputs/</ code ></ li >
</ ol >
< h3 class = "font-semibold mb-2" > Output Formats</ h3 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Format</ th >< th > Extension</ th >< th > Use Case</ th ></ tr ></ thead >
< tbody >
< tr >< td > Debug APK</ td >< td >< code > .apk</ code ></ td >< td > Direct installation, testing</ td ></ tr >
< tr >< td > Release APK</ td >< td >< code > .apk</ code ></ td >< td > Signed production builds</ td ></ tr >
< tr >< td > App Bundle</ td >< td >< code > .aab</ code ></ td >< td > Google Play Store (required for new apps)</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- iOS Pipeline -->
< div id = "cm-ios" class = "card mb-8" >
< h2 class = "font-bold mb-4" > iOS Build Pipeline</ h2 >
< p class = "text-muted text-sm mb-4" > Generates unsigned IPA files for testing. Code signing handled separately for App Store distribution.</ p >
< div class = "callout callout-warning mb-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > CocoaPods Required</ strong >< p class = "text-sm text-muted" > The iOS pipeline explicitly forces < strong > CocoaPods</ strong > (< code > --packagemanager Cocoapods</ code > ) during platform addition. This resolves incompatibilities between certain plugins (e.g., < code > @aparajita/capacitor-biometric-auth</ code > ) and Capacitor 8's default Swift Package Manager (SPM) mode.</ p ></ div >
</ div >
< h3 class = "font-semibold mb-2" > Build Steps</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li >< strong > Install dependencies</ strong > — < code > npm install</ code ></ li >
< li >< strong > Add iOS with CocoaPods</ strong > — < code > npx cap add ios --packagemanager Cocoapods</ code ></ li >
< li >< strong > Install pods</ strong > — < code > cd ios/App && pod install</ code ></ li >
< li >< strong > Sync assets</ strong > — < code > npx cap sync ios</ code ></ li >
< li >< strong > Xcode build</ strong > — < code > xcodebuild</ code > with archive → export</ li >
< li >< strong > Collect artifacts</ strong > — Unsigned IPA from build output</ li >
</ ol >
< p class = "text-muted text-sm" > The default pipeline generates < strong > unsigned IPAs</ strong > . To distribute via App Store or TestFlight, add your provisioning profile and signing certificate to the Codemagic workflow.</ p >
</ div >
<!-- Troubleshooting -->
< div id = "cm-troubleshooting" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Build Troubleshooting</ h2 >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > 403 Forbidden on Build Trigger</ h3 >< p class = "text-muted text-sm" > API token has insufficient permissions. Generate a new token with < code > Owner</ code > or < code > Builds</ code > scope.</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > Build Stuck at "Queued"</ h3 >< p class = "text-muted text-sm" > Check Codemagic dashboard for concurrent build limits. Free accounts: 1 concurrent build.</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > iOS Pod Install Fails</ h3 >< p class = "text-muted text-sm" > Ensure < code > --packagemanager Cocoapods</ code > flag is used. Clear cache: < code > pod cache clean --all</ code > .</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > Invalid Package Name</ h3 >< p class = "text-muted text-sm" > Android requires Java-style names (e.g., < code > com.company.app</ code > ). Lowercase, letters/numbers/dots only.</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > Build Uses Wrong Config</ h3 >< p class = "text-muted text-sm" > Pipeline builds from < code > main</ code > branch only. Push latest: < code > git push origin main</ code > .</ p ></ div >
< div >< h3 class = "font-semibold text-warning mb-1" > Swapped API Token / App ID</ h3 >< p class = "text-muted text-sm" > System auto-detects miswired configs and falls back to environment secrets. Verify values in the server .env file.</ p ></ div >
</ div >
<!-- Codemagic End-to-End Setup Guide -->
< div id = "cm-e2e-setup" class = "card mb-8" >
< h2 class = "font-bold mb-4" > End-to-End Setup Guide</ h2 >
< p class = "text-muted text-sm mb-4" > Complete walkthrough from zero to your first successful cloud build.</ p >
< h3 class = "font-semibold mb-2" > Phase 1: Codemagic Account & Token</ h3 >
< ol class = "text-muted text-sm mb-6" >
< li >< strong > Create a Codemagic account</ strong > at < a href = "https://codemagic.io/signup" target = "_blank" > codemagic.io/signup</ a > . GitHub OAuth is the fastest method.</ li >
< li >< strong > Generate an API token</ strong > : Navigate to < code > Teams → Settings → API tokens</ code > (or your personal settings). Create a new token with < strong > Owner</ strong > or < strong > Builds</ strong > permission scope. Copy the token — it's only shown once.</ li >
< li >< strong > Connect your GitHub repo</ strong > : In Codemagic, click < strong > "Add application"</ strong > → select the GitHub repository containing your AppForge project → choose < code > codemagic.yaml</ code > as the configuration type.</ li >
< li >< strong > Note your App ID</ strong > : After the app is added, the App ID is visible in the URL or app settings (a 24-character hex string like < code > 67abc1234def5678ghij9012</ code > ).</ li >
</ ol >
< h3 class = "font-semibold mb-2" > Phase 2: Configure Secrets</ h3 >
< p class = "text-muted text-sm mb-4" > You have two ways to provide credentials. Environment secrets take priority over database values:</ p >
< div class = "grid grid-2 mb-4" >
< div class = "card card-flat" >
< h4 class = "font-semibold mb-1" > Option A: Environment Secrets < span class = "badge badge-new" > Recommended</ span ></ h4 >
< p class = "text-xs text-muted mb-2" > Set in database admin → Settings → Secrets:</ p >
< pre >< code > CODEMAGIC_API_TOKEN=your-token-here
CODEMAGIC_APP_ID=your-app-id-here</ code ></ pre >
</ div >
< div class = "card card-flat" >
< h4 class = "font-semibold mb-1" > Option B: Admin Panel UI</ h4 >
< p class = "text-xs text-muted mb-2" > Use the guided Setup Wizard:</ p >
< p class = "text-xs text-muted" > Navigate to < code > Admin → Integrations → Codemagic → Setup Wizard</ code > and follow the 6-step flow. The wizard validates tokens and auto-fetches available apps.</ p >
</ div >
</ div >
< div class = "callout callout-warning mb-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > Secret Priority</ strong >< p class = "text-sm text-muted" > If both are set, environment secrets < strong > always override</ strong > values in < code > api_configurations</ code > . The system also detects "miswired" configs (swapped token/ID) and automatically falls back to secrets.</ p ></ div >
</ div >
< h3 class = "font-semibold mb-2" > Phase 3: Push Code to GitHub</ h3 >
< ol class = "text-muted text-sm mb-6" >
< li > Ensure < code > codemagic.yaml</ code > is committed and pushed to the < code > main</ code > branch.</ li >
< li > The build pipeline always triggers from < code > main</ code > — make sure your latest app configuration is merged there.</ li >
< li > The < code > cloud-build</ code > edge function polls GitHub to verify the < code > main</ code > branch HEAD is recent (within 5 minutes) before triggering the Codemagic build, ensuring sync.</ li >
</ ol >
< h3 class = "font-semibold mb-2" > Phase 4: Trigger Your First Build</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li > Go to < code > /builder</ code > → enter a website URL → configure → preview.</ li >
< li > On Step 4 (Build), select < strong > Android</ strong > or < strong > iOS</ strong > platform.</ li >
< li > Click < strong > Build</ strong > . The edge function:
< ul >
< li > Resolves and validates Codemagic credentials</ li >
< li > Checks GitHub sync status (waits up to 60s for a fresh commit)</ li >
< li > Sanitizes the package name to valid Java/iOS format</ li >
< li > Triggers the Codemagic workflow with environment variables (< code > WEBSITE_URL</ code > , < code > APP_NAME</ code > , < code > PACKAGE_NAME</ code > , etc.)</ li >
< li > Registers a webhook callback URL for real-time status updates</ li >
</ ul >
</ li >
< li > Progress is tracked via the < code > codemagic-webhook</ code > endpoint and < code > cloud-build-status</ code > polling.</ li >
< li > On completion, the artifact (APK/IPA) URL is stored in < code > app_builds.download_url</ code > .</ li >
</ ol >
< h3 class = "font-semibold mb-2" > Build Environment Variables</ h3 >
< p class = "text-muted text-sm mb-2" > The < code > cloud-build</ code > function passes these variables to the Codemagic workflow:</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Variable</ th >< th > Source</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > WEBSITE_URL</ code ></ td >< td > User input</ td >< td > Target website to wrap in the native app</ td ></ tr >
< tr >< td >< code > APP_NAME</ code ></ td >< td > User input</ td >< td > Display name for the app</ td ></ tr >
< tr >< td >< code > PACKAGE_NAME</ code ></ td >< td > Auto-generated</ td >< td > Java-style package ID (e.g., < code > com.app.mysite</ code > )</ td ></ tr >
< tr >< td >< code > BUNDLE_ID</ code ></ td >< td > Auto-generated</ td >< td > iOS bundle identifier (same format, used for iOS builds)</ td ></ tr >
< tr >< td >< code > BUILD_ID</ code ></ td >< td > System</ td >< td > Internal build record UUID for status tracking</ td ></ tr >
< tr >< td >< code > PRIMARY_COLOR</ code ></ td >< td > User config</ td >< td > App theme primary color hex</ td ></ tr >
< tr >< td >< code > ACCENT_COLOR</ code ></ td >< td > User config</ td >< td > App theme accent color hex</ td ></ tr >
< tr >< td >< code > CM_WEBHOOK_URL</ code ></ td >< td > System</ td >< td > Codemagic webhook callback for real-time updates</ td ></ tr >
< tr >< td >< code > DATABASE_URL</ code ></ td >< td > System</ td >< td > For the webhook to update build status</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Codemagic Webhook Flow -->
< div id = "cm-webhook-flow" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Webhook Status Flow</ h2 >
< p class = "text-muted text-sm mb-4" > The < code > codemagic-webhook</ code > edge function provides real-time build updates, eliminating the need for aggressive polling.</ p >
< h3 class = "font-semibold mb-2" > Status Mapping</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Codemagic Status</ th >< th > App Status</ th >< th > Progress</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > queued</ code ></ td >< td >< code > building</ code ></ td >< td > 10%</ td ></ tr >
< tr >< td >< code > fetching</ code ></ td >< td >< code > building</ code ></ td >< td > 15%</ td ></ tr >
< tr >< td >< code > preparing</ code ></ td >< td >< code > building</ code ></ td >< td > 20%</ td ></ tr >
< tr >< td >< code > building</ code ></ td >< td >< code > building</ code ></ td >< td > 30– 85%</ td ></ tr >
< tr >< td >< code > finished</ code > / < code > success</ code ></ td >< td >< code > complete</ code ></ td >< td > 100%</ td ></ tr >
< tr >< td >< code > failed</ code > / < code > canceled</ code ></ td >< td >< code > failed</ code ></ td >< td > —</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Failure Diagnostics</ h3 >
< p class = "text-muted text-sm mb-2" > The webhook extracts step-level failure details and provides actionable hints:</ p >
< ul class = "text-muted text-sm" >
< li >< strong > CocoaPods errors</ strong > → Suggests checking < code > --packagemanager Cocoapods</ code > flag</ li >
< li >< strong > Xcode build failures</ strong > → Points to signing and provisioning issues</ li >
< li >< strong > SPM conflicts</ strong > → Recommends using CocoaPods for Capacitor projects</ li >
< li >< strong > Missing Podfile</ strong > → Indicates < code > npx cap add ios</ code > wasn't run before < code > pod install</ code ></ li >
</ ul >
< p class = "text-muted text-sm mt-2" > Failed step name, message, and last 2000 chars of the log are stored in < code > app_builds.config</ code > for the Build Details drawer.</ p >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: STORAGE & FILE MANAGEMENT -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "storage-admin" class = "mb-16" >
< h1 class = "mb-4" > Storage & File Management</ h1 >
< p class = "text-xl text-muted mb-8" > Manage file storage, image optimization, CDN caching, and tiered quotas.</ p >
<!-- Bucket Management -->
< div id = "storage-buckets" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Bucket Management</ h2 >
< p class = "text-muted text-sm mb-4" > Managed via < code > Admin → Settings → Storage</ code > . Local file storage provides file hosting with per-bucket access controls.</ p >
< h3 class = "font-semibold mb-2" > Default Buckets</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Bucket</ th >< th > Public</ th >< th > Purpose</ th >< th > Typical Content</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > avatars</ code ></ td >< td > ✓ Yes</ td >< td > User profile pictures</ td >< td > JPEG/PNG, max 2MB</ td ></ tr >
< tr >< td >< code > app-icons</ code ></ td >< td > ✓ Yes</ td >< td > Generated app icons</ td >< td > PNG, 48– 512px sizes</ td ></ tr >
< tr >< td >< code > splash-screens</ code ></ td >< td > ✓ Yes</ td >< td > Splash screen images</ td >< td > PNG, various device sizes</ td ></ tr >
< tr >< td >< code > apk-builds</ code ></ td >< td > ✓ Yes</ td >< td > Build artifacts</ td >< td > APK/IPA, 10– 100MB</ td ></ tr >
< tr >< td >< code > project-assets</ code ></ td >< td > ✗ No</ td >< td > Private project files</ td >< td > Source assets, certs</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > RLS Policies</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > Users</ strong > — Upload/read/delete in own folder (< code > user_id/</ code > )</ li >
< li >< strong > Admins</ strong > — Full access to all buckets and folders</ li >
< li >< strong > Public buckets</ strong > — Unauthenticated read access</ li >
</ ul >
</ div >
<!-- Image Optimization -->
< div id = "storage-optimization" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Image Optimization</ h2 >
< p class = "text-muted text-sm mb-4" > Configure via < code > Admin → Settings → Storage → Optimization</ code > .</ p >
< div class = "grid grid-2 mb-4" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Auto-Resize</ h4 >< p class = "text-xs text-muted" > Resize uploads to max dimensions. Maintains aspect ratio.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Quality Control</ h4 >< p class = "text-xs text-muted" > Set JPEG/PNG quality (1– 100).</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > WebP Conversion</ h4 >< p class = "text-xs text-muted" > Convert uploads to WebP for smaller files.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > CDN Caching</ h4 >< p class = "text-xs text-muted" > Configure cache headers and TTL.</ p ></ div >
</ div >
< h3 class = "font-semibold mb-2" > Settings Reference</ h3 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Setting</ th >< th > Default</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > auto_resize</ code ></ td >< td >< code > true</ code ></ td >< td > Enable automatic resizing</ td ></ tr >
< tr >< td >< code > max_width</ code ></ td >< td >< code > 1920</ code ></ td >< td > Max width in pixels</ td ></ tr >
< tr >< td >< code > max_height</ code ></ td >< td >< code > 1920</ code ></ td >< td > Max height in pixels</ td ></ tr >
< tr >< td >< code > quality</ code ></ td >< td >< code > 85</ code ></ td >< td > Compression quality</ td ></ tr >
< tr >< td >< code > convert_webp</ code ></ td >< td >< code > false</ code ></ td >< td > Auto-convert to WebP</ td ></ tr >
< tr >< td >< code > cache_ttl</ code ></ td >< td >< code > 86400</ code ></ td >< td > Cache TTL (24 hours)</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Access Control -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Access Control & Security</ h2 >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2" > Signed URLs</ h3 >
< p class = "text-muted text-sm mb-2" > Time-limited URLs for private bucket files. Configurable expiry (1 min – 7 days).</ p >
< pre >< code >< span class = "syntax-keyword" > const</ span > { data } = < span class = "syntax-keyword" > await</ span > backend.storage
.< span class = "syntax-function" > from</ span > (< span class = "syntax-string" > 'project-assets'</ span > )
.< span class = "syntax-function" > createSignedUrl</ span > (path, < span class = "syntax-number" > 3600</ span > );</ code ></ pre >
</ div >
< div >
< h3 class = "font-semibold mb-2" > Hotlink Protection</ h3 >
< p class = "text-muted text-sm" > Restrict which domains can embed your files. Configure allowed origins to prevent bandwidth theft.</ p >
</ div >
</ div >
</ div >
<!-- Storage Providers -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Storage Provider Configuration</ h2 >
< p class = "text-muted text-sm mb-4" > Multiple backends supported. Configure via < code > Admin → Settings → Storage → Provider</ code > .</ p >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Provider</ th >< th > Required Config</ th >< th > Notes</ th ></ tr ></ thead >
< tbody >
< tr >< td > Local file storage < span class = "badge badge-new" > Default</ span ></ td >< td > None (built-in)</ td >< td > Built-in</ td ></ tr >
< tr >< td > AWS S3</ td >< td > Bucket, Region, Access Key, Secret Key</ td >< td > S3 API compatible</ td ></ tr >
< tr >< td > Google Cloud Storage</ td >< td > Bucket, Service Account JSON</ td >< td > GCS with uniform access</ td ></ tr >
< tr >< td > Cloudflare R2</ td >< td > Account ID, Bucket, Access Key, Secret Key</ td >< td > Zero egress fees</ td ></ tr >
< tr >< td > Local Storage</ td >< td > Absolute path (starts with < code > /</ code > )</ td >< td > Self-hosted VPS only</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-sm" > Use the < strong > Test Connection</ strong > button to validate credentials via < code > test-storage-connection</ code > edge function.</ p >
</ div >
<!-- Tiered Quotas -->
< div id = "storage-quotas" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Tiered Storage Quotas</ h2 >
< p class = "text-muted text-sm mb-4" > Configure via < code > Admin → Settings → Storage → Quotas</ code > .</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Tier</ th >< th > Max Storage</ th >< th > Max File Size</ th >< th > Max Projects</ th ></ tr ></ thead >
< tbody >
< tr >< td >< span class = "badge badge-secondary" > Free</ span ></ td >< td > 500 MB</ td >< td > 10 MB</ td >< td > 3</ td ></ tr >
< tr >< td >< span class = "badge badge-primary" > Pro</ span ></ td >< td > 5 GB</ td >< td > 50 MB</ td >< td > 25</ td ></ tr >
< tr >< td >< span class = "badge badge-new" > Enterprise</ span ></ td >< td > 50 GB</ td >< td > 200 MB</ td >< td > Unlimited</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-sm mt-2" > Enforced at upload. Users warned at 80% capacity.</ p >
</ div >
<!-- Storage API Usage -->
< div id = "storage-api-usage" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Storage API — Code Examples</ h2 >
< p class = "text-muted text-sm mb-4" > The < code > useStorage</ code > hook and < code > storageApi</ code > wrapper provide full file management with validation and access control.</ p >
< h3 class = "font-semibold mb-2" > useStorage Hook</ h3 >
< pre >< code >< span class = "syntax-keyword" > import</ span > { useStorage } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/hooks/useStorage'</ span > ;
< span class = "syntax-keyword" > const</ span > { upload, remove, getPublicUrl, getSignedUrl, listFiles, isUploading, uploadProgress } = < span class = "syntax-function" > useStorage</ span > ();
< span class = "syntax-comment" > // Upload a file (auto-validates type and size)</ span >
< span class = "syntax-keyword" > const</ span > result = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > upload</ span > (file, {
bucket: < span class = "syntax-string" > 'app-icons'</ span > , < span class = "syntax-comment" > // 'avatars' | 'app-icons' | 'splash-screens' | 'apk-builds' | 'project-assets'</ span >
path: < span class = "syntax-string" > 'icons'</ span > , < span class = "syntax-comment" > // Optional subfolder within user's directory</ span >
maxSizeMB: 10, < span class = "syntax-comment" > // Override default max size</ span >
allowedTypes: [< span class = "syntax-string" > 'image/png'</ span > ] < span class = "syntax-comment" > // Override default mime types</ span >
});
< span class = "syntax-comment" > // result: { url: 'https://...', path: 'user-id/icons/123-abc.png', error: null }</ span >
< span class = "syntax-comment" > // Get public URL (public buckets only)</ span >
< span class = "syntax-keyword" > const</ span > url = < span class = "syntax-function" > getPublicUrl</ span > (< span class = "syntax-string" > 'avatars'</ span > , < span class = "syntax-string" > 'user-id/avatar.jpg'</ span > );
< span class = "syntax-comment" > // Get signed URL (private buckets, time-limited access)</ span >
< span class = "syntax-keyword" > const</ span > signedUrl = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > getSignedUrl</ span > (< span class = "syntax-string" > 'project-assets'</ span > , < span class = "syntax-string" > 'path/file.pdf'</ span > , 3600); < span class = "syntax-comment" > // 1 hour</ span >
< span class = "syntax-comment" > // List files in a bucket folder</ span >
< span class = "syntax-keyword" > const</ span > files = < span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > listFiles</ span > (< span class = "syntax-string" > 'app-icons'</ span > , < span class = "syntax-string" > 'icons'</ span > );
< span class = "syntax-comment" > // [{ name: 'icon.png', url: 'https://...' }]</ span >
< span class = "syntax-comment" > // Delete a file</ span >
< span class = "syntax-keyword" > await</ span > < span class = "syntax-function" > remove</ span > (< span class = "syntax-string" > 'avatars'</ span > , < span class = "syntax-string" > 'user-id/old-avatar.jpg'</ span > );</ code ></ pre >
< h3 class = "font-semibold mt-6 mb-2" > Default File Limits</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Bucket</ th >< th > Max Size</ th >< th > Allowed Types</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > avatars</ code ></ td >< td > 5 MB</ td >< td > JPEG, PNG, WebP, GIF</ td ></ tr >
< tr >< td >< code > app-icons</ code ></ td >< td > 10 MB</ td >< td > PNG, JPEG, WebP, SVG</ td ></ tr >
< tr >< td >< code > splash-screens</ code ></ td >< td > 10 MB</ td >< td > PNG, JPEG, WebP</ td ></ tr >
< tr >< td >< code > apk-builds</ code ></ td >< td > 500 MB</ td >< td > APK, IPA, ZIP</ td ></ tr >
< tr >< td >< code > project-assets</ code ></ td >< td > 50 MB</ td >< td > Images, PDF, JSON</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Low-Level storageApi</ h3 >
< p class = "text-muted text-sm mb-2" > Direct backend client calls without validation or toast notifications:</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { storageApi } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/lib/api/rest-client'</ span > ;
< span class = "syntax-comment" > // Upload with upsert</ span >
< span class = "syntax-keyword" > const</ span > { data, error } = < span class = "syntax-keyword" > await</ span > storageApi.< span class = "syntax-function" > upload</ span > (< span class = "syntax-string" > 'avatars'</ span > , < span class = "syntax-string" > 'user-id/avatar.jpg'</ span > , file);
< span class = "syntax-comment" > // Delete</ span >
< span class = "syntax-keyword" > await</ span > storageApi.< span class = "syntax-function" > delete</ span > (< span class = "syntax-string" > 'avatars'</ span > , < span class = "syntax-string" > 'user-id/avatar.jpg'</ span > );
< span class = "syntax-comment" > // List files in folder</ span >
< span class = "syntax-keyword" > const</ span > files = < span class = "syntax-keyword" > await</ span > storageApi.< span class = "syntax-function" > list</ span > (< span class = "syntax-string" > 'app-icons'</ span > , < span class = "syntax-string" > 'user-id'</ span > );
< span class = "syntax-comment" > // Get public URL</ span >
< span class = "syntax-keyword" > const</ span > url = storageApi.< span class = "syntax-function" > getPublicUrl</ span > (< span class = "syntax-string" > 'avatars'</ span > , < span class = "syntax-string" > 'user-id/avatar.jpg'</ span > );</ code ></ pre >
< h3 class = "font-semibold mt-6 mb-2" > Admin Storage Management</ h3 >
< p class = "text-muted text-sm mb-2" > Admin operations use the < code > storage-admin</ code > edge function:</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { backend } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/lib/backend-client'</ span > ;
< span class = "syntax-comment" > // List all buckets with stats</ span >
< span class = "syntax-keyword" > const</ span > { data } = < span class = "syntax-keyword" > await</ span > backend.functions.< span class = "syntax-function" > invoke</ span > (< span class = "syntax-string" > 'storage-admin'</ span > , {
method: < span class = "syntax-string" > 'GET'</ span > ,
headers: { < span class = "syntax-string" > 'action'</ span > : < span class = "syntax-string" > 'list'</ span > }
});
< span class = "syntax-comment" > // [{ name: "avatars", public: true, fileCount: 12, totalSize: 4567890 }]</ span >
< span class = "syntax-comment" > // Test connection to external provider</ span >
< span class = "syntax-keyword" > const</ span > { data: result } = < span class = "syntax-keyword" > await</ span > backend.functions.< span class = "syntax-function" > invoke</ span > (< span class = "syntax-string" > 'test-storage-connection'</ span > , {
body: { provider: < span class = "syntax-string" > 's3'</ span > , config: { s3_bucket: < span class = "syntax-string" > '...'</ span > , s3_region: < span class = "syntax-string" > '...'</ span > , ... } }
});</ code ></ pre >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: INTEGRATIONS -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "integrations-admin" class = "mb-16" >
< h1 class = "mb-4" > Integrations</ h1 >
< p class = "text-xl text-muted mb-8" > Configure third-party services from < code > Admin → Integrations</ code > .</ p >
<!-- Resend -->
< div id = "int-resend" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Resend (Transactional Email)</ h2 >
< p class = "text-muted text-sm mb-4" > Powers all transactional emails: welcome, password reset, build completion, subscription confirmations.</ p >
< h3 class = "font-semibold mb-2" > Setup</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li > Create an account at < a href = "https://resend.com" target = "_blank" > resend.com</ a ></ li >
< li > Generate an API key (starts with < code > re_</ code > )</ li >
< li > Add < code > RESEND_API_KEY</ code > to the server .env file</ li >
< li > Verify sending domain in Resend dashboard</ li >
< li > Configure sender address in < code > Admin → Integrations → Email</ code ></ li >
</ ol >
< h3 class = "font-semibold mb-2" > Test Connection</ h3 >
< p class = "text-muted text-sm" > Validates API key format (< code > re_</ code > prefix) rather than making an API call, because browser requests to Resend are blocked by CORS. Edge functions handle sending server-side.</ p >
< h3 class = "font-semibold mt-4 mb-2" > Email Templates</ h3 >
< p class = "text-muted text-sm" > Customize at < code > Admin → Email Templates</ code > . Templates support HTML with < code > {{variable}}</ code > syntax. Available: < code > {{app_name}}</ code > , < code > {{user_name}}</ code > , < code > {{user_email}}</ code > , < code > {{action_url}}</ code > , < code > {{support_email}}</ code > .</ p >
</ div >
<!-- Appetize.io -->
< div id = "int-appetize" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Appetize.io (Device Preview)</ h2 >
< p class = "text-muted text-sm mb-4" > Interactive device previews in the browser — test apps on virtual iOS/Android devices before downloading.</ p >
< h3 class = "font-semibold mb-2" > Setup</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li > Create an account at < a href = "https://appetize.io" target = "_blank" > appetize.io</ a ></ li >
< li > Get your API token from the Appetize dashboard</ li >
< li > Enter the token in < code > Admin → Integrations → Appetize.io</ code ></ li >
< li > Builds automatically upload artifacts for live preview</ li >
</ ol >
< p class = "text-muted text-sm" > When a build completes, the APK/IPA is uploaded to Appetize.io via the < code > appetize-upload</ code > edge function. The embed URL appears in the Builder's Preview step.</ p >
</ div >
<!-- AI Providers -->
< div id = "int-ai" class = "card mb-8" >
< h2 class = "font-bold mb-4" > AI Providers</ h2 >
< p class = "text-muted text-sm mb-4" > Powers < strong > website analysis</ strong > (metadata, colors, branding extraction) and the < strong > AI assistant</ strong > (config guidance chat).</ p >
< h3 class = "font-semibold mb-2" > Supported Providers</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Provider</ th >< th > Secret Key</ th >< th > Models</ th ></ tr ></ thead >
< tbody >
< tr >< td > OpenAI</ td >< td >< code > OPENAI_API_KEY</ code ></ td >< td > GPT-4o, GPT-4o-mini</ td ></ tr >
< tr >< td > Google Gemini</ td >< td >< code > GEMINI_API_KEY</ code ></ td >< td > Gemini Pro, Gemini Flash</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-sm" > Enter keys in < code > Admin → Integrations → AI Providers</ code > . Gemini validation uses the < code > v1beta/models</ code > endpoint.</ p >
</ div >
<!-- Codemagic Setup Wizard -->
< div id = "int-codemagic-wizard" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Codemagic Setup Wizard</ h2 >
< p class = "text-muted text-sm mb-4" > Guided 6-step wizard in < code > Admin → Integrations → Codemagic</ code > .</ p >
< div class = "grid grid-3 mb-4" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 1</ span > Overview</ h4 >< p class = "text-xs text-muted" > Introduction and prerequisites.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 2</ span > Account</ h4 >< p class = "text-xs text-muted" > Link Codemagic account.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 3</ span > API Token</ h4 >< p class = "text-xs text-muted" > Enter and validate token.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 4</ span > App Selection</ h4 >< p class = "text-xs text-muted" > Searchable app dropdown via API.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 5</ span > Verification</ h4 >< p class = "text-xs text-muted" > Test build trigger permissions.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1 flex items-center" >< span class = "step-number" style = "width:22px;height:22px;font-size:11px;" > 6</ span > Completion</ h4 >< p class = "text-xs text-muted" > Save config. Default workflow: < code > android-build</ code > .</ p ></ div >
</ div >
< div class = "callout callout-tip" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M22 11.08V12a10 10 0 1 1-5.93-9.14" />< polyline points = "22 4 12 14.01 9 11.01" /></ svg >
< div >< strong class = "text-sm" > Refresh Apps</ strong >< p class = "text-sm text-muted" > Step 4 includes a < strong > Refresh</ strong > button to re-fetch Codemagic apps without re-testing the connection.</ p ></ div >
</ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: PAYMENT GATEWAYS -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "payment-gateways" class = "mb-16" >
< h1 class = "mb-4" > Payment Gateways</ h1 >
< p class = "text-xl text-muted mb-8" > Configure payment providers from < code > Admin → Payments</ code > . All gateways support sandbox/test mode.</ p >
<!-- Stripe -->
< div id = "pg-stripe" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Stripe</ h2 >
< p class = "text-muted text-sm mb-4" > Primary gateway for card payments, subscriptions, and customer portal.</ p >
< h3 class = "font-semibold mb-2" > Edge Functions</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Function</ th >< th > Purpose</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > stripe-checkout</ code ></ td >< td > Checkout Sessions for subscriptions and credit packs</ td ></ tr >
< tr >< td >< code > stripe-webhook</ code ></ td >< td > Payment success, subscription changes, cancellations</ td ></ tr >
< tr >< td >< code > stripe-portal</ code ></ td >< td > Customer Portal for self-service billing</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Required Secrets</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Secret</ th >< th > Format</ th >< th > Where to Get</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > STRIPE_SECRET_KEY</ code ></ td >< td >< code > sk_test_...</ code > / < code > sk_live_...</ code ></ td >< td > Stripe → Developers → API keys</ td ></ tr >
< tr >< td >< code > STRIPE_WEBHOOK_SECRET</ code ></ td >< td >< code > whsec_...</ code ></ td >< td > Stripe → Webhooks → Signing secret</ td ></ tr >
< tr >< td >< code > PUBLIC_SITE_URL</ code ></ td >< td >< code > https://yourdomain.com</ code ></ td >< td > Your production domain</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Sandbox ↔ Live</ h3 >
< p class = "text-muted text-sm mb-2" > Toggle in < code > Admin → Payments → Stripe</ code > . Key format auto-validated (< code > sk_test_</ code > vs < code > sk_live_</ code > ).</ p >
< h3 class = "font-semibold mt-4 mb-2" > Webhook Setup</ h3 >
< pre >< code >< span class = "syntax-comment" > # Webhook URL</ span >
https://YOUR_DOMAIN/api/functions/v1/stripe-webhook
< span class = "syntax-comment" > # Required events</ span >
checkout.session.completed
customer.subscription.updated
customer.subscription.deleted
invoice.payment_succeeded
invoice.payment_failed</ code ></ pre >
</ div >
<!-- PayPal -->
< div id = "pg-paypal" class = "card mb-8" >
< h2 class = "font-bold mb-4" > PayPal</ h2 >
< p class = "text-muted text-sm mb-4" > One-time payments (orders) and recurring subscriptions (billing plans).</ p >
< h3 class = "font-semibold mb-2" > Edge Functions</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Function</ th >< th > Purpose</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > paypal-checkout</ code ></ td >< td > PayPal orders for credit packs</ td ></ tr >
< tr >< td >< code > paypal-billing</ code ></ td >< td > Billing plans and subscription creation</ td ></ tr >
< tr >< td >< code > paypal-webhook</ code ></ td >< td > Payment capture, subscription events</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Configuration</ h3 >
< ul class = "text-muted text-sm mb-4" >
< li >< strong > Client ID</ strong > — PayPal REST API Client ID</ li >
< li >< strong > Client Secret</ strong > — PayPal REST API Secret</ li >
< li >< strong > Mode</ strong > — Sandbox or Live</ li >
</ ul >
< h3 class = "font-semibold mb-2" > Dynamic Branding</ h3 >
< p class = "text-muted text-sm" > Brand name in PayPal checkout is pulled dynamically from < code > system_settings.app_name</ code > . No hardcoded brand names.</ p >
< h3 class = "font-semibold mt-4 mb-2" > Billing Plans</ h3 >
< p class = "text-muted text-sm" > Managed at < code > Admin → Payments → PayPal → Billing Plans</ code > . Stored in < code > subscription_plans</ code > : < code > paypal_product_id</ code > , < code > paypal_plan_id</ code > , < code > paypal_yearly_plan_id</ code > .</ p >
</ div >
<!-- Coinbase -->
< div id = "pg-coinbase" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Coinbase Commerce</ h2 >
< p class = "text-muted text-sm mb-4" > Cryptocurrency payments (Bitcoin, Ethereum, USDC, etc.).</ p >
< h3 class = "font-semibold mb-2" > Setup</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li > Create account at < a href = "https://commerce.coinbase.com" target = "_blank" > commerce.coinbase.com</ a ></ li >
< li > Generate API key (Settings → Security)</ li >
< li > Get webhook shared secret (Settings → Webhooks)</ li >
< li > Configure in < code > Admin → Payments → Coinbase</ code ></ li >
< li > Add < code > COINBASE_API_KEY</ code > and < code > COINBASE_WEBHOOK_SECRET</ code > to the server .env file</ li >
</ ol >
< h3 class = "font-semibold mb-2" > Edge Functions</ h3 >
< ul class = "text-muted text-sm" >
< li >< code > coinbase-checkout</ code > — Creates charges for credit packs</ li >
< li >< code > coinbase-webhook</ code > — Processes charge:completed, charge:failed events</ li >
</ ul >
</ div >
<!-- Bank Transfer -->
< div id = "pg-bank" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Bank Transfer</ h2 >
< p class = "text-muted text-sm mb-4" > Manual wire transfer with admin verification workflow.</ p >
< h3 class = "font-semibold mb-2" > Workflow</ h3 >
< ol class = "text-muted text-sm mb-4" >
< li > User selects Bank Transfer → receives bank details</ li >
< li > User makes transfer and uploads proof of payment</ li >
< li > Request enters < code > bank_transfer_requests</ code > with < code > pending</ code > status</ li >
< li > Admin reviews at < code > Admin → Payments → Bank Transfers</ code ></ li >
< li > Approve → credits/subscription activated; Reject → user notified</ li >
</ ol >
< p class = "text-muted text-sm" > Configure bank details (account name, IBAN, SWIFT, bank name) in < code > Admin → Payments → Bank Transfer</ code > .</ p >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: DEPLOYMENT -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "deployment" class = "mb-16" >
< h1 class = "mb-4" > Deploy to Production</ h1 >
< p class = "text-xl text-muted mb-8" > Deploy AppForge on shared hosting (cPanel), VPS, or cloud — from build to production.</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 1: Build for Production</ h2 >
< pre >< code > npm run build</ code ></ pre >
< p class = "text-muted text-sm mt-2" > Creates an optimized < code > dist/</ code > folder with code-split chunks, minified CSS/JS, and hashed filenames.</ p >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 2: Upload to Hosting</ h2 >
< p class = "text-muted text-sm mb-2" > Upload the < em > contents</ em > of < code > dist/</ code > (not the folder itself) to your web root:</ p >
< ul class = "text-muted text-sm" >
< li >< strong > cPanel:</ strong > File Manager → < code > public_html/</ code > → Upload all files from < code > dist/</ code ></ li >
< li >< strong > VPS:</ strong > < code > scp -r dist/* user@server:/var/www/html/</ code ></ li >
< li >< strong > Netlify/Vercel:</ strong > Connect your Git repo; set build command to < code > npm run build</ code > and publish directory to < code > dist</ code ></ li >
</ ul >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 3: Configure .htaccess (Apache/cPanel)</ h2 >
< p class = "text-muted text-sm mb-2" > Required for SPA routing. The repo includes a pre-configured < code > .htaccess</ code > file:</ p >
< pre >< code >< span class = "syntax-comment" > # SPA Routing — redirect all requests to index.html</ span >
< IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
< /IfModule>
< span class = "syntax-comment" > # Force HTTPS</ span >
< IfModule mod_rewrite.c>
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
< /IfModule>
< span class = "syntax-comment" > # Gzip Compression</ span >
< IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json
< /IfModule>
< span class = "syntax-comment" > # Security Headers</ span >
< IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
< /IfModule> </ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 4: Enable SSL</ h2 >
< p class = "text-muted mb-3" > HTTPS is < strong > required</ strong > for service workers, push notifications, camera, and biometrics.</ p >
< ul class = "text-muted text-sm" >
< li >< strong > AutoSSL:</ strong > cPanel → SSL/TLS Status → "Run AutoSSL"</ li >
< li >< strong > Let's Encrypt:</ strong > cPanel → Let's Encrypt → Issue for your domain</ li >
</ ul >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 5: Configure the Database for Production</ h2 >
< ul class = "text-muted text-sm" >
< li > Add your production domain to database admin → Authentication → URL Configuration → Site URL</ li >
< li > Add the domain to Redirect URLs as well</ li >
< li > Verify edge function secrets are set (Dashboard → Settings → Secrets)</ li >
< li > Set < code > PUBLIC_SITE_URL</ code > secret to your production domain (used in webhook emails)</ li >
</ ul >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 6: Verify Deployment</ h2 >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2 text-success" > Check These</ h3 >
< ul class = "text-muted text-sm" >
< li > App loads at your domain with HTTPS</ li >
< li > Deep links work on refresh (< code > /dashboard</ code > , < code > /admin</ code > )</ li >
< li > Login/signup functionality works</ li >
< li > Browser console shows no errors</ li >
< li > Setup wizard redirects correctly</ li >
</ ul >
</ div >
< div >
< h3 class = "font-semibold mb-2" > Test Edge Functions</ h3 >
< ul class = "text-muted text-sm" >
< li > Health check: < code > /functions/v1/cloud-build?health=1</ code ></ li >
< li > Create a test build</ li >
< li > Verify payment flow in test mode</ li >
< li > Send a test email from Admin → Integrations</ li >
</ ul >
</ div >
</ div >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Troubleshooting</ h2 >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > 404 on Page Refresh</ h3 >< p class = "text-muted text-sm" > Ensure < code > .htaccess</ code > is configured and < code > mod_rewrite</ code > is enabled. For Nginx, add < code > try_files $uri /index.html;</ code > .</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > Blank White Page</ h3 >< p class = "text-muted text-sm" > Check browser console for JS errors. Common: missing < code > .env</ code > variables, incomplete file upload, wrong < code > base</ code > in vite.config.</ p ></ div >
< div class = "mb-4" >< h3 class = "font-semibold text-warning mb-1" > CORS Errors</ h3 >< p class = "text-muted text-sm" > Add your production domain to database admin → Settings → API → Allowed Origins.</ p ></ div >
< div >< h3 class = "font-semibold text-warning mb-1" > Auth Redirect Loops</ h3 >< p class = "text-muted text-sm" > Add your domain to Better Authentication → URL Configuration → Redirect URLs.</ p ></ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: UPDATING & REDEPLOYMENT -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "updating" class = "mb-16" >
< h1 class = "mb-4" > Updating & Redeployment</ h1 >
< p class = "text-xl text-muted mb-8" > How to update an existing production installation to the latest version.</ p >
< div class = "callout callout-warning mb-6" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />< path d = "M12 9v4" />< path d = "M12 17h.01" /></ svg >
< div >< strong class = "text-sm" > Always backup before updating</ strong >< p class = "text-sm text-muted" > Export your Postgres database and download your < code > .env</ code > file before performing any update.</ p ></ div >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 1: Pull Latest Code</ h2 >
< pre >< code >< span class = "syntax-comment" > # Navigate to your project directory</ span >
cd appforge
< span class = "syntax-comment" > # Pull the latest changes</ span >
git pull origin main
< span class = "syntax-comment" > # Install any new/updated dependencies</ span >
npm install</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 2: Apply New Database Migrations</ h2 >
< p class = "text-muted text-sm mb-4" > New versions may include database schema changes (new tables, columns, functions, or RLS policies).</ p >
< pre >< code >< span class = "syntax-comment" > # Ensure you're linked to the correct project</ span >
(set DATABASE_URL in your .env)
< span class = "syntax-comment" > # Push any new migrations</ span >
npm run db:push</ code ></ pre >
< div class = "callout callout-info mt-4" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< circle cx = "12" cy = "12" r = "10" />< path d = "M12 16v-4" />< path d = "M12 8h.01" /></ svg >
< div >< strong class = "text-sm" > What < code > db push</ code > does</ strong >< p class = "text-sm text-muted" > It applies only the migration files that haven't been run yet. The schema is idempotent (< code > CREATE ... IF NOT EXISTS</ code > ), so re-running only creates what is missing. Your existing data is preserved.</ p ></ div >
</ div >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 3: Deploy Updated Edge Functions</ h2 >
< pre >< code > npm run dev (backend functions run in the Express server)</ code ></ pre >
< p class = "text-muted text-sm mt-2" > This redeploys all edge functions with the latest code. Existing secrets are preserved.</ p >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 4: Rebuild & Upload Frontend</ h2 >
< pre >< code >< span class = "syntax-comment" > # Build production bundle</ span >
npm run build
< span class = "syntax-comment" > # Upload dist/ contents to your hosting</ span >
< span class = "syntax-comment" > # cPanel: File Manager → public_html/ → Upload</ span >
< span class = "syntax-comment" > # VPS: scp -r dist/* user@server:/var/www/html/</ span ></ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Step 5: Verify the Update</ h2 >
< ul class = "text-muted text-sm" >
< li > Hard-refresh your browser (< kbd > Ctrl</ kbd > +< kbd > Shift</ kbd > +< kbd > R</ kbd > ) to clear cached assets</ li >
< li > Check that the app loads without console errors</ li >
< li > Verify login/signup still works</ li >
< li > Check Admin Panel → System Settings for any new options</ li >
< li > Test a sample build to verify edge functions are working</ li >
</ ul >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Quick Reference: Update Commands</ h2 >
< pre >< code >< span class = "syntax-comment" > # Complete update sequence (run from project root)</ span >
git pull origin main
npm install
npm run db:push
npm run dev (backend functions run in the Express server)
npm run build
< span class = "syntax-comment" > # Then upload dist/ to your hosting</ span ></ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Updating Edge Function Secrets</ h2 >
< p class = "text-muted text-sm mb-2" > If the changelog mentions new required secrets:</ p >
< pre >< code >< span class = "syntax-comment" > # Set a new secret via CLI</ span >
(set secrets in the server .env file)
< span class = "syntax-comment" > # Or via database admin → Settings → Secrets</ span ></ code ></ pre >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: PRODUCTION GUIDE -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "production-guide" class = "mb-16" >
< h1 class = "mb-4" > Production Guide</ h1 >
< p class = "text-xl text-muted mb-8" > Security, performance, monitoring, and operational best practices.</ p >
< div id = "checklist" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Production Readiness Checklist</ h2 >
< div class = "grid grid-2" >
< div >
< h3 class = "font-semibold mb-2 text-success" > Security</ h3 >
< div class = "text-sm text-muted" >
< label class = "checklist-item" >< input type = "checkbox" > HTTPS enabled and forced</ label >
< label class = "checklist-item" >< input type = "checkbox" > No secrets in client code</ label >
< label class = "checklist-item" >< input type = "checkbox" > RLS policies on all tables</ label >
< label class = "checklist-item" >< input type = "checkbox" > API keys rotated from defaults</ label >
< label class = "checklist-item" >< input type = "checkbox" > Demo mode disabled</ label >
< label class = "checklist-item" >< input type = "checkbox" > PUBLIC_SITE_URL secret set</ label >
</ div >
</ div >
< div >
< h3 class = "font-semibold mb-2" style = "color: hsl(217,91%,60%);" > Performance</ h3 >
< div class = "text-sm text-muted" >
< label class = "checklist-item" >< input type = "checkbox" > Production build optimized</ label >
< label class = "checklist-item" >< input type = "checkbox" > Gzip compression enabled</ label >
< label class = "checklist-item" >< input type = "checkbox" > Cache headers set for static assets</ label >
< label class = "checklist-item" >< input type = "checkbox" > Images optimized</ label >
< label class = "checklist-item" >< input type = "checkbox" > Edge function health checks passing</ label >
</ div >
</ div >
</ div >
</ div >
< div id = "security" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Security Best Practices</ h2 >
< h3 class = "font-semibold mb-2" > Row Level Security (RLS)</ h3 >
< p class = "text-muted text-sm mb-2" > All tables use RLS. Admin access is verified via < code > has_role(auth.uid(), 'admin')</ code > — a < code > SECURITY DEFINER</ code > function that cannot be bypassed client-side.</ p >
< pre >< code >< span class = "syntax-comment" > -- Users access own data only</ span >
< span class = "syntax-keyword" > CREATE POLICY</ span > < span class = "syntax-string" > "Users access own data"</ span >
< span class = "syntax-keyword" > ON</ span > public.app_builds < span class = "syntax-keyword" > FOR SELECT</ span >
< span class = "syntax-keyword" > USING</ span > (auth.uid() = user_id);
< span class = "syntax-comment" > -- Admin-only tables use SECURITY DEFINER functions</ span >
< span class = "syntax-keyword" > CREATE POLICY</ span > < span class = "syntax-string" > "Admins only"</ span >
< span class = "syntax-keyword" > ON</ span > public.system_settings < span class = "syntax-keyword" > FOR ALL</ span >
< span class = "syntax-keyword" > USING</ span > (has_role(auth.uid(), < span class = "syntax-string" > 'admin'</ span > ));</ code ></ pre >
</ div >
< div id = "payments-prod" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Payment Gateway Production Setup</ h2 >
< ul class = "text-muted text-sm" >
< li >< strong > Stripe:</ strong > Switch < code > sk_test_</ code > → < code > sk_live_</ code > keys in the server .env file. Configure live webhook endpoint.</ li >
< li >< strong > PayPal:</ strong > Switch from sandbox to live credentials in Admin → Payments. Configure webhook URL in PayPal Dashboard.</ li >
< li >< strong > Coinbase:</ strong > Set live API key and webhook secret.</ li >
< li >< strong > Bank Transfer:</ strong > Update bank details in Admin → Payments → Bank Transfer.</ li >
</ ul >
</ div >
< div id = "backups" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Backup Strategy</ h2 >
< ul class = "text-muted text-sm" >
< li >< strong > Database:</ strong > Schedule regular < code > pg_dump</ code > backups (e.g. via cron) for your Postgres instance.</ li >
< li >< strong > Storage:</ strong > Download critical files periodically. Build artifacts can be regenerated.</ li >
< li >< strong > Settings:</ strong > < code > settings_audit_log</ code > tracks all changes for recovery.</ li >
< li >< strong > Code:</ strong > Keep in Git. Tag releases before deploying.</ li >
</ ul >
</ div >
< div id = "scaling" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Scaling</ h2 >
< ul class = "text-muted text-sm" >
< li >< strong > Edge Functions:</ strong > Run inside the Express backend; scale by running more backend instances behind a load balancer.</ li >
< li >< strong > Database:</ strong > Monitor connections and query performance. Add indexes as needed.</ li >
< li >< strong > Storage:</ strong > Use CDN (Cloudflare) in front of your hosting for static assets.</ li >
< li >< strong > Builds:</ strong > < code > max_builds_per_day</ code > setting prevents abuse.</ li >
< li >< strong > Credits:</ strong > Natural throttle. Adjust < code > credits_per_build</ code > as needed.</ li >
</ ul >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: PROJECT STRUCTURE & ROUTES -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "project-structure" class = "mb-16" >
< h1 class = "mb-4" > Project Structure</ h1 >
< p class = "text-xl text-muted mb-8" > File organization and architectural overview.</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Directory Layout</ h2 >
< pre >< code > appforge/
├── docs/ < span class = "syntax-comment" > # Documentation (this site)</ span >
├── public/ < span class = "syntax-comment" > # Static assets (robots.txt, favicon)</ span >
├── src/
│ ├── components/
│ │ ├── admin/ < span class = "syntax-comment" > # Admin panel components (20+)</ span >
│ │ ├── builder/ < span class = "syntax-comment" > # App builder wizard steps</ span >
│ │ │ └── configure/ < span class = "syntax-comment" > # Builder configuration sub-components</ span >
│ │ ├── subscription/ < span class = "syntax-comment" > # Billing & credit pack UI</ span >
│ │ └── ui/ < span class = "syntax-comment" > # Shadcn/Radix design system components</ span >
│ ├── contexts/ < span class = "syntax-comment" > # React context providers (AuthContext)</ span >
│ ├── hooks/ < span class = "syntax-comment" > # Custom hooks (30+)</ span >
│ ├── lib/
│ │ ├── backend-client.ts < span class = "syntax-comment" > # Backend client (auth/db/storage/functions)</ span >
│ │ ├── auth-client.ts < span class = "syntax-comment" > # Better Auth browser client</ span >
│ │ └── api/ < span class = "syntax-comment" > # REST API client wrappers</ span >
│ ├── pages/ < span class = "syntax-comment" > # Route page components (15+)</ span >
│ ├── stores/ < span class = "syntax-comment" > # Zustand state stores</ span >
│ ├── types/ < span class = "syntax-comment" > # TypeScript type definitions</ span >
│ └── utils/ < span class = "syntax-comment" > # Utility functions</ span >
├── server/
│ ├── src/ < span class = "syntax-comment" > # Express backend (routes, auth, functions)</ span >
│ └── db/ < span class = "syntax-comment" > # schema.sql & auth-schema.sql</ span >
├── .htaccess < span class = "syntax-comment" > # Apache SPA routing & security</ span >
├── capacitor.config.ts < span class = "syntax-comment" > # Capacitor native app config</ span >
├── codemagic.yaml < span class = "syntax-comment" > # CI/CD build pipeline</ span >
└── vite.config.ts < span class = "syntax-comment" > # Vite bundler configuration</ span ></ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Key Architecture Patterns</ h2 >
< div class = "grid grid-2" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Lazy Loading</ h4 >< p class = "text-xs text-muted" > All page components use < code > React.lazy()</ code > with < code > Suspense</ code > for code splitting. Reduces initial bundle by ~60%.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Zustand Stores</ h4 >< p class = "text-xs text-muted" >< code > useAppStore</ code > (app state), < code > useThemeStore</ code > (theme), < code > useUserPreferencesStore</ code > (preferences). Persistent via localStorage.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > React Query</ h4 >< p class = "text-xs text-muted" > Server state management via < code > @tanstack/react-query</ code > . Automatic caching, background refetching, and stale data handling.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > RPC-First Database</ h4 >< p class = "text-xs text-muted" > Credit operations use < code > SECURITY DEFINER</ code > database functions (< code > use_credits</ code > , < code > add_credits</ code > ) for atomic, tamper-proof transactions.</ p ></ div >
</ div >
</ div >
<!-- Application Routes -->
< div id = "app-routes" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Application Routes</ h2 >
< p class = "text-muted text-sm mb-4" > All routes use the < code > ProtectedRoute</ code > , < code > PublicRoute</ code > , or < code > SetupRoute</ code > guard components.</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Path</ th >< th > Component</ th >< th > Guard</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > /</ code ></ td >< td > Index</ td >< td > Public</ td >< td > Landing page with hero, features, pricing</ td ></ tr >
< tr >< td >< code > /auth</ code ></ td >< td > Auth</ td >< td > Public</ td >< td > Login / signup / magic link</ td ></ tr >
< tr >< td >< code > /setup</ code ></ td >< td > AdminSetup</ td >< td > Setup</ td >< td > First-run wizard (only when no admin exists)</ td ></ tr >
< tr >< td >< code > /dashboard</ code ></ td >< td > Dashboard</ td >< td > Protected</ td >< td > User dashboard with projects, credits, builds</ td ></ tr >
< tr >< td >< code > /builder</ code ></ td >< td > AppBuilder</ td >< td > Protected</ td >< td > 4-step website-to-app wizard</ td ></ tr >
< tr >< td >< code > /settings</ code ></ td >< td > Settings</ td >< td > Protected</ td >< td > User profile, password, storage, preferences</ td ></ tr >
< tr >< td >< code > /subscription</ code ></ td >< td > Subscription</ td >< td > Protected</ td >< td > Plans, credit packs, payment methods</ td ></ tr >
< tr >< td >< code > /admin</ code ></ td >< td > Admin</ td >< td > Protected</ td >< td > Admin panel (requires admin role)</ td ></ tr >
< tr >< td >< code > /build-history</ code ></ td >< td > BuildHistory</ td >< td > Protected</ td >< td > All past builds with status and downloads</ td ></ tr >
< tr >< td >< code > /payment-history</ code ></ td >< td > PaymentHistory</ td >< td > Protected</ td >< td > Transaction records and invoices</ td ></ tr >
< tr >< td >< code > /help</ code ></ td >< td > Help</ td >< td > None</ td >< td > Help center and FAQ</ td ></ tr >
< tr >< td >< code > /privacy</ code ></ td >< td > Privacy</ td >< td > None</ td >< td > Privacy policy</ td ></ tr >
< tr >< td >< code > /terms</ code ></ td >< td > Terms</ td >< td > None</ td >< td > Terms of service</ td ></ tr >
< tr >< td >< code > /install</ code ></ td >< td > Install</ td >< td > None</ td >< td > PWA install instructions</ td ></ tr >
< tr >< td >< code > /style-guide</ code ></ td >< td > StyleGuide</ td >< td > None</ td >< td > Design system component showcase</ td ></ tr >
< tr >< td >< code > *</ code ></ td >< td > NotFound</ td >< td > None</ td >< td > 404 page</ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- Keyboard Shortcuts -->
< div id = "keyboard-shortcuts" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Keyboard Shortcuts</ h2 >
< p class = "text-muted text-sm mb-4" > Available in the App Builder. Press < kbd > ?</ kbd > to show the shortcuts overlay.</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Shortcut</ th >< th > Action</ th >< th > Context</ th ></ tr ></ thead >
< tbody >
< tr >< td >< kbd > ←</ kbd > < kbd > →</ kbd ></ td >< td > Navigate between steps</ td >< td > All steps</ td ></ tr >
< tr >< td >< kbd > 1</ kbd > – < kbd > 4</ kbd ></ td >< td > Jump to specific step</ td >< td > All steps (up to current + 1)</ td ></ tr >
< tr >< td >< kbd > ⌘</ kbd > +< kbd > R</ kbd ></ td >< td > Refresh preview</ td >< td > Preview step</ td ></ tr >
< tr >< td >< kbd > ⌘</ kbd > +< kbd > S</ kbd ></ td >< td > Take screenshot</ td >< td > Preview step</ td ></ tr >
< tr >< td >< kbd > ⌘</ kbd > +< kbd > O</ kbd ></ td >< td > Rotate device</ td >< td > Preview step</ td ></ tr >
< tr >< td >< kbd > ⌘</ kbd > +< kbd > G</ kbd ></ td >< td > Toggle comparison mode</ td >< td > Preview step</ td ></ tr >
< tr >< td >< kbd > ?</ kbd ></ td >< td > Show shortcuts help</ td >< td > All steps</ td ></ tr >
</ tbody >
</ table >
</ div >
< p class = "text-muted text-sm mt-2" > Shortcuts are disabled when typing in input fields or textareas. < code > ⌘</ code > = < code > Ctrl</ code > on Windows/Linux.</ p >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: GDPR & COOKIE CONSENT -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "gdpr-consent" class = "mb-16" >
< h1 class = "mb-4" > GDPR & Cookie Consent</ h1 >
< p class = "text-xl text-muted mb-8" > Privacy compliance with granular cookie consent and audit trail.</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Cookie Consent Banner</ h2 >
< p class = "text-muted text-sm mb-4" > Displayed on first visit via < code > CookieConsent</ code > component. Users can accept all, reject all, or customize preferences.</ p >
< h3 class = "font-semibold mb-2" > Cookie Categories</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Category</ th >< th > Default</ th >< th > Purpose</ th >< th > Can Disable</ th ></ tr ></ thead >
< tbody >
< tr >< td >< strong > Necessary</ strong ></ td >< td > Always on</ td >< td > Authentication, session management, CSRF protection</ td >< td > No</ td ></ tr >
< tr >< td >< strong > Analytics</ strong ></ td >< td > Off</ td >< td > Usage tracking, error monitoring, performance metrics</ td >< td > Yes</ td ></ tr >
< tr >< td >< strong > Marketing</ strong ></ td >< td > Off</ td >< td > Personalization, third-party ads, retargeting</ td >< td > Yes</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Implementation</ h3 >
< ul class = "text-muted text-sm" >
< li > Preferences stored in < code > localStorage</ code > under < code > cookie-consent</ code > key</ li >
< li > Custom event < code > cookieConsentChanged</ code > dispatched on preference update</ li >
< li > Users can re-open settings dialog anytime from the footer</ li >
</ ul >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Consent Records Table</ h2 >
< p class = "text-muted text-sm mb-4" > Server-side audit trail stored in < code > consent_records</ code > table with RLS.</ p >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Column</ th >< th > Type</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > user_id</ code ></ td >< td > uuid (nullable)</ td >< td > Linked to auth user (null for anonymous)</ td ></ tr >
< tr >< td >< code > consent_type</ code ></ td >< td > text</ td >< td > Category: < code > necessary</ code > , < code > analytics</ code > , < code > marketing</ code ></ td ></ tr >
< tr >< td >< code > consented</ code ></ td >< td > boolean</ td >< td > Whether user accepted this category</ td ></ tr >
< tr >< td >< code > ip_address</ code ></ td >< td > text</ td >< td > IP at time of consent</ td ></ tr >
< tr >< td >< code > user_agent</ code ></ td >< td > text</ td >< td > Browser user agent string</ td ></ tr >
< tr >< td >< code > email</ code ></ td >< td > text</ td >< td > User email (if known)</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mt-4 mb-2" > RLS Policies</ h3 >
< ul class = "text-muted text-sm" >
< li > Users can < strong > insert</ strong > records for themselves or anonymously (< code > user_id IS NULL</ code > )</ li >
< li > Users can < strong > read/update</ strong > only their own records</ li >
< li > No delete access (audit compliance)</ li >
</ ul >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: INVOICE SYSTEM -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "invoice-system" class = "mb-16" >
< h1 class = "mb-4" > Invoice System</ h1 >
< p class = "text-xl text-muted mb-8" > Create, manage, and track invoices with multi-gateway payment support.</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Invoice Table Schema</ h2 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Column</ th >< th > Type</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > invoice_number</ code ></ td >< td > text</ td >< td > Auto-generated unique number (e.g., < code > INV-2026-001</ code > )</ td ></ tr >
< tr >< td >< code > user_id</ code ></ td >< td > uuid</ td >< td > Customer (linked to profiles)</ td ></ tr >
< tr >< td >< code > amount</ code ></ td >< td > numeric</ td >< td > Total invoice amount</ td ></ tr >
< tr >< td >< code > currency</ code ></ td >< td > text</ td >< td > Default: < code > USD</ code ></ td ></ tr >
< tr >< td >< code > status</ code ></ td >< td > text</ td >< td >< code > draft</ code > , < code > pending</ code > , < code > paid</ code > , < code > overdue</ code > , < code > cancelled</ code ></ td ></ tr >
< tr >< td >< code > items</ code ></ td >< td > jsonb</ td >< td > Array of line items: < code > [{description, quantity, unit_price}]</ code ></ td ></ tr >
< tr >< td >< code > due_date</ code ></ td >< td > timestamp</ td >< td > Payment deadline</ td ></ tr >
< tr >< td >< code > paid_at</ code ></ td >< td > timestamp</ td >< td > When payment was received</ td ></ tr >
< tr >< td >< code > notes</ code ></ td >< td > text</ td >< td > Internal admin notes</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Admin Workflow</ h3 >
< ol class = "text-muted text-sm" >
< li > Create invoice at < code > Admin → Payments → Invoices</ code ></ li >
< li > Select user, add line items, set due date</ li >
< li > Invoice auto-calculates total from items</ li >
< li > Users see invoices at < code > /payment-history</ code ></ li >
< li > Payment via any configured gateway (Stripe, PayPal, Coinbase)</ li >
< li > Status transitions: < code > draft → pending → paid</ code > (or < code > overdue/cancelled</ code > )</ li >
</ ol >
</ div >
<!-- Webhook Logs -->
< div id = "webhook-logs" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Webhook Event Logs</ h2 >
< p class = "text-muted text-sm mb-4" > All incoming payment webhooks are logged to < code > webhook_event_logs</ code > for debugging and auditing.</ p >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Column</ th >< th > Type</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > gateway</ code ></ td >< td > text</ td >< td >< code > stripe</ code > , < code > paypal</ code > , < code > coinbase</ code ></ td ></ tr >
< tr >< td >< code > event_type</ code ></ td >< td > text</ td >< td > e.g., < code > checkout.session.completed</ code ></ td ></ tr >
< tr >< td >< code > event_id</ code ></ td >< td > text</ td >< td > Gateway's unique event identifier</ td ></ tr >
< tr >< td >< code > status</ code ></ td >< td > text</ td >< td >< code > received</ code > , < code > processed</ code > , < code > failed</ code ></ td ></ tr >
< tr >< td >< code > payload</ code ></ td >< td > jsonb</ td >< td > Full webhook payload</ td ></ tr >
< tr >< td >< code > error_message</ code ></ td >< td > text</ td >< td > Error details (if failed)</ td ></ tr >
< tr >< td >< code > processing_time_ms</ code ></ td >< td > integer</ td >< td > Time to process the event</ td ></ tr >
< tr >< td >< code > response_status</ code ></ td >< td > integer</ td >< td > HTTP status returned</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > Retry Mechanism</ h3 >
< p class = "text-muted text-sm" > Failed webhooks can be retried from < code > Admin → Payments → Webhooks</ code > . The < code > retry-webhook</ code > edge function resends the original payload to the gateway handler with an < code > X-Webhook-Retry: true</ code > header.</ p >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: AUTOMATION SYSTEM -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "automation-system" class = "mb-16" >
< h1 class = "mb-4" > Automation System</ h1 >
< p class = "text-xl text-muted mb-8" > Configurable workflows for automated build triggers, notifications, and maintenance tasks.</ p >
<!-- Overview -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > System Overview</ h2 >
< p class = "text-muted text-sm mb-4" > The automation system enables per-project workflows that execute on schedules or events. Automations are stored in < code > automation_configs</ code > with execution history in < code > automation_logs</ code > .</ p >
< div class = "grid grid-3 mb-4" >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Per-Project</ h4 >< p class = "text-xs text-muted" > Each automation is linked to an < code > app_project</ code > via < code > project_id</ code > .</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Toggle On/Off</ h4 >< p class = "text-xs text-muted" >< code > is_enabled</ code > flag lets users pause automations without deleting.</ p ></ div >
< div class = "card card-flat" >< h4 class = "font-semibold mb-1" > Run Tracking</ h4 >< p class = "text-xs text-muted" >< code > run_count</ code > , < code > last_run_at</ code > , and < code > next_run_at</ code > for monitoring.</ p ></ div >
</ div >
</ div >
<!-- Workflow Types -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Workflow Types</ h2 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Type</ th >< th > Description</ th >< th > Config Example</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > auto_build</ code ></ td >< td > Automatically trigger a build when source changes are detected</ td >< td >< code > { "trigger": "push", "branch": "main", "platform": "android" }</ code ></ td ></ tr >
< tr >< td >< code > scheduled_build</ code ></ td >< td > Rebuild on a cron-like schedule</ td >< td >< code > { "cron": "0 2 * * 1", "platform": "android" }</ code ></ td ></ tr >
< tr >< td >< code > notify_on_change</ code ></ td >< td > Send email notification when the target website content changes</ td >< td >< code > { "check_interval_hours": 24, "notify_email": "user@..." }</ code ></ td ></ tr >
< tr >< td >< code > scheduled_check</ code ></ td >< td > Periodic health check on the target website URL</ td >< td >< code > { "interval_minutes": 60, "alert_on_downtime": true }</ code ></ td ></ tr >
< tr >< td >< code > auto_update</ code ></ td >< td > Auto-rebuild when a new app version is available</ td >< td >< code > { "check_interval_hours": 12 }</ code ></ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
<!-- API Reference -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Automation API</ h2 >
< p class = "text-muted text-sm mb-4" > Client-side API via < code > automationApi</ code > from < code > @/lib/api/rest-client</ code > .</ p >
< pre >< code >< span class = "syntax-keyword" > import</ span > { automationApi } < span class = "syntax-keyword" > from</ span > < span class = "syntax-string" > '@/lib/api/rest-client'</ span > ;
< span class = "syntax-comment" > // List automations (for current user, optionally filtered by project)</ span >
< span class = "syntax-keyword" > const</ span > { data } = < span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > list</ span > (projectId);
< span class = "syntax-comment" > // data.automations: Array of automation configs</ span >
< span class = "syntax-comment" > // Create a new automation</ span >
< span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > create</ span > (projectId, < span class = "syntax-string" > 'auto_build'</ span > , {
trigger: < span class = "syntax-string" > 'push'</ span > ,
branch: < span class = "syntax-string" > 'main'</ span > ,
platform: < span class = "syntax-string" > 'android'</ span >
});
< span class = "syntax-comment" > // Toggle enabled/disabled</ span >
< span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > toggle</ span > (automationId, < span class = "syntax-keyword" > false</ span > );
< span class = "syntax-comment" > // Update config</ span >
< span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > updateConfig</ span > (automationId, { cron: < span class = "syntax-string" > '0 3 * * *'</ span > });
< span class = "syntax-comment" > // Get execution logs</ span >
< span class = "syntax-keyword" > const</ span > { data: logs } = < span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > getLogs</ span > (automationId);
< span class = "syntax-comment" > // logs.logs: [{ status, started_at, completed_at, message, metadata }]</ span >
< span class = "syntax-comment" > // Trigger manual execution</ span >
< span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > execute</ span > (automationId);
< span class = "syntax-comment" > // Delete an automation</ span >
< span class = "syntax-keyword" > await</ span > automationApi.< span class = "syntax-function" > delete</ span > (automationId);</ code ></ pre >
</ div >
<!-- Schema -->
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Database Schema</ h2 >
< h3 class = "font-semibold mb-2" > automation_configs</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Column</ th >< th > Type</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > id</ code ></ td >< td > uuid</ td >< td > Primary key</ td ></ tr >
< tr >< td >< code > user_id</ code ></ td >< td > uuid</ td >< td > Owner (references auth.users)</ td ></ tr >
< tr >< td >< code > project_id</ code ></ td >< td > uuid</ td >< td > Linked app project (FK → app_projects)</ td ></ tr >
< tr >< td >< code > workflow_type</ code ></ td >< td > text</ td >< td > e.g., < code > auto_build</ code > , < code > scheduled_check</ code > , < code > notify_on_change</ code ></ td ></ tr >
< tr >< td >< code > is_enabled</ code ></ td >< td > boolean</ td >< td > Toggle on/off (default: < code > true</ code > )</ td ></ tr >
< tr >< td >< code > config</ code ></ td >< td > jsonb</ td >< td > Workflow-specific parameters</ td ></ tr >
< tr >< td >< code > last_run_at</ code ></ td >< td > timestamp</ td >< td > Last execution time</ td ></ tr >
< tr >< td >< code > next_run_at</ code ></ td >< td > timestamp</ td >< td > Scheduled next run</ td ></ tr >
< tr >< td >< code > run_count</ code ></ td >< td > integer</ td >< td > Total executions (default: 0)</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > automation_logs</ h3 >
< div class = "table-wrapper mb-4" >
< table >
< thead >< tr >< th > Column</ th >< th > Type</ th >< th > Description</ th ></ tr ></ thead >
< tbody >
< tr >< td >< code > id</ code ></ td >< td > uuid</ td >< td > Primary key</ td ></ tr >
< tr >< td >< code > automation_id</ code ></ td >< td > uuid</ td >< td > FK → automation_configs</ td ></ tr >
< tr >< td >< code > user_id</ code ></ td >< td > uuid</ td >< td > Owner</ td ></ tr >
< tr >< td >< code > status</ code ></ td >< td > text</ td >< td >< code > pending</ code > , < code > running</ code > , < code > success</ code > , < code > failed</ code ></ td ></ tr >
< tr >< td >< code > started_at</ code ></ td >< td > timestamp</ td >< td > Execution start</ td ></ tr >
< tr >< td >< code > completed_at</ code ></ td >< td > timestamp</ td >< td > Execution end (null if running)</ td ></ tr >
< tr >< td >< code > message</ code ></ td >< td > text</ td >< td > Result or error message</ td ></ tr >
< tr >< td >< code > metadata</ code ></ td >< td > jsonb</ td >< td > Additional execution data (build ID, check results, etc.)</ td ></ tr >
</ tbody >
</ table >
</ div >
< h3 class = "font-semibold mb-2" > RLS Policies</ h3 >
< ul class = "text-muted text-sm" >
< li >< strong > automation_configs</ strong > — Users can CRUD their own configs. Admins can view all.</ li >
< li >< strong > automation_logs</ strong > — Users can read their own logs. Admins can read all. Insert/update is server-side only.</ li >
</ ul >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: NGINX CONFIGURATION -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "nginx-config" class = "mb-16" >
< h1 class = "mb-4" > Nginx Configuration</ h1 >
< p class = "text-xl text-muted mb-8" > Server block configuration for VPS/Nginx deployments (alternative to Apache/.htaccess).</ p >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Complete Nginx Server Block</ h2 >
< pre >< code >< span class = "syntax-keyword" > server</ span > {
< span class = "syntax-property" > listen</ span > 443 ssl http2;
< span class = "syntax-property" > server_name</ span > yourdomain.com www.yourdomain.com;
< span class = "syntax-property" > root</ span > /var/www/html;
< span class = "syntax-property" > index</ span > index.html;
< span class = "syntax-comment" > # SSL (Let's Encrypt / Certbot)</ span >
< span class = "syntax-property" > ssl_certificate</ span > /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
< span class = "syntax-property" > ssl_certificate_key</ span > /etc/letsencrypt/live/yourdomain.com/privkey.pem;
< span class = "syntax-comment" > # SPA Routing — send all requests to index.html</ span >
< span class = "syntax-keyword" > location</ span > / {
< span class = "syntax-property" > try_files</ span > $uri $uri/ /index.html;
}
< span class = "syntax-comment" > # Cache static assets (JS, CSS, images)</ span >
< span class = "syntax-keyword" > location</ span > ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
< span class = "syntax-property" > expires</ span > 1y;
< span class = "syntax-property" > add_header</ span > Cache-Control "public, immutable";
< span class = "syntax-property" > access_log</ span > off;
}
< span class = "syntax-comment" > # Security headers</ span >
< span class = "syntax-property" > add_header</ span > X-Content-Type-Options "nosniff" always;
< span class = "syntax-property" > add_header</ span > X-Frame-Options "SAMEORIGIN" always;
< span class = "syntax-property" > add_header</ span > X-XSS-Protection "1; mode=block" always;
< span class = "syntax-property" > add_header</ span > Referrer-Policy "strict-origin-when-cross-origin" always;
< span class = "syntax-comment" > # Gzip compression</ span >
< span class = "syntax-property" > gzip</ span > on;
< span class = "syntax-property" > gzip_types</ span > text/plain text/css application/json application/javascript text/xml;
< span class = "syntax-property" > gzip_min_length</ span > 1000;
}
< span class = "syntax-comment" > # HTTP → HTTPS redirect</ span >
< span class = "syntax-keyword" > server</ span > {
< span class = "syntax-property" > listen</ span > 80;
< span class = "syntax-property" > server_name</ span > yourdomain.com www.yourdomain.com;
< span class = "syntax-keyword" > return</ span > 301 https://$host$request_uri;
}</ code ></ pre >
</ div >
< div class = "card mb-8" >
< h2 class = "font-bold mb-4" > Key Differences from Apache</ h2 >
< div class = "table-wrapper" >
< table >
< thead >< tr >< th > Feature</ th >< th > Apache (.htaccess)</ th >< th > Nginx</ th ></ tr ></ thead >
< tbody >
< tr >< td > SPA routing</ td >< td >< code > RewriteRule ^ index.html [L]</ code ></ td >< td >< code > try_files $uri /index.html</ code ></ td ></ tr >
< tr >< td > Compression</ td >< td >< code > mod_deflate</ code ></ td >< td >< code > gzip on</ code ></ td ></ tr >
< tr >< td > Caching</ td >< td >< code > mod_expires</ code ></ td >< td >< code > expires</ code > directive</ td ></ tr >
< tr >< td > Config location</ td >< td > Per-directory (< code > .htaccess</ code > )</ td >< td > Centralized (< code > /etc/nginx/</ code > )</ td ></ tr >
< tr >< td > Hot reload</ td >< td > Automatic</ td >< td > Requires < code > nginx -s reload</ code ></ td ></ tr >
</ tbody >
</ table >
</ div >
</ div >
< div class = "callout callout-tip" >
< svg class = "callout-icon" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" >< path d = "M22 11.08V12a10 10 0 1 1-5.93-9.14" />< polyline points = "22 4 12 14.01 9 11.01" /></ svg >
< div >< strong class = "text-sm" > Quick Setup with Certbot</ strong >< p class = "text-sm text-muted" > Run < code > sudo certbot --nginx -d yourdomain.com</ code > to automatically configure SSL and the redirect block.</ p ></ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: CHANGELOG -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "changelog" class = "mb-16" >
< h1 class = "mb-4" > Changelog</ h1 >
< p class = "text-xl text-muted mb-8" > Version history with features, improvements, and bug fixes.</ p >
<!-- v2.0.2 -->
< div class = "mb-12" id = "v2.0.2" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-new" > v2.0.2</ span >< span class = "text-muted text-sm" > March 28, 2026</ span ></ div >
< h2 class = "font-bold mb-4" > Dynamic Branding, Streamlined Setup, Cloud Build Pipeline & Documentation</ h2 >
< h3 class = "font-semibold mt-6 mb-3" > 🆕 New Features</ h3 >
< ul class = "feature-list" >
< li >< strong > Fully Dynamic Branding</ strong > — App name is pulled from the < code > system_settings</ code > database table everywhere: navbar, auth, footer, help, legal pages, and PayPal checkout. No hardcoded brand names remain.</ li >
< li >< strong > Streamlined Setup Wizard (4 Steps)</ strong > — Consolidated from 8 steps to 4: Welcome & Environment Check → Admin Account → App Configuration → Launch. Advanced settings deferred to Admin Panel.</ li >
< li >< strong > Codemagic Cloud Builds</ strong > — Android APK and iOS IPA builds via Codemagic CI/CD pipeline with real-time progress tracking. Requires GitHub repo connection.</ li >
< li >< strong > Integrations Manager</ strong > — Unified UI for configuring Resend, Appetize.io, AI providers (OpenAI/Gemini), and Codemagic with test connection utilities.</ li >
< li >< strong > Enhanced AI Edge Functions</ strong > — analyze-website and ai-assistant support OpenAI & Gemini with automatic fallback.</ li >
< li >< strong > Demo Mode Read-Only Admin</ strong > — All admin panel mutations are blocked in demo/test mode with user-friendly toast notifications.</ li >
< li >< strong > Codemagic App ID Auto-Sync</ strong > — Admin → Integrations includes a sync button to auto-resolve the App ID from the active Codemagic token.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 🔧 Improvements</ h3 >
< ul class = "feature-list" >
< li >< strong > Generic Storage Keys</ strong > — localStorage keys renamed from brand-specific (< code > appforge-*</ code > ) to generic (< code > app-theme</ code > , < code > app-storage</ code > , < code > app-user-preferences</ code > ).</ li >
< li >< strong > Edge Function Cleanup</ strong > — Removed all hardcoded < code > appforge.dev</ code > references from send-email, stripe-webhook, and cloud-build functions.</ li >
< li >< strong > Dynamic PayPal Brand Name</ strong > — PayPal checkout function reads brand name from system_settings at runtime.</ li >
< li >< strong > Generic Build Metadata</ strong > — Vite and Capacitor configs use generic app IDs and descriptions.</ li >
< li >< strong > Codemagic Setup Wizard</ strong > — Admin → Integrations includes a guided setup flow for connecting Codemagic.</ li >
< li >< strong > Integration Key Format Validation</ strong > — CORS-safe client-side validation for Resend, Appetize, and AI API keys.</ li >
< li >< strong > Codemagic App ID Fallback</ strong > — Cloud build edge function validates the configured App ID and auto-falls back to the accessible app if the saved ID is stale.</ li >
< li >< strong > Improved Build Error Messages</ strong > — More accurate diagnostics distinguishing token scope issues from stale App ID mismatches.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 🐛 Bug Fixes</ h3 >
< ul class = "feature-list" >
< li >< strong > Setup Wizard Route Guard</ strong > — < code > /setup</ code > now correctly blocks re-entry once an admin exists, using < code > no_admin_exists()</ code > RPC.</ li >
< li >< strong > Gemini API Key Validation</ strong > — Fixed invalid key error by using correct v1beta/models endpoint.</ li >
< li >< strong > Resend Test Connection</ strong > — Resolved CORS "Failed to fetch" error with format-only validation.</ li >
< li >< strong > Logo Variable Reference</ strong > — Fixed build error from renamed import variable in Auth and AdminSetup pages.</ li >
< li >< strong > Codemagic 403 False Positive</ strong > — Fixed misleading "token lacks permission" error when the real cause was a stale/inaccessible App ID.</ li >
< li >< strong > vite-plugin-pwa Compatibility</ strong > — Updated to v1.2.0+ for Vite 7 peer dependency support.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 📚 Documentation</ h3 >
< ul class = "feature-list" >
< li >< strong > Codemagic End-to-End Setup Guide</ strong > — Complete 4-phase walkthrough: account creation, secret configuration (env vs UI), GitHub sync, first build trigger, with build environment variables reference table.</ li >
< li >< strong > Webhook Status Flow</ strong > — Detailed Codemagic webhook status mapping, failure diagnostics (CocoaPods, Xcode, SPM), and step-level error extraction.</ li >
< li >< strong > Edge Functions Detailed Reference</ strong > — Full request/response schemas, error codes, and required secrets for all 19 edge functions including payment gateways (Stripe, PayPal, Coinbase).</ li >
< li >< strong > Storage API Usage</ strong > — Code examples for < code > useStorage</ code > hook, < code > storageApi</ code > wrapper, file upload/download/signed URLs, default bucket limits table, and admin bucket management.</ li >
< li >< strong > Automation System Expansion</ strong > — Workflow types reference table, full < code > automationApi</ code > code examples, complete database schema for both tables, and RLS policy documentation.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 🧹 Cleanup</ h3 >
< ul class = "feature-list" >
< li >< strong > Removed Active Builds Panel</ strong > — Deleted < code > BuildProgressPanel</ code > component and < code > useBuildProgress</ code > hook. Build status is tracked via the builder's Build step and Build History page.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 📋 Migration Notes</ h3 >
< ul class = "feature-list" >
< li > Existing users will have their theme/preferences reset on first visit due to localStorage key rename. One-time only.</ li >
< li > Run < code > npm run db:push</ code > to apply new migrations.</ li >
< li > Run < code > npm run dev (backend functions run in the Express server)</ code > to update edge functions.</ li >
< li > Set < code > PUBLIC_SITE_URL</ code > secret in database admin if using Stripe webhooks.</ li >
</ ul >
</ div >
</ div >
<!-- v1.5.1 -->
< div class = "mb-12" id = "v1.5.1" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-primary" > v1.5.1</ span >< span class = "text-muted text-sm" > February 22, 2026</ span ></ div >
< h2 class = "font-bold mb-4" > Build Pipeline, Preview Dialog & Dashboard UX Improvements</ h2 >
< h3 class = "font-semibold mt-6 mb-3" > 🆕 New Features</ h3 >
< ul class = "feature-list" >
< li >< strong > Buy Credits Dashboard Button</ strong > — Quick access to credit pack purchases from Dashboard header.</ li >
< li >< strong > Binary AndroidManifest.xml</ strong > — APK builds include valid binary manifest with proper resource IDs.</ li >
< li >< strong > App Version Auto-Increment</ strong > — Rebuild actions automatically bump version numbers.</ li >
</ ul >
< h3 class = "font-semibold mt-6 mb-3" > 🐛 Bug Fixes</ h3 >
< ul class = "feature-list" >
< li >< strong > Preview Dialog Overflow</ strong > — Responsive sizing with proper max dimensions.</ li >
< li >< strong > IPA Bundle Layout</ strong > — Correct Payload/*.app structure with valid Info.plist.</ li >
< li >< strong > Build History Status Filter</ strong > — Fixed to recognize both < code > complete</ code > and < code > completed</ code > statuses.</ li >
</ ul >
</ div >
</ div >
<!-- v1.4.0 -->
< div class = "mb-12" id = "v1.4.0" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-primary" > v1.4.0</ span >< span class = "text-muted text-sm" > February 15, 2026</ span ></ div >
< h2 class = "font-bold mb-4" > Documentation Overhaul, Demo Mode & Edge Function Health</ h2 >
< ul class = "feature-list" >
< li >< strong > Environment-Based Demo Mode Control</ strong > — < code > VITE_DEMO_MODE</ code > overrides database setting.</ li >
< li >< strong > Edge Function Health Checks</ strong > — All functions support < code > ?health=1</ code > parameter.</ li >
< li >< strong > Dark Mode Visibility Fixes</ strong > — Separate CSS variables for light/dark themes.</ li >
</ ul >
</ div >
</ div >
<!-- v1.3.0 -->
< div class = "mb-12" id = "v1.3.0" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-primary" > v1.3.0</ span >< span class = "text-muted text-sm" > February 2, 2026</ span ></ div >
< h2 class = "font-bold mb-4" > Payment Gateway Admin Enhancements</ h2 >
< ul class = "feature-list" >
< li >< strong > Stripe Sandbox/Live Mode Toggle</ strong > — Switch modes from admin panel.</ li >
< li >< strong > Key Format Validation</ strong > — Auto-validates Stripe key formats.</ li >
< li >< strong > Transactional Email System</ strong > — Automated emails via Resend.</ li >
</ ul >
</ div >
</ div >
<!-- v1.2.0 -->
< div class = "mb-12" id = "v1.2.0" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-secondary" > v1.2.0</ span >< span class = "text-muted text-sm" > January 2026</ span ></ div >
< h2 class = "font-bold mb-4" > Multi-Payment Gateway Support</ h2 >
< ul class = "feature-list" >
< li >< strong > PayPal Integration</ strong > — Orders, webhooks, billing plans.</ li >
< li >< strong > Coinbase Commerce</ strong > — Cryptocurrency payments.</ li >
< li >< strong > Bank Transfer Support</ strong > — Manual wire with admin approval.</ li >
< li >< strong > Credit Packs</ strong > — One-time purchasable credit bundles.</ li >
</ ul >
</ div >
</ div >
<!-- v1.1.0 -->
< div class = "mb-12" id = "v1.1.0" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-secondary" > v1.1.0</ span >< span class = "text-muted text-sm" > December 2025</ span ></ div >
< h2 class = "font-bold mb-4" > Admin Panel & User Management</ h2 >
< ul class = "feature-list" >
< li >< strong > Role-Based Access Control</ strong > — Admin, moderator, user roles.</ li >
< li >< strong > Credit System</ strong > — Monthly credits with reset, bonus credits from purchases.</ li >
< li >< strong > Setup Wizard</ strong > — Guided first-run configuration.</ li >
< li >< strong > Demo Mode</ strong > — Full demo experience with guided tour.</ li >
</ ul >
</ div >
</ div >
<!-- v1.0.0 -->
< div class = "mb-12" id = "v1.0.0" >
< div class = "card" >
< div class = "flex items-center gap-3 mb-4" >< span class = "badge badge-secondary" > v1.0.0</ span >< span class = "text-muted text-sm" > November 2025</ span ></ div >
< h2 class = "font-bold mb-4" > Initial Release</ h2 >
< ul class = "feature-list" >
< li >< strong > Website to App Conversion</ strong > — 4-step wizard for native iOS/Android apps.</ li >
< li >< strong > AI-Powered Analysis</ strong > — Automatic metadata and color extraction.</ li >
< li >< strong > Native Feature Hooks</ strong > — Camera, biometrics, haptics, push notifications.</ li >
< li >< strong > Device Preview</ strong > — Phone mockups with comparison mode.</ li >
< li >< strong > Stripe Payments</ strong > — Subscription plans with checkout and webhooks.</ li >
< li >< strong > PWA Support</ strong > — Offline caching, install prompt, service worker.</ li >
</ ul >
</ div >
</ div >
</ section >
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- SECTION: HELP CENTER / FAQ -->
<!-- ═══════════════════════════════════════════════════════════ -->
< hr class = "section-divider" >
< section id = "help-center" class = "mb-16" >
< h1 class = "mb-4" > Help Center</ h1 >
< p class = "text-xl text-muted mb-8" > Frequently asked questions and troubleshooting.</ p >
< div class = "mb-8" >
< input type = "text" class = "search-input" placeholder = "Search for help... (Ctrl+K)" id = "searchInput" onkeyup = "filterFAQs()" >
</ div >
< div id = "setup-faq" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Setup & Configuration</ h2 >
< div class = "faq-list" >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do I run the setup wizard?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > The setup wizard runs automatically at < code > /setup</ code > when no admin exists. It's a 4-step process: Welcome & Environment Check → Admin Account → App Configuration → Launch.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > What environment variables are required?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > The key ones: < code > DATABASE_URL</ code > , < code > BETTER_AUTH_SECRET</ code > , and < code > BETTER_AUTH_URL</ code > . Copy < code > .env.example</ code > to < code > .env</ code > and fill in your values.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do I apply database migrations?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Run < code > npm run db:push</ code > — it applies < code > server/db/schema.sql</ code > and < code > server/db/auth-schema.sql</ code > to the database in < code > DATABASE_URL</ code > .</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do I change the app name?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Admin Panel → System Settings → General → App Name. The name updates across the entire app dynamically (navbar, auth page, footer, help, legal pages, and PayPal checkout).</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do I update to a new version?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" >< code > git pull origin main && npm install && npm run db:push && npm run dev (backend functions run in the Express server) && npm run build</ code > . Then upload < code > dist/</ code > to your hosting. See the < a href = "#updating" > Updating guide</ a > for details.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > Can I re-run the setup wizard?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > The wizard only appears when no admin exists. After setup, use Admin Panel → System Settings for all configuration.</ div ></ div >
</ div >
</ div >
< div id = "building-faq" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Building Apps</ h2 >
< div class = "faq-list" >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do cloud builds work?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Builds use the Codemagic CI/CD pipeline. Your project must be connected to a GitHub repository. The edge function (< code > cloud-build</ code > ) triggers Codemagic, which compiles the Capacitor project into an APK or IPA. Progress is tracked in real-time.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > What do I need for cloud builds?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > 1) Codemagic account with API token, 2) GitHub repository connected, 3) Admin → Integrations → Codemagic configured, 4) Sufficient build credits.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > What file formats are generated?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Android: APK (direct install) and AAB (Play Store). iOS: unsigned IPA for testing via TestFlight or App Store distribution.</ div ></ div >
</ div >
</ div >
< div id = "billing-faq" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Billing & Credits</ h2 >
< div class = "faq-list" >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do credits work?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Each build consumes credits (default: 1 per build). New users get free signup credits (default: 5). Monthly credits reset on the billing cycle; bonus credits from purchases never expire.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > What payment methods are supported?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Stripe (cards), PayPal, Coinbase Commerce (crypto), and bank transfers with admin verification.</ div ></ div >
</ div >
</ div >
< div id = "admin-faq" class = "card mb-8" >
< h2 class = "font-bold mb-4" > Admin & Security</ h2 >
< div class = "faq-list" >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How do I access the admin panel?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Navigate to < code > /admin</ code > . You must be logged in with the < code > admin</ code > role. The admin role is assigned during the setup wizard.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > What is Row Level Security (RLS)?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > RLS ensures users can only access their own data at the database level. All AppForge tables have RLS enabled with policies enforced via < code > SECURITY DEFINER</ code > functions.</ div ></ div >
< div class = "faq-item" >< div class = "faq-question" onclick = "toggleFAQ(this)" >< span > How are admin checks secured?</ span >< span class = "faq-arrow" > ▼</ span ></ div >< div class = "faq-answer" > Admin status is verified via < code > has_role()</ code > — a < code > SECURITY DEFINER</ code > database function that runs with elevated privileges. This cannot be bypassed from the client side. Roles are stored in the separate < code > user_roles</ code > table, not in the user profile.</ div ></ div >
</ div >
</ div >
< div class = "card" >
< h2 class = "font-bold mb-4" > Still Need Help?</ h2 >
< p class = "text-muted mb-4" > Check the documentation sections above or reach out to the development team.</ p >
< div class = "flex gap-3" >
2026-06-29 02:40:49 -04:00
2026-06-28 04:50:29 -04:00
< a href = "#getting-started" class = "btn btn-outline" > Re-read Setup Guide</ a >
</ div >
</ div >
</ section >
</ main >
< footer class = "footer" >
2026-06-29 02:40:49 -04:00
< p > © 2026 AppForge. All rights reserved. · < a href = "#top" style = "color: var(--text-faint);" > Back to top ↑</ a ></ p >
2026-06-28 04:50:29 -04:00
</ footer >
</ div >
</ div >
<!-- Back to Top Button -->
< button class = "back-to-top" onclick = "scrollToSection('top')" title = "Back to top" >
< svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >< polyline points = "18 15 12 9 6 15" /></ svg >
</ button >
</ body >
</ html >