Skip to content

Commit aab9cf3

Browse files
committed
chord: basic validate new base
1 parent 8504c81 commit aab9cf3

File tree

5 files changed

+85
-6
lines changed

5 files changed

+85
-6
lines changed

src/app/(outerbase)/local/edit-base/[baseId]/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ConnectionTemplateList } from "@/app/(outerbase)/base-template";
33
import {
44
CommonConnectionConfig,
55
ConnectionConfigEditor,
6+
validateTemplate,
67
} from "@/components/connection-config-editor";
78
import { ConnectionTemplateDictionary } from "@/components/connection-config-editor/template";
89
import { Button } from "@/components/orbit/button";
@@ -18,16 +19,28 @@ export default function LocalEditBasePage() {
1819
const [loading, setLoading] = useState(true);
1920
const [databaseName, setDatabaseName] = useState("");
2021
const [template, setTemplate] = useState<ConnectionTemplateList>();
22+
const [validateErrors, setValidateErrors] = useState<Record<string, string>>(
23+
{}
24+
);
2125

2226
const onSave = useCallback(async () => {
2327
if (!template?.localTo) return;
28+
29+
const errors = validateTemplate(value, template);
30+
setValidateErrors(errors);
31+
if (Object.keys(errors).length > 0) return;
32+
2433
await updateLocalConnection(baseId, template.localTo(value));
2534
router.push("/local");
2635
}, [template, value, router, baseId]);
2736

2837
const onConnect = useCallback(async () => {
2938
if (!template?.localTo) return;
3039

40+
const errors = validateTemplate(value, template);
41+
setValidateErrors(errors);
42+
if (Object.keys(errors).length > 0) return;
43+
3144
setLoading(true);
3245
const tmp = await updateLocalConnection(baseId, template.localTo(value));
3346
router.push(
@@ -78,6 +91,7 @@ export default function LocalEditBasePage() {
7891
template={template}
7992
value={value}
8093
onChange={setValue}
94+
errors={validateErrors}
8195
/>
8296
</div>
8397

src/app/(outerbase)/local/new-base/[driver]/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {
44
CommonConnectionConfig,
55
ConnectionConfigEditor,
6+
validateTemplate,
67
} from "@/components/connection-config-editor";
78
import { ConnectionTemplateDictionary } from "@/components/connection-config-editor/template";
89
import { Button } from "@/components/orbit/button";
@@ -18,6 +19,9 @@ export default function LocalNewBasePage() {
1819
const router = useRouter();
1920
const [value, setValue] = useState<CommonConnectionConfig>({ name: "" });
2021
const [loading, setLoading] = useState(false);
22+
const [validateErrors, setValidateErrors] = useState<Record<string, string>>(
23+
{}
24+
);
2125

2226
const template = useMemo(() => {
2327
return ConnectionTemplateDictionary[driver];
@@ -26,6 +30,10 @@ export default function LocalNewBasePage() {
2630
const onSave = useCallback(async () => {
2731
if (!template?.localTo) return;
2832

33+
const errors = validateTemplate(value, template);
34+
setValidateErrors(errors);
35+
if (Object.keys(errors).length > 0) return;
36+
2937
setLoading(true);
3038
await createLocalConnection(template.localTo(value));
3139
router.push("/local");
@@ -34,6 +42,10 @@ export default function LocalNewBasePage() {
3442
const onConnect = useCallback(async () => {
3543
if (!template?.localTo) return;
3644

45+
const errors = validateTemplate(value, template);
46+
setValidateErrors(errors);
47+
if (Object.keys(errors).length > 0) return;
48+
3749
setLoading(true);
3850
const newConnection = await createLocalConnection(template.localTo(value));
3951

@@ -70,6 +82,7 @@ export default function LocalNewBasePage() {
7082
template={template}
7183
value={value}
7284
onChange={setValue}
85+
errors={validateErrors}
7386
/>
7487
</div>
7588

src/app/(outerbase)/w/[workspaceId]/new-base/[driver]/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import {
33
CommonConnectionConfig,
44
ConnectionConfigEditor,
5+
validateTemplate,
56
} from "@/components/connection-config-editor";
67
import { ConnectionTemplateDictionary } from "@/components/connection-config-editor/template";
78
import { Button } from "@/components/orbit/button";
@@ -25,6 +26,9 @@ export default function WorkspaceNewBasePage() {
2526
const [value, setValue] = useState<CommonConnectionConfig>({ name: "" });
2627
const [loading, setLoading] = useState(false);
2728
const [error, setError] = useState("");
29+
const [validateErrors, setValidateErrors] = useState<Record<string, string>>(
30+
{}
31+
);
2832

2933
const template = useMemo(() => {
3034
return ConnectionTemplateDictionary[driver];
@@ -34,6 +38,10 @@ export default function WorkspaceNewBasePage() {
3438
(overrideRedirect?: string) => {
3539
if (!template.remoteFrom || !template.remoteTo) return;
3640

41+
const errors = validateTemplate(value, template);
42+
setValidateErrors(errors);
43+
if (Object.keys(errors).length > 0) return;
44+
3745
setLoading(true);
3846
setError("");
3947

@@ -103,6 +111,7 @@ export default function WorkspaceNewBasePage() {
103111
template={template}
104112
value={value}
105113
onChange={setValue}
114+
errors={validateErrors}
106115
/>
107116
</div>
108117

src/components/connection-config-editor/index.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,41 @@ import { Label } from "../orbit/label";
1919
import { Toggle } from "../orbit/toggle";
2020
import { Textarea } from "../ui/textarea";
2121

22+
export function validateTemplate(
23+
value: CommonConnectionConfig,
24+
template: ConnectionTemplateList
25+
): Record<string, string> {
26+
const templateRow = template.template;
27+
28+
const validationErrors: Record<string, string> = {};
29+
30+
if (!value.name) {
31+
validationErrors["name"] = "Name is required";
32+
}
33+
34+
for (const row of templateRow) {
35+
for (const column of row.columns) {
36+
if (column.required && !value[column.name]) {
37+
validationErrors[column.name] = `${column.label} is required`;
38+
}
39+
}
40+
}
41+
42+
return validationErrors;
43+
}
44+
2245
export function ConnectionConfigEditor({
2346
template,
2447
onChange,
2548
value,
49+
errors,
2650
}: ConnectionConfigEditorProps) {
2751
const templateRow = template.template;
2852

2953
return (
3054
<div className="flex w-full gap-4">
3155
<div className="flex w-1/2 grow-0 flex-col gap-4">
32-
<Label title="Name">
56+
<Label title="Name" requiredDescription={errors?.["name"]} required>
3357
<Input
3458
autoFocus
3559
size="lg"
@@ -53,7 +77,11 @@ export function ConnectionConfigEditor({
5377

5478
if (column.type === "text" || column.type === "password") {
5579
content = (
56-
<Label title={column.label} required={column.required}>
80+
<Label
81+
title={column.label}
82+
required={column.required}
83+
requiredDescription={errors?.[column.name]}
84+
>
5785
<Input
5886
size="lg"
5987
type={column.type}
@@ -71,7 +99,11 @@ export function ConnectionConfigEditor({
7199
);
72100
} else if (column.type === "textarea") {
73101
content = (
74-
<Label title={column.label} required={column.required}>
102+
<Label
103+
title={column.label}
104+
required={column.required}
105+
requiredDescription={errors?.[column.name]}
106+
>
75107
<Textarea
76108
rows={4}
77109
className="resize-none"
@@ -107,7 +139,11 @@ export function ConnectionConfigEditor({
107139
);
108140
} else if (column.type === "file") {
109141
content = (
110-
<Label title={column.label} required={column.required}>
142+
<Label
143+
title={column.label}
144+
required={column.required}
145+
requiredDescription={errors?.[column.name]}
146+
>
111147
<div>
112148
<CommonDialogProvider>
113149
<FileHandlerPicker
@@ -179,4 +215,5 @@ interface ConnectionConfigEditorProps {
179215
template: ConnectionTemplateList;
180216
value: CommonConnectionConfig;
181217
onChange: (value: CommonConnectionConfig) => void;
218+
errors?: Record<string, string>;
182219
}

src/components/label-input.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { Input, InputProps } from "./orbit/input";
22
import { Label } from "./orbit/label";
33

4-
export default function LabelInput(props: InputProps & { label: string }) {
4+
export default function LabelInput(
5+
props: InputProps & { label: string; requiredDescription?: string }
6+
) {
57
return (
68
<div className="flex flex-col gap-2">
7-
<Label title={props.label} required={props.required}>
9+
<Label
10+
title={props.label}
11+
required={props.required}
12+
requiredDescription="required"
13+
>
814
<Input {...props} />
915
</Label>
1016
</div>

0 commit comments

Comments
 (0)