mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +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() {
|
||||
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 [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [emailFocused, setEmailFocused] = useState(false);
|
||||
const [identifierFocused, setIdentifierFocused] = useState(false);
|
||||
const [passwordFocused, setPasswordFocused] = useState(false);
|
||||
|
||||
const [view, setView] = useState<'login' | 'forgot'>('login');
|
||||
@@ -47,7 +49,7 @@ export default function LoginPage() {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
await login(email, password);
|
||||
await login(identifier.trim(), password);
|
||||
router.push('/dashboard');
|
||||
} catch (err: any) {
|
||||
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' }}>
|
||||
|
||||
{/* Email field */}
|
||||
{/* Identifier field — email, phone number, or username */}
|
||||
<div>
|
||||
<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>
|
||||
<div className={`relative rounded-xl transition-all duration-200 ${
|
||||
emailFocused
|
||||
identifierFocused
|
||||
? 'ring-2 ring-[rgb(20,113,76)] ring-offset-0'
|
||||
: 'ring-1 ring-gray-200 dark:ring-gray-800'
|
||||
}`}>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => { setEmail(e.target.value); setError(''); }}
|
||||
onFocus={() => setEmailFocused(true)}
|
||||
onBlur={() => setEmailFocused(false)}
|
||||
type="text"
|
||||
value={identifier}
|
||||
onChange={(e) => { setIdentifier(e.target.value); setError(''); }}
|
||||
onFocus={() => setIdentifierFocused(true)}
|
||||
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"
|
||||
placeholder="name@edr.com"
|
||||
placeholder="name@edr.com, +251… or username"
|
||||
required
|
||||
autoComplete="email"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,7 +214,7 @@ export default function LoginPage() {
|
||||
<div className="flex justify-end mt-2">
|
||||
<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"
|
||||
>
|
||||
Forgot password?
|
||||
@@ -220,9 +225,9 @@ export default function LoginPage() {
|
||||
{/* Submit */}
|
||||
<button
|
||||
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"
|
||||
style={{ background: loading || !email || !password
|
||||
style={{ background: loading || !identifier.trim() || !password
|
||||
? 'rgb(20,113,76)'
|
||||
: `linear-gradient(135deg, rgb(20,113,76) 0%, rgb(16,143,96) 100%)`
|
||||
}}
|
||||
|
||||
@@ -18,7 +18,8 @@ interface AuthState {
|
||||
token: string | null;
|
||||
refreshToken: string | null;
|
||||
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;
|
||||
setUser: (user: AdminUser, token: string) => void;
|
||||
initialize: () => void;
|
||||
@@ -52,9 +53,10 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
// Step 1: IAM login — returns token + refreshToken only
|
||||
const loginRes = await axios.post(`${API_URL}/v1/auth/login`, { email, password });
|
||||
login: async (identifier: string, password: string) => {
|
||||
// Step 1: IAM login — returns token + refreshToken only.
|
||||
// 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 { token, refreshToken } = loginData;
|
||||
if (!token) throw new Error('No token received from server');
|
||||
|
||||
Reference in New Issue
Block a user