172 lines
6.1 KiB
TypeScript
172 lines
6.1 KiB
TypeScript
import Page from '@/components/page'
|
||
import Image from 'next/image'
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { getProfile } from '@/actions/auth'
|
||
import { format } from 'date-fns'
|
||
import { CheckCircleIcon, CircleAlertIcon } from 'lucide-react'
|
||
import { Button, buttonVariants } from '@/components/ui/button'
|
||
import RechargeModal from '@/components/composites/recharge'
|
||
import { merge } from '@/lib/utils'
|
||
import banner from './_assets/banner.webp'
|
||
import actionBill from './_assets/action-bill.webp'
|
||
import actionBuy from './_assets/action-buy.webp'
|
||
import actionLogout from './_assets/action-logout.webp'
|
||
import Link from 'next/link'
|
||
import { listAnnouncements } from '@/actions/announcement'
|
||
import Charts from './_client/charts'
|
||
import Pins from './_client/pins'
|
||
|
||
export type DashboardPageProps = {}
|
||
|
||
export default function DashboardPage(props: DashboardPageProps) {
|
||
return (
|
||
<Page className={merge(
|
||
`flex-auto grid`,
|
||
`grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_300px]`,
|
||
`grid-rows-[150px_200px_minmax(150px,1fr)_minmax(150px,1fr)]`,
|
||
)}>
|
||
{/* banner */}
|
||
<section className={`col-start-1 row-start-1 col-span-3 relative rounded-lg overflow-hidden`}>
|
||
<Image src={banner} alt={`banner image`} className={`w-full h-full inset-0 absolute object-cover`} />
|
||
<div className={`flex flex-col absolute inset-0 justify-center px-8 gap-1`}>
|
||
<h3 className={`text-2xl text-primary font-medium`}>代理IP资源,先测后买</h3>
|
||
<p className={`text-primary font-medium`}>短效/长效/固定IP代理,一站式服务</p>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 磁贴集 */}
|
||
<Pins />
|
||
|
||
{/* 图表 */}
|
||
<Charts />
|
||
|
||
{/* 信息 */}
|
||
<UserCenter />
|
||
|
||
{/* 通知 */}
|
||
<Announcements />
|
||
|
||
</Page>
|
||
)
|
||
}
|
||
|
||
async function UserCenter() {
|
||
|
||
const resp = await getProfile()
|
||
if (!resp.success) {
|
||
return (
|
||
<div className={`col-start-4 row-start-1 row-span-2 flex justify-center items-center`}>
|
||
获取用户数据失败
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const profile = resp.data
|
||
return (
|
||
<Card className={`col-start-4 row-start-1 row-span-2`}>
|
||
<CardContent className={`flex-auto flex flex-col justify-between`}>
|
||
<div className={`flex flex-col gap-1`}>
|
||
<p>{profile.phone}</p>
|
||
<p className={`text-sm text-weak`}>{`最后登录:${format(profile.last_login, 'yyyy-MM-dd HH:mm')}`}</p>
|
||
</div>
|
||
<div className={merge(
|
||
`flex justify-between p-2 rounded-md`,
|
||
profile.id_token ? `bg-done-muted` : `bg-warn-muted`,
|
||
)}>
|
||
{profile.id_token
|
||
? <>
|
||
<div className={`flex gap-2 items-center`}>
|
||
<CheckCircleIcon size={20} className={`text-done`} />
|
||
<span>已实名</span>
|
||
</div>
|
||
<div className={`flex flex-col items-end`}>
|
||
<span className={`text-sm`}>{profile.name}</span>
|
||
<span className={`text-xs text-weak`}>{profile.id_no}</span>
|
||
</div>
|
||
</>
|
||
: <>
|
||
<span className={`flex gap-2 items-center`}>
|
||
<CircleAlertIcon className={`text-warn`} />
|
||
<span>未实名</span>
|
||
</span>
|
||
<Button className={`h-9`}>去实名</Button>
|
||
</>
|
||
}
|
||
</div>
|
||
<div className={`flex flex-col gap-1`}>
|
||
<h4 className={`text-sm text-weak`}>账户余额</h4>
|
||
<div className={`flex justify-between items-baseline`}>
|
||
<p className={`text-xl text-accent`}>¥{profile.balance}</p>
|
||
<RechargeModal />
|
||
</div>
|
||
</div>
|
||
<div className={`flex flex-col gap-3`}>
|
||
<h4 className={`text-sm text-weak`}>快捷入口</h4>
|
||
<div className={`flex justify-around gap-2`}>
|
||
<Link href="/admin/bills" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
|
||
<Image alt={`bill icon`} src={actionBill} height={48} />
|
||
<span className={`text-sm text-weak`}>我的帐单</span>
|
||
</Link>
|
||
<Link href="/admin/purchase" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
|
||
<Image alt={`buy icon`} src={actionBuy} height={48} />
|
||
<span className={`text-sm text-weak`}>购买产品</span>
|
||
</Link>
|
||
<Link href="/admin/profile" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
|
||
<Image alt={`logout icon`} src={actionLogout} height={48} />
|
||
<span className={`text-sm text-weak`}>个人中心</span>
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
async function Announcements() {
|
||
|
||
const resp = await listAnnouncements({
|
||
page: 1,
|
||
size: 5,
|
||
})
|
||
|
||
if (!resp.success) {
|
||
return (
|
||
<div className={`col-start-4 row-start-3 row-span-2 flex justify-center items-center`}>
|
||
获取公告数据失败
|
||
</div>
|
||
)
|
||
}
|
||
const announcements = resp.data.list
|
||
|
||
return (
|
||
<Card className={`col-start-4 row-start-3 row-span-2`}>
|
||
<CardHeader>
|
||
<div className={`flex justify-between gap-2`}>
|
||
<CardTitle>公告</CardTitle>
|
||
<span className={`text-sm text-weak`}>查看更多</span>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className={`flex-auto p-0`}>
|
||
{announcements.length === 0
|
||
? (
|
||
<div className={`flex flex-col items-center justify-center gap-2 h-full`}>
|
||
{/* <Image alt={`coming soon`} src={soon} />
|
||
<p>暂无公告</p> */}
|
||
</div>
|
||
)
|
||
: announcements.map(item => (
|
||
<div key={item.id} className={merge(
|
||
`transition-colors duration-150 ease-in-out`,
|
||
`flex flex-col gap-1 px-4 py-2`,
|
||
`hover:bg-muted cursor-pointer`,
|
||
)}>
|
||
<h4>{item.title}</h4>
|
||
<p className={`text-sm text-weak`}>{format(item.created_at, 'yyyy-MM-dd HH:mm')}</p>
|
||
</div>
|
||
))
|
||
}
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|