Files
kintone-customize-manager/src/renderer/src/components/DomainManager/DomainForm.tsx
xue jiahao c903733f2c feat(domain): improve domain form UX
- Make name field optional (defaults to domain if empty)
- Simplify domain placeholder and error messages
- Add test connection button for credential validation before save
2026-03-12 12:07:33 +08:00

275 lines
8.1 KiB
TypeScript

/**
* DomainForm Component
* Dialog for creating and editing domain configurations
*/
import React from "react";
import { Modal, Form, Input, Select, Button, Space, message } from "antd";
import { createStyles } from "antd-style";
import { useDomainStore } from "@renderer/stores";
import type { Domain } from "@renderer/types/domain";
import type {
CreateDomainParams,
UpdateDomainParams,
} from "@renderer/types/ipc";
const useStyles = createStyles(({ token, css }) => ({
form: css`
.ant-form-item {
margin-bottom: ${token.marginMD}px;
}
`,
passwordHint: css`
color: ${token.colorTextSecondary};
font-size: ${token.fontSizeSM}px;
margin-top: ${token.marginXXS}px;
`,
}));
interface DomainFormProps {
open: boolean;
onClose: () => void;
domainId: string | null;
}
const DomainForm: React.FC<DomainFormProps> = ({ open, onClose, domainId }) => {
const { styles } = useStyles();
const [form] = Form.useForm();
const { domains, createDomain, updateDomainById, loading } = useDomainStore();
const isEdit = !!domainId;
const editingDomain = domainId
? domains.find((d) => d.id === domainId)
: null;
// Reset form when dialog opens
React.useEffect(() => {
if (open) {
if (editingDomain) {
form.setFieldsValue({
name: editingDomain.name,
domain: editingDomain.domain,
username: editingDomain.username,
authType: editingDomain.authType,
apiToken: editingDomain.apiToken || "",
});
} else {
form.resetFields();
form.setFieldsValue({ authType: "password" });
}
}
}, [open, editingDomain, form]);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
// Process domain: remove protocol prefix and trailing slashes
let processedDomain = values.domain.trim();
if (processedDomain.startsWith("https://")) {
processedDomain = processedDomain.slice(8);
} else if (processedDomain.startsWith("http://")) {
processedDomain = processedDomain.slice(7);
}
processedDomain = processedDomain.replace(/\/+$/, "");
// Use domain as name if name is empty
const name = values.name?.trim() || processedDomain;
if (isEdit && editingDomain) {
const params: UpdateDomainParams = {
id: domainId,
name,
domain: processedDomain,
username: values.username,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
};
// Only include password if provided
if (values.password) {
params.password = values.password;
}
const success = await updateDomainById(params);
if (success) {
message.success("Domain 更新成功");
onClose();
} else {
message.error("更新失败");
}
} else {
const params: CreateDomainParams = {
name,
domain: processedDomain,
username: values.username,
password: values.password,
authType: values.authType,
apiToken:
values.authType === "api_token" ? values.apiToken : undefined,
};
const success = await createDomain(params);
if (success) {
message.success("Domain 创建成功");
onClose();
} else {
message.error("创建失败");
}
}
} catch (error) {
console.error("Form validation failed:", error);
}
};
const authType = Form.useWatch("authType", form);
const [testing, setTesting] = React.useState(false);
// Test connection with current form values
const handleTestConnection = async () => {
try {
const values = await form.validateFields([
"domain",
"username",
"authType",
"password",
"apiToken",
]);
// Process domain
let processedDomain = values.domain.trim();
if (processedDomain.startsWith("https://")) {
processedDomain = processedDomain.slice(8);
} else if (processedDomain.startsWith("http://")) {
processedDomain = processedDomain.slice(7);
}
processedDomain = processedDomain.replace(/\/+$/, "");
setTesting(true);
const result = await window.api.testDomainConnection({
domain: processedDomain,
username: values.username,
authType: values.authType,
password: values.authType === "password" ? values.password : undefined,
apiToken: values.authType === "api_token" ? values.apiToken : undefined,
});
if (result.success) {
message.success("连接成功");
} else {
message.error(result.error || "连接失败");
}
} catch (error) {
console.error("Test connection failed:", error);
} finally {
setTesting(false);
}
};
return (
<Modal
title={isEdit ? "编辑 Domain" : "添加 Domain"}
open={open}
onCancel={onClose}
footer={null}
width={520}
destroyOnClose
>
<Form
form={form}
layout="vertical"
className={styles.form}
initialValues={{ authType: "password" }}
>
<Form.Item name="name" label="名称" style={{ marginTop: 8 }}>
<Input placeholder="可选,留空则使用域名" />
<Form.Item
name="domain"
label="Kintone 域名"
rules={[
{ required: true, message: "请输入域名" },
{
validator: (_, value) => {
if (!value) return Promise.resolve();
// Allow https:// or http:// prefix
let domain = value.trim();
if (domain.startsWith("https://")) {
domain = domain.slice(8);
} else if (domain.startsWith("http://")) {
domain = domain.slice(7);
}
// Remove trailing slashes
domain = domain.replace(/\/+$/, "");
// Validate domain format
if (/^[\w.-]+$/.test(domain)) {
return Promise.resolve();
}
return Promise.reject(new Error("请输入有效的域名"));
},
},
]}
>
<Input placeholder="https://company.kintone.com" />
</Form.Item>
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: "请输入用户名" }]}
>
<Input placeholder="登录 Kintone 的用户名" />
</Form.Item>
<Form.Item
name="authType"
label="认证方式"
rules={[{ required: true }]}
>
<Select>
<Select.Option value="password"></Select.Option>
<Select.Option value="api_token">API Token </Select.Option>
</Select>
</Form.Item>
{authType === "password" && (
<Form.Item
name="password"
label="密码"
rules={isEdit ? [] : [{ required: true, message: "请输入密码" }]}
>
<Input.Password
placeholder={isEdit ? "留空则保持原密码" : "请输入密码"}
/>
</Form.Item>
)}
{authType === "api_token" && (
<Form.Item
name="apiToken"
label="API Token"
rules={[{ required: true, message: "请输入 API Token" }]}
>
<Input placeholder="从 Kintone 设置中获取 API Token" />
</Form.Item>
)}
<Form.Item style={{ marginTop: 24, marginBottom: 0 }}>
<Space style={{ width: "100%", justifyContent: "flex-end" }}>
<Button onClick={onClose}></Button>
<Button onClick={handleTestConnection} loading={testing}>
</Button>
<Button type="primary" onClick={handleSubmit} loading={loading}>
{isEdit ? "更新" : "创建"}
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
);
};
export default DomainForm;