refactor(ui): useAntdForm

This commit is contained in:
Fu Diwei
2024-12-25 14:51:32 +08:00
parent c9024c5611
commit 4d0f7c2e02
43 changed files with 779 additions and 677 deletions

View File

@@ -1,6 +1,6 @@
import { Navigate, Outlet } from "react-router-dom";
import Version from "@/components/ui/Version";
import Version from "@/components/core/Version";
import { getPocketBase } from "@/repository/pocketbase";
const AuthLayout = () => {

View File

@@ -15,7 +15,7 @@ import {
Workflow as WorkflowIcon,
} from "lucide-react";
import Version from "@/components/ui/Version";
import Version from "@/components/core/Version";
import { useBrowserTheme } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Button, Card, Form, Input, notification } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useAntdForm } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";
import { getErrMsg } from "@/utils/error";
@@ -20,21 +20,20 @@ const Login = () => {
password: z.string().min(10, t("login.password.errmsg.invalid")),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [formPending, setFormPending] = useState(false);
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
setFormPending(true);
try {
await getPocketBase().admins.authWithPassword(fields.username, fields.password);
navigage("/");
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
} finally {
setFormPending(false);
}
};
const {
form: formInst,
formPending,
formProps,
} = useAntdForm<z.infer<typeof formSchema>>({
onSubmit: async (values) => {
try {
await getPocketBase().admins.authWithPassword(values.username, values.password);
navigage("/");
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
}
},
});
return (
<>
@@ -45,7 +44,7 @@ const Login = () => {
<img src="/logo.svg" className="w-16" />
</div>
<Form form={form} disabled={formPending} layout="vertical" onFinish={handleFormFinish}>
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="username" label={t("login.username.label")} rules={[formRule]}>
<Input placeholder={t("login.username.placeholder")} />
</Form.Item>

View File

@@ -5,8 +5,9 @@ import { Button, Form, Input, message, notification } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { getErrMsg } from "@/utils/error";
import { useAntdForm } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";
import { getErrMsg } from "@/utils/error";
const SettingsAccount = () => {
const navigate = useNavigate();
@@ -20,37 +21,35 @@ const SettingsAccount = () => {
username: z.string({ message: "settings.account.form.email.placeholder" }).email({ message: t("common.errmsg.email_invalid") }),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [formPending, setFormPending] = useState(false);
const {
form: formInst,
formPending,
formProps,
} = useAntdForm<z.infer<typeof formSchema>>({
initialValues: {
username: getPocketBase().authStore.model?.email,
},
onSubmit: async (values) => {
try {
await getPocketBase().admins.update(getPocketBase().authStore.model?.id, {
email: values.username,
});
const [initialValues] = useState<Partial<z.infer<typeof formSchema>>>({
username: getPocketBase().authStore.model?.email,
messageApi.success(t("common.text.operation_succeeded"));
setTimeout(() => {
getPocketBase().authStore.clear();
navigate("/login");
}, 500);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
}
},
});
const [initialChanged, setInitialChanged] = useState(false);
const [formChanged, setFormChanged] = useState(false);
const handleInputChange = () => {
setInitialChanged(form.getFieldValue("username") !== initialValues.username);
};
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
setFormPending(true);
try {
await getPocketBase().admins.update(getPocketBase().authStore.model?.id, {
email: fields.username,
});
messageApi.success(t("common.text.operation_succeeded"));
setTimeout(() => {
getPocketBase().authStore.clear();
navigate("/login");
}, 500);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
} finally {
setFormPending(false);
}
setFormChanged(formInst.getFieldValue("username") !== formProps.initialValues?.username);
};
return (
@@ -59,13 +58,13 @@ const SettingsAccount = () => {
{NotificationContextHolder}
<div className="md:max-w-[40rem]">
<Form form={form} disabled={formPending} initialValues={initialValues} layout="vertical" onFinish={handleFormFinish}>
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="username" label={t("settings.account.form.email.label")} rules={[formRule]}>
<Input placeholder={t("settings.account.form.email.placeholder")} onChange={handleInputChange} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={formPending}>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={formPending}>
{t("common.button.save")}
</Button>
</Form.Item>

View File

@@ -5,8 +5,9 @@ import { Button, Form, Input, message, notification } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { getErrMsg } from "@/utils/error";
import { useAntdForm } from "@/hooks";
import { getPocketBase } from "@/repository/pocketbase";
import { getErrMsg } from "@/utils/error";
const SettingsPassword = () => {
const navigate = useNavigate();
@@ -33,37 +34,36 @@ const SettingsPassword = () => {
path: ["confirmPassword"],
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const [formPending, setFormPending] = useState(false);
const {
form: formInst,
formPending,
formProps,
} = useAntdForm<z.infer<typeof formSchema>>({
onSubmit: async (values) => {
try {
await getPocketBase().admins.authWithPassword(getPocketBase().authStore.model?.email, values.oldPassword);
await getPocketBase().admins.update(getPocketBase().authStore.model?.id, {
password: values.newPassword,
passwordConfirm: values.confirmPassword,
});
const [initialChanged, setInitialChanged] = useState(false);
messageApi.success(t("common.text.operation_succeeded"));
setTimeout(() => {
getPocketBase().authStore.clear();
navigate("/login");
}, 500);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
}
},
});
const [formChanged, setFormChanged] = useState(false);
const handleInputChange = () => {
const fields = form.getFieldsValue();
setInitialChanged(!!fields.oldPassword && !!fields.newPassword && !!fields.confirmPassword);
};
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
setFormPending(true);
try {
await getPocketBase().admins.authWithPassword(getPocketBase().authStore.model?.email, fields.oldPassword);
await getPocketBase().admins.update(getPocketBase().authStore.model?.id, {
password: fields.newPassword,
passwordConfirm: fields.confirmPassword,
});
messageApi.success(t("common.text.operation_succeeded"));
setTimeout(() => {
getPocketBase().authStore.clear();
navigate("/login");
}, 500);
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
} finally {
setFormPending(false);
}
const values = formInst.getFieldsValue();
setFormChanged(!!values.oldPassword && !!values.newPassword && !!values.confirmPassword);
};
return (
@@ -72,7 +72,7 @@ const SettingsPassword = () => {
{NotificationContextHolder}
<div className="md:max-w-[40rem]">
<Form form={form} disabled={formPending} layout="vertical" onFinish={handleFormFinish}>
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item name="oldPassword" label={t("settings.password.form.old_password.label")} rules={[formRule]}>
<Input.Password placeholder={t("settings.password.form.old_password.placeholder")} onChange={handleInputChange} />
</Form.Item>
@@ -86,7 +86,7 @@ const SettingsPassword = () => {
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={formPending}>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={formPending}>
{t("common.button.save")}
</Button>
</Form.Item>

View File

@@ -1,15 +1,17 @@
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDeepCompareEffect } from "ahooks";
import { Button, Form, Input, message, notification, Skeleton } from "antd";
import { CheckCard } from "@ant-design/pro-components";
import { createSchemaFieldRule } from "antd-zod";
import { produce } from "immer";
import { z } from "zod";
import Show from "@/components/Show";
import { useAntdForm } from "@/hooks";
import { SETTINGS_NAMES, SSLPROVIDERS, type SettingsModel, type SSLProviderSettingsContent, type SSLProviders } from "@/domain/settings";
import { get as getSettings, save as saveSettings } from "@/repository/settings";
import { getErrMsg } from "@/utils/error";
import { useDeepCompareEffect } from "ahooks";
const SSLProviderContext = createContext(
{} as {
@@ -24,36 +26,35 @@ const SSLProviderEditFormLetsEncryptConfig = () => {
const { pending, settings, updateSettings } = useContext(SSLProviderContext);
const [form] = Form.useForm<NonNullable<unknown>>();
const { form: formInst, formProps } = useAntdForm<NonNullable<unknown>>({
initialValues: settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT],
onSubmit: async (values) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT;
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]);
const [initialChanged, setInitialChanged] = useState(false);
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.LETS_ENCRYPT] = values;
});
await updateSettings(newSettings);
setFormChanged(false);
},
});
const [formChanged, setFormChanged] = useState(false);
useDeepCompareEffect(() => {
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.LETS_ENCRYPT]);
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT);
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.LETS_ENCRYPT);
}, [settings]);
const handleFormChange = () => {
setInitialChanged(true);
};
const handleFormFinish = async (fields: NonNullable<unknown>) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.LETS_ENCRYPT;
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.LETS_ENCRYPT] = fields;
});
await updateSettings(newSettings);
setInitialChanged(false);
setFormChanged(true);
};
return (
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
{t("common.button.save")}
</Button>
</Form.Item>
@@ -77,34 +78,33 @@ const SSLProviderEditFormZeroSSLConfig = () => {
.max(256, t("common.errmsg.string_max", { max: 256 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
initialValues: settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL],
onSubmit: async (values) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.ZERO_SSL;
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]);
const [initialChanged, setInitialChanged] = useState(false);
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.ZERO_SSL] = values;
});
await updateSettings(newSettings);
setFormChanged(false);
},
});
const [formChanged, setFormChanged] = useState(false);
useDeepCompareEffect(() => {
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.ZERO_SSL]);
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.ZERO_SSL);
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.ZERO_SSL);
}, [settings]);
const handleFormChange = () => {
setInitialChanged(true);
};
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.ZERO_SSL;
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.ZERO_SSL] = fields;
});
await updateSettings(newSettings);
setInitialChanged(false);
setFormChanged(true);
};
return (
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item
name="eabKid"
label={t("settings.sslprovider.form.zerossl_eab_kid.label")}
@@ -124,7 +124,7 @@ const SSLProviderEditFormZeroSSLConfig = () => {
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
{t("common.button.save")}
</Button>
</Form.Item>
@@ -148,34 +148,33 @@ const SSLProviderEditFormGoogleTrustServicesConfig = () => {
.max(256, t("common.errmsg.string_max", { max: 256 })),
});
const formRule = createSchemaFieldRule(formSchema);
const [form] = Form.useForm<z.infer<typeof formSchema>>();
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
initialValues: settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES],
onSubmit: async (values) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.GOOGLE_TRUST_SERVICES;
const [initialValues, setInitialValues] = useState(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]);
const [initialChanged, setInitialChanged] = useState(false);
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.GOOGLE_TRUST_SERVICES] = values;
});
await updateSettings(newSettings);
setFormChanged(false);
},
});
const [formChanged, setFormChanged] = useState(false);
useDeepCompareEffect(() => {
setInitialValues(settings?.content?.config?.[SSLPROVIDERS.GOOGLE_TRUST_SERVICES]);
setInitialChanged(settings?.content?.provider !== SSLPROVIDERS.GOOGLE_TRUST_SERVICES);
setFormChanged(settings?.content?.provider !== SSLPROVIDERS.GOOGLE_TRUST_SERVICES);
}, [settings]);
const handleFormChange = () => {
setInitialChanged(true);
};
const handleFormFinish = async (fields: z.infer<typeof formSchema>) => {
const newSettings = produce(settings, (draft) => {
draft.content ??= {} as SSLProviderSettingsContent;
draft.content.provider = SSLPROVIDERS.GOOGLE_TRUST_SERVICES;
draft.content.config ??= {} as SSLProviderSettingsContent["config"];
draft.content.config[SSLPROVIDERS.GOOGLE_TRUST_SERVICES] = fields;
});
await updateSettings(newSettings);
setInitialChanged(false);
setFormChanged(true);
};
return (
<Form form={form} disabled={pending} layout="vertical" initialValues={initialValues} onFinish={handleFormFinish} onValuesChange={handleFormChange}>
<Form {...formProps} form={formInst} disabled={pending} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item
name="eabKid"
label={t("settings.sslprovider.form.gts_eab_kid.label")}
@@ -195,7 +194,7 @@ const SSLProviderEditFormGoogleTrustServicesConfig = () => {
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!initialChanged} loading={pending}>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={pending}>
{t("common.button.save")}
</Button>
</Form.Item>
@@ -228,7 +227,7 @@ const SettingsSSLProvider = () => {
fetchData();
}, []);
const [providerType, setFormProviderType] = useState<SSLProviders>();
const [providerType, setFormProviderType] = useState<SSLProviders>(SSLPROVIDERS.LETS_ENCRYPT);
const providerFormComponent = useMemo(() => {
switch (providerType) {
case SSLPROVIDERS.LETS_ENCRYPT:
@@ -267,33 +266,29 @@ const SettingsSSLProvider = () => {
{MessageContextHolder}
{NotificationContextHolder}
{loading ? (
<Skeleton active />
) : (
<>
<Form form={form} disabled={formPending} layout="vertical" initialValues={{ provider: providerType }}>
<Form.Item className="mb-2" name="provider" label={t("settings.sslprovider.form.provider.label")} initialValue={SSLPROVIDERS.LETS_ENCRYPT}>
<CheckCard.Group className="w-full" onChange={(value) => setFormProviderType(value as SSLProviders)}>
<CheckCard
avatar={<img src={"/imgs/acme/letsencrypt.svg"} className="size-8" />}
size="small"
title="Let's Encrypt"
value={SSLPROVIDERS.LETS_ENCRYPT}
/>
<CheckCard avatar={<img src={"/imgs/acme/zerossl.svg"} className="size-8" />} size="small" title="ZeroSSL" value={SSLPROVIDERS.ZERO_SSL} />
<CheckCard
avatar={<img src={"/imgs/acme/google.svg"} className="size-8" />}
size="small"
title="Google Trust Services"
value={SSLPROVIDERS.GOOGLE_TRUST_SERVICES}
/>
</CheckCard.Group>
</Form.Item>
</Form>
<Show when={!loading} fallback={<Skeleton active />}>
<Form form={form} disabled={formPending} layout="vertical" initialValues={{ provider: providerType }}>
<Form.Item className="mb-2" name="provider" label={t("settings.sslprovider.form.provider.label")}>
<CheckCard.Group className="w-full" onChange={(value) => setFormProviderType(value as SSLProviders)}>
<CheckCard
avatar={<img src={"/imgs/acme/letsencrypt.svg"} className="size-8" />}
size="small"
title="Let's Encrypt"
value={SSLPROVIDERS.LETS_ENCRYPT}
/>
<CheckCard avatar={<img src={"/imgs/acme/zerossl.svg"} className="size-8" />} size="small" title="ZeroSSL" value={SSLPROVIDERS.ZERO_SSL} />
<CheckCard
avatar={<img src={"/imgs/acme/google.svg"} className="size-8" />}
size="small"
title="Google Trust Services"
value={SSLPROVIDERS.GOOGLE_TRUST_SERVICES}
/>
</CheckCard.Group>
</Form.Item>
</Form>
<div className="md:max-w-[40rem]">{providerFormComponent}</div>
</>
)}
<div className="md:max-w-[40rem]">{providerFormComponent}</div>
</Show>
</SSLProviderContext.Provider>
);
};

View File

@@ -57,9 +57,9 @@ const WorkflowDetail = () => {
return elements;
}, [workflow]);
const handleBaseInfoFormFinish = async (fields: Pick<WorkflowModel, "name" | "description">) => {
const handleBaseInfoFormFinish = async (values: Pick<WorkflowModel, "name" | "description">) => {
try {
await setBaseInfo(fields.name!, fields.description!);
await setBaseInfo(values.name!, values.description!);
} catch (err) {
console.error(err);
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
@@ -93,6 +93,7 @@ const WorkflowDetail = () => {
});
};
// TODO: 发布更改 撤销更改 立即执行
// const handleWorkflowSaveClick = () => {
// if (!allNodesValidated(workflow.draft as WorkflowNode)) {
// messageApi.warning(t("workflow.detail.action.save.failed.uncompleted"));
@@ -192,7 +193,7 @@ const WorkflowBaseInfoModalForm = memo(
}: {
model: Pick<WorkflowModel, "name" | "description">;
trigger?: React.ReactElement;
onFinish?: (fields: Pick<WorkflowModel, "name" | "description">) => Promise<void | boolean>;
onFinish?: (values: Pick<WorkflowModel, "name" | "description">) => Promise<void | boolean>;
}) => {
const { t } = useTranslation();