Files

2938 lines
230 KiB
HTML
Raw Permalink Normal View History

<!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>
<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.">
<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>
<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>
<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 &lt;YOUR_GIT_URL&gt;
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 &amp; 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 &amp; 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 &lt; 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) =&gt; {
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 &amp; 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 &amp; 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 &amp; 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>3085%</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, 48512px 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, 10100MB</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 (1100).</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>
&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
&lt;/IfModule&gt;
<span class="syntax-comment"># Force HTTPS</span>
&lt;IfModule mod_rewrite.c&gt;
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
&lt;/IfModule&gt;
<span class="syntax-comment"># Gzip Compression</span>
&lt;IfModule mod_deflate.c&gt;
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json
&lt;/IfModule&gt;
<span class="syntax-comment"># Security Headers</span>
&lt;IfModule mod_headers.c&gt;
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"
&lt;/IfModule&gt;</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">
<a href="#getting-started" class="btn btn-outline">Re-read Setup Guide</a>
</div>
</div>
</section>
</main>
<footer class="footer">
<p>© 2026 AppForge. All rights reserved. · <a href="#top" style="color: var(--text-faint);">Back to top ↑</a></p>
</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>