Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(web): ignore invalid characters when auto-filling the key and alias #1187

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/e2e/general/project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test("Project CRUD and searching has succeeded", async ({ reearth, page }) => {

await expect(page.getByText("project name", { exact: true })).toBeVisible();
await expect(page.getByText("project description", { exact: true })).toBeVisible();
await page.locator(".ant-input-affix-wrapper").click();
await page.getByPlaceholder("search projects").click();
await page.getByPlaceholder("search projects").fill("no project");
await page.getByRole("button", { name: "search" }).click();
await expect(page.getByText("project name", { exact: true })).toBeHidden();
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/molecules/Common/Form/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FormInstance } from "@reearth-cms/components/atoms/Form";
import { validateKey } from "@reearth-cms/utils/regex";

export const keyAutoFill = (
e: React.ChangeEvent<HTMLInputElement>,
Expand All @@ -9,7 +10,9 @@ export const keyAutoFill = (
const currentAlias = form.getFieldValue(key);
if (!currentAlias || currentAlias.toLowerCase() === prevName.toLowerCase()) {
const currentName = e.currentTarget.value.replaceAll(" ", "-").toLowerCase();
form.setFieldValue(key, currentName);
if (validateKey(currentName) || currentName === "") {
form.setFieldValue(key, currentName);
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Modal from "@reearth-cms/components/atoms/Modal";
import TextArea from "@reearth-cms/components/atoms/TextArea";
import { keyAutoFill, keyReplace } from "@reearth-cms/components/molecules/Common/Form/utils";
import { useT } from "@reearth-cms/i18n";
import { validateKey } from "@reearth-cms/utils/regex";
import { MAX_KEY_LENGTH, validateKey } from "@reearth-cms/utils/regex";

export interface FormValues {
name: string;
Expand Down Expand Up @@ -41,10 +41,14 @@ const ProjectCreationModal: React.FC<Props> = ({

const values = Form.useWatch([], form);
useEffect(() => {
form
.validateFields({ validateOnly: true })
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
if (form.getFieldValue("name") && form.getFieldValue("alias")) {
form
.validateFields()
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
} else {
setIsDisabled(true);
}
}, [form, values]);

const handleNameChange = useCallback(
Expand Down Expand Up @@ -109,20 +113,22 @@ const ProjectCreationModal: React.FC<Props> = ({
<Form.Item
name="alias"
label={t("Project alias")}
extra={t(
"Project alias must be unique and at least 5 characters long. It can only contain letters, numbers, underscores, and dashes.",
)}
rules={[
{
message: t("Project alias is not valid"),
required: true,
validator: async (_, value) => {
if (!validateKey(value) || value.length <= 4) {
return Promise.reject();
if (value.length >= 5 && validateKey(value) && (await onProjectAliasCheck(value))) {
return Promise.resolve();
}
const isProjectAliasAvailable = await onProjectAliasCheck(value);
return isProjectAliasAvailable ? Promise.resolve() : Promise.reject();
return Promise.reject();
},
},
]}>
<Input onChange={handleAliasChange} />
<Input onChange={handleAliasChange} showCount maxLength={MAX_KEY_LENGTH} />
</Form.Item>
<Form.Item name="description" label={t("Project description")}>
<TextArea rows={4} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
FormValues,
} from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";
import { validateKey } from "@reearth-cms/utils/regex";
import { MAX_KEY_LENGTH, validateKey } from "@reearth-cms/utils/regex";

const { Step } = Steps;
const { TabPane } = Tabs;
Expand Down Expand Up @@ -66,10 +66,14 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
const isDisabledCache = useRef<boolean>(true);

const formValidate = useCallback((form: FormInstance) => {
form
.validateFields({ validateOnly: true })
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
if (form.getFieldValue("model") || (form.getFieldValue("title") && form.getFieldValue("key"))) {
form
.validateFields()
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
} else {
setIsDisabled(true);
}
}, []);

const SettingValues = Form.useWatch([], modelForm);
Expand Down Expand Up @@ -433,6 +437,8 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
onChange={e => {
handleKeyChange(e, field1Form);
}}
showCount
maxLength={MAX_KEY_LENGTH}
/>
</Form.Item>
<Form.Item name="description" label={t("Description")}>
Expand Down Expand Up @@ -534,6 +540,8 @@ const FieldCreationModalWithSteps: React.FC<Props> = ({
onChange={e => {
handleKeyChange(e, field2Form);
}}
showCount
maxLength={MAX_KEY_LENGTH}
/>
</Form.Item>
<Form.Item name="description" label={t("Description")}>
Expand Down
55 changes: 26 additions & 29 deletions web/src/components/molecules/Schema/FieldModal/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export default (
return {
group: { groupId: values.group },
};
case "Text":
default:
return {
text: { defaultValue: values.defaultValue, maxLength: values.maxLength },
Expand All @@ -193,10 +194,14 @@ export default (

const values = Form.useWatch([], form);
useEffect(() => {
form
.validateFields({ validateOnly: true })
.then(() => setButtonDisabled(false))
.catch(() => setButtonDisabled(true));
if (form.getFieldValue("title") && form.getFieldValue("key")) {
form
.validateFields()
.then(() => setButtonDisabled(false))
.catch(() => setButtonDisabled(true));
} else {
setButtonDisabled(true);
}
}, [form, values]);

const handleNameChange = useCallback(
Expand All @@ -214,35 +219,28 @@ export default (
[form],
);

const handleSubmit = useCallback(() => {
form
.validateFields()
.then(async values => {
values.type = selectedType;
values.typeProperty = typePropertyGet(values);
values.metadata = isMeta;
await onSubmit({
...values,
fieldId: selectedField?.id,
});
setMultipleValue(false);
onClose();
})
.catch(info => {
console.log("Validate Failed:", info);
});
}, [form, selectedType, typePropertyGet, isMeta, onSubmit, selectedField?.id, onClose]);

const handleModalReset = useCallback(() => {
form.resetFields();
setActiveTab("settings");
}, [form]);

const handleModalCancel = useCallback(() => {
setMultipleValue(false);
setButtonDisabled(true);
onClose();
}, [onClose]);
}, [form, onClose]);

const handleSubmit = useCallback(async () => {
const values = await form.validateFields();
values.type = selectedType;
values.typeProperty = typePropertyGet(values);
values.metadata = isMeta;
try {
await onSubmit({
...values,
fieldId: selectedField?.id,
});
handleModalReset();
} catch (error) {
console.error(error);
}
}, [form, selectedType, typePropertyGet, isMeta, onSubmit, selectedField?.id, onClose]);

const isRequiredDisabled = useMemo(
() => selectedType === "Group" || selectedType === "Bool" || selectedType === "Checkbox",
Expand All @@ -267,7 +265,6 @@ export default (
handleKeyChange,
handleSubmit,
handleModalReset,
handleModalCancel,
isRequiredDisabled,
isUniqueDisabled,
};
Expand Down
15 changes: 6 additions & 9 deletions web/src/components/molecules/Schema/FieldModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
SortDirection,
} from "@reearth-cms/components/organisms/Project/Asset/AssetList/hooks";
import { useT } from "@reearth-cms/i18n";
import { validateKey } from "@reearth-cms/utils/regex";
import { MAX_KEY_LENGTH, validateKey } from "@reearth-cms/utils/regex";

import useHooks from "./hooks";

Expand Down Expand Up @@ -135,7 +135,6 @@ const FieldModal: React.FC<Props> = ({
handleKeyChange,
handleSubmit,
handleModalReset,
handleModalCancel,
isRequiredDisabled,
isUniqueDisabled,
} = useHooks(selectedType, isMeta, selectedField, onClose, onSubmit);
Expand All @@ -162,10 +161,9 @@ const FieldModal: React.FC<Props> = ({
}
width={572}
open={open}
onCancel={handleModalCancel}
afterClose={handleModalReset}
onCancel={handleModalReset}
footer={[
<Button key="cancel" onClick={handleModalCancel} disabled={fieldLoading}>
<Button key="cancel" onClick={handleModalReset} disabled={fieldLoading}>
{t("Cancel")}
</Button>,
<Button
Expand Down Expand Up @@ -196,16 +194,15 @@ const FieldModal: React.FC<Props> = ({
{
message: t("Key is not valid"),
required: true,
validator: async (_, value) => {
validator: (_, value) => {
if (validateKey(value) && handleFieldKeyUnique(value, selectedField?.id)) {
return Promise.resolve();
} else {
return Promise.reject();
}
return Promise.reject();
},
},
]}>
<Input onChange={handleKeyChange} />
<Input onChange={handleKeyChange} showCount maxLength={MAX_KEY_LENGTH} />
</Form.Item>
<Form.Item name="description" label={t("Description")}>
<TextArea rows={3} showCount maxLength={1000} />
Expand Down
73 changes: 38 additions & 35 deletions web/src/components/molecules/Schema/FormModal.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useCallback, useState, useEffect, useMemo } from "react";

import Button from "@reearth-cms/components/atoms/Button";
import Form, { ValidateErrorEntity } from "@reearth-cms/components/atoms/Form";
import Form from "@reearth-cms/components/atoms/Form";
import Input from "@reearth-cms/components/atoms/Input";
import Modal from "@reearth-cms/components/atoms/Modal";
import TextArea from "@reearth-cms/components/atoms/TextArea";
import { keyAutoFill, keyReplace } from "@reearth-cms/components/molecules/Common/Form/utils";
import { Model } from "@reearth-cms/components/molecules/Model/types";
import { ModelFormValues, Group } from "@reearth-cms/components/molecules/Schema/types";
import { useT } from "@reearth-cms/i18n";
import { validateKey } from "@reearth-cms/utils/regex";
import { MAX_KEY_LENGTH, validateKey } from "@reearth-cms/utils/regex";

interface Props {
data?: Model | Group;
Expand Down Expand Up @@ -41,6 +41,26 @@ const FormModal: React.FC<Props> = ({
const [isLoading, setIsLoading] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);

const values = Form.useWatch([], form);
useEffect(() => {
if (form.getFieldValue("name") && form.getFieldValue("key")) {
if (
data?.name === values.name &&
data.description === values.description &&
data.key === values.key
) {
setIsDisabled(true);
} else {
form
.validateFields()
.then(() => setIsDisabled(false))
.catch(() => setIsDisabled(true));
}
} else {
setIsDisabled(true);
}
}, [data?.description, data?.key, data?.name, form, values]);

useEffect(() => {
if (open) {
if (data) {
Expand Down Expand Up @@ -94,25 +114,6 @@ const FormModal: React.FC<Props> = ({
setIsDisabled(true);
}, [form, data, onClose]);

const handleValuesChange = useCallback(
async (_: unknown, values: FormType) => {
if (
data?.name === values.name &&
data.description === values.description &&
data.key === values.key
) {
setIsDisabled(true);
return;
}
const hasError = await form
.validateFields()
.then(() => false)
.catch((errorInfo: ValidateErrorEntity) => errorInfo.errorFields.length > 0);
setIsDisabled(hasError);
},
[data?.description, data?.key, data?.name, form],
);

const title = useMemo(
() =>
isModel
Expand Down Expand Up @@ -140,10 +141,10 @@ const FormModal: React.FC<Props> = ({
() =>
isModel
? t(
"Model key must be unique and at least 1 character long. It can only contain letters, numbers, underscores and dashes.",
"Model key must be unique and at least 3 characters long. It can only contain letters, numbers, underscores, and dashes.",
)
: t(
"Group key must be unique and at least 1 character long. It can only contain letters, numbers, underscores and dashes.",
"Group key must be unique and at least 3 characters long. It can only contain letters, numbers, underscores, and dashes.",
),
[isModel, t],
);
Expand All @@ -166,7 +167,7 @@ const FormModal: React.FC<Props> = ({
{t("OK")}
</Button>,
]}>
<Form form={form} layout="vertical" onValuesChange={handleValuesChange}>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label={nameLabel}
Expand All @@ -186,20 +187,22 @@ const FormModal: React.FC<Props> = ({
label={keyLabel}
extra={keyExtra}
rules={[
({ getFieldValue }) => ({
async validator() {
const value: string = getFieldValue("key");
if (value.length >= 3 && validateKey(value)) {
const isKeyAvailable = await onKeyCheck(value, data?.key);
if (isKeyAvailable) {
return Promise.resolve();
}
{
message: t("Key is not valid"),
required: true,
validator: async (_, value) => {
if (
value.length >= 3 &&
validateKey(value) &&
(await onKeyCheck(value, data?.key))
) {
return Promise.resolve();
}
return Promise.reject(new Error(t("Key is not valid")));
return Promise.reject();
},
}),
},
]}>
<Input onChange={handleKeyChange} />
<Input onChange={handleKeyChange} showCount maxLength={MAX_KEY_LENGTH} />
</Form.Item>
</Form>
</Modal>
Expand Down
Loading
Loading