1. Email: add a 'Problem with your order? We're here to help' support band to the shared htmlEmail() footer, so EVERY transactional email (confirmation, portal link, receipts) has a prominent 'Get help with your order' button linking to /contact. Less silent frustration -> fewer chargebacks. 2. NPI order form: entering a 10-digit NPI now auto-fills provider name, practice state, and specialty from the live NPPES lookup (same API as the free compliance-check tool), with a 'Found: <name>' confirmation. Only fills empty fields so it never clobbers edits. 3. NPI order form: read ?npi= from the URL so the email 'Start my revalidation' click lands with the NPI prefilled and the rest auto-filled (was being ignored entirely before). 4. Support FAB: add the floating help button + panel to 27 static public pages that were missing it (order, portal, trucking, survey, upload pages), so help is one click away everywhere.
532 lines
45 KiB
HTML
532 lines
45 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="State PUC/PSC registration for VoIP, broadband, and CLEC providers. $399/state plus state filing fees. Bond procurement coordinated.">
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<title>State PUC/PSC Registration — Performance West Inc.</title>
|
|
<script>
|
|
window.__PW_API = (function() {
|
|
var h = window.location.hostname;
|
|
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
|
|
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
|
|
return "https://api.performancewest.net";
|
|
})();
|
|
</script>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f9fafb;line-height:1.6}
|
|
a{color:#1e3a5f;text-decoration:none}
|
|
.wrap{max-width:780px;margin:0 auto;padding:2rem 1.25rem 4rem}
|
|
h1{font-size:1.65rem;font-weight:700;color:#111827;margin-bottom:.25rem}
|
|
.subtitle{font-size:.9rem;color:#6b7280;margin-bottom:1.5rem}
|
|
.card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:1.25rem;margin-bottom:1rem}
|
|
.card h2{font-size:1.1rem;font-weight:700;color:#1e3a5f;margin-bottom:.75rem}
|
|
.label{display:block;font-size:.82rem;font-weight:600;color:#374151;margin-bottom:.3rem}
|
|
select,input[type=text]{width:100%;padding:.5rem .7rem;border:1px solid #d1d5db;border-radius:8px;font-size:.88rem;background:#fff}
|
|
select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px rgba(30,58,95,.15)}
|
|
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;padding:.65rem 1.5rem;border-radius:8px;font-weight:600;font-size:.92rem;cursor:pointer;border:none;transition:all .15s}
|
|
.btn-primary{background:#1e3a5f;color:#fff}.btn-primary:hover{background:#162e4d}
|
|
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
|
|
.state-grid{display:grid;grid-template-columns:1fr;gap:.5rem;max-height:500px;overflow-y:auto;padding:.5rem 0}
|
|
@media(min-width:640px){.state-grid{grid-template-columns:1fr 1fr}}
|
|
.state-row{display:flex;align-items:center;gap:.6rem;padding:.55rem .7rem;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;transition:all .15s}
|
|
.state-row:hover{border-color:#1e3a5f;background:#f0f4f8}
|
|
.state-row.selected{border-color:#1e3a5f;background:#eff6ff}
|
|
.state-row.exempt{opacity:.55;cursor:default;background:#f9fafb}
|
|
.state-row input[type=checkbox]{accent-color:#1e3a5f;width:16px;height:16px;cursor:pointer}
|
|
.state-name{font-weight:600;font-size:.88rem;flex:1}
|
|
.state-meta{font-size:.75rem;color:#6b7280;text-align:right}
|
|
.state-meta .fee{color:#059669;font-weight:600}
|
|
.state-meta .bond{color:#d97706;font-size:.7rem}
|
|
.state-meta .exempt-tag{color:#9ca3af;font-style:italic}
|
|
.summary-box{position:sticky;bottom:0;background:#fff;border-top:2px solid #1e3a5f;padding:1rem 1.25rem;margin:-1.25rem;margin-top:1rem;border-radius:0 0 12px 12px}
|
|
.summary-row{display:flex;justify-content:space-between;font-size:.88rem;padding:.15rem 0}
|
|
.summary-row.total{font-weight:700;font-size:1.05rem;border-top:1px solid #e5e7eb;padding-top:.5rem;margin-top:.35rem}
|
|
.filter-bar{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:.75rem}
|
|
.filter-btn{padding:.3rem .7rem;border:1px solid #d1d5db;border-radius:6px;font-size:.78rem;background:#fff;cursor:pointer;font-weight:500}
|
|
.filter-btn.active{background:#1e3a5f;color:#fff;border-color:#1e3a5f}
|
|
.hidden{display:none}
|
|
.info-banner{padding:.6rem .9rem;background:#ecfdf5;border-left:3px solid #059669;border-radius:0 6px 6px 0;font-size:.85rem;color:#065f46;margin-bottom:1.25rem}
|
|
.bond-banner{padding:.5rem .8rem;background:#fffbeb;border-left:3px solid #d97706;border-radius:0 6px 6px 0;font-size:.82rem;color:#92400e;margin-top:.75rem}
|
|
.step-dots{display:flex;gap:.75rem;margin-bottom:1.5rem}
|
|
.step-dot{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.72rem;font-weight:700;background:#e5e7eb;color:#6b7280}
|
|
.step-dot.active{background:#1e3a5f;color:#fff}
|
|
.step-dot.done{background:#22c55e;color:#fff}
|
|
.entity-form input{margin-bottom:.75rem}
|
|
</style>
|
|
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
|
|
<body>
|
|
<div class="wrap">
|
|
<h1>State PUC/PSC Registration</h1>
|
|
<p class="subtitle">Register your VoIP, broadband, or CLEC service with state Public Utility Commissions. $399/state plus state filing fees.</p>
|
|
|
|
<div class="info-banner">
|
|
<strong>Tax deductible</strong> — State PUC registration fees are deductible as ordinary business expenses under IRC § 162.
|
|
</div>
|
|
|
|
<div class="step-dots">
|
|
<div class="step-dot active" id="dot-1">1</div>
|
|
<div class="step-dot" id="dot-2">2</div>
|
|
<div class="step-dot" id="dot-3">3</div>
|
|
</div>
|
|
|
|
<!-- Step 1: Provider Info -->
|
|
<div id="step-1" class="card">
|
|
<h2>Provider Information</h2>
|
|
<div class="entity-form">
|
|
<label class="label">Legal Entity Name</label>
|
|
<input type="text" id="entity-name" placeholder="Acme Telecom LLC">
|
|
|
|
<label class="label">FRN (FCC Registration Number)</label>
|
|
<input type="text" id="frn" placeholder="0012345678">
|
|
|
|
<label class="label">Registration Type</label>
|
|
<select id="reg-type">
|
|
<option value="voip">VoIP / IPES Provider</option>
|
|
<option value="broadband">Broadband / ISP Provider</option>
|
|
<option value="clec">CLEC (Competitive Local Exchange Carrier)</option>
|
|
<option value="bundle">Bundle (VoIP + CLEC)</option>
|
|
</select>
|
|
|
|
<label class="label" style="margin-top:.5rem">Provider Classification</label>
|
|
<select id="provider-type">
|
|
<option value="">-- Select --</option>
|
|
<option value="facilities_based">Facilities-Based (own network infrastructure)</option>
|
|
<option value="reseller">Reseller (resell another carrier's service)</option>
|
|
<option value="over_the_top">Over-the-Top VoIP (internet-based, no PSTN interconnection)</option>
|
|
<option value="hybrid">Hybrid (own some infrastructure, resell some)</option>
|
|
</select>
|
|
<p style="font-size:.75rem;color:#6b7280;margin-top:.25rem">
|
|
Provider type affects bond requirements and registration category in many states. Resellers and OTT VoIP providers often have lower or no bond requirements.
|
|
</p>
|
|
</div>
|
|
<div style="margin-top:1rem;text-align:right">
|
|
<button class="btn btn-primary" id="btn-next-1">Select States →</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: State Selection -->
|
|
<div id="step-2" class="card hidden">
|
|
<h2>Select States</h2>
|
|
<p style="font-size:.85rem;color:#6b7280;margin-bottom:.75rem">
|
|
Check the states where you need PUC registration. Exempt states (no registration required) are grayed out.
|
|
</p>
|
|
|
|
<div class="filter-bar">
|
|
<button class="filter-btn active" data-filter="all">All States</button>
|
|
<button class="filter-btn" data-filter="required">Registration Required</button>
|
|
<button class="filter-btn" data-filter="bond">Bond Required</button>
|
|
<button class="filter-btn" data-filter="exempt">Exempt / No Reg</button>
|
|
</div>
|
|
|
|
<div style="margin-bottom:.5rem">
|
|
<label style="font-size:.82rem;cursor:pointer">
|
|
<input type="checkbox" id="select-all-required" style="accent-color:#1e3a5f"> Select all states requiring registration
|
|
</label>
|
|
</div>
|
|
|
|
<div class="state-grid" id="state-grid">
|
|
<div style="text-align:center;padding:2rem;color:#9ca3af;grid-column:1/-1">Loading state data...</div>
|
|
</div>
|
|
|
|
<div id="bond-banner" class="bond-banner hidden">
|
|
<strong>Bond required</strong> in <span id="bond-count">0</span> selected state(s). We coordinate surety bond procurement with our bonding partners. Bond amounts shown are typical ranges — exact amounts depend on your provider size and type.
|
|
</div>
|
|
|
|
<div class="summary-box" id="summary-box" style="display:none">
|
|
<div class="summary-row"><span>Selected states</span><span id="sum-states">0</span></div>
|
|
<div class="summary-row"><span>Service fees ($399/state)</span><span id="sum-service">$0</span></div>
|
|
<div class="summary-row"><span>State filing fees</span><span id="sum-state-fees">$0</span></div>
|
|
<div class="summary-row total"><span>Total</span><span id="sum-total">$0</span></div>
|
|
<div style="font-size:.72rem;color:#9ca3af;margin-top:.35rem">
|
|
Bond amounts not included in total — coordinated separately.
|
|
</div>
|
|
<div style="margin-top:.75rem;display:flex;gap:.5rem;justify-content:flex-end">
|
|
<button class="btn" style="background:#e5e7eb;color:#374151" id="btn-back-2">← Back</button>
|
|
<button class="btn btn-primary" id="btn-next-2" disabled>Review & Pay →</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Review & Pay -->
|
|
<div id="step-3" class="card hidden">
|
|
<h2>Review & Checkout</h2>
|
|
<div id="review-content"></div>
|
|
|
|
<div style="margin-top:1.25rem;border-top:1px solid #e5e7eb;padding-top:1rem">
|
|
<h3 style="font-size:.95rem;font-weight:600;margin-bottom:.75rem">Contact Information</h3>
|
|
<label class="label">Full Name</label>
|
|
<input type="text" id="customer-name" placeholder="John Smith" style="margin-bottom:.5rem">
|
|
<label class="label">Email Address</label>
|
|
<input type="text" id="customer-email" placeholder="john@example.com" style="margin-bottom:.5rem">
|
|
<label class="label">Phone (optional)</label>
|
|
<input type="text" id="customer-phone" placeholder="(555) 123-4567" style="margin-bottom:.5rem">
|
|
<label class="label">Discount Code (optional)</label>
|
|
<input type="text" id="promo-code" placeholder="SAVE20" style="margin-bottom:.75rem">
|
|
</div>
|
|
|
|
<label style="display:flex;align-items:flex-start;gap:.5rem;padding:.65rem;margin-top:.75rem;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;font-size:.75rem;color:#6b7280;line-height:1.5">
|
|
<input type="checkbox" id="engage-check" required style="margin-top:2px;accent-color:#1e3a5f">
|
|
<span>I authorize Performance West Inc. to prepare and submit regulatory filings on my behalf as described above. I understand Performance West provides compliance consulting services, not legal advice or legal representation. I confirm the information I provide is accurate to the best of my knowledge. <a href="/terms" target="_blank" style="color:#1e3a5f;text-decoration:underline">Terms of Service</a></span>
|
|
</label>
|
|
|
|
<div style="margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end">
|
|
<button class="btn" style="background:#e5e7eb;color:#374151" id="btn-back-3">← Back</button>
|
|
<button class="btn btn-primary" id="btn-pay" disabled>Continue to Payment</button>
|
|
</div>
|
|
<p id="checkout-error" style="color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
const API = window.__PW_API;
|
|
let allStates = [];
|
|
let selected = new Set();
|
|
|
|
// Load state data
|
|
fetch(API + '/api/v1/puc/requirements/all')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
allStates = data.states || [];
|
|
renderGrid(allStates);
|
|
})
|
|
.catch(err => {
|
|
document.getElementById('state-grid').innerHTML =
|
|
'<div style="color:#dc2626;padding:1rem;grid-column:1/-1">Failed to load state data. Please refresh.</div>';
|
|
});
|
|
|
|
// Get the relevant fee for a state based on registration type
|
|
function getStateFee(s, regType) {
|
|
if (regType === 'broadband') return s.broadband_fee_cents || 0;
|
|
if (regType === 'clec') return s.clec_fee_cents || 0;
|
|
if (regType === 'bundle') return (s.voip_fee_cents || 0) + (s.clec_fee_cents || 0);
|
|
return s.voip_fee_cents || 0;
|
|
}
|
|
|
|
function getStateBond(s, regType) {
|
|
if (regType === 'clec' || regType === 'bundle') {
|
|
return { required: s.clec_bond_required || s.voip_bond_required, cents: Math.max(s.clec_bond_cents || 0, s.voip_bond_cents || 0) };
|
|
}
|
|
if (regType === 'broadband') return { required: false, cents: 0 };
|
|
return { required: s.voip_bond_required, cents: s.voip_bond_cents || 0 };
|
|
}
|
|
|
|
function renderGrid(states, filter) {
|
|
const grid = document.getElementById('state-grid');
|
|
grid.innerHTML = '';
|
|
|
|
const regType = document.getElementById('reg-type').value;
|
|
|
|
let filtered = states;
|
|
if (filter === 'required') filtered = states.filter(s => isRequired(s, regType));
|
|
else if (filter === 'bond') filtered = states.filter(s => getStateBond(s, regType).required && isRequired(s, regType));
|
|
else if (filter === 'exempt') filtered = states.filter(s => !isRequired(s, regType));
|
|
|
|
filtered.forEach(s => {
|
|
const req = isRequired(s, regType);
|
|
const fee = getStateFee(s, regType);
|
|
const bond = getStateBond(s, regType);
|
|
const row = document.createElement('label');
|
|
row.className = 'state-row' + (req ? '' : ' exempt') + (selected.has(s.state_code) ? ' selected' : '');
|
|
row.innerHTML = `
|
|
<input type="checkbox" value="${s.state_code}" ${req ? '' : 'disabled'} ${selected.has(s.state_code) ? 'checked' : ''}>
|
|
<span class="state-name">${s.state_name} (${s.state_code})</span>
|
|
<span class="state-meta">
|
|
${req ? `<span class="fee">$${(fee / 100).toLocaleString()}</span> fee` : '<span class="exempt-tag">No registration required</span>'}
|
|
${req && bond.required ? '<br><span class="bond">Bond: $' + (bond.cents / 100).toLocaleString() + '</span>' : ''}
|
|
</span>
|
|
`;
|
|
const cb = row.querySelector('input');
|
|
cb.addEventListener('change', () => {
|
|
if (cb.checked) { selected.add(s.state_code); row.classList.add('selected'); }
|
|
else { selected.delete(s.state_code); row.classList.remove('selected'); }
|
|
updateSummary();
|
|
});
|
|
grid.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function isRequired(s, regType) {
|
|
if (regType === 'voip' || regType === 'bundle') return s.voip_required;
|
|
if (regType === 'broadband') return s.broadband_required;
|
|
if (regType === 'clec') return s.clec_required;
|
|
return s.voip_required;
|
|
}
|
|
|
|
function updateSummary() {
|
|
const box = document.getElementById('summary-box');
|
|
const regType = document.getElementById('reg-type').value;
|
|
|
|
if (selected.size === 0) {
|
|
box.style.display = 'none';
|
|
document.getElementById('btn-next-2').disabled = true;
|
|
document.getElementById('bond-banner').classList.add('hidden');
|
|
return;
|
|
}
|
|
box.style.display = 'block';
|
|
document.getElementById('btn-next-2').disabled = false;
|
|
|
|
let serviceFees = 0, stateFees = 0, bondTotal = 0, bondCount = 0;
|
|
selected.forEach(code => {
|
|
const s = allStates.find(st => st.state_code === code);
|
|
if (!s) return;
|
|
serviceFees += 39900;
|
|
stateFees += getStateFee(s, regType);
|
|
const bond = getStateBond(s, regType);
|
|
if (bond.required) { bondTotal += bond.cents; bondCount++; }
|
|
});
|
|
|
|
document.getElementById('sum-states').textContent = selected.size;
|
|
document.getElementById('sum-service').textContent = '$' + (serviceFees / 100).toLocaleString();
|
|
document.getElementById('sum-state-fees').textContent = '$' + (stateFees / 100).toLocaleString();
|
|
document.getElementById('sum-total').textContent = '$' + ((serviceFees + stateFees) / 100).toLocaleString();
|
|
|
|
if (bondCount > 0) {
|
|
document.getElementById('bond-banner').classList.remove('hidden');
|
|
document.getElementById('bond-count').textContent = bondCount;
|
|
} else {
|
|
document.getElementById('bond-banner').classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// Filter buttons
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
renderGrid(allStates, btn.dataset.filter);
|
|
});
|
|
});
|
|
|
|
// Select all required
|
|
document.getElementById('select-all-required').addEventListener('change', function() {
|
|
const regType = document.getElementById('reg-type').value;
|
|
if (this.checked) {
|
|
allStates.filter(s => isRequired(s, regType)).forEach(s => selected.add(s.state_code));
|
|
} else {
|
|
selected.clear();
|
|
}
|
|
renderGrid(allStates, document.querySelector('.filter-btn.active')?.dataset?.filter);
|
|
updateSummary();
|
|
});
|
|
|
|
// Re-render when reg type changes
|
|
document.getElementById('reg-type').addEventListener('change', () => {
|
|
selected.clear();
|
|
document.getElementById('select-all-required').checked = false;
|
|
renderGrid(allStates, document.querySelector('.filter-btn.active')?.dataset?.filter);
|
|
updateSummary();
|
|
});
|
|
|
|
// Step navigation
|
|
function showStep(n) {
|
|
[1,2,3].forEach(i => {
|
|
document.getElementById('step-' + i).classList.toggle('hidden', i !== n);
|
|
const dot = document.getElementById('dot-' + i);
|
|
dot.classList.remove('active','done');
|
|
if (i < n) dot.classList.add('done');
|
|
if (i === n) dot.classList.add('active');
|
|
});
|
|
}
|
|
|
|
document.getElementById('btn-next-1').addEventListener('click', () => {
|
|
const name = document.getElementById('entity-name').value.trim();
|
|
if (!name) { alert('Please enter your legal entity name.'); return; }
|
|
showStep(2);
|
|
// Re-render grid in case reg type changed
|
|
renderGrid(allStates);
|
|
updateSummary();
|
|
});
|
|
|
|
document.getElementById('btn-back-2').addEventListener('click', () => showStep(1));
|
|
|
|
document.getElementById('btn-next-2').addEventListener('click', () => {
|
|
showStep(3);
|
|
buildReview();
|
|
});
|
|
|
|
document.getElementById('btn-back-3').addEventListener('click', () => showStep(2));
|
|
|
|
function buildReview() {
|
|
const name = document.getElementById('entity-name').value.trim();
|
|
const frn = document.getElementById('frn').value.trim();
|
|
const regType = document.getElementById('reg-type').value;
|
|
const provType = document.getElementById('provider-type').value;
|
|
|
|
let html = `
|
|
<div style="margin-bottom:1rem">
|
|
<div style="font-size:.85rem;color:#6b7280">Entity</div>
|
|
<div style="font-weight:600">${esc(name)}</div>
|
|
${frn ? '<div style="font-size:.85rem;color:#6b7280">FRN: ' + esc(frn) + '</div>' : ''}
|
|
<div style="font-size:.85rem;color:#6b7280">Type: ${regType.toUpperCase()}${provType ? ' (' + provType.replace(/_/g,' ') + ')' : ''}</div>
|
|
</div>
|
|
<table style="width:100%;border-collapse:collapse;font-size:.85rem">
|
|
<thead><tr style="border-bottom:2px solid #e5e7eb">
|
|
<th style="text-align:left;padding:.4rem .5rem;font-weight:600">State</th>
|
|
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Filing Fee</th>
|
|
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Service Fee</th>
|
|
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Bond</th>
|
|
</tr></thead><tbody>
|
|
`;
|
|
let serviceTot = 0, stateTot = 0, bondTot = 0, bondStates = 0;
|
|
Array.from(selected).sort().forEach(code => {
|
|
const s = allStates.find(st => st.state_code === code);
|
|
if (!s) return;
|
|
const sf = getStateFee(s, regType);
|
|
const bond = getStateBond(s, regType);
|
|
const bondAmt = bond.required ? bond.cents : 0;
|
|
serviceTot += 39900;
|
|
stateTot += sf;
|
|
bondTot += bondAmt;
|
|
if (bond.required) bondStates++;
|
|
html += `<tr style="border-bottom:1px solid #f3f4f6">
|
|
<td style="padding:.35rem .5rem">${s.state_name} (${code})</td>
|
|
<td style="text-align:right;padding:.35rem .5rem">$${(sf/100).toLocaleString()}</td>
|
|
<td style="text-align:right;padding:.35rem .5rem">$399</td>
|
|
<td style="text-align:right;padding:.35rem .5rem;color:${bondAmt?'#d97706':'#9ca3af'}">${bondAmt ? '$'+(bondAmt/100).toLocaleString() : '---'}</td>
|
|
</tr>`;
|
|
});
|
|
html += `</tbody><tfoot>
|
|
<tr style="border-top:2px solid #e5e7eb;font-weight:700">
|
|
<td style="padding:.5rem">Total (${selected.size} states)</td>
|
|
<td style="text-align:right;padding:.5rem">$${(stateTot/100).toLocaleString()}</td>
|
|
<td style="text-align:right;padding:.5rem">$${(serviceTot/100).toLocaleString()}</td>
|
|
<td style="text-align:right;padding:.5rem;color:#d97706">${bondTot ? '$'+(bondTot/100).toLocaleString() : '---'}</td>
|
|
</tr>
|
|
</tfoot></table>
|
|
<div style="margin-top:.75rem;font-size:.92rem;font-weight:700;text-align:right">
|
|
Grand total: $${((serviceTot+stateTot)/100).toLocaleString()}
|
|
</div>
|
|
${bondTot > 0 ? '<div class="bond-banner" style="margin-top:.75rem"><strong>Bond total: $'+(bondTot/100).toLocaleString()+'</strong> across '+bondStates+' state(s). Bond procurement is coordinated separately after checkout.</div>' : ''}
|
|
`;
|
|
document.getElementById('review-content').innerHTML = html;
|
|
document.getElementById('btn-pay').disabled = false;
|
|
}
|
|
|
|
// Checkout — 2-step: create compliance order, then create Stripe session
|
|
document.getElementById('btn-pay').addEventListener('click', async () => {
|
|
const btn = document.getElementById('btn-pay');
|
|
const errEl = document.getElementById('checkout-error');
|
|
errEl.style.display = 'none';
|
|
|
|
if (!document.getElementById('engage-check').checked) {
|
|
errEl.textContent = 'Please accept the authorization terms to continue.';
|
|
errEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
const customerName = document.getElementById('customer-name').value.trim();
|
|
const customerEmail = document.getElementById('customer-email').value.trim();
|
|
if (!customerName || !customerEmail) {
|
|
errEl.textContent = 'Please enter your name and email address.';
|
|
errEl.style.display = 'block';
|
|
return;
|
|
}
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(customerEmail)) {
|
|
errEl.textContent = 'Please enter a valid email address.';
|
|
errEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Creating order...';
|
|
|
|
// Normalize provider_type: empty string -> null (DB CHECK constraint rejects "")
|
|
const provType = document.getElementById('provider-type').value || null;
|
|
|
|
const orderBody = {
|
|
service_slug: 'state-puc',
|
|
customer_name: customerName,
|
|
customer_email: customerEmail,
|
|
customer_phone: document.getElementById('customer-phone').value.trim() || undefined,
|
|
discount_code: document.getElementById('promo-code').value.trim() || undefined,
|
|
intake_data: {
|
|
entity_legal_name: document.getElementById('entity-name').value.trim(),
|
|
frn: document.getElementById('frn').value.trim(),
|
|
registration_type: document.getElementById('reg-type').value,
|
|
provider_type: provType,
|
|
target_states: Array.from(selected),
|
|
},
|
|
};
|
|
|
|
try {
|
|
// Step 1: Create compliance order
|
|
const orderResp = await fetch(API + '/api/v1/compliance-orders', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(orderBody),
|
|
});
|
|
const orderData = await orderResp.json();
|
|
if (!orderResp.ok) throw new Error(orderData.error || 'Order creation failed');
|
|
|
|
btn.textContent = 'Redirecting to payment...';
|
|
|
|
// Step 2: Create Stripe checkout session
|
|
const sessionResp = await fetch(API + '/api/v1/checkout/create-session', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
order_id: orderData.id,
|
|
order_type: 'compliance',
|
|
payment_method: 'card',
|
|
}),
|
|
});
|
|
const sessionData = await sessionResp.json();
|
|
if (!sessionResp.ok) throw new Error(sessionData.error || 'Checkout failed');
|
|
|
|
if (sessionData.checkout_url) {
|
|
window.location.href = sessionData.checkout_url;
|
|
} else {
|
|
throw new Error('No checkout URL returned');
|
|
}
|
|
} catch (err) {
|
|
errEl.textContent = err.message || 'Something went wrong. Please try again.';
|
|
errEl.style.display = 'block';
|
|
btn.disabled = false;
|
|
btn.textContent = 'Continue to Payment';
|
|
}
|
|
});
|
|
|
|
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
})();
|
|
</script>
|
|
<!-- Tawk.to Live Chat --><script>var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();(function(){var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];s1.async=true;s1.src="https://embed.tawk.to/69d5a9ca0d1c3f1c37998081/1jll9ufph";s1.charset="UTF-8";s1.setAttribute("crossorigin","*");s0.parentNode.insertBefore(s1,s0);})();</script>
|
|
<script defer src="/js/pw-cookie-consent.js"></script><!-- Floating help button --><button type="button" id="support-fab" aria-label="Open support" class="fixed bottom-6 left-6 z-[9999] w-14 h-14 rounded-full bg-pw-700 text-white shadow-lg hover:bg-pw-800 transition-all hover:scale-105 flex items-center justify-center"> <svg id="support-fab-icon-open" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> <svg id="support-fab-icon-close" class="w-6 h-6 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Slide-out panel --> <div id="support-panel" class="fixed bottom-24 left-6 z-[9998] w-[380px] max-w-[calc(100vw-2rem)] bg-white rounded-xl shadow-2xl border border-gray-200 transform translate-y-4 opacity-0 pointer-events-none transition-all duration-200 ease-out"> <div class="px-5 py-4 border-b border-gray-100 bg-gray-50 rounded-t-xl"> <h3 class="text-base font-semibold text-gray-900">How can we help?</h3> <p class="text-xs text-gray-500 mt-0.5">Choose a category and tell us what you need.</p> </div> <!-- Category selector (step 1) --> <div id="support-step-categories" class="p-4 space-y-2"> <button type="button" data-category="question" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Ask a Question</span> <span class="block text-xs text-gray-500">About our compliance services or process</span> </div> </div> </button> <button type="button" data-category="support" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 text-green-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Get Support</span> <span class="block text-xs text-gray-500">Help with an ongoing engagement</span> </div> </div> </button> <button type="button" data-category="issue" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Report an Issue</span> <span class="block text-xs text-gray-500">Something isn't right with a deliverable</span> </div> </div> </button> <button type="button" data-category="service_request" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Service</span> <span class="block text-xs text-gray-500">Start a new compliance engagement</span> </div> </div> </button> <button type="button" data-category="quote" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-amber-100 text-amber-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Quote</span> <span class="block text-xs text-gray-500">Get pricing for a complex engagement</span> </div> </div> </button> <!-- Sign-in link (hidden when logged in) --> <div id="support-signin-link" class="pt-2 border-t border-gray-100 mt-2"> <button type="button" id="support-signin-btn" class="w-full flex items-center justify-center gap-2 px-4 py-2 text-xs text-gray-500 hover:text-pw-700 transition-colors"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
|
|
Already a client? <span class="font-medium text-pw-600">Sign in</span> </button> </div> </div> <!-- Ticket form — Report an Issue + guest fallback (step 2a) --> <form id="support-step-form" class="hidden p-4"> <button type="button" id="support-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
|
|
Back
|
|
</button> <div id="support-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="support-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-gray-400">(optional)</span></label> <input type="text" id="support-name" name="name" placeholder="Your name" maxlength="100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-gray-400">(for follow-up)</span></label> <input type="email" id="support-email" name="email" placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-subject" class="block text-xs font-medium text-gray-700 mb-1">Subject <span class="text-red-400">*</span></label> <input type="text" id="support-subject" name="subject" required minlength="3" maxlength="200" placeholder="Brief summary" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-message" class="block text-xs font-medium text-gray-700 mb-1">Message <span class="text-red-400">*</span></label> <textarea id="support-message" name="message" required minlength="10" maxlength="5000" rows="4" placeholder="Describe your question, issue, or request..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> <p class="text-[10px] text-gray-400 mt-0.5 text-right"><span id="support-char-count">0</span> / 5000</p> </div> <button type="submit" id="support-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
|
Submit
|
|
</button> </div> </form> <!-- Opportunity form — Request a Service / Request a Quote (step 2b) --> <form id="support-step-opportunity" class="hidden p-4"> <button type="button" id="opportunity-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
|
|
Back
|
|
</button> <div id="opportunity-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="opp-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-red-400">*</span></label> <input type="text" id="opp-name" name="name" required minlength="2" maxlength="100" placeholder="Your name" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-red-400">*</span></label> <input type="email" id="opp-email" name="email" required placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-company" class="block text-xs font-medium text-gray-700 mb-1">Company <span class="text-gray-400">(optional)</span></label> <input type="text" id="opp-company" name="company" placeholder="Company name" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-phone" class="block text-xs font-medium text-gray-700 mb-1">Phone <span class="text-gray-400">(optional)</span></label> <input type="tel" id="opp-phone" name="phone" placeholder="(555) 555-1234" maxlength="30" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-service" class="block text-xs font-medium text-gray-700 mb-1">Service interest <span class="text-red-400">*</span></label> <select id="opp-service" name="service_slug" required class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow bg-white"> <option value="" disabled selected>Select a service...</option> <option value="canada-crtc">Canada CRTC Carrier Package</option> <option value="llc-formation">LLC Formation</option> <option value="corporation-formation">Corporation Formation</option> <option value="registered-agent">Registered Agent</option> <option value="compliance-audit">Compliance Audit</option> <option value="other">Other / Not sure</option> </select> </div> <div> <label for="opp-details" class="block text-xs font-medium text-gray-700 mb-1">Details <span class="text-gray-400">(optional)</span></label> <textarea id="opp-details" name="details" maxlength="5000" rows="3" placeholder="Tell us about your project or what you need..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> </div> <button type="submit" id="opp-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
|
Submit Request
|
|
</button> </div> </form> <!-- Sign-in prompt — Ask a Question / Get Support when not logged in (step 2c) --> <div id="support-step-signin" class="hidden p-6"> <button type="button" id="signin-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
|
|
Back
|
|
</button> <div class="text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-blue-100 flex items-center justify-center"> <svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Sign in for support</h4> <p class="text-xs text-gray-500 mb-5">Sign in to access your client portal where you can view your orders, submit support requests, and manage your services.</p> <button type="button" id="signin-auth-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors mb-3">
|
|
Sign In
|
|
</button> <button type="button" id="signin-guest-btn" class="text-xs text-gray-500 hover:text-gray-700 transition-colors">
|
|
Continue as guest →
|
|
</button> </div> </div> <!-- Success state (step 3) --> <div id="support-step-success" class="hidden p-6 text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-green-100 flex items-center justify-center"> <svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Request received</h4> <p id="support-success-message" class="text-xs text-gray-500 mb-4">We'll get back to you within one business day.</p> <p id="support-ticket-id" class="text-xs text-gray-400 mb-4 hidden">Ref: <span></span></p> <button type="button" id="support-new-ticket-btn" class="text-xs text-pw-600 hover:text-pw-700 font-medium">Submit another request</button> </div> </div> <!-- Modal backdrop --><div id="auth-modal-backdrop" class="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm hidden items-center justify-center p-4" role="dialog" aria-modal="true" aria-labelledby="auth-modal-title"> <div id="auth-modal" class="relative w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden"> <!-- Close --> <button id="auth-modal-close" type="button" aria-label="Close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"> <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Tab bar --> <div id="auth-tabs" class="flex border-b border-gray-100"> <button type="button" id="auth-tab-login" class="auth-tab flex-1 py-4 text-sm font-semibold text-pw-700 border-b-2 border-pw-600 transition-colors">
|
|
Sign In
|
|
</button> <button type="button" id="auth-tab-register" class="auth-tab flex-1 py-4 text-sm font-semibold text-gray-400 border-b-2 border-transparent hover:text-gray-600 transition-colors">
|
|
Create Account
|
|
</button> </div> <div class="px-6 py-6"> <!-- ── Login form ──────────────────────────────────────────────────── --> <form id="auth-login-form" class="space-y-4" novalidate> <div> <label for="auth-login-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-login-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <div class="flex items-center justify-between mb-1"> <label for="auth-login-password" class="text-sm font-medium text-gray-700">Password</label> <button type="button" id="auth-forgot-link" class="text-xs text-pw-600 hover:text-pw-800 underline underline-offset-2">
|
|
Forgot password?
|
|
</button> </div> <input type="password" id="auth-login-password" autocomplete="current-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="••••••••"> </div> <p id="auth-login-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-login-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
|
|
Sign In
|
|
</button> </form> <!-- ── Register form ──────────────────────────────────────────────── --> <form id="auth-register-form" class="space-y-4 hidden" novalidate> <div> <label for="auth-reg-name" class="block text-sm font-medium text-gray-700 mb-1">Your Name</label> <input type="text" id="auth-reg-name" autocomplete="name" class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="Full name"> </div> <div> <label for="auth-reg-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-reg-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <label for="auth-reg-password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> <input type="password" id="auth-reg-password" autocomplete="new-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="At least 8 characters"> </div> <p id="auth-reg-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-reg-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
|
|
Create Account
|
|
</button> </form> <!-- ── Forgot password form ────────────────────────────────────────── --> <div id="auth-forgot-form" class="hidden"> <button type="button" id="auth-back-to-login" class="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"></path></svg>
|
|
Back to sign in
|
|
</button> <h3 class="text-base font-semibold text-gray-900 mb-1">Reset your password</h3> <p class="text-sm text-gray-500 mb-4">Enter your email and we'll send you a reset link.</p> <form id="auth-forgot-email-form" class="space-y-4" novalidate> <div> <label for="auth-forgot-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-forgot-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <p id="auth-forgot-err" class="hidden text-xs text-red-600 font-medium"></p> <p id="auth-forgot-ok" class="hidden text-xs text-green-700 font-medium"></p> <button type="submit" id="auth-forgot-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
|
|
Send reset link
|
|
</button> </form> </div> </div> </div> </div>
|
|
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
|
</body>
|
|
</html>
|