Skip to content

Commit 5bd343a

Browse files
Changes
Co-authored-by: ghostcodedynamics <295822717+ghostcodedynamics@users.noreply.github.com>
1 parent c9b61e9 commit 5bd343a

1 file changed

Lines changed: 205 additions & 13 deletions

File tree

‎src/routes/verify.tsx‎

Lines changed: 205 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,213 @@
1-
import { ComingSoon } from "@/components/coming-soon";
1+
import { useState } from "react";
2+
import { motion } from "framer-motion";
3+
import { BadgeCheck, QrCode, Search, ShieldCheck, FileText, Loader2 } from "lucide-react";
4+
import { PageHero, SectionHeader, Reveal } from "@/components/section";
25
import { Seo } from "@/components/seo";
6+
import { Faq, FeatureGrid, ProgramCta, StatusNote } from "@/components/program-page";
7+
import { breadcrumbSchema, webPageSchema } from "@/lib/seo";
8+
import { formatDate } from "@/lib/format";
9+
10+
const title = "Certificate Verification - GhostCode Dynamics";
11+
const description =
12+
"Verify a GhostCode Dynamics certificate ID issued for internships, Academy cohorts and programs. Public, tamper-evident certificate lookup.";
13+
const path = "/verify";
14+
15+
const FEATURES = [
16+
{
17+
icon: <Search className="h-5 w-5" />,
18+
title: "Public lookup",
19+
desc: "Anyone with a certificate ID can confirm it without an account, a login or a support email.",
20+
},
21+
{
22+
icon: <ShieldCheck className="h-5 w-5" />,
23+
title: "Tamper-evident IDs",
24+
desc: "Each certificate carries a signed identifier tied to the cohort record, so edited PDFs fail verification.",
25+
},
26+
{
27+
icon: <FileText className="h-5 w-5" />,
28+
title: "Issued-on details",
29+
desc: "Verification returns the recipient name, program, issue date and completion status — nothing more.",
30+
},
31+
{
32+
icon: <QrCode className="h-5 w-5" />,
33+
title: "QR friendly",
34+
desc: "Every certificate prints a QR code that opens this page with the ID already filled in.",
35+
},
36+
];
37+
38+
const FAQS = [
39+
{
40+
q: "Is verification live?",
41+
a: "The lookup form is here and ready, but no certificates have been issued yet. The registry goes live with the first completed cohort.",
42+
},
43+
{
44+
q: "What does a certificate ID look like?",
45+
a: "GCD-YYYY-XXXXXX — for example GCD-2026-A81C4F. It is printed on the certificate and encoded in its QR code.",
46+
},
47+
{
48+
q: "Can an employer trust this?",
49+
a: "Yes. Verification is served from our own registry, and results include the exact program and dates so claims can be checked against the certificate itself.",
50+
},
51+
{
52+
q: "I lost my certificate ID.",
53+
a: "Email us from the address you enrolled with and we will re-issue your ID and a fresh PDF copy.",
54+
},
55+
];
56+
57+
type Status = "idle" | "loading" | "invalid" | "notfound";
58+
59+
const ID_PATTERN = /^GCD-\d{4}-[A-Z0-9]{6}$/i;
360

