mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 16:35:42 +00:00
Merge pull request #562 from Tria-plc/alpha
feat: ( forget password ) add phone number
This commit is contained in:
@@ -29,12 +29,12 @@ export default function LoginPage() {
|
|||||||
const [emailFocused, setEmailFocused] = useState(false);
|
const [emailFocused, setEmailFocused] = useState(false);
|
||||||
const [passwordFocused, setPasswordFocused] = useState(false);
|
const [passwordFocused, setPasswordFocused] = useState(false);
|
||||||
|
|
||||||
const [view, setView] = useState<'login' | 'forgot'>('login');
|
const [view, setView] = useState<'login' | 'forgot'>('login');
|
||||||
const [forgotEmail, setForgotEmail] = useState('');
|
const [forgotIdentifier, setForgotIdentifier] = useState('');
|
||||||
const [forgotLoading, setForgotLoading] = useState(false);
|
const [forgotLoading, setForgotLoading] = useState(false);
|
||||||
const [forgotError, setForgotError] = useState('');
|
const [forgotError, setForgotError] = useState('');
|
||||||
const [forgotSent, setForgotSent] = useState(false);
|
const [forgotSent, setForgotSent] = useState(false);
|
||||||
const [forgotFocused, setForgotFocused] = useState(false);
|
const [forgotFocused, setForgotFocused] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { login } = useAuthStore();
|
const { login } = useAuthStore();
|
||||||
@@ -66,13 +66,13 @@ export default function LoginPage() {
|
|||||||
setForgotLoading(true);
|
setForgotLoading(true);
|
||||||
setForgotError('');
|
setForgotError('');
|
||||||
try {
|
try {
|
||||||
await iamAuthApi.forgotPassword(forgotEmail);
|
await iamAuthApi.forgotPassword(forgotIdentifier.trim());
|
||||||
setForgotSent(true);
|
setForgotSent(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = err.response?.data?.message || err.message || '';
|
const msg = err.response?.data?.message || err.message || '';
|
||||||
setForgotError(
|
setForgotError(
|
||||||
msg === 'user_not_found'
|
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.'
|
: msg || 'Failed to send the reset link. Please try again.'
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -209,7 +209,7 @@ export default function LoginPage() {
|
|||||||
<div className="flex justify-end mt-2">
|
<div className="flex justify-end mt-2">
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="text-xs font-medium text-[rgb(20,113,76)] hover:underline"
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
@@ -260,7 +260,7 @@ export default function LoginPage() {
|
|||||||
Reset your password
|
Reset your password
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||||
Enter your email address and we'll send a reset link to the phone number on your account.
|
Enter your email or phone number and we'll send a reset link to the phone number on your account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -295,10 +295,10 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleForgotSubmit} className="space-y-4 animate-fade-up" style={{ animationDelay: '80ms' }}>
|
<form onSubmit={handleForgotSubmit} className="space-y-4 animate-fade-up" style={{ animationDelay: '80ms' }}>
|
||||||
{/* Email field */}
|
{/* Email or phone field */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<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>
|
</label>
|
||||||
<div className={`relative rounded-xl transition-all duration-200 ${
|
<div className={`relative rounded-xl transition-all duration-200 ${
|
||||||
forgotFocused
|
forgotFocused
|
||||||
@@ -306,15 +306,15 @@ export default function LoginPage() {
|
|||||||
: 'ring-1 ring-gray-200 dark:ring-gray-800'
|
: 'ring-1 ring-gray-200 dark:ring-gray-800'
|
||||||
}`}>
|
}`}>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="text"
|
||||||
value={forgotEmail}
|
value={forgotIdentifier}
|
||||||
onChange={(e) => { setForgotEmail(e.target.value); setForgotError(''); }}
|
onChange={(e) => { setForgotIdentifier(e.target.value); setForgotError(''); }}
|
||||||
onFocus={() => setForgotFocused(true)}
|
onFocus={() => setForgotFocused(true)}
|
||||||
onBlur={() => setForgotFocused(false)}
|
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"
|
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
|
required
|
||||||
autoComplete="email"
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -322,9 +322,9 @@ export default function LoginPage() {
|
|||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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"
|
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)'
|
? 'rgb(20,113,76)'
|
||||||
: `linear-gradient(135deg, rgb(20,113,76) 0%, rgb(16,143,96) 100%)`
|
: `linear-gradient(135deg, rgb(20,113,76) 0%, rgb(16,143,96) 100%)`
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000';
|
|||||||
|
|
||||||
|
|
||||||
export const iamAuthApi = {
|
export const iamAuthApi = {
|
||||||
forgotPassword: (email: string) =>
|
// `identifier` can be an email address or a phone number — the IAM accepts
|
||||||
axios.post(`${API_URL}/v1/auth/forgot-password`, { email }),
|
// 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: {
|
resetPassword: (data: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Train, MailCheck, ArrowLeft } from 'lucide-react';
|
|||||||
import { iamAuthApi } from '@/lib/api/auth';
|
import { iamAuthApi } from '@/lib/api/auth';
|
||||||
|
|
||||||
export default function ForgotPasswordPage() {
|
export default function ForgotPasswordPage() {
|
||||||
const [email, setEmail] = useState('');
|
const [identifier, setIdentifier] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [sent, setSent] = useState(false);
|
const [sent, setSent] = useState(false);
|
||||||
@@ -16,13 +16,13 @@ export default function ForgotPasswordPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
await iamAuthApi.forgotPassword(email);
|
await iamAuthApi.forgotPassword(identifier.trim());
|
||||||
setSent(true);
|
setSent(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = err.response?.data?.message || err.message || '';
|
const msg = err.response?.data?.message || err.message || '';
|
||||||
setError(
|
setError(
|
||||||
msg === 'user_not_found'
|
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.'
|
: msg || 'Failed to send the reset link. Please try again.'
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -41,7 +41,7 @@ export default function ForgotPasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Reset your password</h1>
|
<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">
|
<p className="text-gray-600 dark:text-gray-400 mt-2">
|
||||||
Enter your email and we'll send a reset link to the phone number on your account.
|
Enter your email or phone number and we'll send a reset link to the phone number on your account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,19 +69,19 @@ export default function ForgotPasswordPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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
|
<input
|
||||||
type="email"
|
type="text"
|
||||||
value={email}
|
value={identifier}
|
||||||
onChange={(e) => { setEmail(e.target.value); setError(''); }}
|
onChange={(e) => { setIdentifier(e.target.value); setError(''); }}
|
||||||
className="input-field"
|
className="input-field"
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com or +251..."
|
||||||
autoComplete="email"
|
autoComplete="username"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</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'}
|
{loading ? 'Sending...' : 'Send reset link'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -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
|
// current password is wrong on change-password, and OTP failures must surface
|
||||||
// as inline errors, not a logout.
|
// as inline errors, not a logout.
|
||||||
export const iamAuthApi = {
|
export const iamAuthApi = {
|
||||||
forgotPassword: (email: string) =>
|
// `identifier` can be an email address or a phone number — the IAM accepts
|
||||||
axios.post(`${API_URL}/v1/auth/forgot-password`, { email }),
|
// 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.
|
// Re-sends the registration verification code for a still-pending account.
|
||||||
resendRegistrationCode: (data: { email: string; phoneNumber: string }) =>
|
resendRegistrationCode: (data: { email: string; phoneNumber: string }) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user