- Add CORS headers to backend server to allow mobile app requests - Implement request timeout (10s) in capacitor-bridge.js to prevent hanging - Add comprehensive logging throughout authentication flow - Add detailed error reporting in initApp for better debugging - Log all API requests with request IDs for traceability This fixes the 'Loading Plugin Compass...' infinite loop issue caused by missing CORS headers and unhandled network timeouts.
1004 lines
35 KiB
JavaScript
1004 lines
35 KiB
JavaScript
import fs from "fs-extra";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const root = path.resolve(__dirname, "..", "..");
|
|
const source = path.join(root, "chat", "public");
|
|
const dest = path.resolve(__dirname, "..", "www");
|
|
|
|
async function copyUi() {
|
|
if (!(await fs.pathExists(source))) {
|
|
throw new Error(`Source UI folder not found: ${source}`);
|
|
}
|
|
|
|
await fs.emptyDir(dest);
|
|
await fs.copy(source, dest, {
|
|
filter: (src) => !src.endsWith(".map") && !src.includes(".DS_Store"),
|
|
overwrite: true,
|
|
});
|
|
}
|
|
|
|
async function findHtmlFiles(dir) {
|
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
const files = await Promise.all(
|
|
entries.map(async (entry) => {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
return findHtmlFiles(full);
|
|
}
|
|
return entry.isFile() && entry.name.endsWith(".html") ? [full] : [];
|
|
})
|
|
);
|
|
return files.flat();
|
|
}
|
|
|
|
async function injectMetaTags() {
|
|
const files = await findHtmlFiles(dest);
|
|
await Promise.all(
|
|
files.map(async (fullPath) => {
|
|
let html = await fs.readFile(fullPath, "utf8");
|
|
|
|
// Add viewport meta if missing
|
|
if (!html.includes('viewport')) {
|
|
html = html.replace('<head>', '<head>\n <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">');
|
|
}
|
|
|
|
// Add Capacitor script
|
|
if (!html.includes('capacitor.js')) {
|
|
html = html.replace('</head>', ' <script src="capacitor.js"></script>\n</head>');
|
|
}
|
|
|
|
// Add capacitor-bridge.js before closing body tag
|
|
if (!html.includes('capacitor-bridge.js')) {
|
|
html = html.replace('</body>', ' <script type="module" src="capacitor-bridge.js"></script>\n</body>');
|
|
}
|
|
|
|
await fs.writeFile(fullPath, html, "utf8");
|
|
})
|
|
);
|
|
}
|
|
|
|
async function createMobileIndex() {
|
|
const mobileIndex = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>Plugin Compass</title>
|
|
<link rel="icon" type="image/png" href="assets/Plugin.png">
|
|
<script src="capacitor.js"></script>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
:root {
|
|
--primary: #008060;
|
|
--primary-dark: #004c3f;
|
|
--bg: #fdf6ed;
|
|
--text: #1a1a1a;
|
|
--muted: #6b7280;
|
|
--border: #e5e7eb;
|
|
--white: #ffffff;
|
|
--error: #dc2626;
|
|
}
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.screen { display: none; flex-direction: column; min-height: 100vh; }
|
|
.screen.active { display: flex; }
|
|
|
|
/* Loading Screen */
|
|
.loading-screen {
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
}
|
|
.loading-logo {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 20px;
|
|
margin-bottom: 24px;
|
|
animation: pulse 1.5s ease-in-out infinite;
|
|
}
|
|
.loading-text {
|
|
color: var(--white);
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.05); opacity: 0.8; }
|
|
}
|
|
|
|
/* Auth Screen */
|
|
.auth-screen { background: var(--bg); }
|
|
.auth-header {
|
|
padding: 48px 24px 32px;
|
|
text-align: center;
|
|
}
|
|
.auth-logo {
|
|
width: 64px;
|
|
height: 64px;
|
|
border-radius: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.auth-title {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
margin-bottom: 8px;
|
|
}
|
|
.auth-subtitle {
|
|
font-size: 16px;
|
|
color: var(--muted);
|
|
}
|
|
.auth-form {
|
|
padding: 0 24px;
|
|
flex: 1;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
.form-label {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
margin-bottom: 6px;
|
|
}
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 14px 16px;
|
|
font-size: 16px;
|
|
border: 2px solid var(--border);
|
|
border-radius: 12px;
|
|
background: var(--white);
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
.form-input:focus { border-color: var(--primary); }
|
|
.btn {
|
|
width: 100%;
|
|
padding: 16px;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
border: none;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
color: var(--white);
|
|
}
|
|
.btn-primary:active { transform: scale(0.98); }
|
|
.btn-primary:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
.btn-secondary {
|
|
background: var(--white);
|
|
color: var(--text);
|
|
border: 2px solid var(--border);
|
|
margin-top: 12px;
|
|
}
|
|
.auth-divider {
|
|
display: flex;
|
|
align-items: center;
|
|
margin: 24px 0;
|
|
color: var(--muted);
|
|
font-size: 14px;
|
|
}
|
|
.auth-divider::before, .auth-divider::after {
|
|
content: '';
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--border);
|
|
}
|
|
.auth-divider span { padding: 0 16px; }
|
|
.oauth-buttons { display: flex; flex-direction: column; gap: 12px; }
|
|
.oauth-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
padding: 14px;
|
|
background: var(--white);
|
|
border: 2px solid var(--border);
|
|
border-radius: 12px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
.oauth-btn:active { background: #f5f5f5; }
|
|
.oauth-btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
.error-message {
|
|
color: var(--error);
|
|
font-size: 14px;
|
|
text-align: center;
|
|
margin-top: 12px;
|
|
min-height: 20px;
|
|
}
|
|
|
|
/* Onboarding Screen */
|
|
.onboarding-screen { background: var(--white); }
|
|
.onboarding-header {
|
|
padding: 24px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.onboarding-skip {
|
|
color: var(--muted);
|
|
font-size: 16px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.onboarding-progress {
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: center;
|
|
padding: 0 24px 24px;
|
|
}
|
|
.progress-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--border);
|
|
transition: all 0.3s;
|
|
}
|
|
.progress-dot.active { background: var(--primary); transform: scale(1.25); }
|
|
.progress-dot.completed { background: var(--primary); }
|
|
.onboarding-content {
|
|
flex: 1;
|
|
padding: 0 32px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
}
|
|
.onboarding-icon {
|
|
width: 100px;
|
|
height: 100px;
|
|
border-radius: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-bottom: 32px;
|
|
}
|
|
.onboarding-icon svg { width: 48px; height: 48px; stroke: white; }
|
|
.onboarding-title {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
margin-bottom: 16px;
|
|
}
|
|
.onboarding-text {
|
|
font-size: 17px;
|
|
color: var(--muted);
|
|
line-height: 1.6;
|
|
max-width: 320px;
|
|
}
|
|
.onboarding-prompts {
|
|
width: 100%;
|
|
margin-top: 32px;
|
|
}
|
|
.prompt-card {
|
|
background: var(--bg);
|
|
border: 2px solid var(--border);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
margin-bottom: 12px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.prompt-card:active { transform: scale(0.98); border-color: var(--primary); }
|
|
.prompt-title { font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
|
.prompt-desc { font-size: 14px; color: var(--muted); }
|
|
.onboarding-footer {
|
|
padding: 24px;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
.onboarding-footer .btn { flex: 1; }
|
|
|
|
/* Main Screen */
|
|
.main-screen { background: var(--bg); }
|
|
.main-header {
|
|
background: var(--white);
|
|
padding: 16px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.main-logo { width: 36px; height: 36px; border-radius: 10px; }
|
|
.main-title { font-size: 18px; font-weight: 700; flex: 1; }
|
|
.main-content {
|
|
flex: 1;
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
}
|
|
.welcome-card {
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
border-radius: 20px;
|
|
padding: 24px;
|
|
color: var(--white);
|
|
margin-bottom: 20px;
|
|
}
|
|
.welcome-title { font-size: 22px; font-weight: 700; margin-bottom: 8px; }
|
|
.welcome-text { font-size: 15px; opacity: 0.9; }
|
|
.quick-actions { display: flex; flex-direction: column; gap: 12px; }
|
|
.quick-action {
|
|
background: var(--white);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
border: 2px solid var(--border);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.quick-action:active { transform: scale(0.98); border-color: var(--primary); }
|
|
.quick-action-icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.quick-action-icon svg { width: 22px; height: 22px; }
|
|
.quick-action-text { flex: 1; }
|
|
.quick-action-title { font-weight: 600; font-size: 16px; }
|
|
.quick-action-desc { font-size: 13px; color: var(--muted); }
|
|
|
|
/* Plugins List Screen */
|
|
.plugins-screen { background: var(--bg); }
|
|
.plugins-list {
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
}
|
|
.plugin-card {
|
|
background: var(--white);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
margin-bottom: 12px;
|
|
border: 2px solid var(--border);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.plugin-card:active { transform: scale(0.98); border-color: var(--primary); }
|
|
.plugin-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 8px;
|
|
}
|
|
.plugin-name { font-weight: 600; font-size: 16px; }
|
|
.plugin-status {
|
|
font-size: 12px;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
background: var(--bg);
|
|
}
|
|
.plugin-status.active { background: #d1fae5; color: #065f46; }
|
|
.plugin-desc { font-size: 14px; color: var(--muted); margin-bottom: 8px; }
|
|
.plugin-meta { font-size: 12px; color: var(--muted); }
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: var(--muted);
|
|
}
|
|
.empty-state svg {
|
|
width: 64px;
|
|
height: 64px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.3;
|
|
}
|
|
.loading-spinner {
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--border);
|
|
border-top-color: var(--primary);
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Loading Screen -->
|
|
<div id="loading-screen" class="screen active loading-screen">
|
|
<img src="assets/Plugin.png" class="loading-logo" alt="Plugin Compass">
|
|
<div class="loading-text">Loading Plugin Compass...</div>
|
|
</div>
|
|
|
|
<!-- Auth Screen -->
|
|
<div id="auth-screen" class="screen auth-screen">
|
|
<div class="auth-header">
|
|
<img src="assets/Plugin.png" class="auth-logo" alt="Plugin Compass">
|
|
<h1 class="auth-title">Plugin Compass</h1>
|
|
<p class="auth-subtitle">Build WordPress plugins with AI</p>
|
|
</div>
|
|
<div class="auth-form">
|
|
<div class="form-group">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" id="auth-email" class="form-input" placeholder="you@example.com" autocapitalize="off" autocomplete="email">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Password</label>
|
|
<input type="password" id="auth-password" class="form-input" placeholder="Enter your password" autocomplete="current-password">
|
|
</div>
|
|
<button id="auth-submit" class="btn btn-primary">
|
|
<span id="auth-submit-text">Sign In</span>
|
|
</button>
|
|
<div class="auth-divider"><span>or</span></div>
|
|
<button id="auth-signup" class="btn btn-secondary">Create Account</button>
|
|
<div id="auth-error" class="error-message"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Onboarding Screen -->
|
|
<div id="onboarding-screen" class="screen onboarding-screen">
|
|
<div class="onboarding-header">
|
|
<div></div>
|
|
<button id="onboarding-skip" class="onboarding-skip">Skip</button>
|
|
</div>
|
|
<div class="onboarding-progress">
|
|
<div class="progress-dot active" data-step="1"></div>
|
|
<div class="progress-dot" data-step="2"></div>
|
|
<div class="progress-dot" data-step="3"></div>
|
|
<div class="progress-dot" data-step="4"></div>
|
|
</div>
|
|
|
|
<!-- Step 1: Welcome -->
|
|
<div id="step-1" class="onboarding-content" style="display: flex;">
|
|
<div class="onboarding-icon" style="background: linear-gradient(135deg, #008060, #004c3f);">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
|
</div>
|
|
<h2 class="onboarding-title">Welcome to Plugin Compass</h2>
|
|
<p class="onboarding-text">Build custom WordPress plugins with AI. No coding required - just describe what you need.</p>
|
|
</div>
|
|
|
|
<!-- Step 2: Features -->
|
|
<div id="step-2" class="onboarding-content" style="display: none;">
|
|
<div class="onboarding-icon" style="background: linear-gradient(135deg, #5A31F4, #8B5CF6);">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
</div>
|
|
<h2 class="onboarding-title">Chat to Build</h2>
|
|
<p class="onboarding-text">Describe your plugin in plain English. Our AI understands WordPress and creates working code.</p>
|
|
</div>
|
|
|
|
<!-- Step 3: Prompts -->
|
|
<div id="step-3" class="onboarding-content" style="display: none;">
|
|
<div class="onboarding-icon" style="background: linear-gradient(135deg, #F59E0B, #D97706);">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
|
</div>
|
|
<h2 class="onboarding-title">Quick Start Ideas</h2>
|
|
<p class="onboarding-text" style="margin-bottom: 20px;">Tap a suggestion to get started:</p>
|
|
<div class="onboarding-prompts">
|
|
<div class="prompt-card" data-prompt="Create a contact form plugin with spam protection and email notifications">
|
|
<div class="prompt-title">Contact Form Plugin</div>
|
|
<div class="prompt-desc">Spam protection & email notifications</div>
|
|
</div>
|
|
<div class="prompt-card" data-prompt="Build a testimonial slider plugin with admin management and shortcodes">
|
|
<div class="prompt-title">Testimonial Slider</div>
|
|
<div class="prompt-desc">Admin management & shortcodes</div>
|
|
</div>
|
|
<div class="prompt-card" data-prompt="Create an SEO meta tags plugin with social media preview support">
|
|
<div class="prompt-title">SEO Meta Tags</div>
|
|
<div class="prompt-desc">Social media preview support</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 4: Ready -->
|
|
<div id="step-4" class="onboarding-content" style="display: none;">
|
|
<div class="onboarding-icon" style="background: linear-gradient(135deg, #10B981, #059669);">
|
|
<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>
|
|
<h2 class="onboarding-title">You're All Set!</h2>
|
|
<p class="onboarding-text">Start building your first plugin. You can always ask questions or get help along the way.</p>
|
|
</div>
|
|
|
|
<div class="onboarding-footer">
|
|
<button id="onboarding-back" class="btn btn-secondary" style="display: none;">Back</button>
|
|
<button id="onboarding-next" class="btn btn-primary">Next</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Screen -->
|
|
<div id="main-screen" class="screen main-screen">
|
|
<div class="main-header">
|
|
<img src="assets/Plugin.png" class="main-logo" alt="Plugin Compass">
|
|
<div class="main-title">Plugin Compass</div>
|
|
<button id="logout-btn" style="background: none; border: none; padding: 8px;">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="main-content">
|
|
<div class="welcome-card">
|
|
<div class="welcome-title">Ready to Build!</div>
|
|
<div class="welcome-text" id="welcome-user">What would you like to create today?</div>
|
|
</div>
|
|
<div class="quick-actions">
|
|
<div class="quick-action" id="action-new-plugin">
|
|
<div class="quick-action-icon" style="background: linear-gradient(135deg, #008060, #004c3f);">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
</div>
|
|
<div class="quick-action-text">
|
|
<div class="quick-action-title">New Plugin</div>
|
|
<div class="quick-action-desc">Start building from scratch</div>
|
|
</div>
|
|
</div>
|
|
<div class="quick-action" id="action-my-plugins">
|
|
<div class="quick-action-icon" style="background: linear-gradient(135deg, #5A31F4, #8B5CF6);">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><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>
|
|
</div>
|
|
<div class="quick-action-text">
|
|
<div class="quick-action-title">My Plugins</div>
|
|
<div class="quick-action-desc">View and edit your plugins</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Plugins List Screen -->
|
|
<div id="plugins-screen" class="screen plugins-screen">
|
|
<div class="main-header">
|
|
<button id="plugins-back" style="background: none; border: none; padding: 8px; margin-right: 8px;">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
</button>
|
|
<div class="main-title">My Plugins</div>
|
|
<button id="plugins-refresh" style="background: none; border: none; padding: 8px;">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="plugins-list" id="plugins-list">
|
|
<div class="empty-state">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
|
<p>Loading your plugins...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
import {
|
|
loginWithEmail,
|
|
signupWithEmail,
|
|
logout,
|
|
getCurrentUser,
|
|
isAuthenticated,
|
|
getUserApps,
|
|
getApp,
|
|
createApp,
|
|
updateApp,
|
|
deleteApp,
|
|
Preferences
|
|
} from './capacitor-bridge.js';
|
|
|
|
const APP_ID = 'com.plugincompass.app';
|
|
const ONBOARDING_KEY = 'plugin_compass_onboarding_done';
|
|
|
|
let currentStep = 1;
|
|
const totalSteps = 4;
|
|
let isLoading = false;
|
|
|
|
// Screen management
|
|
function showScreen(screenId) {
|
|
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
|
const screen = document.getElementById(screenId + '-screen');
|
|
if (screen) {
|
|
screen.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// Initialize app
|
|
async function initApp() {
|
|
console.log('[APP] ===========================================');
|
|
console.log('[APP] Starting app initialization...');
|
|
console.log('[APP] Current time:', new Date().toISOString());
|
|
console.log('[APP] User agent:', navigator.userAgent);
|
|
console.log('[APP] Platform:', navigator.platform);
|
|
|
|
try {
|
|
console.log('[APP] Step 1/4: Checking authentication...');
|
|
const authenticated = await isAuthenticated();
|
|
console.log('[APP] Step 1/4 complete - Authenticated:', authenticated);
|
|
|
|
if (authenticated) {
|
|
console.log('[APP] Step 2/4: Getting current user...');
|
|
const user = await getCurrentUser();
|
|
console.log('[APP] Step 2/4 complete - User:', user ? user.email || user.id : 'null');
|
|
|
|
console.log('[APP] Step 3/4: Checking onboarding status...');
|
|
const onboardingDone = await isOnboardingComplete();
|
|
console.log('[APP] Step 3/4 complete - Onboarding done:', onboardingDone);
|
|
|
|
if (!onboardingDone) {
|
|
console.log('[APP] Step 4/4: Showing onboarding screen');
|
|
showScreen('onboarding');
|
|
} else {
|
|
console.log('[APP] Step 4/4: Showing main screen');
|
|
showScreen('main');
|
|
updateWelcome(user);
|
|
}
|
|
} else {
|
|
console.log('[APP] Step 2/2: User not authenticated, showing auth screen');
|
|
showScreen('auth');
|
|
}
|
|
|
|
console.log('[APP] ===========================================');
|
|
console.log('[APP] Initialization complete!');
|
|
} catch (error) {
|
|
console.error('[APP] ===========================================');
|
|
console.error('[APP] FATAL: Initialization error:', error);
|
|
console.error('[APP] Error name:', error.name);
|
|
console.error('[APP] Error message:', error.message);
|
|
console.error('[APP] Error stack:', error.stack);
|
|
console.error('[APP] ===========================================');
|
|
showScreen('auth');
|
|
}
|
|
}
|
|
|
|
async function isOnboardingComplete() {
|
|
try {
|
|
const { value } = await Preferences.get({ key: ONBOARDING_KEY });
|
|
return value === 'true';
|
|
} catch (e) {
|
|
return localStorage.getItem(ONBOARDING_KEY) === 'true';
|
|
}
|
|
}
|
|
|
|
async function completeOnboarding() {
|
|
try {
|
|
await Preferences.set({ key: ONBOARDING_KEY, value: 'true' });
|
|
} catch (e) {
|
|
localStorage.setItem(ONBOARDING_KEY, 'true');
|
|
}
|
|
}
|
|
|
|
function updateWelcome(user) {
|
|
const welcomeEl = document.getElementById('welcome-user');
|
|
if (welcomeEl && user && user.email) {
|
|
const name = user.email.split('@')[0];
|
|
welcomeEl.textContent = \`Hi \${name}! What would you like to build today?\`;
|
|
}
|
|
}
|
|
|
|
// Onboarding navigation
|
|
function showOnboardingStep(step) {
|
|
currentStep = step;
|
|
|
|
for (let i = 1; i <= totalSteps; i++) {
|
|
const stepEl = document.getElementById('step-' + i);
|
|
if (stepEl) {
|
|
stepEl.style.display = i === step ? 'flex' : 'none';
|
|
}
|
|
}
|
|
|
|
document.querySelectorAll('.progress-dot').forEach((dot, index) => {
|
|
dot.classList.remove('active', 'completed');
|
|
if (index + 1 < step) dot.classList.add('completed');
|
|
if (index + 1 === step) dot.classList.add('active');
|
|
});
|
|
|
|
const backBtn = document.getElementById('onboarding-back');
|
|
const nextBtn = document.getElementById('onboarding-next');
|
|
|
|
if (backBtn) backBtn.style.display = step === 1 ? 'none' : 'block';
|
|
if (nextBtn) nextBtn.textContent = step === totalSteps ? 'Get Started' : 'Next';
|
|
}
|
|
|
|
// Load and display plugins
|
|
async function loadPlugins() {
|
|
const listEl = document.getElementById('plugins-list');
|
|
if (!listEl) return;
|
|
|
|
listEl.innerHTML = '<div class="empty-state"><div class="loading-spinner"></div><p>Loading plugins...</p></div>';
|
|
|
|
try {
|
|
const result = await getUserApps();
|
|
|
|
if (!result.success) {
|
|
listEl.innerHTML = \`<div class="empty-state"><p>\${result.error || 'Failed to load plugins'}</p></div>\`;
|
|
return;
|
|
}
|
|
|
|
const apps = result.apps || [];
|
|
|
|
if (apps.length === 0) {
|
|
listEl.innerHTML = \`
|
|
<div class="empty-state">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<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"/>
|
|
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
<polyline points="10 9 9 9 8 9"/>
|
|
</svg>
|
|
<p>No plugins yet</p>
|
|
<p style="font-size: 14px; margin-top: 8px;">Create your first plugin to get started!</p>
|
|
</div>
|
|
\`;
|
|
return;
|
|
}
|
|
|
|
listEl.innerHTML = apps.map(app => \`
|
|
<div class="plugin-card" data-app-id="\${app.id}">
|
|
<div class="plugin-header">
|
|
<div class="plugin-name">\${app.name || 'Untitled Plugin'}</div>
|
|
<div class="plugin-status \${app.status === 'active' ? 'active' : ''}">\${app.status || 'Draft'}</div>
|
|
</div>
|
|
<div class="plugin-desc">\${app.description || 'No description'}</div>
|
|
<div class="plugin-meta">\${app.updatedAt ? new Date(app.updatedAt).toLocaleDateString() : 'Never updated'}</div>
|
|
</div>
|
|
\`).join('');
|
|
|
|
// Add click handlers
|
|
listEl.querySelectorAll('.plugin-card').forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
const appId = card.dataset.appId;
|
|
// Navigate to builder with app ID
|
|
window.location.href = \`builder.html?appId=\${encodeURIComponent(appId)}\`;
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[PLUGINS] Failed to load:', error);
|
|
listEl.innerHTML = '<div class="empty-state"><p>Failed to load plugins. Try again.</p></div>';
|
|
}
|
|
}
|
|
|
|
// Event listeners
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('[APP] ===========================================');
|
|
console.log('[APP] DOMContentLoaded event fired!');
|
|
console.log('[APP] Starting app in 500ms...');
|
|
console.log('[APP] ===========================================');
|
|
// Auth form - Sign In
|
|
const authSubmitBtn = document.getElementById('auth-submit');
|
|
const authSignupBtn = document.getElementById('auth-signup');
|
|
|
|
if (authSubmitBtn) {
|
|
authSubmitBtn.addEventListener('click', async () => {
|
|
if (isLoading) return;
|
|
|
|
const emailEl = document.getElementById('auth-email');
|
|
const passwordEl = document.getElementById('auth-password');
|
|
const errorEl = document.getElementById('auth-error');
|
|
const submitText = document.getElementById('auth-submit-text');
|
|
|
|
const email = emailEl?.value?.trim();
|
|
const password = passwordEl?.value;
|
|
|
|
if (!email) {
|
|
if (errorEl) errorEl.textContent = 'Please enter your email';
|
|
if (emailEl) emailEl.focus();
|
|
return;
|
|
}
|
|
|
|
if (!password) {
|
|
if (errorEl) errorEl.textContent = 'Please enter your password';
|
|
if (passwordEl) passwordEl.focus();
|
|
return;
|
|
}
|
|
|
|
if (errorEl) errorEl.textContent = '';
|
|
|
|
// Show loading state
|
|
isLoading = true;
|
|
authSubmitBtn.disabled = true;
|
|
if (authSignupBtn) authSignupBtn.disabled = true;
|
|
if (submitText) submitText.innerHTML = '<div class="loading-spinner" style="width: 16px; height: 16px; border-width: 2px; display: inline-block; vertical-align: middle; margin-right: 8px;"></div>Signing in...';
|
|
|
|
try {
|
|
console.log('[AUTH] Attempting login...');
|
|
const result = await loginWithEmail(email, password);
|
|
console.log('[AUTH] Login result:', result);
|
|
|
|
if (result.success) {
|
|
const onboardingDone = await isOnboardingComplete();
|
|
if (!onboardingDone) {
|
|
showScreen('onboarding');
|
|
} else {
|
|
showScreen('main');
|
|
updateWelcome(result.user);
|
|
}
|
|
} else {
|
|
if (errorEl) errorEl.textContent = result.error || 'Sign in failed. Please try again.';
|
|
}
|
|
} catch (error) {
|
|
console.error('[AUTH] Login error:', error);
|
|
if (errorEl) errorEl.textContent = error.message || 'An error occurred. Please try again.';
|
|
} finally {
|
|
isLoading = false;
|
|
authSubmitBtn.disabled = false;
|
|
if (authSignupBtn) authSignupBtn.disabled = false;
|
|
if (submitText) submitText.textContent = 'Sign In';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Auth form - Sign Up
|
|
if (authSignupBtn) {
|
|
authSignupBtn.addEventListener('click', async () => {
|
|
if (isLoading) return;
|
|
|
|
const emailEl = document.getElementById('auth-email');
|
|
const passwordEl = document.getElementById('auth-password');
|
|
const errorEl = document.getElementById('auth-error');
|
|
const submitText = document.getElementById('auth-submit-text');
|
|
|
|
const email = emailEl?.value?.trim();
|
|
const password = passwordEl?.value;
|
|
|
|
if (!email) {
|
|
if (errorEl) errorEl.textContent = 'Please enter your email';
|
|
if (emailEl) emailEl.focus();
|
|
return;
|
|
}
|
|
|
|
if (!password || password.length < 6) {
|
|
if (errorEl) errorEl.textContent = 'Password must be at least 6 characters';
|
|
if (passwordEl) passwordEl.focus();
|
|
return;
|
|
}
|
|
|
|
if (errorEl) errorEl.textContent = '';
|
|
|
|
// Show loading state
|
|
isLoading = true;
|
|
authSubmitBtn.disabled = true;
|
|
authSignupBtn.disabled = true;
|
|
authSignupBtn.innerHTML = '<div class="loading-spinner" style="width: 16px; height: 16px; border-width: 2px; display: inline-block; vertical-align: middle; margin-right: 8px;"></div>Creating account...';
|
|
|
|
try {
|
|
console.log('[AUTH] Attempting signup...');
|
|
const result = await signupWithEmail(email, password);
|
|
console.log('[AUTH] Signup result:', result);
|
|
|
|
if (result.success) {
|
|
showScreen('onboarding');
|
|
} else {
|
|
if (errorEl) errorEl.textContent = result.error || 'Account creation failed. Please try again.';
|
|
}
|
|
} catch (error) {
|
|
console.error('[AUTH] Signup error:', error);
|
|
if (errorEl) errorEl.textContent = error.message || 'An error occurred. Please try again.';
|
|
} finally {
|
|
isLoading = false;
|
|
authSubmitBtn.disabled = false;
|
|
authSignupBtn.disabled = false;
|
|
authSignupBtn.textContent = 'Create Account';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Onboarding navigation
|
|
const onboardingNext = document.getElementById('onboarding-next');
|
|
if (onboardingNext) {
|
|
onboardingNext.addEventListener('click', async () => {
|
|
if (currentStep < totalSteps) {
|
|
showOnboardingStep(currentStep + 1);
|
|
} else {
|
|
await completeOnboarding();
|
|
const user = await getCurrentUser();
|
|
updateWelcome(user);
|
|
showScreen('main');
|
|
}
|
|
});
|
|
}
|
|
|
|
const onboardingBack = document.getElementById('onboarding-back');
|
|
if (onboardingBack) {
|
|
onboardingBack.addEventListener('click', () => {
|
|
if (currentStep > 1) {
|
|
showOnboardingStep(currentStep - 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
const onboardingSkip = document.getElementById('onboarding-skip');
|
|
if (onboardingSkip) {
|
|
onboardingSkip.addEventListener('click', async () => {
|
|
await completeOnboarding();
|
|
const user = await getCurrentUser();
|
|
updateWelcome(user);
|
|
showScreen('main');
|
|
});
|
|
}
|
|
|
|
// Prompt cards
|
|
document.querySelectorAll('.prompt-card').forEach(card => {
|
|
card.addEventListener('click', async () => {
|
|
const prompt = card.dataset.prompt;
|
|
await completeOnboarding();
|
|
const user = await getCurrentUser();
|
|
updateWelcome(user);
|
|
// Navigate to builder with prompt
|
|
window.location.href = \`builder.html?prompt=\${encodeURIComponent(prompt)}\`;
|
|
});
|
|
});
|
|
|
|
// Quick actions
|
|
const actionNewPlugin = document.getElementById('action-new-plugin');
|
|
if (actionNewPlugin) {
|
|
actionNewPlugin.addEventListener('click', () => {
|
|
window.location.href = 'builder.html';
|
|
});
|
|
}
|
|
|
|
const actionMyPlugins = document.getElementById('action-my-plugins');
|
|
if (actionMyPlugins) {
|
|
actionMyPlugins.addEventListener('click', () => {
|
|
showScreen('plugins');
|
|
loadPlugins();
|
|
});
|
|
}
|
|
|
|
// Plugins screen
|
|
const pluginsBack = document.getElementById('plugins-back');
|
|
if (pluginsBack) {
|
|
pluginsBack.addEventListener('click', () => {
|
|
showScreen('main');
|
|
});
|
|
}
|
|
|
|
const pluginsRefresh = document.getElementById('plugins-refresh');
|
|
if (pluginsRefresh) {
|
|
pluginsRefresh.addEventListener('click', () => {
|
|
loadPlugins();
|
|
});
|
|
}
|
|
|
|
// Logout
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', async () => {
|
|
try {
|
|
await logout();
|
|
showScreen('auth');
|
|
} catch (error) {
|
|
console.error('[AUTH] Logout error:', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize
|
|
setTimeout(initApp, 500);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
await fs.writeFile(path.join(dest, 'index.html'), mobileIndex, 'utf8');
|
|
}
|
|
|
|
async function main() {
|
|
await copyUi();
|
|
await injectMetaTags();
|
|
await createMobileIndex();
|
|
console.log(`UI prepared in ${dest}`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|