mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
fix: ( backoffice ) accept phone or username in the login identifier field
This commit is contained in:
@@ -20,13 +20,15 @@ const features = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState('');
|
// Accepts an email address, phone number, or username — sent to the IAM in
|
||||||
|
// the `email` field either way (the backend contract does not change).
|
||||||
|
const [identifier, setIdentifier] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const [emailFocused, setEmailFocused] = useState(false);
|
const [identifierFocused, setIdentifierFocused] = 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');
|
||||||
@@ -47,7 +49,7 @@ export default function LoginPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
await login(email, password);
|
await login(identifier.trim(), password);
|
||||||
router.push('/dashboard');
|
router.push('/dashboard');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = err.message || err.response?.data?.message || '';
|
const msg = err.message || err.response?.data?.message || '';
|
||||||
@@ -152,26 +154,29 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 animate-fade-up" style={{ animationDelay: '80ms' }}>
|
<form onSubmit={handleSubmit} className="space-y-4 animate-fade-up" style={{ animationDelay: '80ms' }}>
|
||||||
|
|
||||||
{/* Email field */}
|
{/* Identifier field — email, phone number, or username */}
|
||||||
<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, phone or username
|
||||||
</label>
|
</label>
|
||||||
<div className={`relative rounded-xl transition-all duration-200 ${
|
<div className={`relative rounded-xl transition-all duration-200 ${
|
||||||
emailFocused
|
identifierFocused
|
||||||
? 'ring-2 ring-[rgb(20,113,76)] ring-offset-0'
|
? 'ring-2 ring-[rgb(20,113,76)] ring-offset-0'
|
||||||
: '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={email}
|
value={identifier}
|
||||||
onChange={(e) => { setEmail(e.target.value); setError(''); }}
|
onChange={(e) => { setIdentifier(e.target.value); setError(''); }}
|
||||||
onFocus={() => setEmailFocused(true)}
|
onFocus={() => setIdentifierFocused(true)}
|
||||||
onBlur={() => setEmailFocused(false)}
|
onBlur={() => setIdentifierFocused(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, +251… or username"
|
||||||
required
|
required
|
||||||
autoComplete="email"
|
autoCapitalize="none"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck={false}
|
||||||
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +214,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'); setForgotIdentifier(email); setError(''); }}
|
onClick={() => { setView('forgot'); setForgotIdentifier(identifier); 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?
|
||||||
@@ -220,9 +225,9 @@ export default function LoginPage() {
|
|||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !email || !password}
|
disabled={loading || !identifier.trim() || !password}
|
||||||
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: loading || !email || !password
|
style={{ background: loading || !identifier.trim() || !password
|
||||||
? '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%)`
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ interface AuthState {
|
|||||||
token: string | null;
|
token: string | null;
|
||||||
refreshToken: string | null;
|
refreshToken: string | null;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
login: (email: string, password: string) => Promise<void>;
|
/** `identifier` may be an email, phone number or username — always sent as `email`. */
|
||||||
|
login: (identifier: string, password: string) => Promise<void>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
setUser: (user: AdminUser, token: string) => void;
|
setUser: (user: AdminUser, token: string) => void;
|
||||||
initialize: () => void;
|
initialize: () => void;
|
||||||
@@ -52,9 +53,10 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
login: async (email: string, password: string) => {
|
login: async (identifier: string, password: string) => {
|
||||||
// Step 1: IAM login — returns token + refreshToken only
|
// Step 1: IAM login — returns token + refreshToken only.
|
||||||
const loginRes = await axios.post(`${API_URL}/v1/auth/login`, { email, password });
|
// The IAM accepts an email, phone number or username in the `email` field.
|
||||||
|
const loginRes = await axios.post(`${API_URL}/v1/auth/login`, { email: identifier, password });
|
||||||
const loginData = loginRes.data?.data ?? loginRes.data;
|
const loginData = loginRes.data?.data ?? loginRes.data;
|
||||||
const { token, refreshToken } = loginData;
|
const { token, refreshToken } = loginData;
|
||||||
if (!token) throw new Error('No token received from server');
|
if (!token) throw new Error('No token received from server');
|
||||||
|
|||||||
Reference in New Issue
Block a user