diff --git a/apps/web/src/components/portal-login-form.tsx b/apps/web/src/components/portal-login-form.tsx index a1b81da..9fc9257 100644 --- a/apps/web/src/components/portal-login-form.tsx +++ b/apps/web/src/components/portal-login-form.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, type FormEvent } from 'react' import { useNavigate, useSearch } from '@tanstack/react-router' import { useQueryClient } from '@tanstack/react-query' -import { EyeIcon, EyeOffIcon, FingerprintIcon } from 'lucide-react' +import { EyeIcon, EyeOffIcon, FingerprintIcon, HistoryIcon, ShieldIcon } from 'lucide-react' import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, @@ -35,6 +35,15 @@ import { ApiError } from '@/lib/api-client' import { login, meQueryKey } from '@/queries/auth' import { webauthnLogin, webauthnLoginOptions } from '@/queries/webauthn' import { AuthLogo } from '@/components/auth-logo' +import { Badge } from '@/components/reui/badge' +import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame' +import { IconTile } from '@/components/reui/icon-tile' + +const PERKS = [ + { icon: FingerprintIcon, title: 'Passkey вместо пароля', text: 'Отпечаток, Face ID или ключ безопасности' }, + { icon: ShieldIcon, title: 'Права по приложениям и разделам', text: 'Чтение, запись и администрирование для каждого сервиса' }, + { icon: HistoryIcon, title: 'Журнал входов и изменений', text: 'Кто, когда и откуда заходил в сервисы' }, +] as const export function PortalLoginForm() { const navigate = useNavigate() @@ -153,92 +162,137 @@ export function PortalLoginForm() { } } + const returnHost = (() => { + try { + return search.return_to ? new URL(search.return_to).host : null + } catch { + return null + } + })() + return ( -
-
-
- -
-

- Auth Portal -

-

- Единый вход в приложения shnt.top -

+
+
+
+
+ +
+ Auth Portal + auth.shnt.top +
+

+ Один вход для всех сервисов shnt.top +

+

+ Войдите один раз — и открывайте CFDM, VPS Tracker, EvoBGP, EvoFirewall, CDN Manager, MikrotikManager и + Technitium DNS без повторного ввода пароля. +

+ + + {PERKS.map((perk) => ( +
+ + + +
+ {perk.title} + {perk.text} +
+
+ ))} +
+
- {error ? ( - - Ошибка входа - {error} - - ) : null} + + + Вход в портал + Введите email и пароль или используйте passkey + + + {returnHost ? ( +
+ + SSO + + + После входа вы вернётесь в {returnHost} + +
+ ) : null} -
- - - Email - - + {error ? ( + + Ошибка входа + {error} + + ) : null} - - Пароль - - - - setShowPassword((v) => !v)} - > - {showPassword ? : } - - - - - + + + + Email + + - - + + Пароль + + + + setShowPassword((v) => !v)} + > + {showPassword ? : } + + + + +
- {passkeySupported ? ( -
-
- - или - -
- -
- ) : null} + + + + {passkeySupported ? ( + <> +
+ + или + +
+ + + Пароль остаётся запасным входом. Passkey добавляется в разделе «Безопасность». + + + ) : null} +
+
)