update signup phone validation to enforce Ethiopian mobile format and fix invoice default balance

This commit is contained in:
marshal
2026-06-17 20:56:40 +03:00
parent 51e0d4897a
commit 1141370767
2 changed files with 40 additions and 16 deletions

View File

@@ -54,7 +54,7 @@ export const InvoicesSection = memo(function InvoicesSection({
Outstanding balance
</Text>
<Text fz={24} fw={800} mt={4} c="edr-text">
{formatCurrency(totalOutstanding || 377500, "ETB")}
{formatCurrency(totalOutstanding || 0, "ETB")}
</Text>
<Group
justify="space-between"

View File

@@ -20,11 +20,22 @@ const passwordRequirements = [
{ label: "One special character", test: (v: string) => /[^A-Za-z0-9]/.test(v) },
] as const;
const ETHIOPIA_COUNTRY_CODE = "+251";
const isValidEthiopianMobile = (value: string) => {
const digits = value.replace(/\D/g, "");
const normalized = digits.startsWith("0") ? digits.slice(1) : digits;
return /^9\d{8}$/.test(normalized);
};
const userSchema = z
.object({
email: z.string().email("Invalid email address"),
countryCode: z.string().min(1, "Country code is required"),
phone: z.string().min(9, "Phone number is too short").max(9, "Phone number is too long"),
countryCode: z.literal(ETHIOPIA_COUNTRY_CODE),
phone: z
.string()
.min(1, "Phone number is required")
.refine(isValidEthiopianMobile, "Enter a valid mobile number (e.g. 0912345678)"),
userType: z.string(),
firstName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }),
lastName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }),
@@ -64,7 +75,7 @@ export default function SignupPage() {
resolver: zodResolver(userSchema),
defaultValues: {
email: "",
countryCode: "+251",
countryCode: ETHIOPIA_COUNTRY_CODE,
phone: "",
userType: userType.individual,
firstName: { en: "", am: "" },
@@ -78,7 +89,8 @@ export default function SignupPage() {
setError(null);
setLoading(true);
try {
const normalizedPhone = data.phone.startsWith("0") ? data.phone.slice(1) : data.phone;
const digits = data.phone.replace(/\D/g, "");
const normalizedPhone = digits.startsWith("0") ? digits.slice(1) : digits;
const payload: SignupPayload = {
email: data.email,
username: data.email,
@@ -168,23 +180,35 @@ export default function SignupPage() {
</div>
<div className="space-y-1.5">
<label className="text-sm font-medium text-gray-800">
<label htmlFor="signup-phone" className="text-sm font-medium text-gray-800">
Phone <span className="text-red-500">*</span>
</label>
<div className="flex items-start gap-2">
<input type="hidden" {...register("countryCode")} />
<div
className={`flex overflow-hidden rounded-xl border bg-white shadow-sm transition-all duration-200 hover:border-gray-300 focus-within:border-primary focus-within:ring-4 focus-within:ring-primary/10 ${
errors.phone ? "border-red-300 focus-within:border-red-400 focus-within:ring-red-100" : "border-gray-200/90"
}`}
>
<span className="flex h-11 shrink-0 items-center border-r border-gray-200/90 bg-gray-50 px-3 text-sm font-medium text-gray-600">
{ETHIOPIA_COUNTRY_CODE}
</span>
<input
id="signup-phone"
type="tel"
inputMode="numeric"
autoComplete="tel-national"
placeholder="0912345678"
maxLength={10}
disabled={loading}
className={`${fieldClass} w-20 text-center`}
{...register("countryCode")}
/>
<input
placeholder="912345678"
disabled={loading}
className={`${fieldClass} flex-1`}
{...register("phone")}
className="h-11 min-w-0 flex-1 border-0 bg-transparent px-4 text-sm text-gray-900 outline-none placeholder:text-gray-400"
{...register("phone", {
onChange: (event) => {
event.target.value = event.target.value.replace(/\D/g, "").slice(0, 10);
},
})}
/>
</div>
{errorText(errors.countryCode?.message ?? errors.phone?.message)}
{errorText(errors.phone?.message)}
</div>
<div className="space-y-1.5">