# Auth Page Redesign — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Минималистичный редизайн страницы входа: Outfit локально, BearLogo, Paper-карточка, pill-кнопки, радиальный градиент. **Architecture:** MUI sx prop only, замена `client/src/pages/auth/ui/AuthPage.tsx` полностью. Шрифт в `client/public/fonts/` + `@font-face` в `global.css`. **Tech Stack:** React, MUI, lucide-react (иконки) --- ### Task 1: Download Outfit font and add @font-face **Files:** - Create: `client/public/fonts/Outfit-Regular.woff2` - Create: `client/public/fonts/Outfit-Medium.woff2` - Create: `client/public/fonts/Outfit-SemiBold.woff2` - Create: `client/public/fonts/Outfit-Bold.woff2` - Modify: `client/src/app/styles/global.css` - [ ] **Step 1: Create fonts directory** ```bash mkdir -p /mnt/d/my_projects/shop/client/public/fonts ``` - [ ] **Step 2: Download Outfit woff2 files** ```bash cd /mnt/d/my_projects/shop/client/public/fonts curl -sL 'https://fonts.google.com/download?family=Outfit' -o outfit.zip # OR download individual woff2 files from a CDN: curl -sL 'https://cdn.jsdelivr.net/fontsource/fonts/outfit@latest/latin-400-normal.woff2' -o Outfit-Regular.woff2 curl -sL 'https://cdn.jsdelivr.net/fontsource/fonts/outfit@latest/latin-500-normal.woff2' -o Outfit-Medium.woff2 curl -sL 'https://cdn.jsdelivr.net/fontsource/fonts/outfit@latest/latin-600-normal.woff2' -o Outfit-SemiBold.woff2 curl -sL 'https://cdn.jsdelivr.net/fontsource/fonts/outfit@latest/latin-700-normal.woff2' -o Outfit-Bold.woff2 ``` Wait — the jsdelivr URLs may not be exact. Better approach: use `@fontsource/outfit` npm package or download from fontsource CDN: ```bash cd /mnt/d/my_projects/shop/client/public/fonts # Outfit Regular (400) curl -sLo Outfit-Regular.woff2 'https://cdn.jsdelivr.net/npm/@fontsource/outfit@5/files/outfit-latin-400-normal.woff2' # Outfit Medium (500) curl -sLo Outfit-Medium.woff2 'https://cdn.jsdelivr.net/npm/@fontsource/outfit@5/files/outfit-latin-500-normal.woff2' # Outfit SemiBold (600) curl -sLo Outfit-SemiBold.woff2 'https://cdn.jsdelivr.net/npm/@fontsource/outfit@5/files/outfit-latin-600-normal.woff2' # Outfit Bold (700) curl -sLo Outfit-Bold.woff2 'https://cdn.jsdelivr.net/npm/@fontsource/outfit@5/files/outfit-latin-700-normal.woff2' ``` - [ ] **Step 3: Verify files downloaded** ```bash ls -la /mnt/d/my_projects/shop/client/public/fonts/ ``` Expected: 4 woff2 files, each > 10KB. - [ ] **Step 4: Add @font-face to global.css** Read `/mnt/d/my_projects/shop/client/src/app/styles/global.css`. It currently has: ```css :root { color-scheme: light; } html, body, #root { min-height: 100%; } body { margin: 0; } ``` Replace entire file with: ```css @font-face { font-family: 'Outfit'; font-style: normal; font-weight: 400; src: url('/fonts/Outfit-Regular.woff2') format('woff2'); font-display: swap; } @font-face { font-family: 'Outfit'; font-style: normal; font-weight: 500; src: url('/fonts/Outfit-Medium.woff2') format('woff2'); font-display: swap; } @font-face { font-family: 'Outfit'; font-style: normal; font-weight: 600; src: url('/fonts/Outfit-SemiBold.woff2') format('woff2'); font-display: swap; } @font-face { font-family: 'Outfit'; font-style: normal; font-weight: 700; src: url('/fonts/Outfit-Bold.woff2') format('woff2'); font-display: swap; } :root { color-scheme: light; } html, body, #root { min-height: 100%; } body { margin: 0; } ``` - [ ] **Step 5: Commit** ```bash cd /mnt/d/my_projects/shop git add client/public/fonts/ client/src/app/styles/global.css git commit -m "feat: load Outfit font from static files" ``` --- ### Task 2: Rewrite AuthPage with new design **Files:** - Modify: `client/src/pages/auth/ui/AuthPage.tsx` (replace entirely) - [ ] **Step 1: Read the current file for reference** Read `/mnt/d/my_projects/shop/client/src/pages/auth/ui/AuthPage.tsx` — keep the imports, hooks and mutation logic. Only the render JSX changes. - [ ] **Step 2: Replace AuthPage.tsx** Write the entire file: ```tsx import { useEffect, useState } from 'react' import { alpha, useTheme } from '@mui/material/styles' import Alert from '@mui/material/Alert' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import InputAdornment from '@mui/material/InputAdornment' import Paper from '@mui/material/Paper' import Stack from '@mui/material/Stack' import TextField from '@mui/material/TextField' import Typography from '@mui/material/Typography' import { useMutation } from '@tanstack/react-query' import { useUnit } from 'effector-react' import { Lock, Mail } from 'lucide-react' import { useForm } from 'react-hook-form' import { useNavigate, useSearchParams } from 'react-router-dom' import { OAuthButtons } from '@/features/auth-oauth' import { apiClient } from '@/shared/api/client' import { $user, tokenSet } from '@/shared/model/auth' import { BearLogo } from '@/shared/ui/BearLogo' type AuthResponse = { token: string user: { id: string email: string displayName?: string | null avatar?: string | null avatarStyle?: string | null } } function getApiErrorMessage(err: unknown): string | null { if (!err || typeof err !== 'object') return null const anyErr = err as Record const response = anyErr.response as Record | undefined const data = response?.data as Record | undefined const msg = data?.error return typeof msg === 'string' ? msg : null } export function AuthPage() { const theme = useTheme() const [message, setMessage] = useState(null) const [oauthError, setOauthError] = useState(null) const [tab, setTab] = useState(0) const [isRegister, setIsRegister] = useState(false) const [searchParams, setSearchParams] = useSearchParams() const navigate = useNavigate() const user = useUnit($user) const { register, watch } = useForm<{ email: string password: string passwordConfirm: string displayName: string code: string }>({ defaultValues: { email: '', password: '', passwordConfirm: '', displayName: '', code: '' }, mode: 'onChange', }) const email = watch('email') const password = watch('password') const passwordConfirm = watch('passwordConfirm') const code = watch('code') useEffect(() => { if (user) navigate('/', { replace: true }) }, [navigate, user]) useEffect(() => { const err = searchParams.get('oauthError') if (!err) return setOauthError(err) setSearchParams({}, { replace: true }) }, [searchParams, setSearchParams]) const loginMutation = useMutation({ mutationFn: async () => { const { data } = await apiClient.post('auth/login', { email, password }) tokenSet(data.token) navigate('/', { replace: true }) }, }) const registerMutation = useMutation({ mutationFn: async () => { const { data } = await apiClient.post('auth/register', { email, password, displayName: watch('displayName') || undefined, }) tokenSet(data.token) navigate('/', { replace: true }) }, }) const requestCode = useMutation({ mutationFn: async () => { await apiClient.post('auth/request-code', { email }) }, onSuccess: () => setMessage('Код отправлен. Проверьте почту (в dev может быть в логах сервера).'), }) const verifyCode = useMutation({ mutationFn: async () => { const { data } = await apiClient.post('auth/verify-code', { email, code }) tokenSet(data.token) navigate('/', { replace: true }) }, }) const errMsg = getApiErrorMessage(loginMutation.error) || getApiErrorMessage(registerMutation.error) || getApiErrorMessage(requestCode.error) || getApiErrorMessage(verifyCode.error) const passwordError = isRegister && passwordConfirm && password !== passwordConfirm ? 'Пароли не совпадают' : null return ( Добро пожаловать в Любимый Креатив Войдите или зарегистрируйтесь, чтобы продолжить {[ { label: 'Пароль', idx: 0 }, { label: 'Код', idx: 1 }, { label: 'Другой способ', idx: 2 }, ].map(({ label, idx }) => ( ))} {(errMsg || oauthError) && ( { setOauthError(null) }} > {errMsg || oauthError} )} {message && ( setMessage(null)}> {message} )} {tab === 0 && ( ), }, }} /> {isRegister && ( )} ), }, }} /> {isRegister && ( )} {isRegister ? ( ) : ( )} )} {tab === 1 && ( ), }, }} /> )} {tab === 2 && ( )} ) } ``` - [ ] **Step 3: Run typecheck** ```bash cd /mnt/d/my_projects/shop/client && npx tsc --noEmit 2>&1 | head -20 ``` Expected: no errors. - [ ] **Step 4: Run tests** ```bash cd /mnt/d/my_projects/shop/client && npx vitest run ``` Expected: all tests pass (7 files, 29 tests). - [ ] **Step 5: Commit** ```bash cd /mnt/d/my_projects/shop git add client/src/pages/auth/ui/AuthPage.tsx git commit -m "feat(client): redesign auth page with minimal style, BearLogo, pill buttons" ``` --- ### Task 3: Run full verification - [ ] **Step 1: Client lint + format + build** ```bash cd /mnt/d/my_projects/shop/client npm run lint npm run format:check npm run build ``` Expected: 0 errors, format clean, build success. - [ ] **Step 2: Server tests (regression check)** ```bash cd /mnt/d/my_projects/shop/server && npx vitest run ``` Expected: all pass (ignore pre-existing user-payments.test.js failures if any). - [ ] **Step 3: Commit if anything changed** ```bash cd /mnt/d/my_projects/shop git add -A git diff --cached --quiet || git commit -m "chore: post-redesign lint fixes" ```