From d3c9662f821825df0a625d9578b70b4413820e7d Mon Sep 17 00:00:00 2001 From: rassadin11 Date: Mon, 29 Jun 2026 19:22:24 +0300 Subject: [PATCH] profile refactor --- src/app/providers/GuestRoute.tsx | 3 +- src/app/providers/ProtectedRoute.tsx | 3 +- src/app/providers/RouterProvider.tsx | 3 + src/pages/not-found/index.ts | 1 + .../not-found/ui/NotFoundPage.module.css | 141 ++++++++++++++++++ src/pages/not-found/ui/NotFoundPage.tsx | 78 ++++++++++ src/shared/ui/Spinner/Spinner.module.css | 18 +++ src/shared/ui/Spinner/Spinner.tsx | 11 +- .../wallet-header/ui/WalletHeader.module.css | 66 ++++---- src/widgets/wallet-header/ui/WalletHeader.tsx | 4 - tsconfig.tsbuildinfo | 2 +- 11 files changed, 281 insertions(+), 49 deletions(-) create mode 100644 src/pages/not-found/index.ts create mode 100644 src/pages/not-found/ui/NotFoundPage.module.css create mode 100644 src/pages/not-found/ui/NotFoundPage.tsx diff --git a/src/app/providers/GuestRoute.tsx b/src/app/providers/GuestRoute.tsx index bc2e9fb..e6cd3a1 100644 --- a/src/app/providers/GuestRoute.tsx +++ b/src/app/providers/GuestRoute.tsx @@ -1,13 +1,14 @@ import { Navigate, Outlet, useLocation } from 'react-router-dom' import { useIsAuthenticated } from '@features/auth' import { ROUTES } from '@shared/config/routes' +import { Spinner } from '@shared/ui' export function GuestRoute() { const { isAuthenticated, isLoading } = useIsAuthenticated() const location = useLocation() const from = (location.state as { from?: { pathname: string } })?.from?.pathname ?? ROUTES.WALLET - if (isLoading) return null + if (isLoading) return if (isAuthenticated) return return } diff --git a/src/app/providers/ProtectedRoute.tsx b/src/app/providers/ProtectedRoute.tsx index 595b2d0..4c29fd2 100644 --- a/src/app/providers/ProtectedRoute.tsx +++ b/src/app/providers/ProtectedRoute.tsx @@ -1,12 +1,13 @@ import { Navigate, Outlet, useLocation } from 'react-router-dom' import { useIsAuthenticated } from '@features/auth' import { ROUTES } from '@shared/config/routes' +import { Spinner } from '@shared/ui' export function ProtectedRoute() { const { isAuthenticated, isLoading } = useIsAuthenticated() const location = useLocation() - if (isLoading) return null + if (isLoading) return if (!isAuthenticated) return return } diff --git a/src/app/providers/RouterProvider.tsx b/src/app/providers/RouterProvider.tsx index b3bc0b1..6516f14 100644 --- a/src/app/providers/RouterProvider.tsx +++ b/src/app/providers/RouterProvider.tsx @@ -18,6 +18,7 @@ import { SoglasiePage } from '@pages/soglasie-personalnyh-dannyh' import { ReestryPage } from '@pages/reestr-pd-rkn' import { StakingPage } from '@pages/staking' import { PoolsPage } from '@pages/pools' +import { NotFoundPage } from '@pages/not-found' import { WalletLayout } from '@widgets/wallet-layout' import { ROUTES } from '@shared/config/routes' import { ScrollToTop } from './ScrollToTop' @@ -73,6 +74,8 @@ export function RouterProvider() { } /> } /> + + } /> ) diff --git a/src/pages/not-found/index.ts b/src/pages/not-found/index.ts new file mode 100644 index 0000000..4773243 --- /dev/null +++ b/src/pages/not-found/index.ts @@ -0,0 +1 @@ +export { NotFoundPage } from './ui/NotFoundPage' diff --git a/src/pages/not-found/ui/NotFoundPage.module.css b/src/pages/not-found/ui/NotFoundPage.module.css new file mode 100644 index 0000000..2e524d5 --- /dev/null +++ b/src/pages/not-found/ui/NotFoundPage.module.css @@ -0,0 +1,141 @@ +.page { + position: relative; + min-height: 70vh; + display: flex; + align-items: center; + justify-content: center; + padding: 80px 20px; + overflow: hidden; +} + +/* ---- Falling coins (decorative background) ---- */ +.rain { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 0; +} + +.drop { + position: absolute; + top: -80px; + opacity: 0.55; + animation-name: fall; + animation-timing-function: linear; + animation-iteration-count: infinite; + filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.35)); +} + +@keyframes fall { + 0% { + transform: translateY(-80px) rotate(0deg); + opacity: 0; + } + 10% { + opacity: 0.55; + } + 90% { + opacity: 0.55; + } + 100% { + transform: translateY(85vh) rotate(360deg); + opacity: 0; + } +} + +/* ---- Foreground content ---- */ +.content { + position: relative; + z-index: 1; + text-align: center; + max-width: 520px; +} + +.code { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; +} + +.digit { + font-family: var(--font-mono); + font-size: 160px; + font-weight: 800; + line-height: 1; + color: var(--text-primary); + text-shadow: 0 8px 40px rgba(74, 109, 255, 0.4); +} + +/* The middle "0" is a coin that flips like it's spinning in the air. */ +.coin { + display: inline-flex; + animation: flip 2.6s ease-in-out infinite; + transform-style: preserve-3d; + filter: drop-shadow(0 12px 30px rgba(247, 147, 26, 0.45)); +} + +@keyframes flip { + 0%, + 100% { + transform: rotateY(0deg) translateY(0); + } + 50% { + transform: rotateY(180deg) translateY(-16px); + } +} + +.title { + margin: 36px 0 12px; + font-size: 28px; + font-weight: 700; + color: var(--text-primary); +} + +.subtitle { + margin: 0 0 32px; + font-size: 16px; + line-height: 1.5; + color: var(--text-secondary); +} + +.button { + display: inline-block; + padding: 14px 32px; + border-radius: 12px; + background: var(--interactive); + color: var(--text-primary); + font-size: 16px; + font-weight: 600; + text-decoration: none; + transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.button:hover { + background: var(--highlight); + transform: translateY(-2px); + box-shadow: 0 10px 28px rgba(0, 212, 255, 0.35); +} + +@media (max-width: 600px) { + .digit { + font-size: 96px; + } + + .coin > :global(div) { + width: 88px !important; + height: 88px !important; + font-size: 40px !important; + } + + .title { + font-size: 22px; + } +} + +@media (prefers-reduced-motion: reduce) { + .drop, + .coin { + animation: none; + } +} diff --git a/src/pages/not-found/ui/NotFoundPage.tsx b/src/pages/not-found/ui/NotFoundPage.tsx new file mode 100644 index 0000000..69f333a --- /dev/null +++ b/src/pages/not-found/ui/NotFoundPage.tsx @@ -0,0 +1,78 @@ +import { Link } from 'react-router-dom' +import { Header } from '@widgets/header' +import { Footer } from '@widgets/footer' +import { TokenIcon } from '@shared/ui/TokenIcon' +import { ROUTES } from '@shared/config/routes' +import styles from './NotFoundPage.module.css' + +/** Decorative coin set — purely visual, no live rates. */ +const COINS = [ + { letter: '₿', color: '#f7931a' }, + { letter: 'Ξ', color: '#627eea' }, + { letter: '₮', color: '#26a17b' }, + { letter: '◎', color: '#9945ff' }, + { letter: '$', color: '#2775ca' }, + { letter: '₽', color: '#4a6dff' }, + { letter: 'Ʉ', color: '#00d4ff' }, +] + +/** Pre-baked rain layout so the scene looks the same on every render. */ +const RAIN = [ + { left: 6, size: 34, delay: 0, duration: 7.5 }, + { left: 18, size: 26, delay: 2.1, duration: 9 }, + { left: 29, size: 44, delay: 4.3, duration: 6.5 }, + { left: 41, size: 22, delay: 1.2, duration: 10 }, + { left: 54, size: 38, delay: 3.4, duration: 8 }, + { left: 67, size: 28, delay: 0.6, duration: 9.5 }, + { left: 78, size: 48, delay: 2.8, duration: 7 }, + { left: 88, size: 24, delay: 5, duration: 8.5 }, + { left: 95, size: 32, delay: 1.7, duration: 9.2 }, +] + +export function NotFoundPage() { + return ( + <> +
+
+ + +
+ + +

Такой страницы не существует

+

+ Похоже, курс этого маршрута обнулился. Проверьте адрес или вернитесь на главную. +

+ + + На главную + +
+
+