feat(ui): new Login UI using antd
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BookOpen } from "lucide-react";
|
||||
import { Divider, Space, Typography } from "antd";
|
||||
import { BookOpen as BookOpenIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/components/ui/utils";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { version } from "@/domain/version";
|
||||
|
||||
type VersionProps = {
|
||||
@@ -13,18 +12,18 @@ const Version = ({ className }: VersionProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={cn("w-full flex pb-5 ", className)}>
|
||||
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
|
||||
<a href="https://docs.certimate.me" target="_blank" className="flex items-center">
|
||||
<BookOpen size={16} />
|
||||
<div className="ml-1">{t("common.menu.document")}</div>
|
||||
</a>
|
||||
<Separator orientation="vertical" className="mx-2" />
|
||||
<a href="https://github.com/usual2970/certimate/releases" target="_blank">
|
||||
{version}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Space className={className} size={4}>
|
||||
<Typography.Link type="secondary" href="https://docs.certimate.me" target="_blank">
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<BookOpenIcon size={16} />
|
||||
<span>{t("common.menu.document")}</span>
|
||||
</div>
|
||||
</Typography.Link>
|
||||
<Divider type="vertical" />
|
||||
<Typography.Link type="secondary" href="https://github.com/usual2970/certimate/releases" target="_blank">
|
||||
{version}
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function Dashboard() {
|
||||
<div className="flex h-full max-h-screen flex-col gap-2">
|
||||
<div className="flex h-14 items-center border-b dark:border-stone-500 px-4 lg:h-[60px] lg:px-6">
|
||||
<Link to="/" className="flex items-center gap-2 font-semibold">
|
||||
<img src="/vite.svg" className="w-[36px] h-[36px]" />
|
||||
<img src="/logo.svg" className="w-[36px] h-[36px]" />
|
||||
<span className="dark:text-white">Certimate</span>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -97,7 +97,7 @@ export default function Dashboard() {
|
||||
<SheetContent side="left" className="flex flex-col">
|
||||
<nav className="grid gap-2 text-lg font-medium">
|
||||
<Link to="/" className="flex items-center gap-2 text-lg font-semibold">
|
||||
<img src="/vite.svg" className="w-[36px] h-[36px]" />
|
||||
<img src="/logo.svg" className="w-[36px] h-[36px]" />
|
||||
<span className="dark:text-white">Certimate</span>
|
||||
<span className="sr-only">Certimate</span>
|
||||
</Link>
|
||||
|
||||
@@ -4,7 +4,8 @@ import Version from "@/components/certimate/Version";
|
||||
import { getPocketBase } from "@/repository/pocketbase";
|
||||
|
||||
const LoginLayout = () => {
|
||||
if (getPocketBase().authStore.isValid && getPocketBase().authStore.isAdmin) {
|
||||
const auth = getPocketBase().authStore;
|
||||
if (auth.isValid && auth.isAdmin) {
|
||||
return <Navigate to="/" />;
|
||||
}
|
||||
|
||||
@@ -12,7 +13,7 @@ const LoginLayout = () => {
|
||||
<div className="container">
|
||||
<Outlet />
|
||||
|
||||
<Version className="fixed right-0 bottom-0 justify-end pr-5" />
|
||||
<Version className="fixed right-8 bottom-4" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -138,6 +138,7 @@ const AccessList = () => {
|
||||
title: t("access.action.delete"),
|
||||
content: t("access.action.delete.confirm"),
|
||||
onOk: async () => {
|
||||
// TODO: 有关联数据的不允许被删除
|
||||
try {
|
||||
const res = await removeAccess(data);
|
||||
configContext.deleteAccess(res.id);
|
||||
|
||||
@@ -11,11 +11,11 @@ import { list as listCertificate, type CertificateListReq } from "@/repository/c
|
||||
import { diffDays, getLeftDays } from "@/lib/time";
|
||||
|
||||
const CertificateList = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const tableColumns: TableProps<CertificateType>["columns"] = [
|
||||
|
||||
@@ -1,90 +1,67 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card, Form, Input, notification } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { getPocketBase } from "@/repository/pocketbase";
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().email({
|
||||
message: "login.username.errmsg.invalid",
|
||||
}),
|
||||
password: z.string().min(10, {
|
||||
message: "login.password.errmsg.invalid",
|
||||
}),
|
||||
});
|
||||
|
||||
const Login = () => {
|
||||
const navigage = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().email(t("login.username.errmsg.invalid")),
|
||||
password: z.string().min(10, t("login.password.errmsg.invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// 2. Define a submit handler.
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
await getPocketBase().admins.authWithPassword(values.username, values.password);
|
||||
navigage("/");
|
||||
} catch (e) {
|
||||
const message = getErrMessage(e);
|
||||
form.setError("username", { message });
|
||||
form.setError("password", { message });
|
||||
} catch (err) {
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const navigage = useNavigate();
|
||||
return (
|
||||
<div className="max-w-[35em] border dark:border-stone-500 mx-auto mt-32 p-10 rounded-md shadow-md">
|
||||
<div className="flex justify-center mb-10">
|
||||
<img src="/vite.svg" className="w-16" />
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 dark:text-stone-200">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("login.username.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("login.username.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
<>
|
||||
{NotificationContextHolder}
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Card className="mx-auto mt-32 p-10 max-w-[35em] border dark:border-stone-500 rounded-md shadow-md">
|
||||
<div className="flex items-center justify-center mb-10">
|
||||
<img src="/logo.svg" className="w-16" />
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("login.password.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("login.password.placeholder")} {...field} type="password" />
|
||||
</FormControl>
|
||||
<Form form={form} disabled={submitting} layout="vertical" onFinish={onSubmit}>
|
||||
<Form.Item name="username" label={t("login.username.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("login.username.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("login.submit")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
<Form.Item name="password" label={t("login.password.label")} rules={[formRule]}>
|
||||
<Input type="password" placeholder={t("login.password.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block loading={submitting}>
|
||||
{t("login.submit")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ import { Workflow as WorkflowType } from "@/domain/workflow";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow, type WorkflowListReq } from "@/repository/workflow";
|
||||
|
||||
const WorkflowList = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PocketBase from "pocketbase";
|
||||
|
||||
const apiDomain = import.meta.env.VITE_API_DOMAIN;
|
||||
console.log(apiDomain);
|
||||
console.log("VITE_API_DOMAIN:", apiDomain);
|
||||
|
||||
let pb: PocketBase;
|
||||
export const getPocketBase = () => {
|
||||
|
||||
Reference in New Issue
Block a user