File size: 6,854 Bytes
baf8a7b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import { useEffect, useState } from "react";
import { IdentificationCard, ArrowRight, WarningCircle } from "@phosphor-icons/react";
import { api, type RunSummary, ApiError } from "../lib/api";
import { ErrorNote, Note, Spinner } from "./common";
interface Parsed {
raw: string; valid: boolean; error: string;
is_listed: boolean | null; industry_code: string; nic_division_name: string;
state: string; state_code: string; year: number | null; age_years: number | null;
ownership_code: string; ownership: string; is_public_company: boolean | null;
registration_number: string; listing: string;
}
/** Intake for a pre-IPO issuer: no exchange history, so the CIN is the only
* record that exists until documents are supplied. */
export function CinIntake({ onStarted }: { onStarted: (run: RunSummary) => void }) {
const [cin, setCin] = useState("");
const [name, setName] = useState("");
const [parsed, setParsed] = useState<Parsed | null>(null);
const [error, setError] = useState<string | null>(null);
const [starting, setStarting] = useState(false);
useEffect(() => {
const v = cin.trim().toUpperCase();
if (v.length < 21) { setParsed(null); return; }
const t = setTimeout(() => {
api.inspectCin(v).then(setParsed).catch(() => setParsed(null));
}, 150);
return () => clearTimeout(t);
}, [cin]);
const start = async () => {
setStarting(true); setError(null);
try {
onStarted(await api.createRunFromCin(cin.trim().toUpperCase(), name));
} catch (e) {
setError(e instanceof ApiError ? e.message : String(e));
setStarting(false);
}
};
const seg = (label: string, value: string, chars: string) => (
<div style={{ display: "grid", gridTemplateColumns: "116px 1fr", gap: 10, fontSize: 12.5 }}>
<span style={{ color: "var(--text-2)" }}>{label}</span>
<span>
<span className="mono">{value}</span>
<span className="f-id" style={{ display: "block", marginTop: 1 }}>{chars}</span>
</span>
</div>
);
return (
<div className="grid cols-sidebar">
<section className="panel">
<div className="panel-h">
<IdentificationCard size={16} />
<h2>Pre-IPO issuer</h2>
</div>
<div className="panel-b">
<Note tone="quiet">
An unlisted company has no exchange filing history, so it cannot be found by
search. Its Corporate Identity Number is a real registry identifier and settles
several eligibility questions on its own. Financial disclosures stay absent until
documents are supplied, and the draft will say so rather than fill them in.
</Note>
<div className="field" style={{ marginTop: 14 }}>
<label htmlFor="cin">Corporate Identity Number</label>
<input id="cin" className="ctl" placeholder="U62090KA2025PTC207753"
maxLength={21} value={cin}
onChange={(e) => setCin(e.target.value.toUpperCase())} />
<div className="help">21 characters, as issued by the Ministry of Corporate Affairs.</div>
</div>
<div className="field">
<label htmlFor="coname">Registered name (optional)</label>
<input id="coname" className="ctl" style={{ fontFamily: "var(--sans)" }}
placeholder="As on the certificate of incorporation"
value={name} onChange={(e) => setName(e.target.value)} />
<div className="help">
Recorded as unverified: a name typed by hand is not corroborated by any register.
</div>
</div>
{cin.trim().length >= 21 && parsed && !parsed.valid && (
<ErrorNote message={parsed.error} />
)}
{error && <div style={{ marginTop: 12 }}><ErrorNote message={error} /></div>}
<button className="btn btn-primary" onClick={start}
disabled={!parsed?.valid || starting}
style={{ width: "100%", justifyContent: "center", marginTop: 6 }}>
{starting ? <><Spinner /> Building ledger</>
: <>Build disclosure ledger <ArrowRight size={15} /></>}
</button>
</div>
</section>
<aside style={{ display: "grid", gap: 14 }}>
{parsed?.valid ? (
<>
<section className="panel">
<div className="panel-h"><h2>What the identifier encodes</h2></div>
<div className="panel-b" style={{ display: "grid", gap: 10 }}>
{seg("Listing", parsed.is_listed ? "Listed" : "Unlisted",
`letter ${parsed.listing}, character 1`)}
{seg("Industry", `${parsed.industry_code}`,
parsed.nic_division_name)}
{seg("Registrar", parsed.state, `code ${parsed.state_code}`)}
{seg("Incorporated", String(parsed.year ?? ""),
`${parsed.age_years} year(s) ago`)}
{seg("Class", parsed.ownership, `code ${parsed.ownership_code}`)}
{seg("Registration", parsed.registration_number, "with the Registrar")}
</div>
</section>
{parsed.is_public_company === false && (
<Note tone="warn">
<b>A private company cannot make a public issue.</b> The engine will report
this as a blocker: conversion to a public limited company under the Companies
Act is a prerequisite, not a drafting fix.
</Note>
)}
{parsed.year !== null && new Date().getFullYear() - parsed.year < 3 && (
<Note tone="warn">
<b>Incorporated {parsed.year}.</b> Regulation 229(6) tests operating profit in
two of the last three financial years. The engine will report whether enough
financial years can have closed.
</Note>
)}
</>
) : (
<div className="panel">
<div className="panel-b">
<div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.6 }}>
Enter a Corporate Identity Number to see what it encodes. Every one of its
21 characters means something.
</div>
<pre className="mono" style={{
fontSize: 11, lineHeight: 1.7, color: "var(--text-3)",
marginTop: 12, whiteSpace: "pre-wrap",
}}>{`U 62090 KA 2025 PTC 207753
β β β β β β registration no.
β β β β β class of company
β β β β year of incorporation
β β β state of the registrar
β β NIC industry code
β L listed, U unlisted`}</pre>
</div>
</div>
)}
</aside>
</div>
);
}
|