Skip to content

Commit 70059ee

Browse files
committed
Implement PIN setup and confirmation steps in RegisterPage, enhancing user onboarding with validation and step navigation. Update stepper UI for better visual feedback.
1 parent 241532e commit 70059ee

1 file changed

Lines changed: 119 additions & 53 deletions

File tree

frontend/app/register/page.tsx

Lines changed: 119 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export default function RegisterPage() {
1515
const [step, setStep] = useState<number>(1);
1616
const [submitting, setSubmitting] = useState<boolean>(false);
1717
const [message, setMessage] = useState<string>("");
18-
const [debug, setDebug] = useState<string>("");
18+
const [pin, setPin] = useState<string>("");
19+
const [confirmPin, setConfirmPin] = useState<string>("");
1920

2021
async function handleCaptured(args: {
2122
embedding: number[];
@@ -38,9 +39,6 @@ export default function RegisterPage() {
3839
: (args.embedding || []).length,
3940
modelVersion: args.modelVersion,
4041
};
41-
setDebug(
42-
`payload: len=${payload.embedding.length}, dim=${payload.embeddingDim}`
43-
);
4442
setMessage("Registering your face template...");
4543
try {
4644
const res = await fetch("/api/face/register", {
@@ -63,6 +61,39 @@ export default function RegisterPage() {
6361
setSubmitting(false);
6462
}
6563
}
64+
65+
function handleBack() {
66+
if (submitting) return;
67+
setMessage("");
68+
setStep((s) => Math.max(1, s - 1));
69+
}
70+
71+
function handleNext() {
72+
if (submitting) return;
73+
setMessage("");
74+
if (step === 2) {
75+
const normalized = pin.trim();
76+
if (normalized.length !== 6 || /\D/.test(normalized)) {
77+
setMessage("Enter a 6-digit PIN.");
78+
return;
79+
}
80+
setStep(3);
81+
return;
82+
}
83+
if (step === 3) {
84+
const normalized = confirmPin.trim();
85+
if (normalized.length !== 6 || /\D/.test(normalized)) {
86+
setMessage("Confirm with a 6-digit PIN.");
87+
return;
88+
}
89+
if (normalized !== pin) {
90+
setMessage("PINs do not match.");
91+
return;
92+
}
93+
setStep(4);
94+
return;
95+
}
96+
}
6697
return (
6798
<div className="min-h-screen bg-gradient-to-br from-white to-indigo-50">
6899
<main className="mx-auto max-w-4xl px-6 py-12">
@@ -74,47 +105,87 @@ export default function RegisterPage() {
74105
</p>
75106
</div>
76107

77-
{/* Stepper */}
78-
<ol className="flex w-full items-center justify-center gap-4 text-sm text-gray-600">
79-
<li className="flex items-center gap-2">
80-
<span className="h-6 w-6 rounded-full bg-indigo-600 text-white text-xs grid place-items-center">
81-
1
82-
</span>
83-
Face
84-
</li>
85-
<div className="h-px w-10 bg-gray-300" />
86-
<li className="flex items-center gap-2 opacity-60">
87-
<span className="h-6 w-6 rounded-full bg-gray-200 text-gray-700 text-xs grid place-items-center">
88-
2
89-
</span>
90-
PIN
91-
</li>
92-
<div className="h-px w-10 bg-gray-300" />
93-
<li className="flex items-center gap-2 opacity-60">
94-
<span className="h-6 w-6 rounded-full bg-gray-200 text-gray-700 text-xs grid place-items-center">
95-
3
96-
</span>
97-
Confirm
98-
</li>
99-
<div className="h-px w-10 bg-gray-300" />
100-
<li className="flex items-center gap-2 opacity-60">
101-
<span className="h-6 w-6 rounded-full bg-gray-200 text-gray-700 text-xs grid place-items-center">
102-
4
103-
</span>
104-
Sign
105-
</li>
106-
<div className="h-px w-10 bg-gray-300" />
107-
<li className="flex items-center gap-2 opacity-60">
108-
<span className="h-6 w-6 rounded-full bg-gray-200 text-gray-700 text-xs grid place-items-center">
109-
5
110-
</span>
111-
Email
112-
</li>
113-
</ol>
108+
{/* Stepper with colored circles and connectors */}
109+
{(() => {
110+
const stepsLabels = ["Face", "PIN", "Confirm", "Proof", "Email"];
111+
return (
112+
<div className="flex w-full items-center justify-center gap-4 text-sm">
113+
{stepsLabels.map((label, idx) => {
114+
const circleActive = idx < step; // 1-based step
115+
const connectorActive = idx < step - 1; // connectors behind current step
116+
return (
117+
<div key={label} className="flex items-center gap-4">
118+
<div className="flex items-center gap-2">
119+
<span
120+
className={`h-6 w-6 rounded-full grid place-items-center text-xs ${
121+
circleActive
122+
? "bg-indigo-600 text-white"
123+
: "bg-gray-200 text-gray-700"
124+
}`}
125+
>
126+
{idx + 1}
127+
</span>
128+
<span
129+
className={
130+
circleActive ? "text-gray-900" : "text-gray-500"
131+
}
132+
>
133+
{label}
134+
</span>
135+
</div>
136+
{idx < stepsLabels.length - 1 && (
137+
<div
138+
className={`h-0.5 w-10 ${
139+
connectorActive ? "bg-indigo-600" : "bg-gray-300"
140+
}`}
141+
/>
142+
)}
143+
</div>
144+
);
145+
})}
146+
</div>
147+
);
148+
})()}
114149

115150
<div className="mt-4">
116151
{step === 1 ? (
117152
<FaceCapture onCaptured={handleCaptured} />
153+
) : step === 2 ? (
154+
<div className="w-full max-w-sm mx-auto">
155+
<label className="block text-sm font-medium text-gray-700">
156+
Set a PIN (6 digits)
157+
</label>
158+
<input
159+
type="password"
160+
inputMode="numeric"
161+
pattern="[0-9]*"
162+
value={pin}
163+
onChange={(e) =>
164+
setPin(e.target.value.replace(/\D/g, "").slice(0, 6))
165+
}
166+
maxLength={6}
167+
className="mt-2 w-full rounded-lg border border-gray-300 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-400"
168+
placeholder="••••••"
169+
/>
170+
</div>
171+
) : step === 3 ? (
172+
<div className="w-full max-w-sm mx-auto">
173+
<label className="block text-sm font-medium text-gray-700">
174+
Confirm your PIN
175+
</label>
176+
<input
177+
type="password"
178+
inputMode="numeric"
179+
pattern="[0-9]*"
180+
value={confirmPin}
181+
onChange={(e) =>
182+
setConfirmPin(e.target.value.replace(/\D/g, "").slice(0, 6))
183+
}
184+
maxLength={6}
185+
className="mt-2 w-full rounded-lg border border-gray-300 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-400"
186+
placeholder="••••••"
187+
/>
188+
</div>
118189
) : (
119190
<div className="relative h-64 w-64 rounded-full bg-green-50 border border-green-200 shadow grid place-items-center">
120191
<span className="text-green-700">Face captured ✓</span>
@@ -123,36 +194,31 @@ export default function RegisterPage() {
123194
</div>
124195

125196
<div className="w-full max-w-md">
126-
<div className="h-2 w-full rounded bg-gray-200">
127-
<div
128-
className="h-2 rounded bg-indigo-600 transition-all"
129-
style={{ width: `${(step / 5) * 100}%` }}
130-
/>
131-
</div>
132197
<div className="mt-2 text-center text-sm text-gray-600 min-h-5">
133198
{message}
134199
</div>
135-
{debug && (
136-
<div className="mt-1 text-center text-xs text-gray-400 break-all">
137-
{debug}
138-
</div>
139-
)}
140200
</div>
141201
<div className="flex gap-3">
142202
<button
203+
onClick={handleBack}
143204
disabled={step === 1 || submitting}
144205
className="rounded-lg border border-gray-300 px-4 py-2 text-gray-700 disabled:opacity-50"
145206
>
146207
Back
147208
</button>
148209
<button
149-
disabled={step < 1 || submitting}
210+
onClick={handleNext}
211+
disabled={step === 1 || submitting}
150212
className="rounded-lg bg-indigo-600 px-5 py-2 text-white font-semibold disabled:opacity-50"
151213
>
152214
{step === 1
153215
? "Awaiting face..."
154216
: step === 2
155217
? "Set PIN"
218+
: step === 3
219+
? "Confirm PIN"
220+
: step === 4
221+
? "Generate proof"
156222
: "Next"}
157223
</button>
158224
</div>

0 commit comments

Comments
 (0)