new-site/site/src/components/intake/steps/JurisdictionStep.astro
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

268 lines
10 KiB
Text

---
// JurisdictionStep — Line 227 multi-select of states/territories served.
// First-service date (Line 228) moved to its own HistoryStep.
//
// After state selection, runs a foreign qualification check against
// entity_cache. Shows states where no foreign corp registration was
// found with opt-in checkboxes to add foreign qual filing to the order.
---
<div class="pw-step">
<h2>Jurisdictions served (Line 227)</h2>
<p class="pw-help">
Check every state and US territory where you have provided
telecommunications service in the past 15 months — plus any where
you expect to provide service in the next 12 months. For switched
services, include origination states; for called-party-pays
services, also include termination states.
</p>
<div id="pw-states" class="pw-states-grid"></div>
<!-- Foreign Qualification Check Results -->
<div id="pw-fq-section" class="fq-section" hidden>
<div id="pw-fq-loading" class="fq-loading" hidden>
<span class="fq-spinner"></span> Checking corporate registrations in selected states...
</div>
<div id="pw-fq-results" hidden>
<div class="fq-header">
<h3>Corporate Registration Check</h3>
<p class="fq-desc">
We checked each state you serve for a foreign corporation registration.
States marked below may require a Certificate of Authority filing.
</p>
</div>
<div id="pw-fq-missing" class="fq-missing-list"></div>
<div id="pw-fq-ok" class="fq-ok-summary"></div>
</div>
</div>
<div id="pw-jx-err" class="pw-err" hidden></div>
</div>
<style>
.pw-step h2 { margin: 0 0 0.5rem; color: #1a2744; }
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
.pw-states-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 0.35rem; padding: 0.75rem; background: #f8fafc;
border: 1px solid #e2e8f0; border-radius: 6px;
}
.pw-states-grid label { font-size: 0.85rem; color: #334155; }
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; }
/* Foreign qualification check styles */
.fq-section { margin-top: 1.25rem; }
.fq-loading {
padding: 0.75rem; background: #f8fafc; border: 1px solid #e2e8f0;
border-radius: 8px; font-size: 0.88rem; color: #6b7280;
display: flex; align-items: center; gap: 0.5rem;
}
.fq-spinner {
width: 16px; height: 16px; border: 2px solid #d1d5db;
border-top-color: #1e3a5f; border-radius: 50%;
animation: spin 0.6s linear infinite; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }
.fq-header h3 { font-size: 0.95rem; color: #1a2744; margin-bottom: 0.25rem; }
.fq-desc { font-size: 0.82rem; color: #6b7280; margin-bottom: 0.75rem; }
.fq-missing-list { display: flex; flex-direction: column; gap: 0.4rem; }
.fq-row {
display: flex; align-items: center; gap: 0.6rem;
padding: 0.55rem 0.75rem; border: 1px solid #fbbf24;
background: #fffbeb; border-radius: 8px; font-size: 0.85rem;
}
.fq-row input { accent-color: #1e3a5f; width: 16px; height: 16px; flex-shrink: 0; }
.fq-row .fq-state { font-weight: 700; color: #92400e; min-width: 28px; }
.fq-row .fq-reason { flex: 1; color: #78350f; font-size: 0.82rem; }
.fq-row .fq-price { font-weight: 600; color: #1e3a5f; white-space: nowrap; }
.fq-ok-summary {
margin-top: 0.5rem; padding: 0.5rem 0.75rem;
background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px;
font-size: 0.82rem; color: #166534;
}
.fq-total {
margin-top: 0.6rem; padding: 0.5rem 0.75rem;
background: #f0f4f8; border-radius: 8px;
font-size: 0.88rem; font-weight: 600; color: #1e3a5f;
display: flex; justify-content: space-between;
}
</style>
<script>
const STATES = [
"AL","AK","AS","AZ","AR","CA","CO","CT","DE","DC","FL","GA","GU","HI",
"ID","IL","IN","IA","KS","KY","LA","ME","MD","MA","MI","MN","MS","MO",
"MT","NE","NV","NH","NJ","NM","NY","NC","ND","MP","OH","OK","OR","PA",
"PR","RI","SC","SD","TN","TX","UT","VT","VA","VI","WA","WV","WI","WY",
];
const grid = document.getElementById("pw-states")!;
STATES.forEach((s) => {
const lbl = document.createElement("label");
lbl.innerHTML = `<input type="checkbox" value="${s}" data-state="1"/> ${s}`;
grid.appendChild(lbl);
});
const err = document.getElementById("pw-jx-err") as HTMLDivElement;
const fqSection = document.getElementById("pw-fq-section") as HTMLDivElement;
const fqLoading = document.getElementById("pw-fq-loading") as HTMLDivElement;
const fqResults = document.getElementById("pw-fq-results") as HTMLDivElement;
const fqMissing = document.getElementById("pw-fq-missing") as HTMLDivElement;
const fqOk = document.getElementById("pw-fq-ok") as HTMLDivElement;
// Determine API base URL
const API = (() => {
const 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";
})();
let fqCheckResults: any[] = [];
let fqServiceFee = 9900; // $99/state
async function runForeignQualCheck(entityName: string, homeState: string, states: string[]) {
// Only check US states (2-letter, no territories like AS, GU, MP, PR, VI)
const usStates = states.filter((s) => s.length === 2 && !["AS","GU","MP","PR","VI"].includes(s));
if (!entityName || usStates.length === 0) {
fqSection.hidden = true;
return;
}
fqSection.hidden = false;
fqLoading.hidden = false;
fqResults.hidden = true;
try {
const resp = await fetch(`${API}/api/v1/corp/foreign-qual-check`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ entity_name: entityName, home_state: homeState, states: usStates }),
});
const data = await resp.json();
fqCheckResults = data.results || [];
fqServiceFee = data.foreign_qual_service_fee_cents || 9900;
renderFQResults();
} catch {
fqSection.hidden = true;
} finally {
fqLoading.hidden = true;
}
}
function renderFQResults() {
const missing = fqCheckResults.filter((r: any) => r.needs_foreign_qual);
const ok = fqCheckResults.filter((r: any) => !r.needs_foreign_qual);
if (missing.length === 0) {
fqResults.hidden = false;
fqMissing.innerHTML = "";
fqOk.innerHTML = `All ${fqCheckResults.length} state(s) checked — corporate registrations found and active in every state.`;
return;
}
fqResults.hidden = false;
// Render missing states with opt-in checkboxes
fqMissing.innerHTML = missing.map((r: any) => `
<label class="fq-row">
<input type="checkbox" data-fq-state="${r.state_code}" checked>
<span class="fq-state">${r.state_code}</span>
<span class="fq-reason">${r.reason}</span>
<span class="fq-price">$${(fqServiceFee / 100).toFixed(0)}/state</span>
</label>
`).join("");
// Add total line
const totalEl = document.createElement("div");
totalEl.className = "fq-total";
totalEl.id = "fq-total-line";
fqMissing.appendChild(totalEl);
updateFQTotal();
// Wire up checkbox changes
fqMissing.querySelectorAll<HTMLInputElement>("input[data-fq-state]").forEach((cb) => {
cb.addEventListener("change", updateFQTotal);
});
if (ok.length > 0) {
fqOk.innerHTML = `${ok.length} state(s) have active registrations.`;
} else {
fqOk.innerHTML = "";
}
}
function updateFQTotal() {
const checked = fqMissing.querySelectorAll<HTMLInputElement>("input[data-fq-state]:checked");
const total = checked.length * fqServiceFee;
const totalEl = document.getElementById("fq-total-line");
if (totalEl) {
totalEl.innerHTML = `<span>Foreign qualification: ${checked.length} state(s)</span><span>+$${(total / 100).toLocaleString()}</span>`;
}
}
function getSelectedFQStates(): string[] {
return Array.from(fqMissing.querySelectorAll<HTMLInputElement>("input[data-fq-state]:checked"))
.map((cb) => cb.dataset.fqState!);
}
// Restore state
window.addEventListener("pw:step-shown", (evt: any) => {
if (evt.detail.step !== "jurisdiction") return;
const s = (window as any).PWIntake.get();
const set = new Set<string>(s.intake_data?.jurisdictions_served || s.entity?.jurisdictions_served || []);
grid.querySelectorAll<HTMLInputElement>("input[data-state='1']").forEach((cb) => {
cb.checked = set.has(cb.value);
});
// If states already selected, run the check
if (set.size > 0) {
const entityName = s.entity?.legal_name || s.intake_data?.entity_legal_name || "";
const homeState = s.entity?.state_of_formation || s.entity?.address_state || s.intake_data?.state_of_formation || "";
runForeignQualCheck(entityName, homeState, Array.from(set));
}
});
// On "Check Registrations" or when user changes state selection, debounce the check
let checkTimer: ReturnType<typeof setTimeout> | null = null;
grid.addEventListener("change", () => {
if (checkTimer) clearTimeout(checkTimer);
checkTimer = setTimeout(() => {
const PW = (window as any).PWIntake;
if (!PW) return;
const s = PW.get();
const selected = Array.from(grid.querySelectorAll<HTMLInputElement>("input[data-state='1']:checked")).map((cb) => cb.value);
if (selected.length > 0) {
const entityName = s.entity?.legal_name || s.intake_data?.entity_legal_name || "";
const homeState = s.entity?.state_of_formation || s.entity?.address_state || s.intake_data?.state_of_formation || "";
runForeignQualCheck(entityName, homeState, selected);
} else {
fqSection.hidden = true;
}
}, 800); // Debounce 800ms
});
// Save on step-next
window.addEventListener("pw:step-next", (evt: any) => {
const PW = (window as any).PWIntake;
if (PW.steps[PW.get().step_index] !== "jurisdiction") return;
const selected = Array.from(
grid.querySelectorAll<HTMLInputElement>("input[data-state='1']:checked"),
).map((cb) => cb.value);
if (selected.length === 0) {
err.hidden = false; err.textContent = "Pick at least one state/territory."; evt.preventDefault(); return;
}
err.hidden = true;
// Save jurisdictions + foreign qual selections
const fqStates = getSelectedFQStates();
PW.patchIntakeData({
jurisdictions_served: selected,
foreign_qual_add_on_states: fqStates.length > 0 ? fqStates : undefined,
});
const st = PW.get();
PW.set({ entity: { ...st.entity, jurisdictions_served: selected } });
});
</script>