14.05.2026 rip

This commit is contained in:
2026-05-14 18:30:57 +03:00
parent 22bb446309
commit 2de30fbde6
14 changed files with 415 additions and 78 deletions

View File

@@ -0,0 +1 @@
export { ReceiveModal } from './ui/ReceiveModal'

View File

@@ -0,0 +1,151 @@
@keyframes dialogIn {
from { opacity: 0; transform: scale(0.96); }
to { opacity: 1; transform: scale(1); }
}
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 16px;
}
.dialog {
background: var(--bg-mid, #151520);
border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
border-radius: 20px;
width: 100%;
max-width: 480px;
animation: dialogIn 0.18s ease;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px 0;
}
.title {
font-size: 17px;
font-weight: 700;
color: var(--text-primary, #fff);
}
.closeBtn {
background: none;
border: none;
color: var(--text-secondary, rgba(255,255,255,0.4));
font-size: 16px;
cursor: pointer;
padding: 4px;
line-height: 1;
transition: color 0.15s;
}
.closeBtn:hover {
color: var(--text-primary, #fff);
}
.body {
padding: 20px 24px 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
.label {
font-size: 13px;
color: var(--text-secondary, rgba(255,255,255,0.5));
font-weight: 500;
}
.fieldRow {
display: flex;
gap: 8px;
}
.addressInput {
flex: 1;
min-width: 0;
background: var(--glass-bg, rgba(255,255,255,0.05));
border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
border-radius: 10px;
color: var(--text-primary, #fff);
font-family: var(--font-mono, monospace);
font-size: 13px;
padding: 10px 14px;
outline: none;
cursor: text;
transition: border-color 0.15s;
}
.addressInput:focus {
border-color: var(--interactive, #4a6dff);
}
.copyBtn {
flex-shrink: 0;
height: 40px;
padding: 0 16px;
background: rgba(0, 196, 140, 0.12);
border: 1px solid rgba(0, 196, 140, 0.3);
color: #00c48c;
border-radius: 10px;
font-size: 13px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: background 0.2s, border-color 0.2s, color 0.2s;
white-space: nowrap;
}
.copyBtn:hover:not(:disabled) {
background: rgba(0, 196, 140, 0.25);
border-color: #00c48c;
}
.copyBtn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.copyBtnDone {
background: rgba(0, 196, 140, 0.25);
border-color: #00c48c;
}
.skeleton {
height: 40px;
border-radius: 10px;
background: linear-gradient(90deg, rgba(255,255,255,0.05) 25%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0.05) 75%);
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.error {
font-size: 13px;
color: #ff4d4d;
margin: 0;
}
@media (max-width: 520px) {
.fieldRow {
flex-direction: column;
}
.copyBtn {
width: 100%;
height: 40px;
}
}

View File

@@ -0,0 +1,96 @@
import { useEffect, useState } from 'react'
import { useWalletAddresses, type Chain } from '@features/wallet'
import styles from './ReceiveModal.module.css'
const CHAIN_LABEL: Record<Chain, string> = {
ETH: 'Ethereum',
BSC: 'BNB Smart Chain',
BTC: 'Bitcoin',
TRX: 'Tron',
SOL: 'Solana',
}
interface ReceiveModalProps {
open: boolean
onClose: () => void
chain: Chain
}
export function ReceiveModal({ open, onClose, chain }: ReceiveModalProps) {
const { data: addresses, isLoading, isError } = useWalletAddresses()
const [copied, setCopied] = useState(false)
useEffect(() => {
if (!open) return
function onKey(e: KeyboardEvent) {
if (e.key === 'Escape') onClose()
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [open, onClose])
useEffect(() => {
if (!open) setCopied(false)
}, [open])
if (!open) return null
const entry = addresses?.find(a => a.chain === chain)
const address = entry?.address ?? ''
function handleCopy() {
if (!address) return
navigator.clipboard.writeText(address).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 2000)
})
}
function handleOverlay(e: React.MouseEvent<HTMLDivElement>) {
if (e.target === e.currentTarget) onClose()
}
return (
<div className={styles.overlay} onMouseDown={handleOverlay}>
<div className={styles.dialog}>
<div className={styles.header}>
<span className={styles.title}>Получить {CHAIN_LABEL[chain]}</span>
<button className={styles.closeBtn} type="button" onClick={onClose} aria-label="Закрыть"></button>
</div>
<div className={styles.body}>
{isLoading && (
<div className={styles.skeleton} />
)}
{isError && (
<p className={styles.error}>Не удалось загрузить адрес. Попробуйте позже.</p>
)}
{!isLoading && !isError && (
<>
<label className={styles.label}>Ваш {chain}-адрес</label>
<div className={styles.fieldRow}>
<input
className={styles.addressInput}
type="text"
readOnly
value={address}
onFocus={e => e.target.select()}
/>
<button
className={`${styles.copyBtn} ${copied ? styles.copyBtnDone : ''}`}
type="button"
onClick={handleCopy}
disabled={!address}
>
{copied ? 'Скопировано!' : 'Копировать'}
</button>
</div>
</>
)}
</div>
</div>
</div>
)
}