refactor: replace forgot password modal with inline screen, fix Safari iOS animation jank

This commit is contained in:
Fringg
2026-02-11 06:06:22 +03:00
parent d596b05048
commit 2964236cdc

View File

@@ -551,14 +551,101 @@ export default function Login() {
className={`grid transition-[grid-template-rows] duration-300 ease-in-out ${
showEmailForm ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'
}`}
style={{ transform: 'translateZ(0)' }}
>
<div
className={`overflow-hidden transition-opacity duration-300 ${
showEmailForm ? 'opacity-100' : 'opacity-0'
}`}
>
<div className="overflow-hidden">
<div className="space-y-4 pb-1 pt-1">
{/* Login / Register toggle */}
{showForgotPassword ? (
/* Forgot password screen - replaces login/register */
forgotPasswordSent ? (
<div className="space-y-4 text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-2xl bg-success-500/20">
<svg
className="h-6 w-6 text-success-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={1.5}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
/>
</svg>
</div>
<p className="text-sm font-medium text-dark-100">
{t('auth.checkEmail', 'Check your email')}
</p>
<p className="text-xs text-dark-400">
{t(
'auth.passwordResetSent',
'If an account exists with this email, we sent password reset instructions.',
)}
</p>
<button
type="button"
onClick={closeForgotPasswordModal}
className="text-sm text-accent-400 transition-colors hover:text-accent-300"
>
{t('common.back', 'Back')}
</button>
</div>
) : (
<div className="space-y-4">
<p className="text-center text-sm text-dark-400">
{t(
'auth.forgotPasswordHint',
'Enter your email and we will send you instructions to reset your password.',
)}
</p>
<form onSubmit={handleForgotPassword} className="space-y-3">
<div>
<label htmlFor="forgotEmail" className="label">
Email
</label>
<input
id="forgotEmail"
type="email"
value={forgotPasswordEmail}
onChange={(e) => setForgotPasswordEmail(e.target.value)}
placeholder="you@example.com"
className="input"
autoFocus
/>
</div>
{forgotPasswordError && (
<p className="text-sm text-error-400">{forgotPasswordError}</p>
)}
<button
type="submit"
disabled={forgotPasswordLoading}
className="btn-primary w-full py-2.5"
>
{forgotPasswordLoading ? (
<span className="flex items-center justify-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
{t('common.loading')}
</span>
) : (
t('auth.sendResetLink', 'Send reset link')
)}
</button>
</form>
<div className="text-center">
<button
type="button"
onClick={closeForgotPasswordModal}
className="text-sm text-dark-400 transition-colors hover:text-dark-200"
>
{t('common.back', 'Back')}
</button>
</div>
</div>
)
) : (
/* Normal login / register */
<>
<div className="flex rounded-lg bg-dark-800 p-1">
<button
type="button"
@@ -585,7 +672,6 @@ export default function Login() {
</div>
<form className="space-y-3" onSubmit={handleEmailSubmit}>
{/* First name field - only for registration */}
{authMode === 'register' && (
<div>
<label htmlFor="firstName" className="label">
@@ -597,7 +683,10 @@ export default function Login() {
type="text"
autoComplete="given-name"
className="input"
placeholder={t('auth.firstNamePlaceholder', 'Your name (optional)')}
placeholder={t(
'auth.firstNamePlaceholder',
'Your name (optional)',
)}
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
@@ -640,7 +729,6 @@ export default function Login() {
/>
</div>
{/* Confirm password - only for registration */}
{authMode === 'register' && (
<div>
<label htmlFor="confirmPassword" className="label">
@@ -678,7 +766,6 @@ export default function Login() {
</button>
</form>
{/* Verification notice for registration */}
{authMode === 'register' && (
<p className="text-center text-xs text-dark-500">
{t(
@@ -688,7 +775,6 @@ export default function Login() {
</p>
)}
{/* Forgot password link - only for login */}
{authMode === 'login' && (
<div className="text-center">
<button
@@ -700,6 +786,8 @@ export default function Login() {
</button>
</div>
)}
</>
)}
</div>
</div>
</div>
@@ -708,111 +796,6 @@ export default function Login() {
</div>
)}
</div>
{/* Forgot Password Modal */}
{showForgotPassword && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/60" onClick={closeForgotPasswordModal} />
<div className="relative w-full max-w-sm rounded-2xl border border-dark-700 bg-dark-900 p-5">
<button
onClick={closeForgotPasswordModal}
className="absolute right-3 top-3 text-dark-400 transition-colors hover:text-dark-200"
>
<svg
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{forgotPasswordSent ? (
<div className="text-center">
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-success-500/20">
<svg
className="h-7 w-7 text-success-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={1.5}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
/>
</svg>
</div>
<h3 className="mb-2 text-lg font-bold text-dark-50">
{t('auth.checkEmail', 'Check your email')}
</h3>
<p className="mb-4 text-sm text-dark-400">
{t(
'auth.passwordResetSent',
'If an account exists with this email, we sent password reset instructions.',
)}
</p>
<button onClick={closeForgotPasswordModal} className="btn-primary w-full">
{t('common.close', 'Close')}
</button>
</div>
) : (
<>
<h3 className="mb-2 text-lg font-bold text-dark-50">
{t('auth.forgotPassword', 'Forgot password?')}
</h3>
<p className="mb-4 text-sm text-dark-400">
{t(
'auth.forgotPasswordHint',
'Enter your email and we will send you instructions to reset your password.',
)}
</p>
<form onSubmit={handleForgotPassword} className="space-y-3">
<div>
<label htmlFor="forgotEmail" className="label">
Email
</label>
<input
id="forgotEmail"
type="email"
value={forgotPasswordEmail}
onChange={(e) => setForgotPasswordEmail(e.target.value)}
placeholder="you@example.com"
className="input"
autoFocus
/>
</div>
{forgotPasswordError && (
<div className="rounded-xl border border-error-500/30 bg-error-500/10 px-4 py-2.5 text-sm text-error-400">
{forgotPasswordError}
</div>
)}
<button
type="submit"
disabled={forgotPasswordLoading}
className="btn-primary w-full"
>
{forgotPasswordLoading ? (
<span className="flex items-center justify-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
{t('common.loading')}
</span>
) : (
t('auth.sendResetLink', 'Send reset link')
)}
</button>
</form>
</>
)}
</div>
</div>
)}
</div>
);
}