/admin/profile 页面从服务端获取用户数据
This commit is contained in:
189
src/app/admin/profile/clients.tsx
Normal file
189
src/app/admin/profile/clients.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client'
|
||||
import {update} from '@/actions/user'
|
||||
import {useProfileStore} from '@/components/stores-provider'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {Form, FormField} from '@/components/ui/form'
|
||||
import {Input} from '@/components/ui/input'
|
||||
import {User} from '@/lib/models'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {zodResolver} from '@hookform/resolvers/zod'
|
||||
import {useEffect, useRef} from 'react'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import {toast} from 'sonner'
|
||||
import z from 'zod'
|
||||
import * as qrcode from 'qrcode'
|
||||
import {Card, CardHeader, CardTitle, CardContent} from '@/components/ui/card'
|
||||
import {QrCodeIcon} from 'lucide-react'
|
||||
|
||||
export function Aftersale(props: {
|
||||
profile: User
|
||||
}) {
|
||||
const admin = props.profile.admin_id
|
||||
const canvasRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (admin && canvasRef.current) {
|
||||
qrcode.toCanvas(canvasRef.current, String(admin), {
|
||||
width: 180,
|
||||
margin: 0,
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}, [admin])
|
||||
|
||||
return (
|
||||
<Card className="flex-none max-sm:flex-col">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<QrCodeIcon size={18}/>
|
||||
关于售后
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-8">
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-weak text-sm">
|
||||
1.全国100万+动态IP代理资源免费测试,先测后买让您安心使用。
|
||||
</p>
|
||||
|
||||
<p className="text-weak text-sm">
|
||||
2.注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关答疑
|
||||
</p>
|
||||
|
||||
<p className="text-weak text-sm">
|
||||
3.1V1专属售后答疑,技术团队7*24小时在线支持提供专属解决方案
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<p>您的专属客服经理</p>
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="180" height="180" className="mx-auto bg-muted"/>
|
||||
</div>
|
||||
<p className="text-xs text-weak">
|
||||
扫描上方二维码添加客服经理微信
|
||||
<br/>
|
||||
获取更多帮助与支持
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function BasicForm(props: {
|
||||
profile: User
|
||||
}) {
|
||||
const schema = z.object({
|
||||
username: z.string(),
|
||||
email: z.string().email('请输入正确的邮箱'),
|
||||
contact_qq: z.string(),
|
||||
contact_wechat: z.string(),
|
||||
})
|
||||
type Schema = z.infer<typeof schema>
|
||||
const form = useForm<Schema>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
},
|
||||
})
|
||||
const handler = form.handleSubmit(async (value) => {
|
||||
try {
|
||||
const resp = await update(value)
|
||||
if (!resp.success) {
|
||||
throw new Error(resp.message)
|
||||
}
|
||||
|
||||
await refreshProfile()
|
||||
toast.success(`保存成功`)
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`保存失败`, {
|
||||
description: e instanceof Error ? e.message : String(e),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 flex-1 w-full">
|
||||
<h3 className="font-normal">基本信息</h3>
|
||||
<Form
|
||||
form={form}
|
||||
handler={handler}
|
||||
className={merge(
|
||||
`grid grid-cols-2 gap-4 max-md:grid-cols-1 items-start`,
|
||||
)}
|
||||
>
|
||||
<FormField<Schema>
|
||||
name="username"
|
||||
label={<span className="w-full flex justify-end">用户名</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入用户名" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="email"
|
||||
label={<span className="w-full flex justify-end">邮箱</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入邮箱" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="contact_qq"
|
||||
label={<span className="w-full flex justify-end">QQ</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入QQ号" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="contact_wechat"
|
||||
label={<span className="w-full flex justify-end">微信</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入微信号" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<div className="flex justify-end gap-4 md:col-span-2 justify-self-stretch">
|
||||
<Button
|
||||
theme="outline"
|
||||
type="button"
|
||||
onClick={() => form.reset({
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
})}>
|
||||
撤销
|
||||
</Button>
|
||||
<Button>保存</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,48 +1,27 @@
|
||||
'use client'
|
||||
import {useEffect, useRef} from 'react'
|
||||
import Page from '@/components/page'
|
||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'
|
||||
import {Form, FormField} from '@/components/ui/form'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import {zodResolver} from '@hookform/resolvers/zod'
|
||||
import * as z from 'zod'
|
||||
import {useProfileStore} from '@/components/stores-provider'
|
||||
import {toast} from 'sonner'
|
||||
import {CheckCircle, QrCodeIcon} from 'lucide-react'
|
||||
import * as qrcode from 'qrcode'
|
||||
import {CheckCircle} from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import banner from '@/app/admin/identify/_assets/banner.webp'
|
||||
import {Input} from '@/components/ui/input'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {User} from '@/lib/models'
|
||||
import {update} from '@/actions/user'
|
||||
import RechargeModal from '@/components/composites/recharge'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {RealnameAuthDialog} from '@/components/composites/dialogs/realname-auth-dialog'
|
||||
import {ChangePasswordDialog} from '@/components/composites/dialogs/change-password-dialog'
|
||||
import {getProfile} from '@/actions/auth'
|
||||
import {Aftersale, BasicForm} from './clients'
|
||||
|
||||
export type ProfilePageProps = {}
|
||||
|
||||
export default function ProfilePage(props: ProfilePageProps) {
|
||||
const router = useRouter()
|
||||
const profile = useProfileStore(store => store.profile)
|
||||
|
||||
// ======================
|
||||
// render
|
||||
// ======================
|
||||
|
||||
if (!profile) {
|
||||
export default async function ProfilePage(props: ProfilePageProps) {
|
||||
const profile = await getProfile()
|
||||
if (!profile.success) {
|
||||
return (
|
||||
<Page>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="text-lg font-bold">加载中...</h3>
|
||||
<p className="text-sm text-gray-600">请稍等片刻</p>
|
||||
</div>
|
||||
获取用户信息失败
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
const user = profile.data
|
||||
return (
|
||||
<Page className="lg:flex-row lg:items-stretch md:flex-col max-sm:flex-col">
|
||||
<div className="flex-3/4 flex flex-col gap-4">
|
||||
@@ -61,7 +40,7 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
<CardTitle className="font-normal">帐号余额(元)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-auto flex justify-between items-center px-8">
|
||||
<p className="text-xl">{profile?.balance}</p>
|
||||
<p className="text-xl">{user.balance}</p>
|
||||
<RechargeModal classNames={{
|
||||
trigger: `h-10 px-6`,
|
||||
}}/>
|
||||
@@ -73,12 +52,12 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
<CardTitle className="font-normal">实名认证</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-auto flex justify-between items-center gap-4 px-4 pr-8">
|
||||
{!profile?.id_token
|
||||
{!user.id_token
|
||||
? (
|
||||
<>
|
||||
<p className="text-sm">为了保障您的账户安全和正常使用服务,请您尽快完成实名认证</p>
|
||||
<RealnameAuthDialog
|
||||
hasAuthenticated={!!profile?.id_token}
|
||||
hasAuthenticated={!!user.id_token}
|
||||
triggerClassName="w-24"
|
||||
/>
|
||||
</>
|
||||
@@ -86,8 +65,8 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
: (
|
||||
<>
|
||||
<p className="flex flex-col gap-1">
|
||||
<span>{profile.name}</span>
|
||||
<span className="text-sm">{profile.id_no}</span>
|
||||
<span>{user.name}</span>
|
||||
<span className="text-sm">{user.id_no}</span>
|
||||
</p>
|
||||
<p className="flex gap-1 items-center">
|
||||
<CheckCircle className="text-done" size={18}/>
|
||||
@@ -105,192 +84,19 @@ export default function ProfilePage(props: ProfilePageProps) {
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="font-normal">安全信息</h3>
|
||||
<div className="flex gap-4 items-center">
|
||||
<p>{profile.phone}</p>
|
||||
<p>{user.phone}</p>
|
||||
<ChangePasswordDialog triggerClassName="w-24 h-9"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 基本信息 */}
|
||||
<BasicForm profile={profile}/>
|
||||
<BasicForm profile={user}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 侧边栏:客服经理信息 */}
|
||||
<Aftersale profile={profile}/>
|
||||
<Aftersale profile={user}/>
|
||||
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
function Aftersale(props: {
|
||||
profile: User
|
||||
}) {
|
||||
const admin = props.profile.admin_id
|
||||
const canvasRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (admin && canvasRef.current) {
|
||||
qrcode.toCanvas(canvasRef.current, String(admin), {
|
||||
width: 180,
|
||||
margin: 0,
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}, [admin])
|
||||
|
||||
return (
|
||||
<Card className="flex-none max-sm:flex-col">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<QrCodeIcon size={18}/>
|
||||
关于售后
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-8">
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-weak text-sm">
|
||||
1.全国100万+动态IP代理资源免费测试,先测后买让您安心使用。
|
||||
</p>
|
||||
|
||||
<p className="text-weak text-sm">
|
||||
2.注册即享新人福利,专业的客户经理,多维度为您提供在线代理相关答疑
|
||||
</p>
|
||||
|
||||
<p className="text-weak text-sm">
|
||||
3.1V1专属售后答疑,技术团队7*24小时在线支持提供专属解决方案
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<p>您的专属客服经理</p>
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="180" height="180" className="mx-auto bg-muted"/>
|
||||
</div>
|
||||
<p className="text-xs text-weak">
|
||||
扫描上方二维码添加客服经理微信
|
||||
<br/>
|
||||
获取更多帮助与支持
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function BasicForm(props: {
|
||||
profile: User
|
||||
}) {
|
||||
const schema = z.object({
|
||||
username: z.string(),
|
||||
email: z.string().email('请输入正确的邮箱'),
|
||||
contact_qq: z.string(),
|
||||
contact_wechat: z.string(),
|
||||
})
|
||||
type Schema = z.infer<typeof schema>
|
||||
const form = useForm<Schema>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
},
|
||||
})
|
||||
const handler = form.handleSubmit(async (value) => {
|
||||
try {
|
||||
const resp = await update(value)
|
||||
if (!resp.success) {
|
||||
throw new Error(resp.message)
|
||||
}
|
||||
|
||||
await refreshProfile()
|
||||
toast.success(`保存成功`)
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`保存失败`, {
|
||||
description: e instanceof Error ? e.message : String(e),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 flex-1 w-full">
|
||||
<h3 className="font-normal">基本信息</h3>
|
||||
<Form
|
||||
form={form}
|
||||
handler={handler}
|
||||
className={merge(
|
||||
`grid grid-cols-2 gap-4 max-md:grid-cols-1 items-start`,
|
||||
)}
|
||||
>
|
||||
<FormField<Schema>
|
||||
name="username"
|
||||
label={<span className="w-full flex justify-end">用户名</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入用户名" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="email"
|
||||
label={<span className="w-full flex justify-end">邮箱</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入邮箱" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="contact_qq"
|
||||
label={<span className="w-full flex justify-end">QQ</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入QQ号" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField<Schema>
|
||||
name="contact_wechat"
|
||||
label={<span className="w-full flex justify-end">微信</span>}
|
||||
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
|
||||
classNames={{
|
||||
message: `col-start-2`,
|
||||
}}
|
||||
>
|
||||
{({field}) => (
|
||||
<Input {...field} placeholder="请输入微信号" className="w-48 max-xl:w-full"/>
|
||||
)}
|
||||
</FormField>
|
||||
<div className="flex justify-end gap-4 md:col-span-2 justify-self-stretch">
|
||||
<Button
|
||||
theme="outline"
|
||||
type="button"
|
||||
onClick={() => form.reset({
|
||||
username: props.profile.username || '',
|
||||
email: props.profile.email || '',
|
||||
contact_qq: props.profile.contact_qq || '',
|
||||
contact_wechat: props.profile.contact_wechat || '',
|
||||
})}>
|
||||
撤销
|
||||
</Button>
|
||||
<Button>保存</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user