Adds the full Next.js app (app/, components/, lib/, scripts/, assets/, docs/, Dockerfile, compose, configs) to version control. Security: - CIPC BizPortal customer-code logins (Login 1 + Login 3) now read CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE / CIPC_LOGIN3_PASSWORD from .env.local instead of source. - BizPortal personal login in apply-bizportal route reads BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD. - Added .env.example as the committed template. .gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright storage_state.json (live CIPC cookies), scratch dirs, client PDFs and debug screenshots.
513 lines
23 KiB
TypeScript
513 lines
23 KiB
TypeScript
// components/Step2Actions.tsx
|
||
// Action bar for the Step 2 page — Back to Step 1, Apply (BizPortal), Cancel.
|
||
|
||
"use client";
|
||
|
||
import { useState } from "react";
|
||
import Link from "next/link";
|
||
|
||
interface Props {
|
||
registrationId: string;
|
||
status: string;
|
||
proposedNames: string[];
|
||
}
|
||
|
||
export default function Step2Actions({ registrationId, status, proposedNames }: Props) {
|
||
const [busy, setBusy] = useState<string | null>(null);
|
||
const [msg, setMsg] = useState<string>("");
|
||
const [msgKind, setMsgKind] = useState<"info" | "success" | "error" | "warn">("info");
|
||
const [showConfirm, setShowConfirm] = useState(false);
|
||
const [showNameOnlyConfirm, setShowNameOnlyConfirm] = useState(false);
|
||
const [bizportalResult, setBizportalResult] = useState<any>(null);
|
||
const [checkMailResult, setCheckMailResult] = useState<any>(null);
|
||
const [showRevertConfirm, setShowRevertConfirm] = useState(false);
|
||
|
||
async function runAction(action: string, confirmText?: string) {
|
||
if (confirmText && !confirm(confirmText)) return;
|
||
setBusy(action);
|
||
setMsg("");
|
||
setMsgKind("info");
|
||
try {
|
||
const res = await fetch(`/api/register/${registrationId}/status?action=${action}`, { method: "POST" });
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${data.error || `HTTP ${res.status}`}`);
|
||
return;
|
||
}
|
||
if (action === "back_to_step1" || action === "bizportal_done") {
|
||
window.location.reload();
|
||
} else if (action === "cancel" || action === "client_requested_changes") {
|
||
window.location.href = "/registrations";
|
||
}
|
||
} catch (err: unknown) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${err instanceof Error ? err.message : "Error"}`);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
async function runBizportalApply(nameOnly: boolean = false) {
|
||
setShowConfirm(false);
|
||
setShowNameOnlyConfirm(false);
|
||
setBusy(nameOnly ? "apply_name_only" : "apply");
|
||
setMsg("");
|
||
setMsgKind("info");
|
||
setBizportalResult(null);
|
||
try {
|
||
const res = await fetch(`/api/register/${registrationId}/apply-bizportal`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ nameOnly }),
|
||
});
|
||
const data = await res.json();
|
||
setBizportalResult(data);
|
||
if (data.ok && data.status === "bizportal_done") {
|
||
setMsgKind("success");
|
||
setMsg(`✅ ${data.message || "BizPortal name reservation confirmed. Now wait for the approval letter."}`);
|
||
setTimeout(() => window.location.reload(), 2500);
|
||
} else if (data.ok) {
|
||
setMsgKind("warn");
|
||
setMsg(`⚠ ${data.message || "Submitted but status not auto-confirmed. Check BizPortal."}`);
|
||
} else {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${data.error || data.message || "Submission failed."}`);
|
||
}
|
||
} catch (err: unknown) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${err instanceof Error ? err.message : "Error"}`);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
async function handleApplyClick() {
|
||
setShowConfirm(true);
|
||
}
|
||
|
||
async function handleNameOnlyClick() {
|
||
setShowNameOnlyConfirm(true);
|
||
}
|
||
|
||
async function runRevertNewNames() {
|
||
setShowRevertConfirm(false);
|
||
setBusy("revert_new_names");
|
||
setMsg("");
|
||
setMsgKind("info");
|
||
try {
|
||
const res = await fetch(`/api/register/${registrationId}/status?action=revert_new_names`, { method: "POST" });
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${data.error || `HTTP ${res.status}`}`);
|
||
} else {
|
||
setMsgKind("success");
|
||
setMsg(`✅ CIPC fields cleared. Registration reverted to Step 1 — client can submit 4 new proposed names.`);
|
||
setTimeout(() => window.location.href = "/registrations", 2500);
|
||
}
|
||
} catch (err: unknown) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${err instanceof Error ? err.message : "Error"}`);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
async function runCheckMail() {
|
||
setMsg("");
|
||
setMsgKind("info");
|
||
setCheckMailResult(null);
|
||
try {
|
||
const res = await fetch(`/api/register/${registrationId}/check-cipc-mail`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({}),
|
||
});
|
||
const data = await res.json();
|
||
setCheckMailResult(data);
|
||
if (!res.ok) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${data.error || `HTTP ${res.status}`}`);
|
||
} else if (data.firstScanner?.found || data.secondScanner?.found) {
|
||
setMsgKind("success");
|
||
setMsg(`✅ ${data.summary}`);
|
||
setTimeout(() => window.location.reload(), 3000);
|
||
} else {
|
||
setMsgKind("info");
|
||
setMsg(`ℹ️ ${data.summary}`);
|
||
}
|
||
} catch (err: unknown) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${err instanceof Error ? err.message : "Error"}`);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
async function runNameAlreadyApproved() {
|
||
setBusy("name_already_approved");
|
||
setMsg("");
|
||
setMsgKind("info");
|
||
try {
|
||
const res = await fetch(`/api/register/${registrationId}/name-already-approved`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok || !data.ok) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${data.error || "Failed to process."}`);
|
||
} else if (data.decision === "approved") {
|
||
setMsgKind("success");
|
||
setMsg(`✅ Name approved! Documents saved to PB. Reloading…`);
|
||
setTimeout(() => window.location.reload(), 3000);
|
||
} else if (data.decision === "rejected") {
|
||
setMsgKind("warn");
|
||
setMsg(`⚠ Name rejected by CIPC. Client needs to propose new names.`);
|
||
} else {
|
||
setMsgKind("info");
|
||
setMsg(data.message || "Done.");
|
||
}
|
||
} catch (err: unknown) {
|
||
setMsgKind("error");
|
||
setMsg(`❌ ${err instanceof Error ? err.message : "Error"}`);
|
||
} finally {
|
||
setBusy(null);
|
||
}
|
||
}
|
||
|
||
const isRegistered = status === "registered";
|
||
const isBizportalInProgress = status === "bizportal_in_progress";
|
||
const isBizportalDone = status === "bizportal_done";
|
||
|
||
return (
|
||
<div className="bg-surface border border-border rounded-lg p-5">
|
||
<p className="text-xs uppercase tracking-[0.08em] text-muted font-bold mb-3">Step 2 actions</p>
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
{isRegistered && (
|
||
<>
|
||
<button
|
||
onClick={handleApplyClick}
|
||
disabled={busy === "apply"}
|
||
className="px-4 py-2 rounded-md border border-accent/30 bg-accent/10 text-accent text-sm font-bold hover:bg-accent/20 disabled:opacity-50 transition-colors"
|
||
>
|
||
{busy === "apply" ? "⏳ Running BizPortal…" : "🌐 Apply Name Reservation on BizPortal"}
|
||
</button>
|
||
<button
|
||
onClick={handleNameOnlyClick}
|
||
disabled={busy === "apply_name_only"}
|
||
className="px-4 py-2 rounded-md border border-purple-500/30 bg-purple-500/10 text-purple-700 text-sm font-bold hover:bg-purple-500/20 disabled:opacity-50 transition-colors"
|
||
title="Apply only for a CIPC name reservation — workflow ends once CIPC approves. No follow-up Steps 3/4."
|
||
>
|
||
{busy === "apply_name_only" ? "⏳ Running…" : "🪪 Name Reservation Only"}
|
||
</button>
|
||
<button
|
||
onClick={runNameAlreadyApproved}
|
||
disabled={busy === "name_already_approved"}
|
||
className="px-4 py-2 rounded-md border border-green-500/30 bg-green-500/10 text-green-700 text-sm font-bold hover:bg-green-500/20 disabled:opacity-50 transition-colors"
|
||
title="Name was already approved on BizPortal by the company secretary — search DeltaDet for existing CIPC emails and complete the workflow"
|
||
>
|
||
{busy === "name_already_approved" ? "⏳ Searching emails…" : "✅ Name Already Approved"}
|
||
</button>
|
||
<button
|
||
onClick={() => runAction("back_to_step1")}
|
||
disabled={busy === "back_to_step1"}
|
||
className="px-4 py-2 rounded-md border border-yellow-500/30 bg-yellow-500/10 text-yellow-700 text-sm font-semibold hover:bg-yellow-500/20 disabled:opacity-50 transition-colors"
|
||
>
|
||
{busy === "back_to_step1" ? "…" : "← Back to Step 1"}
|
||
</button>
|
||
<button
|
||
disabled
|
||
title="Coming soon"
|
||
className="px-4 py-2 rounded-md border border-border bg-surface-warm text-muted text-sm font-semibold cursor-not-allowed opacity-60"
|
||
>
|
||
Step 3: Resignation →
|
||
</button>
|
||
</>
|
||
)}
|
||
{isBizportalInProgress && (
|
||
<>
|
||
<span className="px-3 py-2 rounded-md border border-blue-500/30 bg-blue-500/10 text-blue-700 text-sm font-semibold">
|
||
⏳ Bizportal submission in progress
|
||
</span>
|
||
<button
|
||
onClick={runCheckMail}
|
||
disabled={busy === "check_mail"}
|
||
className="px-4 py-2 rounded-md border border-amber-500/30 bg-amber-500/10 text-amber-700 text-sm font-bold hover:bg-amber-500/20 disabled:opacity-50 transition-colors"
|
||
title="Check deltadet mailbox for new CIPC emails now"
|
||
>
|
||
{busy === "check_mail" ? "⏳ Checking mail…" : "📧 Check Mail"}
|
||
</button>
|
||
<button
|
||
onClick={() => setShowRevertConfirm(true)}
|
||
disabled={busy === "revert_new_names"}
|
||
className="px-4 py-2 rounded-md border border-red-500/30 bg-red-500/10 text-red-700 text-sm font-bold hover:bg-red-500/20 disabled:opacity-50 transition-colors"
|
||
title="CIPC rejected the name — revert to Step 1 so client can submit 4 new proposed names"
|
||
>
|
||
{busy === "revert_new_names" ? "⏳ Reverting…" : "🔄 Revert to Step 1 (New Names)"}
|
||
</button>
|
||
</>
|
||
)}
|
||
{isBizportalDone && (
|
||
<>
|
||
<span className="px-3 py-2 rounded-md border border-success/30 bg-success/10 text-success text-sm font-bold">
|
||
✓ BizPortal Done — awaiting approval letter
|
||
</span>
|
||
<button
|
||
onClick={runCheckMail}
|
||
disabled={busy === "check_mail"}
|
||
className="px-4 py-2 rounded-md border border-amber-500/30 bg-amber-500/10 text-amber-700 text-sm font-bold hover:bg-amber-500/20 disabled:opacity-50 transition-colors"
|
||
title="Check deltadet mailbox for CIPC decision email now"
|
||
>
|
||
{busy === "check_mail" ? "⏳ Checking mail…" : "📧 Check Mail"}
|
||
</button>
|
||
<button
|
||
onClick={() => runAction("client_requested_changes", "Client wants to change something — move back to step 1?")}
|
||
disabled={busy === "client_requested_changes"}
|
||
className="px-4 py-2 rounded-md border border-yellow-500/30 bg-yellow-500/10 text-yellow-700 text-sm font-semibold hover:bg-yellow-500/20 disabled:opacity-50 transition-colors"
|
||
>
|
||
← Client Requested Changes
|
||
</button>
|
||
<button
|
||
onClick={() => setShowRevertConfirm(true)}
|
||
disabled={busy === "revert_new_names"}
|
||
className="px-4 py-2 rounded-md border border-red-500/30 bg-red-500/10 text-red-700 text-sm font-bold hover:bg-red-500/20 disabled:opacity-50 transition-colors"
|
||
title="CIPC rejected the name — revert to Step 1 so client can submit 4 new proposed names"
|
||
>
|
||
{busy === "revert_new_names" ? "⏳ Reverting…" : "🔄 Revert to Step 1 (New Names)"}
|
||
</button>
|
||
</>
|
||
)}
|
||
{status === "name_approved" && (
|
||
<>
|
||
<span className="px-3 py-2 rounded-md border border-success/30 bg-success/10 text-success text-sm font-bold">
|
||
✓ Name approved by Bizportal
|
||
</span>
|
||
<button
|
||
onClick={() => runAction("step3")}
|
||
disabled={busy === "step3"}
|
||
className="px-4 py-2 rounded-md border border-purple-500/30 bg-purple-500/10 text-purple-700 text-sm font-bold hover:bg-purple-500/20 disabled:opacity-50 transition-colors"
|
||
>
|
||
{busy === "step3" ? "⏳ Moving…" : "➡️ Proceed to Step 3"}
|
||
</button>
|
||
<button
|
||
onClick={() => setShowRevertConfirm(true)}
|
||
disabled={busy === "revert_new_names"}
|
||
className="px-4 py-2 rounded-md border border-red-500/30 bg-red-500/10 text-red-700 text-sm font-bold hover:bg-red-500/20 disabled:opacity-50 transition-colors"
|
||
title="Name was approved but client wants different names — revert to Step 1 for new proposals"
|
||
>
|
||
{busy === "revert_new_names" ? "⏳ Reverting…" : "🔄 Revert to Step 1 (New Names)"}
|
||
</button>
|
||
</>
|
||
)}
|
||
{status === "name_reserved" && (
|
||
<span className="px-3 py-2 rounded-md border border-purple-500/30 bg-purple-500/10 text-purple-700 text-sm font-bold">
|
||
✓ Name reserved — workflow complete
|
||
</span>
|
||
)}
|
||
{status === "cancelled" && (
|
||
<span className="px-4 py-2 rounded-md border border-danger/30 bg-danger/10 text-danger text-sm font-semibold">
|
||
⚠ Cancelled
|
||
</span>
|
||
)}
|
||
<Link
|
||
href="/registrations"
|
||
className="px-4 py-2 rounded-md border border-border bg-surface text-fg text-sm font-semibold no-underline hover:bg-surface-warm transition-colors"
|
||
>
|
||
← Back to list
|
||
</Link>
|
||
</div>
|
||
|
||
{msg && (
|
||
<div className={`mt-3 px-4 py-3 rounded-md text-sm border ${
|
||
msgKind === "success" ? "bg-success/10 border-success/30 text-success" :
|
||
msgKind === "warn" ? "bg-yellow-500/10 border-yellow-500/30 text-yellow-700" :
|
||
msgKind === "error" ? "bg-danger/10 border-danger/30 text-danger" :
|
||
"bg-surface-warm border-border text-fg"
|
||
}`}>
|
||
{msg}
|
||
</div>
|
||
)}
|
||
|
||
{bizportalResult && (
|
||
<div className="mt-3 px-4 py-3 rounded-md text-sm bg-surface-warm border border-border">
|
||
<p className="font-semibold mb-2">BizPortal flow details</p>
|
||
{bizportalResult.balance_before !== null && bizportalResult.balance_before !== undefined && (
|
||
<p className="text-xs text-muted">Balance before: <span className="font-mono text-fg">R {bizportalResult.balance_before.toFixed(2)}</span></p>
|
||
)}
|
||
{bizportalResult.balance_after !== null && bizportalResult.balance_after !== undefined && (
|
||
<p className="text-xs text-muted">Balance after: <span className="font-mono text-fg">R {bizportalResult.balance_after.toFixed(2)}</span></p>
|
||
)}
|
||
{bizportalResult.deduction !== null && bizportalResult.deduction !== undefined && (
|
||
<p className="text-xs text-muted">Deduction: <span className="font-mono text-fg">R {bizportalResult.deduction.toFixed(2)}</span></p>
|
||
)}
|
||
{bizportalResult.log && (
|
||
<details className="mt-2">
|
||
<summary className="text-xs text-muted cursor-pointer">Show log</summary>
|
||
<pre className="text-[10px] text-muted mt-2 whitespace-pre-wrap font-mono">
|
||
{bizportalResult.log.join("\n")}
|
||
</pre>
|
||
</details>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{checkMailResult && (
|
||
<div className="mt-3 px-4 py-3 rounded-md text-sm bg-surface-warm border border-border">
|
||
<p className="font-semibold mb-2">📧 Check Mail result</p>
|
||
<p className="text-xs text-muted mb-1">
|
||
Reference: <span className="font-mono text-fg">{checkMailResult.registration?.reference_name}</span>
|
||
{" | "}
|
||
Status: <span className="font-mono text-fg">{checkMailResult.registration?.status_after || checkMailResult.registration?.status_before}</span>
|
||
</p>
|
||
{checkMailResult.registration?.cipc_reference_no && (
|
||
<p className="text-xs text-muted">
|
||
CIPC Ref: <span className="font-mono text-fg">{checkMailResult.registration.cipc_reference_no}</span>
|
||
</p>
|
||
)}
|
||
{checkMailResult.registration?.cipc_decision && (
|
||
<p className="text-xs text-muted">
|
||
Decision: <span className="font-mono text-fg">{checkMailResult.registration.cipc_decision}</span>
|
||
</p>
|
||
)}
|
||
{checkMailResult.firstScanner?.found && (
|
||
<p className="text-xs text-amber-700 mt-1">✓ First email found and processed</p>
|
||
)}
|
||
{checkMailResult.secondScanner?.found && (
|
||
<p className="text-xs text-amber-700 mt-1">✓ Second email found — client notified</p>
|
||
)}
|
||
{!checkMailResult.firstScanner?.found && !checkMailResult.secondScanner?.found && (
|
||
<p className="text-xs text-muted mt-1">No new emails found for this registration.</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Confirmation popup */}
|
||
{showConfirm && (
|
||
<div
|
||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4"
|
||
onClick={() => setShowConfirm(false)}
|
||
>
|
||
<div
|
||
className="bg-surface border border-border rounded-lg p-6 max-w-md w-full shadow-xl"
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
<p className="text-xs uppercase tracking-[0.08em] text-accent font-bold mb-2">Apply Name Reservation</p>
|
||
<h3 className="text-lg font-bold text-fg mb-3">Apply on BizPortal?</h3>
|
||
<p className="text-sm text-muted mb-4">
|
||
I will open BizPortal, login if needed, fill the 4 name boxes in the order supplied, and click Continue twice.
|
||
The second Continue <strong>costs R50</strong> from your BizPortal balance.
|
||
</p>
|
||
<p className="text-sm text-danger font-semibold mb-5">
|
||
⚠ Before this runs I'll record your BizPortal balance, after the run I'll check it again. If R50 was deducted, I'll auto-mark <strong>BizPortal Done</strong> (green) so you know to wait for the approval letter.
|
||
</p>
|
||
<p className="text-sm text-fg mb-1 font-semibold">Names to enter:</p>
|
||
<ol className="text-sm text-fg list-decimal list-inside mb-5 space-y-1">
|
||
{proposedNames.length === 0 && <li className="text-muted italic">No names provided</li>}
|
||
{proposedNames.map((n, i) => (
|
||
<li key={i} className="font-mono">{n}</li>
|
||
))}
|
||
</ol>
|
||
<div className="flex justify-end gap-3">
|
||
<button
|
||
onClick={() => setShowConfirm(false)}
|
||
className="px-4 py-2 rounded-md border border-border bg-surface text-fg text-sm font-semibold hover:bg-surface-warm transition-colors"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={() => runBizportalApply(false)}
|
||
disabled={busy === "apply"}
|
||
className="px-4 py-2 rounded-md border border-accent bg-accent text-accent-on text-sm font-bold hover:opacity-90 transition-opacity disabled:opacity-50"
|
||
>
|
||
{busy === "apply" ? "Running…" : "OK"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Name Reservation Only confirmation popup */}
|
||
{showNameOnlyConfirm && (
|
||
<div
|
||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4"
|
||
onClick={() => setShowNameOnlyConfirm(false)}
|
||
>
|
||
<div
|
||
className="bg-surface border border-border rounded-lg p-6 max-w-md w-full shadow-xl"
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
<p className="text-xs uppercase tracking-[0.08em] text-purple-700 font-bold mb-2">Name Reservation Only</p>
|
||
<h3 className="text-lg font-bold text-fg mb-3">Apply just for the name reservation?</h3>
|
||
<p className="text-sm text-muted mb-4">
|
||
I will lodge a CIPC Name Reservation on BizPortal — <strong>R50</strong> is debited from your prepaid balance.
|
||
Once CIPC approves, this registration is <strong>done</strong>. No Steps 3/4.
|
||
</p>
|
||
<p className="text-sm text-fg mb-1 font-semibold">Names to enter:</p>
|
||
<ol className="text-sm text-fg list-decimal list-inside mb-5 space-y-1">
|
||
{proposedNames.length === 0 && <li className="text-muted italic">No names provided</li>}
|
||
{proposedNames.map((n, i) => (
|
||
<li key={i} className="font-mono">{n}</li>
|
||
))}
|
||
</ol>
|
||
<div className="flex justify-end gap-3">
|
||
<button
|
||
onClick={() => setShowNameOnlyConfirm(false)}
|
||
className="px-4 py-2 rounded-md border border-border bg-surface text-fg text-sm font-semibold hover:bg-surface-warm transition-colors"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={() => runBizportalApply(true)}
|
||
disabled={busy === "apply_name_only"}
|
||
className="px-4 py-2 rounded-md border border-purple-500 bg-purple-500 text-white text-sm font-bold hover:opacity-90 transition-opacity disabled:opacity-50"
|
||
>
|
||
{busy === "apply_name_only" ? "Running…" : "OK — Name Reservation Only"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
{/* Revert to Step 1 (New Names) confirmation popup */}
|
||
{showRevertConfirm && (
|
||
<div
|
||
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4"
|
||
onClick={() => setShowRevertConfirm(false)}
|
||
>
|
||
<div
|
||
className="bg-surface border border-border rounded-lg p-6 max-w-md w-full shadow-xl"
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
<p className="text-xs uppercase tracking-[0.08em] text-red-700 font-bold mb-2">CIPC Name Rejected</p>
|
||
<h3 className="text-lg font-bold text-fg mb-3">Revert to Step 1 — New Names?</h3>
|
||
<p className="text-sm text-muted mb-4">
|
||
This will <strong>clear the CIPC reference number and decision</strong>, and move the registration back to Step 1.
|
||
</p>
|
||
<p className="text-sm text-muted mb-1">
|
||
The client will receive the wizard link with their saved data and can submit <strong>4 new proposed names</strong>.
|
||
</p>
|
||
<p className="text-sm text-red-700 font-semibold mb-5">
|
||
This cannot be undone.
|
||
</p>
|
||
<div className="flex justify-end gap-3">
|
||
<button
|
||
onClick={() => setShowRevertConfirm(false)}
|
||
className="px-4 py-2 rounded-md border border-border bg-surface text-fg text-sm font-semibold hover:bg-surface-warm transition-colors"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={runRevertNewNames}
|
||
disabled={busy === "revert_new_names"}
|
||
className="px-4 py-2 rounded-md border border-red-500 bg-red-500 text-white text-sm font-bold hover:opacity-90 transition-opacity disabled:opacity-50"
|
||
>
|
||
{busy === "revert_new_names" ? "Reverting…" : "Revert to Step 1"}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|