Merge pull request #562 from Tria-plc/alpha

feat: ( forget password ) add  phone number
This commit is contained in:
Abubeker Yasin
2026-07-09 09:39:40 +03:00
committed by GitHub
4 changed files with 38 additions and 34 deletions

View File

@@ -29,12 +29,12 @@ export default function LoginPage() {
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
const [view, setView] = useState<'login' | 'forgot'>('login');
const [forgotEmail, setForgotEmail] = useState('');
const [forgotLoading, setForgotLoading] = useState(false);
const [forgotError, setForgotError] = useState('');
const [forgotSent, setForgotSent] = useState(false);
const [forgotFocused, setForgotFocused] = useState(false);
const [view, setView] = useState<'login' | 'forgot'>('login');
const [forgotIdentifier, setForgotIdentifier] = useState('');
const [forgotLoading, setForgotLoading] = useState(false);
const [forgotError, setForgotError] = useState('');
const [forgotSent, setForgotSent] = useState(false);
const [forgotFocused, setForgotFocused] = useState(false);
const router = useRouter();
const { login } = useAuthStore();
@@ -66,13 +66,13 @@ export default function LoginPage() {
setForgotLoading(true);
setForgotError('');
try {
await iamAuthApi.forgotPassword(forgotEmail);
await iamAuthApi.forgotPassword(forgotIdentifier.trim());
setForgotSent(true);
} catch (err: any) {
const msg = err.response?.data?.message || err.message || '';
setForgotError(
msg === 'user_not_found'
? 'No account found with that email address.'
? 'No account found with that email or phone number.'
: msg || 'Failed to send the reset link. Please try again.'
);
} finally {
@@ -209,7 +209,7 @@ export default function LoginPage() {
<div className="flex justify-end mt-2">
<button
type="button"
onClick={() => { setView('forgot'); setForgotEmail(email); setError(''); }}
onClick={() => { setView('forgot'); setForgotIdentifier(email); setError(''); }}
className="text-xs font-medium text-[rgb(20,113,76)] hover:underline"
>
Forgot password?
@@ -260,7 +260,7 @@ export default function LoginPage() {
Reset your password
</h2>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Enter your email address and we&apos;ll send a reset link to the phone number on your account.
Enter your email or phone number and we&apos;ll send a reset link to the phone number on your account.
</p>
</div>
@@ -295,10 +295,10 @@ export default function LoginPage() {
)}
<form onSubmit={handleForgotSubmit} className="space-y-4 animate-fade-up" style={{ animationDelay: '80ms' }}>
{/* Email field */}
{/* Email or phone field */}
<div>
<label className="block text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-2">
Email address
Email or phone number
</label>
<div className={`relative rounded-xl transition-all duration-200 ${
forgotFocused
@@ -306,15 +306,15 @@ export default function LoginPage() {
: 'ring-1 ring-gray-200 dark:ring-gray-800'
}`}>
<input
type="email"
value={forgotEmail}
onChange={(e) => { setForgotEmail(e.target.value); setForgotError(''); }}
type="text"
value={forgotIdentifier}
onChange={(e) => { setForgotIdentifier(e.target.value); setForgotError(''); }}
onFocus={() => setForgotFocused(true)}
onBlur={() => setForgotFocused(false)}
className="w-full px-4 py-3 rounded-xl bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-600 text-sm focus:outline-none"
placeholder="name@edr.com"
placeholder="name@edr.com or +251..."
required
autoComplete="email"
autoComplete="username"
/>
</div>
</div>
@@ -322,9 +322,9 @@ export default function LoginPage() {
{/* Submit */}
<button
type="submit"
disabled={forgotLoading || !forgotEmail}
disabled={forgotLoading || !forgotIdentifier.trim()}
className="group w-full mt-2 flex items-center justify-center gap-2 py-3 px-4 rounded-xl font-semibold text-sm text-white transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
style={{ background: forgotLoading || !forgotEmail
style={{ background: forgotLoading || !forgotIdentifier.trim()
? 'rgb(20,113,76)'
: `linear-gradient(135deg, rgb(20,113,76) 0%, rgb(16,143,96) 100%)`
}}

View File

@@ -4,8 +4,10 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';
export const iamAuthApi = {
forgotPassword: (email: string) =>
axios.post(`${API_URL}/v1/auth/forgot-password`, { email }),
// `identifier` can be an email address or a phone number — the IAM accepts
// either in the `email` field of the forgot-password body.
forgotPassword: (identifier: string) =>
axios.post(`${API_URL}/v1/auth/forgot-password`, { email: identifier }),
resetPassword: (data: {
userId: string;

View File

@@ -6,7 +6,7 @@ import { Train, MailCheck, ArrowLeft } from 'lucide-react';
import { iamAuthApi } from '@/lib/api/auth';
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [identifier, setIdentifier] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [sent, setSent] = useState(false);
@@ -16,13 +16,13 @@ export default function ForgotPasswordPage() {
setLoading(true);
setError('');
try {
await iamAuthApi.forgotPassword(email);
await iamAuthApi.forgotPassword(identifier.trim());
setSent(true);
} catch (err: any) {
const msg = err.response?.data?.message || err.message || '';
setError(
msg === 'user_not_found'
? 'No account found with that email address.'
? 'No account found with that email or phone number.'
: msg || 'Failed to send the reset link. Please try again.'
);
} finally {
@@ -41,7 +41,7 @@ export default function ForgotPasswordPage() {
</div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Reset your password</h1>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Enter your email and we&apos;ll send a reset link to the phone number on your account.
Enter your email or phone number and we&apos;ll send a reset link to the phone number on your account.
</p>
</div>
@@ -69,19 +69,19 @@ export default function ForgotPasswordPage() {
)}
<div>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Email</label>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Email or phone number</label>
<input
type="email"
value={email}
onChange={(e) => { setEmail(e.target.value); setError(''); }}
type="text"
value={identifier}
onChange={(e) => { setIdentifier(e.target.value); setError(''); }}
className="input-field"
placeholder="your@email.com"
autoComplete="email"
placeholder="your@email.com or +251..."
autoComplete="username"
required
/>
</div>
<button type="submit" className="btn-primary w-full" disabled={loading || !email}>
<button type="submit" className="btn-primary w-full" disabled={loading || !identifier.trim()}>
{loading ? 'Sending...' : 'Send reset link'}
</button>
</form>

View File

@@ -8,8 +8,10 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';
// current password is wrong on change-password, and OTP failures must surface
// as inline errors, not a logout.
export const iamAuthApi = {
forgotPassword: (email: string) =>
axios.post(`${API_URL}/v1/auth/forgot-password`, { email }),
// `identifier` can be an email address or a phone number — the IAM accepts
// either in the `email` field of the forgot-password body.
forgotPassword: (identifier: string) =>
axios.post(`${API_URL}/v1/auth/forgot-password`, { email: identifier }),
// Re-sends the registration verification code for a still-pending account.
resendRegistrationCode: (data: { email: string; phoneNumber: string }) =>