feat: 添加账户信息、通行证注册功能

This commit is contained in:
2025-12-18 22:04:54 +08:00
parent 45ae85d56e
commit 4356e355fe

View File

@@ -1,5 +1,217 @@
'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 { Checkbox } from "@radix-ui/react-checkbox";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { ReactElement, useMemo, 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></div>
<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>
)
}