diff --git a/README.md b/README.md
index f100b80..d5e6de9 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
## TODO
-引入 redis,客户端密钥使用 redis 保存
-
使用 pure js 的包代替 canvas,加快编译速度
提取后刷新提取页套餐可用余量
-保存客户端信息时用 jwt 序列化
+白名单复用表格组件
----
+首次登录弹窗:需要设置初始密码,展示 实名->购买->提取 的流程介绍
+
+提取页表单性能优化,树组件性能优化
+
+后台页面:
+- [ ] 总览
+- [ ] 个人中心
+- [ ] IP 管理
+- [ ] 提取记录
+- [ ] 使用记录
-页面数据:
- - [ ] dashboard
diff --git a/src/app/admin/_assets/logo-mini.webp b/src/app/admin/_assets/logo-mini.webp
new file mode 100644
index 0000000..3a586bb
Binary files /dev/null and b/src/app/admin/_assets/logo-mini.webp differ
diff --git a/src/app/admin/_client/header.tsx b/src/app/admin/_client/header.tsx
new file mode 100644
index 0000000..c60d39a
--- /dev/null
+++ b/src/app/admin/_client/header.tsx
@@ -0,0 +1,42 @@
+'use client'
+import {PanelLeftCloseIcon, PanelLeftOpenIcon} from 'lucide-react'
+import {Button} from '@/components/ui/button'
+import Profile from './profile'
+import {useLayoutStore} from '@/components/providers/StoreProvider'
+import {merge} from '@/lib/utils'
+
+export type HeaderProps = {}
+
+export default function Header(props: HeaderProps) {
+
+ const navbar = useLayoutStore(store => store.navbar)
+ const toggleNavbar = useLayoutStore(store => store.toggleNavbar)
+
+ return (
+
+ {/* left */}
+
+
+
+ 欢迎来到,蓝狐代理
+
+
+
+ {/* right */}
+
+
+ )
+}
diff --git a/src/app/admin/_client/navbar.tsx b/src/app/admin/_client/navbar.tsx
new file mode 100644
index 0000000..a30ebe1
--- /dev/null
+++ b/src/app/admin/_client/navbar.tsx
@@ -0,0 +1,108 @@
+'use client'
+import {ReactNode} from 'react'
+import {merge} from '@/lib/utils'
+import {useLayoutStore} from '@/components/providers/StoreProvider'
+import Link from 'next/link'
+import Image from 'next/image'
+import logo from '@/assets/logo.webp'
+import logoMini from '../_assets/logo-mini.webp'
+
+export type NavbarProps = {}
+
+export default function Navbar(props: NavbarProps) {
+
+ const navbar = useLayoutStore(store => store.navbar)
+
+ return (
+
+ )
+}
+
+function Logo(props: {
+ mini?: boolean
+}) {
+ return (
+
+
+ {props.mini
+ ?
+ :
+ }
+
+
+ )
+}
+
+function NavTitle(props: {
+ label: string
+}) {
+ return (
+
+ {props.label}
+
+
+ )
+}
+
+function NavItem(props: {
+ href: string
+ icon?: ReactNode
+ label: string
+ expand: boolean
+}) {
+ return (
+
+ {props.icon}
+ {props.label}
+
+ )
+}
diff --git a/src/app/admin/_server/profile.tsx b/src/app/admin/_client/profile.tsx
similarity index 100%
rename from src/app/admin/_server/profile.tsx
rename to src/app/admin/_client/profile.tsx
diff --git a/src/app/admin/identify/page.tsx b/src/app/admin/identify/page.tsx
index 86f90a5..fdc17f6 100644
--- a/src/app/admin/identify/page.tsx
+++ b/src/app/admin/identify/page.tsx
@@ -98,7 +98,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {
// ======================
return (
-
+
{/* banner */}
diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx
index 7622064..fdefde8 100644
--- a/src/app/admin/layout.tsx
+++ b/src/app/admin/layout.tsx
@@ -1,11 +1,9 @@
import {ReactNode} from 'react'
-import Image from 'next/image'
-import logo from '@/assets/logo.webp'
-import Profile from '@/app/admin/_server/profile'
import {merge} from '@/lib/utils'
-import Link from 'next/link'
import {redirect} from 'next/navigation'
import {getProfile} from '@/actions/auth/auth'
+import Header from './_client/header'
+import Navbar from '@/app/admin/_client/navbar'
export type DashboardLayoutProps = {
children: ReactNode
@@ -13,88 +11,31 @@ export type DashboardLayoutProps = {
export default async function DashboardLayout(props: DashboardLayoutProps) {
+ // ======================
+ // profile
+ // ======================
+
const user = await getProfile()
if (!user) {
return redirect(`/login?redirect=${encodeURIComponent('/admin')}`)
}
+ // ======================
+ // render
+ // ======================
+
return (
-
- {/* background */}
-
+
- {/* content */}
-
- {/* logo */}
-
-
-
-
- {/* title */}
-
- 欢迎来到,蓝狐代理
-
-
- {/* profile */}
-
-
-
-
-
+
+
+
{props.children}
)
}
-
-function NavTitle(props: {
- label: string
-}) {
- return (
-
- {props.label}
-
- )
-}
-
-function NavItem(props: {
- href: string
- icon?: ReactNode
- label: string
-}) {
- return (
-
-
{props.icon}
-
{props.label}
-
- )
-}
diff --git a/src/app/admin/purchase/page.tsx b/src/app/admin/purchase/page.tsx
index cd99552..f684176 100644
--- a/src/app/admin/purchase/page.tsx
+++ b/src/app/admin/purchase/page.tsx
@@ -5,7 +5,7 @@ export type PurchasePageProps = {}
export default async function PurchasePage(props: PurchasePageProps) {
return (
-
+
)
diff --git a/src/app/globals.css b/src/app/globals.css
index 082e65e..9cdd582 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -28,7 +28,7 @@
--fail: oklch(0.65 0.16 25);
--fail-text: oklch(1 0 0);
- --card: oklch(0.985 0 0);
+ --card: oklch(1 0 0);
--card-text: oklch(0.25 0 0);
--popover: oklch(1 0 0);
diff --git a/src/components/page.tsx b/src/components/page.tsx
index c070e53..1893c3c 100644
--- a/src/components/page.tsx
+++ b/src/components/page.tsx
@@ -8,14 +8,25 @@ export type PageProps = {
export default function Page(props: ComponentProps<'main'> & PageProps) {
return (
- {props.children}
+
+ {/* background */}
+
+
+ {/* content */}
+
+ {props.children}
+
)
}
diff --git a/src/components/providers/StoreProvider.tsx b/src/components/providers/StoreProvider.tsx
index 59b9482..d7d1ec1 100644
--- a/src/components/providers/StoreProvider.tsx
+++ b/src/components/providers/StoreProvider.tsx
@@ -1,13 +1,15 @@
'use client'
import {User} from '@/lib/models'
import {createContext, ReactNode, useContext, useRef} from 'react'
-import {createProfileStore, ProfileStore} from '@/stores/profile-store'
import {StoreApi} from 'zustand/vanilla'
import {useStore} from 'zustand/react'
+import {createProfileStore, ProfileStore} from '@/stores/profile'
+import {createLayoutStore, LayoutStore} from '@/stores/layout'
export type StoreContextType = {
profile: StoreApi
+ layout: StoreApi
}
export const StoreContext = createContext(null)
@@ -18,15 +20,23 @@ export type ProfileProviderProps = {
}
export default function StoreProvider(props: ProfileProviderProps) {
+
const profile = useRef>(null)
if (!profile.current) {
console.log('create profile store')
profile.current = createProfileStore(props.user)
}
+ const layout = useRef>(null)
+ if (!layout.current) {
+ console.log('create layout store')
+ layout.current = createLayoutStore()
+ }
+
return (
{props.children}
@@ -41,3 +51,11 @@ export function useProfileStore(selector: (store: ProfileStore) => T) {
}
return useStore(ctx.profile, selector)
}
+
+export function useLayoutStore(selector: (store: LayoutStore) => T) {
+ const ctx = useContext(StoreContext)
+ if (!ctx) {
+ throw new Error('useLayoutStore must be used within a StoreProvider')
+ }
+ return useStore(ctx.layout, selector)
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index e39257b..1830cbf 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -33,7 +33,7 @@ const buttonVariants = cva(
)
type ButtonProps = React.ComponentProps<'button'> & {
- theme?: 'default' | 'outline' | 'gradient' | 'error' | 'accent'
+ theme?: 'default' | 'outline' | 'gradient' | 'error' | 'accent' | 'ghost'
}
function Button(rawProps: ButtonProps) {
@@ -55,6 +55,7 @@ function Button(rawProps: ButtonProps) {
accent: 'bg-accent text-accent-foreground hover:bg-accent/90',
error: 'bg-fail text-white hover:bg-fail/90',
outline: 'border bg-background hover:bg-secondary hover:text-secondary-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ ghost: "text-foreground hover:bg-muted",
}[theme ?? 'default'],
className,
)}
diff --git a/src/stores/layout.ts b/src/stores/layout.ts
new file mode 100644
index 0000000..ac9dabb
--- /dev/null
+++ b/src/stores/layout.ts
@@ -0,0 +1,24 @@
+import {createStore} from 'zustand/vanilla'
+
+export type LayoutStore = LayoutState & LayoutActions
+
+export type LayoutState = {
+ navbar: boolean
+}
+
+export type LayoutActions = {
+ toggleNavbar: () => void
+ setNavbar: (navbar: boolean) => void
+}
+
+export const createLayoutStore = () => {
+ return createStore()(setState => ({
+ navbar: true,
+ toggleNavbar: () => setState(state => {
+ return {navbar: !state.navbar}
+ }),
+ setNavbar: (navbar) => setState(_ => {
+ return {navbar}
+ }),
+ }))
+}
diff --git a/src/stores/profile-store.ts b/src/stores/profile.ts
similarity index 100%
rename from src/stores/profile-store.ts
rename to src/stores/profile.ts