215 lines
8.5 KiB
TypeScript
215 lines
8.5 KiB
TypeScript
'use client';
|
||
|
||
import { Button } from "@/components/ui/button";
|
||
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "@/components/ui/field";
|
||
import { useUserStore } from "@/store/useUserStore";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table"
|
||
import { ReactElement, useState } from "react";
|
||
import {
|
||
Dialog,
|
||
DialogClose,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from "@/components/ui/dialog"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Label } from "@/components/ui/label"
|
||
import { AuthAPI } from "@/lib/api/client";
|
||
import { GeneralErrorHandler, handleAPIError } from "@/lib/api/common";
|
||
import { startRegistration } from '@simplewebauthn/browser';
|
||
import { toast } from "sonner";
|
||
|
||
export default function Page() {
|
||
const userStore = useUserStore();
|
||
const user = userStore.user;
|
||
|
||
return (
|
||
<div className="w-full">
|
||
<form onSubmit={e => {
|
||
e.preventDefault();
|
||
}}>
|
||
<FieldGroup className="gap-5">
|
||
<FieldSet>
|
||
<FieldLegend>账户基础信息</FieldLegend>
|
||
<FieldDescription>这是当前账户的基础信息</FieldDescription>
|
||
<FieldGroup className="gap-5">
|
||
{
|
||
[
|
||
{
|
||
name: 'username',
|
||
localName: '用户名',
|
||
required: true,
|
||
defaultValue: user?.username,
|
||
},
|
||
{
|
||
name: 'nickname',
|
||
localName: '昵称',
|
||
required: true,
|
||
defaultValue: user?.nickname,
|
||
},
|
||
{
|
||
name: 'email',
|
||
localName: '电子邮箱',
|
||
required: false,
|
||
defaultValue: user?.email,
|
||
},
|
||
{
|
||
name: 'phone',
|
||
localName: '手机号',
|
||
required: false,
|
||
defaultValue: user?.phone,
|
||
description: '当前仅支持中国大陆(+86)手机号'
|
||
},
|
||
].map(({ name, localName, required, defaultValue, description }) => (
|
||
<Field key={name} className="gap-2">
|
||
<FieldLabel htmlFor={`console-profile-${name}`} className="text-zinc-800">
|
||
{localName}
|
||
</FieldLabel>
|
||
<Input
|
||
id={`console-profile-${name}`}
|
||
name={name}
|
||
defaultValue={defaultValue}
|
||
placeholder={localName}
|
||
required={required}
|
||
disabled
|
||
/>
|
||
{
|
||
description && (
|
||
<FieldDescription>
|
||
{description}
|
||
</FieldDescription>
|
||
)
|
||
}
|
||
</Field>
|
||
))
|
||
}
|
||
</FieldGroup>
|
||
</FieldSet>
|
||
<Field orientation="horizontal">
|
||
<Button type="submit" disabled>保存</Button>
|
||
<Button variant="outline" type="button" disabled>
|
||
编辑
|
||
</Button>
|
||
</Field>
|
||
</FieldGroup>
|
||
</form>
|
||
<FieldSeparator className="mt-2 mb-2" />
|
||
<FieldGroup className="gap-5">
|
||
<FieldSet>
|
||
<FieldLegend>通行证列表</FieldLegend>
|
||
<FieldDescription>
|
||
通行证(PassKey),一种先进的无密码身份验证技术。
|
||
</FieldDescription>
|
||
<PasskeyList />
|
||
<div>
|
||
<AddPasskeyDialog>
|
||
<Button>添加通行证</Button>
|
||
</AddPasskeyDialog>
|
||
</div>
|
||
</FieldSet>
|
||
</FieldGroup>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
interface AddPasskeyDialogProps {
|
||
children: ReactElement;
|
||
}
|
||
function AddPasskeyDialog({ children }: AddPasskeyDialogProps) {
|
||
const [open, setOpen] = useState(false);
|
||
|
||
const handleSubmit = async (name: string) => {
|
||
try {
|
||
name = name.trim();
|
||
if (name.length === 0) {
|
||
throw new Error('通行证名称不能为空')
|
||
}
|
||
|
||
const options = await AuthAPI.getPasskeyRegisterOptions();
|
||
|
||
const credential = await startRegistration({ optionsJSON: options }).catch(() => null);
|
||
|
||
if (credential === null) {
|
||
throw new Error('认证超时');
|
||
}
|
||
|
||
const registerRes = await AuthAPI.passkeyRegister(name, credential);
|
||
if (registerRes.id) {
|
||
toast.success('添加成功');
|
||
setOpen(false);
|
||
}
|
||
} catch (error) {
|
||
console.log(error)
|
||
handleAPIError(error, GeneralErrorHandler);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={setOpen}>
|
||
<DialogTrigger asChild>
|
||
{children}
|
||
</DialogTrigger>
|
||
<DialogContent className="sm:max-w-100">
|
||
<DialogHeader>
|
||
<DialogTitle>添加通行证</DialogTitle>
|
||
<DialogDescription>
|
||
如果能添加成功,则说明您的设备支持;如果添加失败了,说明您的设备不支持😊
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<form onSubmit={(e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.currentTarget);
|
||
handleSubmit(formData.get('name')?.toString() || '');
|
||
}}>
|
||
<div className="grid gap-4">
|
||
<div className="grid gap-3">
|
||
<Label htmlFor="console-add-passkey-name">通行证名称</Label>
|
||
<Input id="console-add-passkey-name" name="name" required />
|
||
</div>
|
||
</div>
|
||
<DialogFooter className="mt-6">
|
||
<DialogClose asChild>
|
||
<Button variant="outline">取消</Button>
|
||
</DialogClose>
|
||
<Button type="submit">下一步</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog >
|
||
)
|
||
}
|
||
|
||
function PasskeyList() {
|
||
return (
|
||
<Table>
|
||
{/* <TableCaption>A list of your recent invoices.</TableCaption> */}
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>名称</TableHead>
|
||
<TableHead>状态</TableHead>
|
||
<TableHead>创建时间</TableHead>
|
||
<TableHead className="text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
<TableRow>
|
||
<TableCell className="font-medium">INV001</TableCell>
|
||
<TableCell>Paid</TableCell>
|
||
<TableCell>Credit Card</TableCell>
|
||
<TableCell className="text-right">$250.00</TableCell>
|
||
</TableRow>
|
||
</TableBody>
|
||
</Table>
|
||
)
|
||
} |