| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- 'use client'
- import { useEffect, useState } from 'react'
- import { Button, Modal, Input, Table, Spin, Image, App, Typography } from 'antd'
- import type { ColumnsType } from 'antd/es/table'
- import dayjs from 'dayjs'
- interface MintInfo {
- symbol: string
- decimals: number
- logoURI: string
- price: string
- address: string
- }
- interface LPInfo {
- mintA: MintInfo
- mintB: MintInfo
- }
- interface RecordInfo {
- walletAddress: string
- poolAddress: string
- nftMintAddress: string
- positionAddress: string
- pnlUsd: string
- pnlUsdPercent: string
- PriceRange: string
- totalDeposit: string
- earnedUsd: string
- earnedUsdPercent: string
- openTime: number
- bonusInfo?: {
- fromCreatorPositionStatus: number
- fromCreatorPosition: string
- }
- }
- function MyLPPageContent() {
- const { message } = App.useApp()
- const [userAddress, setUserAddress] = useState<string>('')
- const [isModalOpen, setIsModalOpen] = useState(false)
- const [inputValue, setInputValue] = useState<string>('')
- const [lpList, setLpList] = useState<RecordInfo[]>([])
- const [loading, setLoading] = useState(false)
- const [total, setTotal] = useState(0)
- const [page, setPage] = useState(1)
- const [pageSize, setPageSize] = useState(50)
- const [poolMap, setPoolMap] = useState<Record<string, LPInfo>>({})
- const fetchLPDetail = async (positions: RecordInfo[]) => {
- // const newLpList = [...lpList]
- message.loading(`正在查询当前页面仓位详细信息,请稍等...`)
- for (let index = 0; index < positions.length; index++) {
- const position = positions[index]
- const response = await fetch(
- `/api/my-lp/detail?address=${position.positionAddress}`
- )
- const data = await response.json()
- console.log(data, 'data')
- const newLpList = positions.map((item) => {
- if (item.positionAddress === position.positionAddress) {
- return Object.assign(item, data.result.data)
- }
- return item
- })
- setLpList(newLpList)
- }
- message.destroy()
- }
- const fetchLPList = async (adddr: string) => {
- if (!adddr) return
- setLoading(true)
- try {
- const response = await fetch(
- `/api/my-lp?userAddress=${encodeURIComponent(adddr)}&page=${page}&pageSize=${pageSize}`
- )
- const data = await response.json()
- console.log(data, 'data')
- const { positions, total, poolMap } = data.result?.data
- if (data.retCode === 0 && data.result) {
- setLpList(positions as RecordInfo[])
- setTotal(total)
- setPoolMap(poolMap)
- message.success(`查询成功,找到 ${data.result.data.total} 个 LP token`)
- fetchLPDetail(positions)
- } else {
- message.error(data.retMsg || '查询失败')
- setLpList([])
- }
- } catch (error) {
- console.error('查询 LP 失败:', error)
- message.error('查询失败,请检查网络连接')
- setLpList([])
- } finally {
- setLoading(false)
- }
- }
- const handleAddAddress = () => {
- setInputValue(userAddress)
- setIsModalOpen(true)
- }
- const handleAddressChange = () => {
- setInputValue(userAddress)
- setIsModalOpen(true)
- }
- const handleModalOk = () => {
- setUserAddress(inputValue)
- localStorage.setItem('userAddress', inputValue)
- setIsModalOpen(false)
- }
- const handleModalCancel = () => {
- setIsModalOpen(false)
- setInputValue('')
- }
- function getPoolInfo(poolAddress: string) {
- const poolInfo = poolMap[poolAddress]
- if (!poolInfo) {
- return {
- lpToken: '',
- logoURI: [],
- price: [],
- tokenAAddress: '',
- tokenBAddress: '',
- }
- }
- const tokenA = poolInfo.mintA.symbol
- const tokenB = poolInfo.mintB.symbol
- return {
- tokenAAddress: poolInfo.mintA.address,
- tokenBAddress: poolInfo.mintB.address,
- lpToken: `${tokenA}/${tokenB}`,
- logoURI: [poolInfo.mintA.logoURI, poolInfo.mintB.logoURI],
- price: [poolInfo.mintA.price, poolInfo.mintB.price],
- }
- }
- const handleClosePosition = (record: RecordInfo) => {
- console.log(record, 'record')
- }
- const columns: ColumnsType<RecordInfo> = [
- {
- title: 'LP Token',
- dataIndex: 'lpToken',
- key: 'lpToken',
- render: (text: string, record: RecordInfo) => (
- <div className="flex items-center gap-2">
- <span className="inline-flex items-center">
- <Image
- src={getPoolInfo(record.poolAddress).logoURI[0]}
- alt="logo"
- width={20}
- height={20}
- style={{ borderRadius: '50%', marginLeft: 8 }}
- />
- <Image
- src={getPoolInfo(record.poolAddress).logoURI[1]}
- alt="logo"
- width={20}
- height={20}
- style={{ borderRadius: '50%' }}
- />
- </span>
- <span className="font-mono text-sm">
- {getPoolInfo(record.poolAddress).lpToken}
- </span>
- </div>
- ),
- },
- {
- title: '当前价格',
- dataIndex: 'price',
- key: 'price',
- render: (text: string, record: RecordInfo) => (
- <span className="font-mono text-sm">
- ${Number(getPoolInfo(record.poolAddress).price[0]).toFixed(8)}
- </span>
- ),
- },
- {
- title: 'NFT Token Address',
- dataIndex: 'nftMintAddress',
- key: 'nftMintAddress',
- render: (text: string) => (
- <span className="font-mono text-sm">
- {text.slice(0, 6)}...{text.slice(-4)}
- </span>
- ),
- },
- {
- title: 'Total Deposit',
- dataIndex: 'totalDeposit',
- key: 'totalDeposit',
- render: (text: string) => (
- <span className="font-mono text-sm">${Number(text).toFixed(2)}</span>
- ),
- },
- {
- title: 'PNL',
- dataIndex: 'pnlUsd',
- key: 'pnlUsd',
- render: (text: string) => (
- <span className="font-mono text-sm">${Number(text).toFixed(2)}</span>
- ),
- },
- {
- title: 'PNL Percent',
- dataIndex: 'pnlUsd',
- key: 'pnlUsdPercent',
- render: (text: string) => (
- <span className="font-mono text-sm">{Number(text).toFixed(2)}%</span>
- ),
- },
- {
- title: 'Earned',
- dataIndex: 'earnedUsd',
- key: 'pnlUsd',
- render: (text: string) => (
- <span className="font-mono text-sm">${Number(text).toFixed(2)}</span>
- ),
- },
- {
- title: 'Earned Percent',
- dataIndex: 'earnedUsdPercent',
- key: 'earnedUsdPercent',
- render: (text: string) => (
- <span className="font-mono text-sm">{Number(text).toFixed(2)}%</span>
- ),
- },
- {
- title: 'Earned Percent',
- dataIndex: 'earnedUsdPercent',
- key: 'earnedUsdPercent',
- render: (text: string) => (
- <span className="font-mono text-sm">{Number(text).toFixed(2)}%</span>
- ),
- },
- {
- title: 'Bonus',
- dataIndex: 'bonusUsd',
- key: 'bonusUsd',
- render: (text: string) => (
- <span className="font-mono text-sm">${Number(text).toFixed(2)}</span>
- ),
- },
- {
- title: '开仓时间',
- dataIndex: 'openTime',
- key: 'openTime',
- render: (text: string) => (
- <span className="font-mono text-sm">
- {dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
- </span>
- ),
- },
- {
- title: '仓位来源',
- dataIndex: 'bonusInfo',
- key: 'bonusInfo',
- render: (text: string, record: RecordInfo) => (
- <span className="font-mono text-sm">
- {record?.bonusInfo?.fromCreatorPosition ? (
- <span className="text-blue-500 mr-2">复制</span>
- ) : (
- <span className="text-green-500 mr-2">新开</span>
- )}
- {record?.bonusInfo?.fromCreatorPosition ? (
- record?.bonusInfo?.fromCreatorPositionStatus === 0 ? (
- <span className="text-green-500">上级未关仓</span>
- ) : (
- <span className="text-red-500">上级已关仓</span>
- )
- ) : (
- ''
- )}
- </span>
- ),
- },
- {
- title: '操作',
- dataIndex: 'bonusInfo',
- key: 'bonusInfo',
- render: (text: string, record: RecordInfo) => (
- <div className="flex items-center gap-2">
- <Typography.Link
- href={`https://www.byreal.io/en/portfolio?userAddress=${record.walletAddress}&tab=current&positionAddress=${record.positionAddress}&tokenAddress=${getPoolInfo(record.poolAddress).tokenAAddress}&tokenAddress=${getPoolInfo(record.poolAddress).tokenBAddress}`}
- target="_blank"
- >
- 去关仓
- </Typography.Link>
- <Typography.Link onClick={() => handleClosePosition(record)}>
- 快速关仓
- </Typography.Link>
- </div>
- ),
- },
- ]
- useEffect(() => {
- const userAddress = localStorage.getItem('userAddress')
- if (userAddress) {
- setUserAddress(userAddress)
- fetchLPList(userAddress)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
- return (
- <main style={{ padding: '24px' }}>
- <div className="mb-4">
- {userAddress ? (
- <div className="flex items-center gap-2 mb-4">
- <p className="text-lg font-bold">你的地址: {userAddress}</p>
- <Button type="primary" onClick={handleAddressChange}>
- 更换地址
- </Button>
- <Button onClick={() => fetchLPList(userAddress)} loading={loading}>
- 刷新
- </Button>
- </div>
- ) : (
- <Button type="primary" onClick={handleAddAddress}>
- 请先添加地址
- </Button>
- )}
- </div>
- {userAddress && (
- <Spin spinning={loading}>
- <Table
- columns={columns}
- dataSource={lpList}
- rowKey="nftMintAddress"
- pagination={{
- pageSize: pageSize,
- showSizeChanger: true,
- showTotal: (total) => `共 ${total} 条记录`,
- total: total,
- }}
- onChange={(pagination) => {
- setPage(pagination.current || 1)
- setPageSize(pagination.pageSize || 50)
- fetchLPList(userAddress)
- }}
- />
- </Spin>
- )}
- <Modal
- title="请输入你的地址"
- open={isModalOpen}
- onOk={handleModalOk}
- onCancel={handleModalCancel}
- >
- <Input
- placeholder="请输入你的 Solana 地址"
- value={inputValue}
- onChange={(e) => {
- console.log(e.target.value)
- setInputValue(e.target.value)
- }}
- />
- </Modal>
- </main>
- )
- }
- export default function MyLPPage() {
- return <MyLPPageContent />
- }
|