feat: support phone number input for password recovery and move route to public access

This commit is contained in:
estifanos
2026-08-20 11:08:03 +00:00
parent 12daba2951
commit 2c8895fed8
2 changed files with 22 additions and 12 deletions

View File

@@ -70,6 +70,10 @@ export const router = createBrowserRouter([
{ path: "/set-password", element: <SetPasswordPage /> }, { path: "/set-password", element: <SetPasswordPage /> },
{ path: "/reset-password", element: <SetPasswordPage /> }, { path: "/reset-password", element: <SetPasswordPage /> },
// Reached from the login page by a logged-out user, so it must stay
// public — ProtectedRoute would bounce them straight to the landing page.
{ path: "/forgot-password", element: <ForgotPasswordPage /> },
// Protected auth pages // Protected auth pages
{ {
element: ( element: (
@@ -79,14 +83,6 @@ export const router = createBrowserRouter([
), ),
path: "/otp-verify", path: "/otp-verify",
}, },
{
element: (
<ProtectedRoute>
<ForgotPasswordPage />
</ProtectedRoute>
),
path: "/forgot-password",
},
// The two-step setup wizard is gone. Signing up lands on the dashboard, and // The two-step setup wizard is gone. Signing up lands on the dashboard, and
// profile details are collected where they are actually needed: on /profile, // profile details are collected where they are actually needed: on /profile,
// via the dashboard nudge, or inline in an application flow. The path stays // via the dashboard nudge, or inline in an application flow. The path stays

View File

@@ -23,11 +23,25 @@ import { z } from 'zod';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api'; import { useApiMutation } from '@ema-platform/api';
import { notify, useErrorHandler } from '@ema-platform/ui'; import { notify, useErrorHandler } from '@ema-platform/ui';
import { isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
import { AuthShell } from '../components/AuthShell'; import { AuthShell } from '../components/AuthShell';
import { useAuthConfig } from '../AuthConfig'; import { useAuthConfig } from '../AuthConfig';
// Same email-or-phone rule as LoginPage: a phone-looking value normalizes to
// E.164 (bare Ethiopian national numbers default to +251) so the backend
// always gets a value it can look the account up by, under the `email` key.
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const schema = z.object({ const schema = z.object({
email: z.string().email({ message: 'Enter a valid email' }), email: z
.string()
.trim()
.transform((value) => {
if (emailRegex.test(value)) return value;
return parsePhoneNumberFromString(value, 'ET')?.number ?? value;
})
.refine((value) => emailRegex.test(value) || isValidPhoneNumber(value), {
message: 'Enter a valid email or phone number',
}),
}); });
type FormValues = z.infer<typeof schema>; type FormValues = z.infer<typeof schema>;
@@ -156,8 +170,8 @@ export function ForgotPasswordPage() {
Forgot your password? Forgot your password?
</Title> </Title>
<Text c="dimmed" mt={6}> <Text c="dimmed" mt={6}>
Enter the email linked to your account and we&apos;ll send you a link Enter the email or phone number linked to your account and
to reset your password. we&apos;ll send you a link to reset your password.
</Text> </Text>
</div> </div>
@@ -170,7 +184,7 @@ export function ForgotPasswordPage() {
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Stack gap="md"> <Stack gap="md">
<TextInput <TextInput
label="Email address" label="Email or phone"
placeholder="you@example.com" placeholder="you@example.com"
size="md" size="md"
leftSection={<IconMail size={18} />} leftSection={<IconMail size={18} />}