From 01685e3811fea07e354cda8f95039536ce1f60f3 Mon Sep 17 00:00:00 2001 From: rassadin11 Date: Sun, 10 May 2026 18:43:35 +0300 Subject: [PATCH] feat: add window with profile and logout buttons. add notification component --- src/features/auth/api/registrationApi.ts | 4 + .../ui/Notification/Notification.module.css | 108 ++++++++++++++++++ src/shared/ui/Notification/Notification.tsx | 42 +++++++ src/shared/ui/Notification/index.ts | 1 + src/shared/ui/index.ts | 1 + .../wallet-header/ui/WalletHeader.module.css | 45 ++++++++ src/widgets/wallet-header/ui/WalletHeader.tsx | 92 +++++++++++---- 7 files changed, 273 insertions(+), 20 deletions(-) create mode 100644 src/shared/ui/Notification/Notification.module.css create mode 100644 src/shared/ui/Notification/Notification.tsx create mode 100644 src/shared/ui/Notification/index.ts diff --git a/src/features/auth/api/registrationApi.ts b/src/features/auth/api/registrationApi.ts index 9f8c45a..d1d1d84 100644 --- a/src/features/auth/api/registrationApi.ts +++ b/src/features/auth/api/registrationApi.ts @@ -18,3 +18,7 @@ export function registrationStart(payload: RegistrationStartPayload): Promise { return api.post('/auth/registration/complete', payload) } + +export function logout(): Promise { + return api.post('/logout', {}) +} diff --git a/src/shared/ui/Notification/Notification.module.css b/src/shared/ui/Notification/Notification.module.css new file mode 100644 index 0000000..c1685f4 --- /dev/null +++ b/src/shared/ui/Notification/Notification.module.css @@ -0,0 +1,108 @@ +.notification { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 1000; + display: flex; + align-items: center; + gap: 12px; + padding: 16px 18px; + min-width: 280px; + max-width: 360px; + border-radius: 12px; + background: var(--bg-mid); + border: 1px solid var(--glass-border); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + animation: slideIn 0.3s cubic-bezier(0.22, 1, 0.36, 1) forwards; +} + +.notificationWrapper { + display: flex; + gap: 12px; +} + +.notification.closing { + animation: slideOut 0.25s cubic-bezier(0.55, 0, 1, 0.45) forwards; +} + +@keyframes slideIn { + from { + transform: translateX(calc(100% + 24px)); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + + to { + transform: translateX(calc(100% + 24px)); + opacity: 0; + } +} + +.icon { + flex-shrink: 0; + width: 22px; + height: 22px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + margin-top: 1px; +} + +.success .icon { + background: var(--success); + color: #fff; +} + +.error .icon { + background: var(--error); + color: #fff; +} + +.info .icon { + background: var(--interactive); + color: #fff; +} + +.warning .icon { + background: #f59e0b; + color: #fff; +} + +.message { + flex: 1; + font-size: 14px; + color: var(--text-secondary); + line-height: 1.5; + margin: 0; +} + +.close { + flex-shrink: 0; + background: none; + border: none; + color: var(--text-secondary); + font-size: 14px; + cursor: pointer; + padding: 0; + line-height: 1; + margin-top: 2px; + transition: color 0.15s; +} + +.close:hover { + color: var(--text-primary); +} \ No newline at end of file diff --git a/src/shared/ui/Notification/Notification.tsx b/src/shared/ui/Notification/Notification.tsx new file mode 100644 index 0000000..1d5b154 --- /dev/null +++ b/src/shared/ui/Notification/Notification.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react' +import styles from './Notification.module.css' + +type Status = 'success' | 'error' | 'info' | 'warning' + +interface Props { + message: string + status: Status + onClose: () => void +} + +const ICONS: Record = { + success: '✓', + error: '✕', + info: 'i', + warning: '!', +} + +export function Notification({ message, status, onClose }: Props) { + const [closing, setClosing] = useState(false) + + function handleClose() { + setClosing(true) + } + + function handleAnimationEnd() { + if (closing) onClose() + } + + return ( +
+
+ {ICONS[status]} +

{message}

+
+ +
+ ) +} diff --git a/src/shared/ui/Notification/index.ts b/src/shared/ui/Notification/index.ts new file mode 100644 index 0000000..2e2470d --- /dev/null +++ b/src/shared/ui/Notification/index.ts @@ -0,0 +1 @@ +export { Notification } from './Notification' diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 36744a4..0e8b3f4 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -1,5 +1,6 @@ export { Button } from './Button' export { FormField } from './FormField' +export { Notification } from './Notification' export { Pill } from './Pill' export { PrimaryButton } from './PrimaryButton' export { TokenIcon } from './TokenIcon' diff --git a/src/widgets/wallet-header/ui/WalletHeader.module.css b/src/widgets/wallet-header/ui/WalletHeader.module.css index 4726d36..8dadec5 100644 --- a/src/widgets/wallet-header/ui/WalletHeader.module.css +++ b/src/widgets/wallet-header/ui/WalletHeader.module.css @@ -39,10 +39,55 @@ color: #ff4d4d; } +.accountWrapper { + position: relative; +} + .account { display: flex; align-items: center; gap: 10px; + background: none; + border: none; + cursor: pointer; + padding: 0; +} + +.dropdown { + position: absolute; + top: calc(100% + 12px); + right: 0; + background: var(--bg-card, #1a1a2e); + border: 1px solid var(--glass-border); + border-radius: 10px; + overflow: hidden; + min-width: 180px; + z-index: 100; + display: flex; + flex-direction: column; +} + +.dropdownItem { + display: block; + padding: 12px 16px; + font-size: 14px; + color: var(--text-secondary); + text-decoration: none; + background: none; + border: none; + cursor: pointer; + text-align: left; + width: 100%; + transition: background 0.15s, color 0.15s; +} + +.dropdownItem:hover { + background: var(--glass-border); + color: var(--text-primary); +} + +.dropdownItem.danger:hover { + color: var(--error); } .avatar { diff --git a/src/widgets/wallet-header/ui/WalletHeader.tsx b/src/widgets/wallet-header/ui/WalletHeader.tsx index 4f7604b..2eceb24 100644 --- a/src/widgets/wallet-header/ui/WalletHeader.tsx +++ b/src/widgets/wallet-header/ui/WalletHeader.tsx @@ -2,6 +2,11 @@ import logo from '@shared/assets/logo-full-white.png' import styles from './WalletHeader.module.css' import { Link } from 'react-router-dom' import { ROUTES } from '@shared/config/routes' +import { useEffect, useRef, useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { useNavigate } from 'react-router-dom' +import { logout } from '@features/auth/api/registrationApi' +import { Notification } from '@shared/ui' const TICKERS = [ { symbol: 'BTC', price: '$66,916.00', change: 0.12, }, @@ -10,26 +15,73 @@ const TICKERS = [ ] export function WalletHeader() { + const [open, setOpen] = useState(false) + const [error, setError] = useState(false) + const ref = useRef(null) + const navigate = useNavigate() + + const { mutate: logoutMutate } = useMutation({ + mutationFn: logout, + onSuccess: () => navigate(ROUTES.HOME), + onError: () => setError(true), + }) + + useEffect(() => { + function handleClickOutside(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) { + setOpen(false) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) + + function handleLogout() { + logoutMutate() + setOpen(false) + } + return ( - + <> + + {error && ( + setError(false)} + /> + )} + ) }