From 428536fb1b34ea257c93021c15516bbb351b2a72 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Fri, 31 Jul 2026 23:27:26 +0500 Subject: [PATCH] add number validation --- src/lib/phone.ts | 23 +++++++++++++++++++++++ src/pages/Devices.tsx | 33 ++++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 src/lib/phone.ts diff --git a/src/lib/phone.ts b/src/lib/phone.ts new file mode 100644 index 0000000..c4fb20f --- /dev/null +++ b/src/lib/phone.ts @@ -0,0 +1,23 @@ +// Mirrors the API's phone rule (radapi schemas._normalize_phone): +// 7 digits starting with 9 or 7. A 960/+960 country code is stripped ONLY when the +// number is 10 digits (960 + 7) — a bare 7-digit number like 9601234 is a valid +// local number and is never stripped. + +export const PHONE_ERROR = 'Enter a valid phone number' + +/** Returns the normalized 7-digit local number, or null if invalid. */ +export function normalizePhone(raw: string): string | null { + let digits = (raw ?? '').replace(/\D/g, '') + if (digits.length === 10 && digits.startsWith('960')) { + digits = digits.slice(3) + } + if (digits.length === 7 && (digits[0] === '9' || digits[0] === '7')) { + return digits + } + return null +} + +/** Error message if invalid, else null. */ +export function phoneError(raw: string): string | null { + return normalizePhone(raw) === null ? PHONE_ERROR : null +} diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index f408c89..bf49b0b 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState, type ReactNode } from 'react' import { Loader2, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react' import { toast } from 'sonner' import { api, ApiError, type Device, type DeviceStatus, type Vlan } from '@/lib/api' +import { normalizePhone, phoneError } from '@/lib/phone' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' @@ -187,7 +188,8 @@ function AddDeviceDialog({ } }, [open]) - const valid = mac.trim() && group && name.trim() && phone.trim() + const phoneErr = phone.trim() ? phoneError(phone) : null + const valid = mac.trim() && group && name.trim() && phone.trim() && !phoneErr async function submit() { if (!valid) return @@ -197,7 +199,7 @@ function AddDeviceDialog({ mac_address: mac.trim(), group, name: name.trim(), - phone: phone.trim(), + phone: normalizePhone(phone) ?? phone.trim(), alias: alias.trim() || null, }) toast.success(`Device ${mac.trim()} added`) @@ -233,8 +235,12 @@ function AddDeviceDialog({ setName(e.target.value)} /> - - setPhone(e.target.value)} /> + + setPhone(e.target.value)} + /> @@ -284,8 +290,10 @@ function EditDeviceDialog({ } }, [device]) + const phoneErr = phone.trim() ? phoneError(phone) : 'Phone is required' + async function submit() { - if (!device) return + if (!device || phoneErr) return setBusy(true) try { await api.devices.edit({ @@ -293,7 +301,7 @@ function EditDeviceDialog({ group: group || undefined, status, name, - phone, + phone: normalizePhone(phone) ?? phone, alias, }) toast.success(`Device ${device.mac_address} updated`) @@ -337,8 +345,12 @@ function EditDeviceDialog({ setName(e.target.value)} /> - - setPhone(e.target.value)} /> + + setPhone(e.target.value)} + /> @@ -349,7 +361,7 @@ function EditDeviceDialog({ - @@ -414,10 +426,12 @@ function DeleteDeviceDialog({ function Field({ label, required, + error, children, }: { label: string required?: boolean + error?: string | null children: ReactNode }) { return ( @@ -427,6 +441,7 @@ function Field({ {required && *} {children} + {error &&

{error}

} ) }