<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ConnectWatch — Get started</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@2.47.0/tabler-icons.min.css">
<script src="config.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#080c14;--bg2:#0d1220;--bg3:#121929;--border:#1e2a40;--border2:#263347;
--text:#e8edf5;--sub:#6b7a96;--dim:#374357;
--blue:#3b82f6;--blue2:#1d4ed8;--blue-glow:rgba(59,130,246,.15);
--ok:#10b981;--ok-bg:rgba(16,185,129,.1);
--warn:#f59e0b;--err:#ef4444;
--font-head:'Syne',sans-serif;--font-mono:'DM Mono',monospace;
}
body{font-family:var(--font-head);background:var(--bg);color:var(--text);min-height:100vh;display:flex;overflow:hidden}
/* ── Left panel ── */
.left{width:420px;flex-shrink:0;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:40px;position:relative;overflow:hidden}
.left-grid{position:absolute;inset:0;background-image:linear-gradient(var(--border) 1px,transparent 1px),linear-gradient(90deg,var(--border) 1px,transparent 1px);background-size:32px 32px;opacity:.4;pointer-events:none}
.left-glow{position:absolute;top:-100px;left:-100px;width:400px;height:400px;background:radial-gradient(circle,rgba(59,130,246,.12) 0%,transparent 70%);pointer-events:none}
.logo{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:700;letter-spacing:-.02em;position:relative;z-index:1}
.logo-icon{width:36px;height:36px;background:var(--blue);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px}
.left-content{flex:1;display:flex;flex-direction:column;justify-content:center;position:relative;z-index:1}
.left-title{font-size:32px;font-weight:700;line-height:1.15;letter-spacing:-.03em;margin-bottom:16px}
.left-title span{color:var(--blue)}
.left-sub{font-size:14px;color:var(--sub);line-height:1.7;margin-bottom:40px}
.feature{display:flex;align-items:flex-start;gap:12px;margin-bottom:20px}
.feature-icon{width:32px;height:32px;border-radius:8px;background:var(--bg3);border:1px solid var(--border2);display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;color:var(--blue)}
.feature-text{font-size:13px;color:var(--sub);line-height:1.5}
.feature-text strong{color:var(--text);display:block;margin-bottom:2px;font-weight:500}
.left-footer{font-size:12px;color:var(--dim);position:relative;z-index:1}
.left-footer a{color:var(--sub);text-decoration:none}
.left-footer a:hover{color:var(--text)}
/* ── Right panel ── */
.right{flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto}
.form-wrap{width:100%;max-width:480px}
/* ── Steps indicator ── */
.steps{display:flex;align-items:center;gap:0;margin-bottom:40px}
.step-item{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:500;color:var(--dim)}
.step-item.active{color:var(--text)}
.step-item.done{color:var(--ok)}
.step-num{width:24px;height:24px;border-radius:50%;border:1.5px solid var(--dim);display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0;transition:.2s}
.step-item.active .step-num{border-color:var(--blue);background:var(--blue);color:#fff}
.step-item.done .step-num{border-color:var(--ok);background:var(--ok);color:#fff}
.step-line{flex:1;height:1px;background:var(--border);margin:0 8px}
.step-line.done{background:var(--ok)}
/* ── Form ── */
.step-panel{display:none;animation:fadeIn .25s ease}
.step-panel.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.step-title{font-size:22px;font-weight:700;letter-spacing:-.02em;margin-bottom:6px}
.step-sub{font-size:14px;color:var(--sub);margin-bottom:28px;line-height:1.6}
.fl{font-size:11px;color:var(--sub);margin-bottom:5px;display:block;text-transform:uppercase;letter-spacing:.06em;font-family:var(--font-mono)}
.fg{margin-bottom:16px}
.fi{width:100%;padding:10px 14px;font-size:14px;background:var(--bg2);border:1px solid var(--border2);border-radius:8px;color:var(--text);outline:none;font-family:var(--font-head);transition:.15s}
.fi:focus{border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-glow)}
.fi::placeholder{color:var(--dim)}
.fi-mono{font-family:var(--font-mono);font-size:12px;min-height:100px;resize:vertical}
.fi-err{border-color:var(--err)!important}
.field-err{font-size:12px;color:var(--err);margin-top:5px;display:none}
.field-err.show{display:block}
/* Plan cards */
.plan-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:20px}
.plan-card{border:1.5px solid var(--border);border-radius:10px;padding:14px;cursor:pointer;transition:.2s;position:relative}
.plan-card:hover{border-color:var(--border2)}
.plan-card.selected{border-color:var(--blue);background:var(--blue-glow)}
.plan-card.popular::before{content:'Popular';position:absolute;top:-10px;left:50%;transform:translateX(-50%);background:var(--blue);color:#fff;font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;white-space:nowrap;font-family:var(--font-mono)}
.plan-name{font-size:13px;font-weight:600;margin-bottom:4px}
.plan-price{font-size:20px;font-weight:700;letter-spacing:-.02em;color:var(--text)}
.plan-price sub{font-size:12px;font-weight:400;color:var(--sub)}
.plan-envs{font-size:11px;color:var(--sub);margin-top:6px;font-family:var(--font-mono)}
.plan-feat{font-size:11px;color:var(--sub);margin-top:8px;line-height:1.7}
/* Cloud grid */
.cloud-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:20px}
.cloud-card{border:1.5px solid var(--border);border-radius:10px;padding:14px;cursor:pointer;transition:.2s;text-align:center}
.cloud-card:hover{border-color:var(--border2)}
.cloud-card.selected{border-color:var(--blue);background:var(--blue-glow)}
.cloud-card i{font-size:24px;color:var(--sub);display:block;margin-bottom:6px}
.cloud-card.selected i{color:var(--blue)}
.cloud-card span{font-size:12px;color:var(--sub);font-family:var(--font-mono)}
.cloud-card.selected span{color:var(--text)}
/* SSH key status */
.key-configured{display:none;align-items:center;gap:8px;padding:10px 14px;background:var(--ok-bg);border:1px solid var(--ok);border-radius:8px;font-size:13px;color:var(--ok);margin-top:6px}
.auth-toggle{display:flex;gap:8px;margin-bottom:12px}
.auth-btn{flex:1;padding:8px;font-size:12px;border:1.5px solid var(--border);border-radius:7px;background:transparent;color:var(--sub);cursor:pointer;font-family:var(--font-mono);transition:.15s}
.auth-btn.active{border-color:var(--blue);color:var(--blue);background:var(--blue-glow)}
/* Buttons */
.btn-primary{width:100%;padding:12px;font-size:14px;font-weight:600;background:var(--blue);color:#fff;border:none;border-radius:8px;cursor:pointer;font-family:var(--font-head);transition:.15s;display:flex;align-items:center;justify-content:center;gap:8px;letter-spacing:-.01em}
.btn-primary:hover{background:var(--blue2)}
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
.btn-secondary{background:transparent;border:1.5px solid var(--border2);color:var(--sub);width:auto;padding:10px 18px;font-size:13px;border-radius:8px;cursor:pointer;font-family:var(--font-head);transition:.15s}
.btn-secondary:hover{border-color:var(--text);color:var(--text)}
.btn-row{display:flex;gap:10px;align-items:center;margin-top:20px}
/* Result box */
.result-box{padding:12px 14px;border-radius:8px;font-size:13px;margin-top:14px;display:none;font-family:var(--font-mono);line-height:1.6}
.result-box.ok{background:var(--ok-bg);border:1px solid var(--ok);color:var(--ok);display:block}
.result-box.err{background:rgba(239,68,68,.1);border:1px solid var(--err);color:var(--err);display:block}
.result-box.loading{background:var(--bg3);border:1px solid var(--border2);color:var(--sub);display:block}
/* Success screen */
.success-wrap{text-align:center;padding:20px 0}
.success-icon{width:72px;height:72px;background:var(--ok-bg);border:2px solid var(--ok);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:32px;color:var(--ok);margin:0 auto 24px}
.success-title{font-size:26px;font-weight:700;letter-spacing:-.02em;margin-bottom:10px}
.success-sub{font-size:14px;color:var(--sub);line-height:1.7;margin-bottom:32px}
.probe-result{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px;text-align:left;font-family:var(--font-mono);font-size:12px;margin-bottom:24px}
.probe-row{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border);color:var(--sub)}
.probe-row:last-child{border-bottom:none}
.probe-row span:last-child{color:var(--ok);font-weight:500}
.skip-link{font-size:13px;color:var(--sub);cursor:pointer;text-decoration:underline;margin-top:16px;display:inline-block}
.skip-link:hover{color:var(--text)}
.err-banner{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);border-radius:8px;padding:10px 14px;font-size:13px;color:var(--err);margin-bottom:20px;display:none}
.err-banner.show{display:block}
select.fi option{background:var(--bg2)}
</style>
</head>
<body>
<!-- Left panel -->
<div class="left">
<div class="left-grid"></div>
<div class="left-glow"></div>
<div class="logo">
<div class="logo-icon"><i class="ti ti-radar" style="color:#fff"></i></div>
ConnectWatch
</div>
<div class="left-content">
<div class="left-title">Monitor your<br>infrastructure<br><span>before it fails</span></div>
<div class="left-sub">SSH, filesystem, S3, HTTPS — all your connectivity checks in one place. Know the moment something degrades.</div>
<div class="feature">
<div class="feature-icon"><i class="ti ti-database"></i></div>
<div class="feature-text"><strong>Filesystem write probing</strong>Prove EFS, NFS, and Azure Files can actually write — not just ping.</div>
</div>
<div class="feature">
<div class="feature-icon"><i class="ti ti-bell"></i></div>
<div class="feature-text"><strong>Alerts before your users notice</strong>Email, Slack, and PagerDuty the moment latency degrades.</div>
</div>
<div class="feature">
<div class="feature-icon"><i class="ti ti-clock"></i></div>
<div class="feature-text"><strong>5-minute probe intervals</strong>Not hourly — every 5 minutes on Enterprise, 15 on Pro.</div>
</div>
</div>
<div class="left-footer">
Already have an account? <a href="index.html">Sign in</a>
</div>
</div>
<!-- Right panel -->
<div class="right">
<div class="form-wrap">
<!-- Steps -->
<div class="steps">
<div class="step-item active" id="s-ind-1">
<div class="step-num" id="s-num-1">1</div>
<span>Account</span>
</div>
<div class="step-line" id="s-line-1"></div>
<div class="step-item" id="s-ind-2">
<div class="step-num" id="s-num-2">2</div>
<span>Plan</span>
</div>
<div class="step-line" id="s-line-2"></div>
<div class="step-item" id="s-ind-3">
<div class="step-num" id="s-num-3">3</div>
<span>Environment</span>
</div>
<div class="step-line" id="s-line-3"></div>
<div class="step-item" id="s-ind-4">
<div class="step-num" id="s-num-4">4</div>
<span>Connect</span>
</div>
</div>
<!-- Step 1: Account -->
<div class="step-panel active" id="step-1">
<div class="step-title">Create your account</div>
<div class="step-sub">Takes 2 minutes. No credit card required for the free trial.</div>
<div class="err-banner" id="err-1"></div>
<div class="fg"><label class="fl">Company / organisation name</label>
<input class="fi" id="company" placeholder="Acme Infrastructure Ltd" autocomplete="organization">
</div>
<div class="fg"><label class="fl">Work email</label>
<input class="fi" id="email" type="email" placeholder="you@company.com" autocomplete="email">
</div>
<div class="fg"><label class="fl">Password</label>
<input class="fi" id="password" type="password" placeholder="Min 8 chars, uppercase, number, special char" autocomplete="new-password" oninput="checkPasswordStrength(this.value)">
<div id="pw-strength" style="margin-top:6px;display:none">
<div style="height:4px;background:#2a3147;border-radius:2px;overflow:hidden;margin-bottom:4px">
<div id="pw-bar" style="height:100%;border-radius:2px;transition:width .3s,background .3s"></div>
</div>
<div id="pw-label" style="font-size:11px"></div>
</div>
</div>
<div class="fg"><label class="fl">Confirm password</label>
<input class="fi" id="password2" type="password" placeholder="••••••••" autocomplete="new-password"
onkeydown="if(event.key==='Enter')nextStep(1)">
</div>
<button class="btn-primary" onclick="nextStep(1)">
<span>Continue</span><i class="ti ti-arrow-right"></i>
</button>
</div>
<!-- Step 2: Plan -->
<div class="step-panel" id="step-2">
<div class="step-title">Choose your plan</div>
<div class="step-sub">Start with Starter — upgrade any time. All plans include a 14-day free trial.</div>
<div class="err-banner" id="err-2"></div>
<div class="plan-grid">
<div class="plan-card selected" id="plan-basic" onclick="selectPlan('basic')">
<div class="plan-name">Starter</div>
<div class="plan-price">$29<sub>/mo</sub></div>
<div class="plan-envs">2 environments</div>
<div class="plan-feat">60-min probes<br>Email alerts<br>7-day history</div>
</div>
<div class="plan-card popular" id="plan-pro" onclick="selectPlan('pro')">
<div class="plan-name">Pro</div>
<div class="plan-price">$79<sub>/mo</sub></div>
<div class="plan-envs">5 environments</div>
<div class="plan-feat">15-min probes<br>Slack + PagerDuty<br>30-day history</div>
</div>
<div class="plan-card" id="plan-enterprise" onclick="selectPlan('enterprise')">
<div class="plan-name">Enterprise</div>
<div class="plan-price">$299<sub>/mo</sub></div>
<div class="plan-envs">10 environments</div>
<div class="plan-feat">5-min probes<br>Custom webhooks<br>90-day history</div>
</div>
</div>
<div style="font-size:12px;color:var(--sub);margin-bottom:20px;font-family:var(--font-mono)">
<i class="ti ti-info-circle"></i> 14-day free trial on all plans. No credit card required.
</div>
<div class="btn-row">
<button class="btn-secondary" onclick="goStep(1)"><i class="ti ti-arrow-left"></i> Back</button>
<button class="btn-primary" onclick="nextStep(2)" style="flex:1">
<span>Continue</span><i class="ti ti-arrow-right"></i>
</button>
</div>
</div>
<!-- Step 3: First environment -->
<div class="step-panel" id="step-3">
<div class="step-title">Add your first environment</div>
<div class="step-sub">An environment is a server or cloud deployment you want to monitor — e.g. "AWS Production".</div>
<div class="err-banner" id="err-3"></div>
<div class="fg"><label class="fl">Environment name</label>
<input class="fi" id="env-name" placeholder="AWS — Production">
</div>
<label class="fl" style="margin-bottom:10px">Cloud provider</label>
<div class="cloud-grid">
<div class="cloud-card selected" id="cloud-aws" onclick="selectCloud('aws')">
<i class="ti ti-brand-aws"></i><span>AWS</span>
</div>
<div class="cloud-card" id="cloud-azure" onclick="selectCloud('azure')">
<i class="ti ti-brand-azure"></i><span>Azure</span>
</div>
<div class="cloud-card" id="cloud-gcp" onclick="selectCloud('gcp')">
<i class="ti ti-brand-google"></i><span>GCP</span>
</div>
<div class="cloud-card" id="cloud-onprem" onclick="selectCloud('onprem')">
<i class="ti ti-server"></i><span>On-premise</span>
</div>
<div class="cloud-card" id="cloud-do" onclick="selectCloud('do')">
<i class="ti ti-brand-digitalocean"></i><span>DigitalOcean</span>
</div>
<div class="cloud-card" id="cloud-other" onclick="selectCloud('other')">
<i class="ti ti-dots"></i><span>Other</span>
</div>
</div>
<div class="fg"><label class="fl">Region <span style="color:var(--dim)">(optional)</span></label>
<input class="fi" id="env-region" placeholder="ap-south-1">
</div>
<div class="btn-row">
<button class="btn-secondary" onclick="goStep(2)"><i class="ti ti-arrow-left"></i> Back</button>
<button class="btn-primary" onclick="nextStep(3)" style="flex:1">
<span>Continue</span><i class="ti ti-arrow-right"></i>
</button>
</div>
</div>
<!-- Step 4: First connector -->
<div class="step-panel" id="step-4">
<div class="step-title">Connect your first server</div>
<div class="step-sub">Configure SSH access so ConnectWatch can probe your filesystem. You can add more connectors later.</div>
<!-- Prominent setup guide banner -->
<div style="background:#1a2a3f;border:1px solid #1e3a5f;border-radius:8px;padding:14px 16px;margin-bottom:20px;display:flex;align-items:center;justify-content:space-between;gap:12px">
<div>
<div style="font-size:13px;font-weight:600;color:#e2e8f0;margin-bottom:4px"><i class="ti ti-terminal-2" style="color:#4a9eff"></i> First time? Read the setup guide</div>
<div style="font-size:12px;color:#8892a4;line-height:1.6">How to create a <code style="background:#0f1117;padding:2px 5px;border-radius:3px;color:#4a9eff">probeuser</code> on your server, generate an SSH key, and set up the probe directory. Takes 5 minutes.</div>
</div>
<a href="fs-probe-setup.html" target="_blank" style="display:inline-flex;align-items:center;gap:5px;padding:8px 14px;font-size:12px;font-weight:500;border-radius:6px;border:1px solid #4a9eff;color:#4a9eff;text-decoration:none;white-space:nowrap;flex-shrink:0">
<i class="ti ti-external-link" style="font-size:13px"></i>Open guide
</a>
</div>
<div class="err-banner" id="err-4"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px">
<div class="fg" style="margin:0"><label class="fl">SSH host / IP</label>
<input class="fi" id="ssh-host" placeholder="192.168.1.10">
</div>
<div class="fg" style="margin:0"><label class="fl">SSH port</label>
<input class="fi" type="number" id="ssh-port" value="22">
</div>
</div>
<div class="fg"><label class="fl">SSH username</label>
<input class="fi" id="ssh-user" placeholder="probeuser">
</div>
<div class="fg"><label class="fl">Filesystem path to probe</label>
<input class="fi" id="fs-path" placeholder="/mnt/efs/connectwatch-probe">
</div>
<label class="fl" style="margin-bottom:8px">Authentication</label>
<div class="auth-toggle">
<button class="auth-btn active" id="auth-key-btn" onclick="setAuth('key')"><i class="ti ti-key"></i> Private key</button>
<button class="auth-btn" id="auth-pass-btn" onclick="setAuth('password')"><i class="ti ti-lock"></i> Password</button>
</div>
<div id="auth-key-wrap">
<div class="fg"><label class="fl">SSH private key</label>
<textarea class="fi fi-mono" id="ssh-key" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----"></textarea>
</div>
<div style="font-size:11px;color:var(--sub);margin-top:-10px;margin-bottom:14px;font-family:var(--font-mono)">
<a href="fs-probe-setup.html" target="_blank" style="color:var(--blue);text-decoration:none"><i class="ti ti-external-link"></i> Setup guide</a> — how to create a probe user and generate a key
</div>
</div>
<div id="auth-pass-wrap" style="display:none">
<div class="fg"><label class="fl">SSH password</label>
<input class="fi" type="password" id="ssh-password" placeholder="••••••••">
</div>
</div>
<div class="result-box" id="test-result"></div>
<div class="btn-row">
<button class="btn-secondary" onclick="goStep(3)"><i class="ti ti-arrow-left"></i> Back</button>
<button class="btn-secondary" onclick="testConnection()" id="test-btn">
<i class="ti ti-plug"></i> Test connection
</button>
<button class="btn-primary" onclick="nextStep(4)" style="flex:1" id="finish-btn">
<span>Finish setup</span><i class="ti ti-check"></i>
</button>
</div>
<div style="text-align:center;margin-top:16px">
<span class="skip-link" onclick="skipConnector()">Skip for now — set up connectors later</span>
</div>
</div>
<!-- Step 5: Success -->
<div class="step-panel" id="step-5">
<div class="success-wrap">
<div class="success-icon"><i class="ti ti-check"></i></div>
<div class="success-title">You're all set</div>
<div class="success-sub">Your first environment is live and being monitored.<br>Here's what we found in the first probe:</div>
<div class="probe-result" id="probe-results">
<div class="probe-row"><span>SSH connection</span><span>—</span></div>
<div class="probe-row"><span>Filesystem write</span><span>—</span></div>
<div class="probe-row"><span>Environment</span><span>—</span></div>
</div>
<button class="btn-primary" onclick="goToDashboard()">
<i class="ti ti-layout-dashboard"></i> Go to dashboard
</button>
<div style="margin-top:16px;font-size:13px;color:var(--sub)">
Next: <a href="env-config.html" style="color:var(--blue);text-decoration:none">add more connectors</a> or <a href="subscription.html" style="color:var(--blue);text-decoration:none">invite your team</a>
</div>
</div>
</div>
</div>
</div>
<script>
const API = window.CW_API + '/api';
if (localStorage.getItem('cw_token')) window.location.href = 'portal.html';
let currentStep = 1;
let selectedPlan = 'basic';
let selectedCloud = 'aws';
let authToken = null;
let createdEnvId = null;
let testPassed = false;
// ── Step navigation ────────────────────────────────────────────────────────────
function goStep(n) {
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.getElementById(`step-${n}`).classList.add('active');
// Update step indicators
for (let i = 1; i <= 4; i++) {
const ind = document.getElementById(`s-ind-${i}`);
const num = document.getElementById(`s-num-${i}`);
ind.classList.remove('active','done');
if (i < n) { ind.classList.add('done'); num.innerHTML = '<i class="ti ti-check" style="font-size:11px"></i>'; }
else if (i === n) { ind.classList.add('active'); num.textContent = i; }
else { num.textContent = i; }
if (i < 4) {
const line = document.getElementById(`s-line-${i}`);
line.classList.toggle('done', i < n);
}
}
currentStep = n;
hideErr(n);
}
async function nextStep(from) {
if (from === 1) {
if (!validateAccount()) return;
const ok = await registerAccount();
if (!ok) return;
goStep(2);
} else if (from === 2) {
goStep(3);
} else if (from === 3) {
if (!validateEnv()) return;
const ok = await createEnvironment();
if (!ok) return;
goStep(4);
} else if (from === 4) {
await finishSetup();
}
}
// ── Validation ─────────────────────────────────────────────────────────────────
function validateAccount() {
const company = v('company'), email = v('email'), pass = v('password'), pass2 = v('password2');
if (!company) return showErr(1, 'Please enter your company name');
if (!email || !email.includes('@')) return showErr(1, 'Please enter a valid email address');
if (pass.length < 8) return showErr(1, 'Password must be at least 8 characters');
if (pass !== pass2) return showErr(1, 'Passwords do not match');
return true;
}
function validateEnv() {
if (!v('env-name')) return showErr(3, 'Please enter an environment name');
return true;
}
// ── API calls ──────────────────────────────────────────────────────────────────
async function registerAccount() {
setLoading(1, true);
try {
const res = await fetch(`${API}/auth/register`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ company: v('company'), email: v('email'), password: v('password'), plan: selectedPlan }),
});
const data = await res.json();
if (!res.ok) { showErr(1, data.error || 'Registration failed'); return false; }
// Email verification required — show success message, don't auto-login
if (data.email_verification_required) {
document.querySelector('.form-wrap').innerHTML = `
<div style="text-align:center;padding:40px 0">
<div style="width:64px;height:64px;border-radius:50%;background:#0d2d22;border:2px solid #34d399;display:flex;align-items:center;justify-content:center;font-size:28px;color:#34d399;margin:0 auto 20px">
<i class="ti ti-mail-check"></i>
</div>
<div style="font-size:22px;font-weight:700;margin-bottom:10px">Check your email</div>
<div style="font-size:14px;color:#8892a4;line-height:1.7;margin-bottom:28px;max-width:360px;margin-left:auto;margin-right:auto">
We sent a verification link to <strong style="color:#e2e8f0">${v('email')}</strong>.<br>
Click the link to activate your account.
</div>
<a href="index.html" style="display:inline-flex;align-items:center;gap:6px;padding:10px 22px;font-size:14px;font-weight:500;border-radius:8px;text-decoration:none;background:#4a9eff;color:#fff">
<i class="ti ti-login"></i>Go to login
</a>
<div style="font-size:12px;color:#4a5568;margin-top:16px">Didn't receive it? Check spam or <a href="register.html" style="color:#4a9eff">try again</a>.</div>
</div>`;
return false; // Don't proceed to next step
}
authToken = data.token;
localStorage.setItem('cw_token', data.token);
localStorage.setItem('cw_tenant', JSON.stringify(data.tenant));
return true;
} catch (e) { showErr(1, 'Network error — please try again'); return false; }
finally { setLoading(1, false); }
}
async function createEnvironment() {
setLoading(3, true);
try {
const res = await fetch(`${API}/environments`, {
method: 'POST', headers: hdrs(),
body: JSON.stringify({ name: v('env-name'), cloud: selectedCloud, region: v('env-region') }),
});
const data = await res.json();
if (!res.ok) { showErr(3, data.error || 'Failed to create environment'); return false; }
createdEnvId = data.id;
return true;
} catch (e) { showErr(3, 'Network error — please try again'); return false; }
finally { setLoading(3, false); }
}
async function testConnection() {
const btn = document.getElementById('test-btn');
const box = document.getElementById('test-result');
btn.disabled = true;
box.className = 'result-box loading';
box.textContent = '⏳ Testing SSH connection and filesystem write…';
try {
const res = await fetch(`${API}/environments/test-ssh`, {
method: 'POST', headers: hdrs(),
body: JSON.stringify({
environment_id: createdEnvId,
ssh_host: v('ssh-host'), ssh_port: parseInt(v('ssh-port')) || 22,
ssh_user: v('ssh-user'), fs_path: v('fs-path'),
ssh_key: v('ssh-key') || null,
ssh_password: document.getElementById('ssh-password')?.value || null,
}),
});
const d = await res.json();
if (d.success) {
let msg = `✅ SSH connected in ${d.ssh_connect_ms}ms`;
if (d.fs_write_ms != null) msg += `\n✅ Filesystem write in ${d.fs_write_ms}ms — path is writable`;
box.className = 'result-box ok'; box.textContent = msg;
testPassed = true;
} else {
box.className = 'result-box err'; box.textContent = `❌ ${d.error}`;
testPassed = false;
}
} catch (e) { box.className = 'result-box err'; box.textContent = `❌ Network error: ${e.message}`; }
finally { btn.disabled = false; }
}
async function finishSetup() {
if (!v('ssh-host') && !testPassed) {
// No SSH details entered — skip connector setup
goStep(5);
document.getElementById('probe-results').innerHTML =
`<div class="probe-row"><span>Environment created</span><span style="color:var(--ok)">✓</span></div>
<div class="probe-row"><span>Connectors</span><span style="color:var(--warn)">Configure in dashboard</span></div>`;
return;
}
setLoading(4, true);
try {
// Save SSH + FS connector config
if (createdEnvId && v('ssh-host')) {
await fetch(`${API}/connectors/environment/${createdEnvId}/ssh`, {
method: 'PUT', headers: hdrs(),
body: JSON.stringify({ enabled: true, config: {
host: v('ssh-host'), port: parseInt(v('ssh-port')) || 22,
user: v('ssh-user'), key: v('ssh-key') || null,
password: document.getElementById('ssh-password')?.value || null,
}}),
});
await fetch(`${API}/connectors/environment/${createdEnvId}/fs`, {
method: 'PUT', headers: hdrs(),
body: JSON.stringify({ enabled: true, config: {
host: v('ssh-host'), port: parseInt(v('ssh-port')) || 22,
user: v('ssh-user'), path: v('fs-path'),
key: v('ssh-key') || null,
password: document.getElementById('ssh-password')?.value || null,
}}),
});
}
// Show success with probe results
document.getElementById('probe-results').innerHTML =
`<div class="probe-row"><span>SSH connection</span><span>${testPassed ? '✓ ' + document.getElementById('test-result').textContent.match(/\d+ms/) : 'Not tested'}</span></div>
<div class="probe-row"><span>Filesystem write</span><span>${testPassed && v('fs-path') ? '✓ Writable' : 'Not tested'}</span></div>
<div class="probe-row"><span>Environment</span><span>${v('env-name')} · ${selectedCloud.toUpperCase()}</span></div>
<div class="probe-row"><span>Plan</span><span>${selectedPlan.charAt(0).toUpperCase()+selectedPlan.slice(1)}</span></div>`;
goStep(5);
// Hide step indicators on success
document.querySelector('.steps').style.display = 'none';
} catch (e) { showErr(4, 'Failed to save configuration. You can set it up from the dashboard.'); }
finally { setLoading(4, false); }
}
function skipConnector() {
goStep(5);
document.querySelector('.steps').style.display = 'none';
document.getElementById('probe-results').innerHTML =
`<div class="probe-row"><span>Environment created</span><span style="color:var(--ok)">✓ ${v('env-name')}</span></div>
<div class="probe-row"><span>Plan</span><span>${selectedPlan.charAt(0).toUpperCase()+selectedPlan.slice(1)}</span></div>
<div class="probe-row"><span>Connectors</span><span style="color:var(--warn)">Configure from dashboard</span></div>`;
}
function goToDashboard() { window.location.href = 'portal.html'; }
// ── UI helpers ─────────────────────────────────────────────────────────────────
function selectPlan(p) {
selectedPlan = p;
['basic','pro','enterprise'].forEach(k => document.getElementById(`plan-${k}`).classList.toggle('selected', k === p));
}
function selectCloud(c) {
selectedCloud = c;
['aws','azure','gcp','onprem','do','other'].forEach(k => document.getElementById(`cloud-${k}`).classList.toggle('selected', k === c));
}
function setAuth(type) {
document.getElementById('auth-key-wrap').style.display = type === 'key' ? '' : 'none';
document.getElementById('auth-pass-wrap').style.display = type === 'password' ? '' : 'none';
document.getElementById('auth-key-btn').classList.toggle('active', type === 'key');
document.getElementById('auth-pass-btn').classList.toggle('active', type === 'password');
}
function showErr(step, msg) { const el = document.getElementById(`err-${step}`); el.textContent = msg; el.classList.add('show'); return false; }
function hideErr(step) { const el = document.getElementById(`err-${step}`); if(el){ el.textContent=''; el.classList.remove('show');} }
function setLoading(step, on) { document.querySelector(`#step-${step} .btn-primary`).disabled = on; }
function v(id) { return document.getElementById(id)?.value?.trim() || ''; }
function hdrs() { return { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }; }
</script>
<div style="text-align:center;padding:24px;font-size:12px;color:#4a5568;border-top:1px solid #2a3147;margin-top:40px">
<a href="terms.html" style="color:#4a5568;text-decoration:none;margin:0 12px">Terms of Service</a>
<a href="privacy.html" style="color:#4a5568;text-decoration:none;margin:0 12px">Privacy Policy</a>
<a href="refund.html" style="color:#4a5568;text-decoration:none;margin:0 12px">Refund Policy</a>
<span style="margin:0 12px">© 2026 ConnectWatch · GreenShareHub</span>
</div>
</body>
</html>