Commit b8e34e6e authored by BangNSK's avatar BangNSK

fix: keep verification resend action visible

parent 673afd42
...@@ -11,15 +11,15 @@ export const Login: React.FC = () => { ...@@ -11,15 +11,15 @@ export const Login: React.FC = () => {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [canResendVerification, setCanResendVerification] = useState(false);
const [resending, setResending] = useState(false); const [resending, setResending] = useState(false);
const [resendMessage, setResendMessage] = useState(''); const [resendMessage, setResendMessage] = useState('');
const [resendStatus, setResendStatus] = useState<'success' | 'error' | null>(null);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
setResendMessage(''); setResendMessage('');
setCanResendVerification(false); setResendStatus(null);
setSubmitting(true); setSubmitting(true);
try { try {
await login(email, password); await login(email, password);
...@@ -27,9 +27,11 @@ export const Login: React.FC = () => { ...@@ -27,9 +27,11 @@ export const Login: React.FC = () => {
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);
const code = err.response?.data?.code; const code = err.response?.data?.code;
setCanResendVerification(code === 'USER_INACTIVE'); const isInactiveAccount = code === 'USER_INACTIVE' || err.response?.status === 403;
setError( setError(
code === 'INVALID_CREDENTIALS' isInactiveAccount
? 'Tài khoản chưa được xác thực hoặc đã bị khóa. Bạn có thể gửi lại email xác thực bên dưới.'
: code === 'INVALID_CREDENTIALS'
? 'Tài khoản hoặc mật khẩu không chính xác.' ? 'Tài khoản hoặc mật khẩu không chính xác.'
: err.response?.data?.message || 'Đăng nhập thất bại. Vui lòng kiểm tra lại thông tin.', : err.response?.data?.message || 'Đăng nhập thất bại. Vui lòng kiểm tra lại thông tin.',
); );
...@@ -39,14 +41,23 @@ export const Login: React.FC = () => { ...@@ -39,14 +41,23 @@ export const Login: React.FC = () => {
}; };
const handleResendVerification = async () => { const handleResendVerification = async () => {
if (!email.trim()) {
setResendStatus('error');
setResendMessage('Vui lòng nhập địa chỉ email trước khi gửi lại email xác thực.');
return;
}
setResending(true); setResending(true);
setResendMessage(''); setResendMessage('');
setResendStatus(null);
try { try {
const response = await api.post('/auth/resend-verification', { email }); const response = await api.post('/auth/resend-verification', { email });
setResendStatus('success');
setResendMessage( setResendMessage(
response.data.message || 'Nếu tài khoản chưa được xác thực, email xác thực đã được gửi lại.', response.data.message || 'Nếu tài khoản chưa được xác thực, email xác thực đã được gửi lại.',
); );
} catch (err: any) { } catch (err: any) {
setResendStatus('error');
setResendMessage(err.response?.data?.message || 'Không thể gửi lại email xác thực. Vui lòng thử lại sau.'); setResendMessage(err.response?.data?.message || 'Không thể gửi lại email xác thực. Vui lòng thử lại sau.');
} finally { } finally {
setResending(false); setResending(false);
...@@ -71,21 +82,18 @@ export const Login: React.FC = () => { ...@@ -71,21 +82,18 @@ export const Login: React.FC = () => {
{error && ( {error && (
<div className="rounded-lg bg-rose-50 p-4 text-sm text-rose-700 border border-rose-100" role="alert"> <div className="rounded-lg bg-rose-50 p-4 text-sm text-rose-700 border border-rose-100" role="alert">
<p>{error}</p> <p>{error}</p>
{canResendVerification && (
<button
type="button"
onClick={() => void handleResendVerification()}
disabled={resending}
className="mt-3 inline-flex min-h-11 items-center font-semibold text-indigo-700 hover:text-indigo-900 disabled:opacity-60"
>
{resending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Gửi lại email xác thực
</button>
)}
</div> </div>
)} )}
{resendMessage && ( {resendMessage && (
<div className="rounded-lg border border-emerald-100 bg-emerald-50 p-4 text-sm text-emerald-700" aria-live="polite"> <div
className={`rounded-lg border p-4 text-sm ${
resendStatus === 'error'
? 'border-rose-100 bg-rose-50 text-rose-700'
: 'border-emerald-100 bg-emerald-50 text-emerald-700'
}`}
role={resendStatus === 'error' ? 'alert' : undefined}
aria-live="polite"
>
{resendMessage} {resendMessage}
</div> </div>
)} )}
...@@ -157,6 +165,19 @@ export const Login: React.FC = () => { ...@@ -157,6 +165,19 @@ export const Login: React.FC = () => {
</div> </div>
</form> </form>
<div className="text-center text-sm text-slate-500">
<span>Chưa nhận được email xác thực? </span>
<button
type="button"
onClick={() => void handleResendVerification()}
disabled={resending}
className="inline-flex min-h-11 items-center font-semibold text-indigo-600 hover:text-indigo-500 disabled:cursor-not-allowed disabled:opacity-60"
>
{resending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Gửi lại email xác thực
</button>
</div>
<div className="text-center text-sm text-slate-500"> <div className="text-center text-sm text-slate-500">
Chưa có tài khoản?{' '} Chưa có tài khoản?{' '}
<Link to="/register" className="font-medium text-indigo-600 hover:text-indigo-500 transition-colors"> <Link to="/register" className="font-medium text-indigo-600 hover:text-indigo-500 transition-colors">
......
...@@ -20,6 +20,10 @@ test('login puts forgot-password below the password field and localizes invalid ...@@ -20,6 +20,10 @@ test('login puts forgot-password below the password field and localizes invalid
); );
assert.match(login, /Tài khoản hoặc mật khẩu không chính xác/); assert.match(login, /Tài khoản hoặc mật khẩu không chính xác/);
assert.match(login, /Gửi lại email xác thực/); assert.match(login, /Gửi lại email xác thực/);
assert.ok(
login.indexOf('</form>') < login.indexOf('Gửi lại email xác thực'),
'resend verification must always be visible below the login form',
);
}); });
test('all requested password forms expose visibility controls', () => { test('all requested password forms expose visibility controls', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment