Files
Leon SerfatyandClaude Opus 4.8 fd72540b6a Production deploy setup + finish AppForge rebrand
- Backend now serves the built frontend (dist) + SPA fallback for single-service deploy
- Schema applied idempotently on boot (ensureSchema) + working db:push script
- Add Dockerfile + .dockerignore + start script
- Footer credit removed; default app name -> AppForge; docs/package.json de-attributed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 02:40:49 -04:00

2938 lines
230 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>