- Update BACKEND_BASE_URL to plugincompass.com:9445 - Add comprehensive authentication system (login, signup, logout) - Implement JWT token management with automatic refresh - Add plugin/app management API (CRUD operations) - Add session management for chat/builder - Update mobile UI with real backend integration - Add user account and usage tracking - Enable plugin loading and editing from backend - Fix module type for ES6 support
980 lines
33 KiB
JavaScript
980 lines
33 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] Initializing...');
|
|
|
|
try {
|
|
// Check if user is authenticated
|
|
const authenticated = await isAuthenticated();
|
|
console.log('[APP] Authenticated:', authenticated);
|
|
|
|
if (authenticated) {
|
|
const user = await getCurrentUser();
|
|
const onboardingDone = await isOnboardingComplete();
|
|
|
|
if (!onboardingDone) {
|
|
showScreen('onboarding');
|
|
} else {
|
|
showScreen('main');
|
|
updateWelcome(user);
|
|
}
|
|
} else {
|
|
showScreen('auth');
|
|
}
|
|
} catch (error) {
|
|
console.error('[APP] Initialization error:', error);
|
|
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', () => {
|
|
// 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);
|
|
});
|