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>
268 lines
10 KiB
Text
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>
|