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:
parent
18b4c515a4
commit
9ae3d5e9c7
@ -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({
|
function EidInput({
|
||||||
simType,
|
simType,
|
||||||
eid,
|
eid,
|
||||||
@ -150,6 +156,8 @@ function EidInput({
|
|||||||
errors: Record<string, string | undefined>;
|
errors: Record<string, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const [showEidInfo, setShowEidInfo] = useState(false);
|
const [showEidInfo, setShowEidInfo] = useState(false);
|
||||||
|
const warning = getEidWarning(eid);
|
||||||
|
const hasError = Boolean(errors["eid"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -176,16 +184,18 @@ function EidInput({
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
id="eid"
|
id="eid"
|
||||||
value={eid}
|
value={eid}
|
||||||
onChange={e => onEidChange(e.target.value)}
|
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 placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors ${
|
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 ${
|
||||||
errors["eid"] ? "border-destructive" : "border-border"
|
hasError ? "border-destructive" : warning ? "border-warning" : "border-border"
|
||||||
}`}
|
}`}
|
||||||
placeholder="32-digit EID number"
|
placeholder="32-digit EID number (numbers only)"
|
||||||
maxLength={32}
|
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -131,7 +131,10 @@ export const orderConfigurationsSchema = z.object({
|
|||||||
scheduledAt: z.string().optional(),
|
scheduledAt: z.string().optional(),
|
||||||
accessMode: z.enum(ACCESS_MODE_VALUES).optional(),
|
accessMode: z.enum(ACCESS_MODE_VALUES).optional(),
|
||||||
simType: z.enum(SIM_TYPE_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(),
|
isMnp: z.string().optional(),
|
||||||
mnpNumber: z.string().optional(),
|
mnpNumber: z.string().optional(),
|
||||||
mnpExpiry: z.string().optional(),
|
mnpExpiry: z.string().optional(),
|
||||||
@ -164,7 +167,10 @@ export const orderSelectionsSchema = z
|
|||||||
activationType: z.enum(ACTIVATION_TYPE_VALUES).optional(),
|
activationType: z.enum(ACTIVATION_TYPE_VALUES).optional(),
|
||||||
scheduledAt: z.string().optional(),
|
scheduledAt: z.string().optional(),
|
||||||
simType: z.enum(SIM_TYPE_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(),
|
isMnp: z.string().optional(),
|
||||||
mnpNumber: z.string().optional(),
|
mnpNumber: z.string().optional(),
|
||||||
mnpExpiry: z.string().optional(),
|
mnpExpiry: z.string().optional(),
|
||||||
|
|||||||
@ -426,8 +426,7 @@ export const simConfigureFormSchema = z
|
|||||||
simType: simCardTypeSchema,
|
simType: simCardTypeSchema,
|
||||||
eid: z
|
eid: z
|
||||||
.string()
|
.string()
|
||||||
.min(15, "EID must be at least 15 characters")
|
.regex(/^\d{32}$/, "EID must be exactly 32 digits")
|
||||||
.max(32, "EID must be at most 32 characters")
|
|
||||||
.optional(),
|
.optional(),
|
||||||
selectedAddons: z.array(z.string()).default([]),
|
selectedAddons: z.array(z.string()).default([]),
|
||||||
activationType: simActivationTypeSchema,
|
activationType: simActivationTypeSchema,
|
||||||
@ -436,11 +435,11 @@ export const simConfigureFormSchema = z
|
|||||||
mnpData: simMnpFormSchema.optional(),
|
mnpData: simMnpFormSchema.optional(),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.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({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
path: ["eid"],
|
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({
|
.object({
|
||||||
planSku: z.string().min(1, "Plan SKU is required"),
|
planSku: z.string().min(1, "Plan SKU is required"),
|
||||||
simType: simCardTypeSchema,
|
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,
|
activationType: simActivationTypeSchema,
|
||||||
scheduledAt: z.string().regex(YYYYMMDD_REGEX, MSG_SCHEDULED_DATE_FORMAT).optional(),
|
scheduledAt: z.string().regex(YYYYMMDD_REGEX, MSG_SCHEDULED_DATE_FORMAT).optional(),
|
||||||
addons: simOrderActivationAddonsSchema.optional(),
|
addons: simOrderActivationAddonsSchema.optional(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user