461
export default function Page() {
62+
const [value, setValue] = useState("");
63+
const [status, setStatus] = useState<Status>("idle");
64+
65+
async function onSubmit(e: React.FormEvent) {
66+
e.preventDefault();
67+
const id = value.trim();
68+
if (!ID_PATTERN.test(id)) {
69+
setStatus("invalid");
70+
return;
71+
}
72+
setStatus("loading");
73+
// Placeholder lookup. Swap for a real registry call when cohorts go live.
74+
await new Promise((r) => setTimeout(r, 700));
75+
setStatus("notfound");
76+
}
77+
578
return (
679
<>
7-
<Seo title={"Certificate Verification - GhostCode Dynamics"} description={"Future certificate lookup for GhostCode cohorts} path={internships and programs."} robots="noindex, follow" />
8-
<ComingSoon
9-
module="Verify"
10-
title="Verify a GhostCode certificate."
11-
description="Once cohorts and internships go live, certificate IDs will be verifiable here in seconds."
12-
bullets={[
13-
"Public certificate lookup",
14-
"Tamper-proof IDs",
15-
"Issued-on / recipient info",
16-
"QR-code friendly",
17-
]}
18-
/>
80+
<Seo
81+
title={title}
82+
description={description}
83+
path={path}
84+
schemas={[
85+
webPageSchema(title, description, path),
86+
breadcrumbSchema([
87+
{ name: "Home", path: "/" },
88+
{ name: "Verify", path },
89+
]),
90+
]}
91+
/>
92+
<PageHero
93+
eyebrow="Verify · Registry opening soon"
94+
title="Verify a GhostCode certificate."
95+
description="Certificates issued for internships, Academy cohorts and programs can be checked here in seconds using the ID printed on the document."
96+
>
97+
<StatusNote>Registry empty until the first cohort completes</StatusNote>
98+
</PageHero>
99+
100+
<section className="relative py-8 md:py-12">
101+
<div className="container-prose">
102+
<Reveal className="mx-auto max-w-2xl rounded-3xl border border-border bg-card/60 p-6 backdrop-blur sm:p-8">
103+
<h2 className="font-display text-xl font-semibold tracking-tight text-foreground">
104+
Certificate lookup
105+
</h2>
106+
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
107+
Enter the ID exactly as printed, for example{" "}
108+
<span className="font-mono text-foreground">GCD-2026-A81C4F</span>.
109+
</p>
110+
111+
<form onSubmit={onSubmit} className="mt-6 flex flex-col gap-3 sm:flex-row">
112+
<label htmlFor="cert-id" className="sr-only">
113+
Certificate ID
114+
</label>
115+
<input
116+
id="cert-id"
117+
name="cert-id"
118+
value={value}
119+
onChange={(e) => {
120+
setValue(e.target.value);
121+
setStatus("idle");
122+
}}
123+
placeholder="GCD-2026-A81C4F"
124+
autoComplete="off"
125+
spellCheck={false}
126+
className="w-full rounded-full border border-border bg-surface/60 px-5 py-3 font-mono text-sm text-foreground outline-none transition placeholder:text-muted-foreground focus:border-primary/50"
127+
/>
128+
<button
129+
type="submit"
130+
disabled={status === "loading"}
131+
className="inline-flex shrink-0 items-center justify-center gap-2 rounded-full bg-foreground px-6 py-3 text-sm font-medium text-background shadow-elevated transition hover:opacity-90 disabled:opacity-60"
132+
>
133+
{status === "loading" ? (
134+
<Loader2 className="h-4 w-4 animate-spin" />
135+
) : (
136+
<BadgeCheck className="h-4 w-4" />
137+
)}
138+
Verify
139+
</button>
140+
</form>
141+
142+
<div aria-live="polite" className="mt-5 min-h-[1.5rem]">
143+
{status === "invalid" && (
144+
<motion.p
145+
initial={{ opacity: 0, y: 4 }}
146+
animate={{ opacity: 1, y: 0 }}
147+
className="text-sm text-destructive"
148+
>
149+
That does not look like a GhostCode certificate ID. The format is GCD-YYYY-XXXXXX.
150+
</motion.p>
151+
)}
152+
{status === "notfound" && (
153+
<motion.div
154+
initial={{ opacity: 0, y: 4 }}
155+
animate={{ opacity: 1, y: 0 }}
156+
className="rounded-2xl border border-border bg-surface/60 p-4 text-sm leading-relaxed text-muted-foreground"
157+
>
158+
No certificate found for that ID. The registry is empty until our first cohort
159+
completes — checked on {formatDate(new Date().toISOString())}. If you hold a
160+
certificate and see this message, contact us and we will confirm it manually.
161+
</motion.div>
162+
)}
163+
</div>
164+
</Reveal>
165+
</div>
166+
</section>
167+
168+
<FeatureGrid
169+
eyebrow="How it works"
170+
title="What verification guarantees"
171+
items={FEATURES}
172+
columns={4}
173+
/>
174+
175+
<section className="relative border-t border-border/60 py-16 md:py-24">
176+
<div className="container-prose">
177+
<SectionHeader
178+
eyebrow="Anatomy"
179+
title="What's on a GhostCode certificate"
180+
description="Every issued certificate carries the same fields so verification results can be matched line for line."
181+
/>
182+
<ul className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
183+
{[
184+
{ k: "Recipient", v: "Full name as enrolled" },
185+
{ k: "Program", v: "Cohort, track or internship block" },
186+
{ k: "Issued on", v: "Completion date" },
187+
{ k: "Certificate ID", v: "GCD-YYYY-XXXXXX + QR" },
188+
].map((f) => (
189+
<Reveal key={f.k}>
190+
<li className="h-full rounded-2xl border border-border bg-card/60 p-5 backdrop-blur">
191+
<p className="font-mono text-[10px] uppercase tracking-[0.3em] text-primary">
192+
{f.k}
193+
</p>
194+
<p className="mt-2 text-sm text-muted-foreground">{f.v}</p>
195+
</li>
196+
</Reveal>
197+
))}
198+
</ul>
199+
</div>
200+
</section>
201+
202+
<Faq items={FAQS} />
203+
204+
<ProgramCta
205+
title="Need a certificate checked manually?"
206+
description="Send us the ID or a copy of the document and we will confirm its authenticity directly."
207+
primaryLabel="Contact us"
208+
secondaryLabel="See the Academy"
209+
secondaryTo="/academy"
210+
/>
19211
</>
20212
);
21213
}

0 commit comments

Comments
 (0)