From 45cbfb5ecb194eb9cdcee5a9cf8b4f79c20c1444 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 11 Feb 2026 05:29:19 +0300 Subject: [PATCH] feat: compact login page with collapsible email, icon OAuth row, safe areas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OAuth providers: horizontal icon row with labels instead of stacked full-width buttons - Email form: collapsed by default behind "Войти по Email" pill toggle with smooth CSS Grid animation - Telegram safe areas: use useTelegramSDK() for top/bottom insets with env() CSS fallback - Compact layout: smaller logo (48x48), tighter spacing, fits on one mobile screen - Language switcher offset by safe area to avoid Telegram header overlap - 100dvh for proper Telegram Mini App viewport handling --- src/pages/Login.tsx | 441 +++++++++++++++++++++++++------------------- 1 file changed, 253 insertions(+), 188 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index e3c71bf..8c534e8 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -15,7 +15,7 @@ import { type EmailAuthEnabled, } from '../api/branding'; import { getAndClearReturnUrl } from '../utils/token'; -import { isInTelegramWebApp, getTelegramInitData } from '../hooks/useTelegramSDK'; +import { isInTelegramWebApp, getTelegramInitData, useTelegramSDK } from '../hooks/useTelegramSDK'; import LanguageSwitcher from '../components/LanguageSwitcher'; import TelegramLoginButton from '../components/TelegramLoginButton'; import OAuthProviderIcon from '../components/OAuthProviderIcon'; @@ -54,6 +54,12 @@ export default function Login() { const [forgotPasswordSent, setForgotPasswordSent] = useState(false); const [forgotPasswordLoading, setForgotPasswordLoading] = useState(false); const [forgotPasswordError, setForgotPasswordError] = useState(''); + const [showEmailForm, setShowEmailForm] = useState(() => !!referralCode); + + // Telegram safe area insets + const { safeAreaInset, contentSafeAreaInset } = useTelegramSDK(); + const safeTop = Math.max(safeAreaInset.top, contentSafeAreaInset.top); + const safeBottom = Math.max(safeAreaInset.bottom, contentSafeAreaInset.bottom); // Получаем URL для возврата после авторизации const getReturnUrl = useCallback(() => { @@ -313,27 +319,40 @@ export default function Login() { }; return ( -
+
0 ? `${safeTop + 16}px` : 'calc(1rem + env(safe-area-inset-top, 0px))', + paddingBottom: + safeBottom > 0 ? `${safeBottom + 16}px` : 'calc(1rem + env(safe-area-inset-bottom, 0px))', + }} + > {/* Background gradient */}
{/* Language switcher */} -
+
0 ? `${safeTop + 12}px` : 'calc(12px + env(safe-area-inset-top, 0px))', + }} + >
-
- {/* Logo */} +
+ {/* Logo & branding */}
-
- {/* Always show letter as fallback */} +
+ {/* Letter fallback */} {appLogo} - {/* Logo image with smooth fade-in */} + {/* Logo image */} {branding?.has_custom_logo && logoUrl && ( )}
- {appName &&

{appName}

} -

{t('auth.loginSubtitle')}

+ {appName &&

{appName}

} +

{t('auth.loginSubtitle')}

- {/* Referral Banner - only show when email auth is enabled */} + {/* Referral Banner */} {referralCode && isEmailAuthEnabled && ( -
-
+
+
- {t('auth.referralInvite')} + {t('auth.referralInvite')}
)} @@ -372,9 +391,9 @@ export default function Login() { {/* Check Email Screen */} {registeredEmail ? (
-
+
-

+

{t('auth.checkEmail', 'Check your email')}

-

+

{t('auth.verificationSent', 'We sent a verification link to:')}

-

{registeredEmail}

-

+

{registeredEmail}

+

{t( 'auth.clickLinkToVerify', 'Click the link in the email to verify your account and log in.', @@ -411,31 +430,31 @@ export default function Login() {

) : ( - /* Card */ + /* Main auth card */
{error && ( -
+
{error}
)} {/* Telegram auth section */} -
-

{t('auth.registerHint')}

+
+

{t('auth.registerHint')}

{isLoading && isTelegramWebApp ? ( -
+

{t('auth.authenticating')}

) : isTelegramWebApp && error ? ( -
+
))}
)} - {/* Email auth section - only when enabled */} + {/* Email auth section - collapsible */} {isEmailAuthEnabled && ( <> - {/* Divider */} -
+
- {t('auth.or', 'or')} +
-
- {/* Login / Register toggle */} -
- - + {/* Collapsible email form */} +
+
+
+ {/* Login / Register toggle */} +
+ + +
+ +
+ {/* First name field - only for registration */} + {authMode === 'register' && ( +
+ + setFirstName(e.target.value)} + /> +
+ )} + +
+ + setEmail(e.target.value)} + /> +
+ +
+ + setPassword(e.target.value)} + /> +
+ + {/* Confirm password - only for registration */} + {authMode === 'register' && ( +
+ + setConfirmPassword(e.target.value)} + /> +
+ )} + + +
+ + {/* Verification notice for registration */} + {authMode === 'register' && ( +

+ {t( + 'auth.verificationEmailNotice', + 'After registration, a verification email will be sent to your address', + )} +

+ )} + + {/* Forgot password link - only for login */} + {authMode === 'login' && ( +
+ +
+ )} +
- -
- {/* First name field - only for registration */} - {authMode === 'register' && ( -
- - setFirstName(e.target.value)} - /> -
- )} - -
- - setEmail(e.target.value)} - /> -
- -
- - setPassword(e.target.value)} - /> -
- - {/* Confirm password - only for registration */} - {authMode === 'register' && ( -
- - setConfirmPassword(e.target.value)} - /> -
- )} - - -
- - {/* Verification notice for registration */} - {authMode === 'register' && ( -

- {t( - 'auth.verificationEmailNotice', - 'After registration, a verification email will be sent to your address', - )} -

- )} - - {/* Forgot password link - only for login */} - {authMode === 'login' && ( -
- -
- )}
)} @@ -651,10 +716,10 @@ export default function Login() { {showForgotPassword && (
-
+