完善产品购买页面,抽取公共组件,优化导航链接
This commit is contained in:
BIN
src/app/(auth)/login/_assets/bg.webp
Normal file
BIN
src/app/(auth)/login/_assets/bg.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -1,11 +1,10 @@
|
||||
'use client'
|
||||
import {useState, useCallback, useRef} from 'react'
|
||||
import {useState, useCallback, useRef, useContext} from 'react'
|
||||
import {Input} from '@/components/ui/input'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {Checkbox} from '@/components/ui/checkbox'
|
||||
import {merge} from '@/lib/utils'
|
||||
import Image from 'next/image'
|
||||
import logo from '@/assets/logo.webp'
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
@@ -21,11 +20,13 @@ import {useForm} from 'react-hook-form'
|
||||
import zod from 'zod'
|
||||
import Captcha from './captcha'
|
||||
import verify from '@/actions/auth/verify'
|
||||
import {login} from '@/actions/auth/login'
|
||||
import {login} from '@/actions/auth/auth'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {toast} from 'sonner'
|
||||
import {ApiResponse} from '@/lib/api'
|
||||
import {Label} from '@/components/ui/label'
|
||||
import logo from '@/assets/logo.webp'
|
||||
import bg from './_assets/bg.webp'
|
||||
|
||||
export type LoginPageProps = {}
|
||||
|
||||
@@ -162,7 +163,7 @@ export default function LoginPage(props: LoginPageProps) {
|
||||
|
||||
if (result.success) {
|
||||
// 登录成功
|
||||
toast.success('登陆成功', {
|
||||
toast.success('登录成功', {
|
||||
description: '欢迎回来!',
|
||||
})
|
||||
|
||||
@@ -190,10 +191,12 @@ export default function LoginPage(props: LoginPageProps) {
|
||||
return (
|
||||
<main className={merge(
|
||||
`relative`,
|
||||
`h-screen w-screen xl:pr-64 bg-[url(/login/bg.webp)] bg-cover bg-left`,
|
||||
`h-screen w-screen xl:pr-64 bg-cover bg-left`,
|
||||
`flex justify-center xl:justify-end items-center`,
|
||||
)}>
|
||||
<Image src={logo} alt={`logo`} height={64} className={`absolute top-8 left-8`}/>
|
||||
<Image src={bg} alt={`背景图`} fill priority className={`absolute -z-10 object-cover`}/>
|
||||
|
||||
<Image src={logo} alt={`logo`} priority height={64} className={`absolute top-8 left-8`}/>
|
||||
|
||||
{/* 登录表单 */}
|
||||
<Card className="w-96 mx-4 shadow-lg">
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function UserCenter(props: UserCenterProps) {
|
||||
</Link>
|
||||
</>
|
||||
: <>
|
||||
<Link href={`/admin/dashboard`}>
|
||||
<Link href={`/admin`}>
|
||||
<Button variant={`gradient`}>
|
||||
进入控制台
|
||||
</Button>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
'use client'
|
||||
import {useState} from 'react'
|
||||
|
||||
export function Combo(props: {
|
||||
name: string
|
||||
level?: {
|
||||
number: number
|
||||
discount: number
|
||||
}[]
|
||||
}) {
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<li>
|
||||
<p className={`flex justify-between items-center`}>
|
||||
<span>{props.name}</span>
|
||||
<button
|
||||
className={`text-gray-500 text-sm`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? '收起' : '展开'}
|
||||
</button>
|
||||
</p>
|
||||
{props.level && (
|
||||
<ul className={[
|
||||
`flex flex-col gap-3 overflow-hidden`,
|
||||
`transition-[opacity,padding,max-height] transition-discrete duration-200 ease-in-out`,
|
||||
open
|
||||
? 'delay-[0s, 0s] opacity-100 py-3 max-h-80'
|
||||
: 'delay-[0s, 0.2s] opacity-0 p-0 max-h-0',
|
||||
].join(' ')}>
|
||||
{props.level.map((item, index) => (
|
||||
<li key={index} className={`flex flex-row justify-between items-center`}>
|
||||
<span className={`text-gray-500 text-sm`}>{item.number}</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>赠送 {item.discount} %</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -1,265 +1,19 @@
|
||||
import BreadCrumb from '@/components/bread-crumb'
|
||||
import Wrap from '@/components/wrap'
|
||||
import {Combo} from '@/app/(root)/product/_client/combo'
|
||||
|
||||
import Purchase from '@/components/composites/purchase'
|
||||
|
||||
export type ProductPageProps = {}
|
||||
|
||||
export default function ProductPage(props: ProductPageProps) {
|
||||
return (
|
||||
<main className={`mt-20`}>
|
||||
<Wrap className="flex flex-col py-8 gap-8">
|
||||
<Wrap className="flex flex-col py-8 gap-4">
|
||||
<BreadCrumb items={[
|
||||
{label: '产品中心', href: '/product'},
|
||||
]}/>
|
||||
|
||||
<ul role={`tablist`} className={`flex justify-center items-stretch bg-white rounded-lg`}>
|
||||
<li role={`tab`}>
|
||||
<button className={`h-14 px-8 text-lg`}>短效动态套餐</button>
|
||||
</li>
|
||||
<li role={`tab`}>
|
||||
<button className={`h-14 px-8 text-lg`}>长效静态套餐</button>
|
||||
</li>
|
||||
<li role={`tab`}>
|
||||
<button className={`h-14 px-8 text-lg`}>固定套餐</button>
|
||||
</li>
|
||||
<li role={`tab`}>
|
||||
<button className={`h-14 px-8 text-lg`}>定制套餐</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<section role={`tabpanel`} className={`flex flex-row bg-white rounded-lg`}>
|
||||
<Left/>
|
||||
<Center/>
|
||||
<Right/>
|
||||
</section>
|
||||
<Purchase/>
|
||||
</Wrap>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function Left() {
|
||||
return (
|
||||
<div className="flex-none basis-56 p-8 flex flex-col gap-4">
|
||||
<img src={`/product/banner.webp`} alt={`banner`} className={`w-full`}/>
|
||||
<h3 className={`text-lg`}>包量套餐</h3>
|
||||
<ul className={`flex flex-col gap-3`}>
|
||||
<Combo name={`3分钟`} level={[
|
||||
{number: 30000, discount: 10},
|
||||
{number: 80000, discount: 20},
|
||||
{number: 200000, discount: 30},
|
||||
{number: 450000, discount: 40},
|
||||
{number: 1000000, discount: 50},
|
||||
{number: 1600000, discount: 65},
|
||||
]}/>
|
||||
<Combo name={`5分钟`} level={[
|
||||
{number: 30000, discount: 10},
|
||||
{number: 80000, discount: 20},
|
||||
{number: 200000, discount: 30},
|
||||
{number: 450000, discount: 40},
|
||||
{number: 1000000, discount: 50},
|
||||
{number: 1600000, discount: 65},
|
||||
]}/>
|
||||
<Combo name={`10分钟`} level={[
|
||||
{number: 30000, discount: 10},
|
||||
{number: 80000, discount: 20},
|
||||
{number: 200000, discount: 30},
|
||||
{number: 450000, discount: 40},
|
||||
{number: 1000000, discount: 50},
|
||||
{number: 1600000, discount: 65},
|
||||
]}/>
|
||||
<Combo name={`15分钟`} level={[
|
||||
{number: 30000, discount: 10},
|
||||
{number: 80000, discount: 20},
|
||||
{number: 200000, discount: 30},
|
||||
{number: 450000, discount: 40},
|
||||
{number: 1000000, discount: 50},
|
||||
{number: 1600000, discount: 65},
|
||||
]}/>
|
||||
<Combo name={`30分钟`} level={[
|
||||
{number: 30000, discount: 10},
|
||||
{number: 80000, discount: 20},
|
||||
{number: 200000, discount: 30},
|
||||
{number: 450000, discount: 40},
|
||||
{number: 1000000, discount: 50},
|
||||
{number: 1600000, discount: 65},
|
||||
]}/>
|
||||
</ul>
|
||||
<div className={`border-b border-gray-200`}></div>
|
||||
<h3 className={`text-lg`}>包时套餐</h3>
|
||||
<ul className={`flex flex-col gap-3`}>
|
||||
<li className={`flex justify-between`}>
|
||||
<span className={`text-sm text-gray-500`}>7天</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>9折</span>
|
||||
</li>
|
||||
<li className={`flex justify-between`}>
|
||||
<span className={`text-sm text-gray-500`}>30天</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>8折</span>
|
||||
</li>
|
||||
<li className={`flex justify-between`}>
|
||||
<span className={`text-sm text-gray-500`}>90天</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>7折</span>
|
||||
</li>
|
||||
<li className={`flex justify-between`}>
|
||||
<span className={`text-sm text-gray-500`}>180天</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>6折</span>
|
||||
</li>
|
||||
<li className={`flex justify-between`}>
|
||||
<span className={`text-sm text-gray-500`}>360天</span>
|
||||
<span className={`text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full`}>5折</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Center() {
|
||||
return (
|
||||
<div className={`flex-auto p-8 flex flex-col relative gap-8`}>
|
||||
|
||||
<div className={`space-y-4`}>
|
||||
<h3>计费方式</h3>
|
||||
<div className={`grid grid-cols-2 auto-cols-fr place-items-stretch gap-4`}>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col items-start gap-2 cursor-pointer`}>
|
||||
<h4>包量套餐</h4>
|
||||
<p className={`text-sm text-gray-500`}>适用于短期或不定期高提取业务场景</p>
|
||||
</button>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col items-start gap-2 cursor-pointer`}>
|
||||
<h4>包时套餐</h4>
|
||||
<p className={`text-sm text-gray-500`}>适用于每日提取量稳定的业务场景</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`space-y-4`}>
|
||||
<h3>IP 时效</h3>
|
||||
<div className={`grid grid-cols-5 auto-cols-fr place-items-stretch gap-4`}>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col gap-2 cursor-pointer`}>
|
||||
<span>3 分钟</span>
|
||||
<span className={`text-sm text-gray-500`}>¥0.005/IP</span>
|
||||
</button>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col gap-2 cursor-pointer`}>
|
||||
<span>3 分钟</span>
|
||||
<span className={`text-sm text-gray-500`}>¥0.005/IP</span>
|
||||
</button>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col gap-2 cursor-pointer`}>
|
||||
<span>3 分钟</span>
|
||||
<span className={`text-sm text-gray-500`}>¥0.005/IP</span>
|
||||
</button>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col gap-2 cursor-pointer`}>
|
||||
<span>3 分钟</span>
|
||||
<span className={`text-sm text-gray-500`}>¥0.005/IP</span>
|
||||
</button>
|
||||
<button className={`p-4 bg-blue-50 border border-blue-500 rounded-lg flex flex-col gap-2 cursor-pointer`}>
|
||||
<span>3 分钟</span>
|
||||
<span className={`text-sm text-gray-500`}>¥0.005/IP</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 赠送 IP 数 */}
|
||||
<div className={`space-y-4`}>
|
||||
<h3>赠送IP总数</h3>
|
||||
<div className={`flex gap-4`}>
|
||||
<button className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}>-</button>
|
||||
<input type="number" className={`w-40 h-10 border border-gray-200 rounded-sm`}/>
|
||||
<button className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 产品特性 */}
|
||||
<div className={`space-y-6`}>
|
||||
<h3>产品特性</h3>
|
||||
<div className={`grid grid-cols-3 auto-rows-fr gap-y-6`}>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>支持高并发提取</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>指定省份、城市或混播</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>账密+白名单验证</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>完备的API接口</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>IP时效3-30分钟(可定制)</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>IP资源定期筛选</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>完备的API接口</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>包量/包时计费方式</span>
|
||||
</p>
|
||||
<p className={`flex gap-2 items-center`}>
|
||||
<img src={`/product/check.svg`} alt={`check`} aria-hidden className={`w-4 h-4`}/>
|
||||
<span className={`text-sm text-gray-500`}>每日去重量:500万</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 左右的边框 */}
|
||||
<div className={`absolute inset-0 my-8 border-l border-r border-gray-200 pointer-events-none`}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Right() {
|
||||
return (
|
||||
<div className={`flex-none basis-80 p-8 flex flex-col gap-4`}>
|
||||
<h3>订单详情</h3>
|
||||
<ul className={`flex flex-col gap-4`}>
|
||||
<li className={`flex justify-between items-center`}>
|
||||
<span className={`text-sm text-gray-500`}>套餐名称</span>
|
||||
<span className={`text-sm`}>包量套餐</span>
|
||||
</li>
|
||||
<li className={`flex justify-between items-center`}>
|
||||
<span className={`text-sm text-gray-500`}>套餐时长</span>
|
||||
<span className={`text-sm`}>3分钟</span>
|
||||
</li>
|
||||
<li className={`flex justify-between items-center`}>
|
||||
<span className={`text-sm text-gray-500`}>购买 IP 量</span>
|
||||
<span className={`text-sm`}>1000个</span>
|
||||
</li>
|
||||
<li className={`flex justify-between items-center`}>
|
||||
<span className={`text-sm text-gray-500`}>实到 IP 量</span>
|
||||
<span className={`text-sm`}>1000个</span>
|
||||
</li>
|
||||
<li className={`flex justify-between items-center`}>
|
||||
<span className={`text-sm text-gray-500`}>原价</span>
|
||||
<span className={`text-sm`}>¥50</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className={`border-b border-gray-200`}></div>
|
||||
<p className={`flex justify-between items-center`}>
|
||||
<span>实付价格</span>
|
||||
<span className={`text-xl text-orange-500`}>¥50</span>
|
||||
</p>
|
||||
<div className={`flex gap-4`}>
|
||||
<button className={`flex-1 p-3 bg-blue-50 border border-blue-500 rounded-lg flex justify-center items-center gap-2`}>
|
||||
<img src={`/product/alipay.svg`} alt={`alipay`} className={`w-5 h-5`}/>
|
||||
<span className={`text-sm`}>支付宝</span>
|
||||
</button>
|
||||
<button className={`flex-1 p-3 bg-blue-50 border border-blue-500 rounded-lg flex justify-center items-center gap-2`}>
|
||||
<img src={`/product/wechat.svg`} alt={`wechat`} className={`w-5 h-5`}/>
|
||||
<span className={`text-sm`}>微信</span>
|
||||
</button>
|
||||
</div>
|
||||
<button className={`mt-4 h-12 bg-blue-500 text-white rounded-lg`}>
|
||||
立即支付
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
17
src/app/(temp)/pay/page.tsx
Normal file
17
src/app/(temp)/pay/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import {Button} from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
|
||||
export type PayPageProps = {}
|
||||
|
||||
export default async function PayPage(props: PayPageProps) {
|
||||
return (
|
||||
<div className={`w-screen h-screen items-center justify-start pt-60 flex flex-col gap-8`}>
|
||||
<div className={`w-36 h-36 bg-gray-50 rounded-lg flex items-center justify-center`}>
|
||||
模拟支付页
|
||||
</div>
|
||||
<Link href={'/admin/resources'} className={`p-4 bg-blue-500 text-white rounded-md`}>
|
||||
模拟支付成功
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ export type DashboardPageProps = {}
|
||||
export default async function DashboardPage(props: DashboardPageProps) {
|
||||
|
||||
return (
|
||||
<main className={`flex-auto overflow-hidden grid grid-cols-4 grid-rows-4 p-4 pt-0 gap-4`}>
|
||||
<main className={`flex-auto overflow-hidden grid grid-cols-4 grid-rows-4 gap-4 mr-4 mb-4`}>
|
||||
{/* banner */}
|
||||
<section className={`col-start-1 row-start-1 col-span-3 bg-red-200`}>
|
||||
|
||||
10
src/app/admin/extract/page.tsx
Normal file
10
src/app/admin/extract/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import {ReactNode} from 'react'
|
||||
|
||||
export type ExtractPageProps = {
|
||||
}
|
||||
|
||||
export default async function ExtractPage(props: ExtractPageProps) {
|
||||
return (
|
||||
<main></main>
|
||||
)
|
||||
}
|
||||
BIN
src/app/admin/identify/_assets/banner.webp
Normal file
BIN
src/app/admin/identify/_assets/banner.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 453 KiB |
BIN
src/app/admin/identify/_assets/personal.webp
Normal file
BIN
src/app/admin/identify/_assets/personal.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
39
src/app/admin/identify/page.tsx
Normal file
39
src/app/admin/identify/page.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import {Button} from '@/components/ui/button'
|
||||
import banner from './_assets/banner.webp'
|
||||
import personal from './_assets/personal.webp'
|
||||
import Image from 'next/image'
|
||||
|
||||
export type IdentifyPageProps = {}
|
||||
|
||||
export default async function IdentifyPage(props: IdentifyPageProps) {
|
||||
return (
|
||||
<main className={`flex-auto flex items-stretch gap-4 pb-4 pr-4`}>
|
||||
<div className={`flex-3/4 flex flex-col bg-white rounded-lg overflow-hidden gap-16`}>
|
||||
|
||||
{/* banner */}
|
||||
<section className={`flex-none basis-40 relative flex flex-col gap-4 pl-8 justify-center`}>
|
||||
<Image src={banner} alt={`背景图`} aria-hidden className={`absolute inset-0 w-full h-full object-cover`}/>
|
||||
<h3 className={`text-lg font-bold z-10 relative`}>蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
||||
<p className={`text-sm text-gray-600 z-10 relative`}>为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
||||
</section>
|
||||
|
||||
<div className={`flex-auto flex justify-center items-start`}>
|
||||
{/* 个人认证 */}
|
||||
<section className={`w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center`}>
|
||||
<Image src={personal} alt={`个人认证`}/>
|
||||
<div>
|
||||
<h3 className={`text-center text-lg font-bold`}>个人认证</h3>
|
||||
<p className={`text-sm text-gray-600`}>平台授权支付宝安全认证,不会泄露您的认证信息</p>
|
||||
</div>
|
||||
<Button className={`w-full`}>去认证</Button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className={`flex-1/4 flex flex-col bg-white rounded-lg`}>
|
||||
<h3>操作引导</h3>
|
||||
<p>为响应国家相关规定,使用HTTP代理需完成实名认证。认证服务由支付宝提供,您的个人信息将受到严格保护,仅用于账户安全认证</p>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -2,22 +2,30 @@ import {ReactNode} from 'react'
|
||||
import Image from 'next/image'
|
||||
import logo from '@/assets/logo.webp'
|
||||
import Profile from '@/app/admin/_server/profile'
|
||||
import Wrap from '@/components/wrap'
|
||||
import {merge} from '@/lib/utils'
|
||||
import Link from 'next/link'
|
||||
import {getProfile} from '@/actions/auth/auth'
|
||||
import {redirect} from 'next/navigation'
|
||||
|
||||
export type DashboardLayoutProps = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default async function DashboardLayout(props: DashboardLayoutProps) {
|
||||
|
||||
const profile = await getProfile()
|
||||
if (!profile) {
|
||||
return redirect('/login')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`h-screen flex flex-col overflow-hidden`}>
|
||||
<div className={`h-screen flex flex-col overflow-hidden min-w-7xl overflow-y-hidden relative`}>
|
||||
{/* background */}
|
||||
<div className={`-z-10 relative`}>
|
||||
<div className={`-z-10 absolute inset-0 overflow-hidden`}>
|
||||
<div className={`absolute w-screen h-screen bg-gray-50`}></div>
|
||||
<div className={`absolute w-[2000px] h-[2000px] -left-[1000px] -top-[1000px] bg-radial from-blue-50 from-10% to-transparent to-50%`}></div>
|
||||
<div className={`absolute w-[2000px] h-[2000px] -right-[1000px] -top-[1000px] bg-radial from-blue-50 from-10% to-transparent to-50%`}></div>
|
||||
<div className={`absolute w-[2000px] h-[2000px] left-[calc(50%-1000px)] -bottom-[1000px] bg-radial from-blue-50 from-10% to-transparent to-50%`}></div>
|
||||
</div>
|
||||
|
||||
{/* content */}
|
||||
@@ -43,20 +51,20 @@ export default async function DashboardLayout(props: DashboardLayoutProps) {
|
||||
`flex-none basis-60 rounded-tr-xl bg-white p-4`,
|
||||
`flex flex-col overflow-auto`,
|
||||
)}>
|
||||
<NavItem href={'/admin/dashboard'} icon={`🏠`} label={`账户总览`}/>
|
||||
<NavTitle label={`套餐管理`}/>
|
||||
<NavItem href={`/admin/package`} icon={`🛒`} label={`购买套餐`}/>
|
||||
<NavItem href={`/admin/package/my`} icon={`📦`} label={`我的套餐`}/>
|
||||
<NavTitle label={`IP 管理`}/>
|
||||
<NavItem href={`/admin/ip/extract`} icon={`📤`} label={`IP提取`}/>
|
||||
<NavItem href={`/admin/ip/extract/record`} icon={`📜`} label={`IP提取记录`}/>
|
||||
<NavItem href={`/admin/ip/view`} icon={`👁️`} label={`查看提取IP`}/>
|
||||
<NavItem href={`/admin/ip/view/used`} icon={`🗂️`} label={`查看使用IP`}/>
|
||||
<NavItem href={'/admin'} icon={`🏠`} label={`账户总览`}/>
|
||||
<NavTitle label={`个人信息`}/>
|
||||
<NavItem href={`/admin/profile`} icon={`📝`} label={`个人信息修改`}/>
|
||||
<NavItem href={`/admin/bill`} icon={`💰`} label={`我的账单`}/>
|
||||
<NavItem href={`/admin`} icon={`📝`} label={`修改信息`}/>
|
||||
<NavItem href={`/admin/identify`} icon={`🆔`} label={`实名认证`}/>
|
||||
<NavItem href={`/admin/whitelist`} icon={`🔒`} label={`白名单`}/>
|
||||
<NavItem href={`/admin`} icon={`💰`} label={`我的账单`}/>
|
||||
<NavTitle label={`套餐管理`}/>
|
||||
<NavItem href={`/admin/purchase`} icon={`🛒`} label={`购买套餐`}/>
|
||||
<NavItem href={`/admin`} icon={`📦`} label={`我的套餐`}/>
|
||||
<NavTitle label={`IP 管理`}/>
|
||||
<NavItem href={`/admin/extract`} icon={`📤`} label={`IP提取`}/>
|
||||
<NavItem href={`/admin`} icon={`📜`} label={`IP提取记录`}/>
|
||||
<NavItem href={`/admin`} icon={`👁️`} label={`查看提取IP`}/>
|
||||
<NavItem href={`/admin`} icon={`🗂️`} label={`查看使用IP`}/>
|
||||
</nav>
|
||||
|
||||
{props.children}
|
||||
@@ -85,7 +93,7 @@ function NavItem(props: {
|
||||
`px-4 py-2 flex items-center rounded-md`,
|
||||
`hover:bg-gray-100`,
|
||||
)} href={props.href}>
|
||||
{props.icon}
|
||||
<span className={`w-6 h-6 flex items-center justify-center`}>{props.icon}</span>
|
||||
<span className={`ml-2`}>{props.label}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
11
src/app/admin/purchase/page.tsx
Normal file
11
src/app/admin/purchase/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import Purchase from '@/components/composites/purchase'
|
||||
|
||||
export type PurchasePageProps = {}
|
||||
|
||||
export default async function PurchasePage(props: PurchasePageProps) {
|
||||
return (
|
||||
<main className={`flex-auto mb-4 mr-4`}>
|
||||
<Purchase/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
12
src/app/admin/resources/page.tsx
Normal file
12
src/app/admin/resources/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {ReactNode} from 'react'
|
||||
|
||||
export type ResourcesPageProps = {
|
||||
}
|
||||
|
||||
export default async function ResourcesPage(props: ResourcesPageProps) {
|
||||
return (
|
||||
<main>
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
--secondary-foreground: oklch(0.21 0.034 264.665);
|
||||
--muted: oklch(0.967 0.003 264.542);
|
||||
--muted-foreground: oklch(0.551 0.027 264.364);
|
||||
--accent: oklch(0.967 0.003 264.542);
|
||||
--accent-foreground: oklch(0.21 0.034 264.665);
|
||||
--accent: oklch(0.769 0.188 70.08);
|
||||
--accent-foreground: oklch(0.985 0.002 247.839);
|
||||
--destructive: oklch(0.64 0.1597 25);
|
||||
--destructive-foreground: oklch(0.985 0.002 247.839);
|
||||
--border: oklch(0.928 0.006 264.531);
|
||||
|
||||
@@ -3,6 +3,8 @@ import {Metadata} from 'next'
|
||||
import './globals.css'
|
||||
import localFont from 'next/font/local'
|
||||
import {Toaster} from '@/components/ui/sonner'
|
||||
import AuthProvider from '@/components/providers/AuthProvider'
|
||||
import {getProfile} from '@/actions/auth/auth'
|
||||
|
||||
const font = localFont({
|
||||
src: './NotoSansSC-VariableFont_wght.ttf',
|
||||
@@ -13,7 +15,7 @@ export const metadata: Metadata = {
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: ReactNode
|
||||
@@ -21,8 +23,10 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="zh-Cn">
|
||||
<body className={`${font.className}`}>
|
||||
{children}
|
||||
<Toaster position={'top-center'}/>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
<Toaster position={'top-center'} richColors expand/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user