add staking page
This commit is contained in:
139
src/widgets/staking/ui/StakingWidget.tsx
Normal file
139
src/widgets/staking/ui/StakingWidget.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useState } from 'react'
|
||||
import { Notification } from '@shared/ui'
|
||||
import { ChainToggle } from './ChainToggle'
|
||||
import { StakeCard } from './StakeCard'
|
||||
import { PositionsPanel } from './PositionsPanel'
|
||||
import { UnstakeModal } from './UnstakeModal'
|
||||
import {
|
||||
useStake,
|
||||
useUnstake,
|
||||
useClaim,
|
||||
getStakingErrorMessage,
|
||||
type StakeChain,
|
||||
type SolPosition,
|
||||
} from '@features/staking'
|
||||
import { CHAIN_META } from '../model/meta'
|
||||
import styles from './StakingWidget.module.css'
|
||||
|
||||
type ModalState = { kind: 'eth'; amount: string } | { kind: 'sol'; position: SolPosition } | null
|
||||
|
||||
type Toast = { status: 'success' | 'error' | 'info'; message: string } | null
|
||||
|
||||
export function StakingWidget() {
|
||||
const [chain, setChain] = useState<StakeChain>('ETH')
|
||||
const [amount, setAmount] = useState('')
|
||||
const [modal, setModal] = useState<ModalState>(null)
|
||||
const [toast, setToast] = useState<Toast>(null)
|
||||
|
||||
const stakeM = useStake()
|
||||
const unstakeM = useUnstake()
|
||||
const claimM = useClaim()
|
||||
|
||||
function handleChain(next: StakeChain) {
|
||||
setChain(next)
|
||||
setAmount('')
|
||||
}
|
||||
|
||||
function handleStake() {
|
||||
stakeM.mutate(
|
||||
{ chain, amountHuman: amount },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setToast({ status: 'success', message: `Стейкинг ${amount} ${CHAIN_META[chain].symbol} выполнен` })
|
||||
setAmount('')
|
||||
},
|
||||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleConfirm(mode?: 'swap' | 'queue') {
|
||||
if (modal?.kind === 'eth') {
|
||||
const selected = mode ?? 'swap'
|
||||
unstakeM.mutate(
|
||||
{ chain: 'ETH', amountHuman: modal.amount, mode: selected },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setModal(null)
|
||||
setToast({
|
||||
status: 'success',
|
||||
message:
|
||||
selected === 'queue' ? 'Заявка на вывод добавлена в очередь Lido' : 'Вывод stETH → ETH выполнен',
|
||||
})
|
||||
},
|
||||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||||
}
|
||||
)
|
||||
} else if (modal?.kind === 'sol') {
|
||||
unstakeM.mutate(
|
||||
{ chain: 'SOL', stakeAccount: modal.position.stakeAccount },
|
||||
{
|
||||
onSuccess: (res) => {
|
||||
setModal(null)
|
||||
const withdrawn = 'action' in res && res.action === 'withdraw'
|
||||
setToast({
|
||||
status: 'success',
|
||||
message: withdrawn ? 'Средства выведены на кошелёк' : 'Деактивация запущена — вывод через 1–2 эпохи',
|
||||
})
|
||||
},
|
||||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function handleClaim(requestId: string) {
|
||||
claimM.mutate(requestId, {
|
||||
onSuccess: () => setToast({ status: 'success', message: 'ETH забран на кошелёк' }),
|
||||
onError: (e) => setToast({ status: 'error', message: getStakingErrorMessage(e) }),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.widget}>
|
||||
<ChainToggle value={chain} onChange={handleChain} />
|
||||
|
||||
<div className={styles.grid}>
|
||||
<StakeCard
|
||||
chain={chain}
|
||||
amount={amount}
|
||||
onAmountChange={setAmount}
|
||||
onStake={handleStake}
|
||||
isStaking={stakeM.isPending}
|
||||
/>
|
||||
<PositionsPanel
|
||||
chain={chain}
|
||||
onUnstakeEth={(amountHuman) => setModal({ kind: 'eth', amount: amountHuman })}
|
||||
onUnstakeSol={(position) => setModal({ kind: 'sol', position })}
|
||||
onClaim={handleClaim}
|
||||
unstakePending={unstakeM.isPending}
|
||||
claimPending={claimM.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{modal?.kind === 'eth' && (
|
||||
<UnstakeModal
|
||||
chain="ETH"
|
||||
symbol="ETH"
|
||||
amount={modal.amount}
|
||||
pending={unstakeM.isPending}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={() => setModal(null)}
|
||||
/>
|
||||
)}
|
||||
{modal?.kind === 'sol' && (
|
||||
<UnstakeModal
|
||||
chain="SOL"
|
||||
symbol="SOL"
|
||||
amount={modal.position.lamportsHuman}
|
||||
solState={modal.position.state}
|
||||
pending={unstakeM.isPending}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={() => setModal(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{toast && <Notification status={toast.status} message={toast.message} onClose={() => setToast(null)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user