feat: implement login modal and altcha captcha for registration

- Added a login modal to the home page with mobile and password fields.
- Integrated altcha captcha for the registration process to enhance security.
- Updated the registration submission logic to include altcha payload.
- Improved error handling and user feedback for both login and registration processes.
This commit is contained in:
hamed
2026-07-10 10:55:37 +03:30
parent 10b0743d9a
commit aded526718
7 changed files with 923 additions and 709 deletions
+12 -3
View File
@@ -4,6 +4,7 @@ import { toast } from 'sonner';
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
import { useAuthStore } from '../stores/authStore';
import PwaLoginCard from '../components/ui/PwaLoginCard';
import Altcha from '../components/ui/Altcha';
import { sanitizeMobileInput } from '../lib/utils';
type Mode = 'password' | 'sms' | 'forgot';
@@ -37,6 +38,8 @@ export default function LoginPage() {
const [forgotLoading, setForgotLoading] = useState(false);
const [cooldown, setCooldown] = useState(0);
// آخرین payload حل‌شده‌ی ALTCHA برای مرحله‌ی جاری (یک‌بارمصرف؛ بین مراحل ریست می‌شود).
const [altcha, setAltcha] = useState('');
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => () => { if (timerRef.current) clearInterval(timerRef.current); }, []);
@@ -57,6 +60,7 @@ export default function LoginPage() {
setForgotStep(1); setForgotMobile(''); setForgotCode(''); setForgotUuid('');
setForgotPass(''); setForgotPass2('');
setCooldown(0);
setAltcha('');
if (timerRef.current) { clearInterval(timerRef.current); }
};
@@ -84,10 +88,11 @@ export default function LoginPage() {
const res = await fetch('/api/v1/user/send-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mobile }),
body: JSON.stringify({ mobile, altcha }),
});
const json = await res.json();
if (!res.ok) { toast.error(json?.errors?.[0]?.message ?? 'خطا در ارسال کد'); return; }
setAltcha('');
onSuccess(json.uuid);
startCooldown();
toast.success('کد تأیید ارسال شد');
@@ -117,7 +122,7 @@ export default function LoginPage() {
const lRes = await fetch('/api/v1/user/otp-login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant }),
body: JSON.stringify({ grant, altcha }),
});
const lJson = await lRes.json();
if (!lRes.ok) { toast.error(lJson?.errors?.[0]?.message ?? 'کاربری با این شماره یافت نشد'); return; }
@@ -157,7 +162,7 @@ export default function LoginPage() {
const res = await fetch('/api/v1/user/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant: forgotGrant, new_password: forgotPass }),
body: JSON.stringify({ grant: forgotGrant, new_password: forgotPass, altcha }),
});
const json = await res.json();
if (!res.ok) { toast.error(json?.errors?.[0]?.message ?? 'خطا در تغییر رمز'); return; }
@@ -267,6 +272,7 @@ export default function LoginPage() {
value={smsMobile} onChange={(e) => setSmsMobile(sanitizeMobileInput(e.target.value))}
onKeyDown={(e) => e.key === 'Enter' && handleSmsSend()} />
</div>
<div style={{ marginBottom: 12 }}><Altcha key="sms-send" onVerified={setAltcha} /></div>
<button className="btn primary block" disabled={smsLoading} onClick={handleSmsSend}
style={{ height: 46, fontSize: 15 }}>
{smsLoading ? 'در حال ارسال...' : 'ارسال کد تأیید'}
@@ -285,6 +291,7 @@ export default function LoginPage() {
value={smsCode} onChange={(e) => setSmsCode(e.target.value.replace(/\D/g, ''))}
onKeyDown={(e) => e.key === 'Enter' && handleSmsVerify()} autoFocus />
</div>
<div style={{ marginBottom: 12 }}><Altcha key="sms-login" onVerified={setAltcha} /></div>
<button className="btn primary block" disabled={smsLoading} onClick={handleSmsVerify}
style={{ height: 46, fontSize: 15 }}>
{smsLoading ? 'در حال تأیید...' : 'تأیید و ورود'}
@@ -315,6 +322,7 @@ export default function LoginPage() {
value={forgotMobile} onChange={(e) => setForgotMobile(sanitizeMobileInput(e.target.value))}
onKeyDown={(e) => e.key === 'Enter' && handleForgotSend()} />
</div>
<div style={{ marginBottom: 12 }}><Altcha key="forgot-send" onVerified={setAltcha} /></div>
<button className="btn primary block" disabled={forgotLoading} onClick={handleForgotSend}
style={{ height: 46, fontSize: 15 }}>
{forgotLoading ? 'در حال ارسال...' : 'ارسال کد تأیید'}
@@ -368,6 +376,7 @@ export default function LoginPage() {
<div className="err-text">رمزهای عبور یکسان نیستند</div>
)}
</div>
<div style={{ marginBottom: 12 }}><Altcha key="forgot-reset" onVerified={setAltcha} /></div>
<button className="btn primary block" disabled={forgotLoading} onClick={handleForgotReset}
style={{ height: 46, fontSize: 15 }}>
{forgotLoading ? 'در حال ذخیره...' : 'تغییر رمز عبور'}