fix: enforce 32-digit numeric EID validation with inline user feedback

EID input now strips non-numeric characters, shows a digit counter warning
while typing, and enforces exactly 32 digits via Zod schemas across domain,
order configurations, and order selections layers. Also installs missing
input-otp dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Temuulen Ankhbayar 2026-03-07 11:16:58 +09:00
parent 18b4c515a4
commit 9ae3d5e9c7
3 changed files with 30 additions and 12 deletions

View File

@ -138,6 +138,12 @@ function PhysicalSimOption({
);
}
function getEidWarning(eid: string): string | undefined {
if (!eid) return undefined;
if (eid.length < 32) return `EID must be 32 digits (${eid.length}/32)`;
return undefined;
}
function EidInput({
simType,
eid,
@ -150,6 +156,8 @@ function EidInput({
errors: Record<string, string | undefined>;
}) {
const [showEidInfo, setShowEidInfo] = useState(false);
const warning = getEidWarning(eid);
const hasError = Boolean(errors["eid"]);
return (
<div
@ -176,16 +184,18 @@ function EidInput({
</label>
<input
type="text"
inputMode="numeric"
id="eid"
value={eid}
onChange={e => onEidChange(e.target.value)}
className={`w-full px-4 py-3 bg-card border rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors ${
errors["eid"] ? "border-destructive" : "border-border"
onChange={e => onEidChange(e.target.value.replace(/\D/g, "").slice(0, 32))}
className={`w-full px-4 py-3 bg-card border rounded-lg text-foreground font-mono tracking-wider placeholder:text-muted-foreground placeholder:font-sans placeholder:tracking-normal focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors ${
hasError ? "border-destructive" : warning ? "border-warning" : "border-border"
}`}
placeholder="32-digit EID number"
placeholder="32-digit EID number (numbers only)"
maxLength={32}
/>
{errors["eid"] && <p className="text-destructive text-sm mt-2">{errors["eid"]}</p>}
{hasError && <p className="text-destructive text-sm mt-2">{errors["eid"]}</p>}
{!hasError && warning && <p className="text-warning text-sm mt-2">{warning}</p>}
<button
type="button"

View File

@ -131,7 +131,10 @@ export const orderConfigurationsSchema = z.object({
scheduledAt: z.string().optional(),
accessMode: z.enum(ACCESS_MODE_VALUES).optional(),
simType: z.enum(SIM_TYPE_VALUES).optional(),
eid: z.string().optional(),
eid: z
.string()
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
.optional(),
isMnp: z.string().optional(),
mnpNumber: z.string().optional(),
mnpExpiry: z.string().optional(),
@ -164,7 +167,10 @@ export const orderSelectionsSchema = z
activationType: z.enum(ACTIVATION_TYPE_VALUES).optional(),
scheduledAt: z.string().optional(),
simType: z.enum(SIM_TYPE_VALUES).optional(),
eid: z.string().optional(),
eid: z
.string()
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
.optional(),
isMnp: z.string().optional(),
mnpNumber: z.string().optional(),
mnpExpiry: z.string().optional(),

View File

@ -426,8 +426,7 @@ export const simConfigureFormSchema = z
simType: simCardTypeSchema,
eid: z
.string()
.min(15, "EID must be at least 15 characters")
.max(32, "EID must be at most 32 characters")
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
.optional(),
selectedAddons: z.array(z.string()).default([]),
activationType: simActivationTypeSchema,
@ -436,11 +435,11 @@ export const simConfigureFormSchema = z
mnpData: simMnpFormSchema.optional(),
})
.superRefine((data, ctx) => {
if (data.simType === "eSIM" && (!data.eid || data.eid.trim().length < 15)) {
if (data.simType === "eSIM" && (!data.eid || !/^\d{32}$/.test(data.eid.trim()))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["eid"],
message: "EID is required for eSIM configuration",
message: "EID must be exactly 32 digits",
});
}
@ -515,7 +514,10 @@ export const simOrderActivationRequestSchema = z
.object({
planSku: z.string().min(1, "Plan SKU is required"),
simType: simCardTypeSchema,
eid: z.string().min(15, "EID must be at least 15 characters").optional(),
eid: z
.string()
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
.optional(),
activationType: simActivationTypeSchema,
scheduledAt: z.string().regex(YYYYMMDD_REGEX, MSG_SCHEDULED_DATE_FORMAT).optional(),
addons: simOrderActivationAddonsSchema.optional(),