validator update

This commit is contained in:
Nate Kelley 2025-03-25 13:04:40 -06:00
parent 1d37f2fe2f
commit 550dda19ca
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 109 additions and 84 deletions

View File

@ -57,7 +57,7 @@ export enum DataSourceEnvironment {
} }
export const PostgresCredentialsSchema = v.object({ export const PostgresCredentialsSchema = v.object({
name: v.string(), name: v.pipe(v.string(), v.minLength(3, 'Name must be at least 3 characters')),
type: v.union([v.literal('postgres'), v.literal('supabase')]), type: v.union([v.literal('postgres'), v.literal('supabase')]),
host: v.string(), host: v.string(),
port: v.pipe( port: v.pipe(

View File

@ -14,28 +14,6 @@ import { useAppForm } from '@/components/ui/form/useFormBaseHooks';
import { MultipleInlineFields } from '@/components/ui/form/FormBase'; import { MultipleInlineFields } from '@/components/ui/form/FormBase';
import { useDataSourceFormSuccess } from './helpers'; import { useDataSourceFormSuccess } from './helpers';
import * as v from 'valibot'; import * as v from 'valibot';
import { useForm } from '@tanstack/react-form';
const ValibotSchema = v.object({
// firstName: v.pipe(
// v.string(),
// v.minLength(3, '[Valibot] You must have a length of at least 3'),
// v.startsWith('A', "[Valibot] First name must start with 'A'")
// ),
// lastName: v.pipe(v.string(), v.minLength(3, '[Valibot] You must have a length of at least 3'))
name: v.string(),
type: v.union([v.literal('postgres'), v.literal('supabase')]),
host: v.string(),
port: v.pipe(
v.number(),
v.minValue(1, 'Port must be greater than 0'),
v.maxValue(65535, 'Port must be less than or equal to 65535')
),
username: v.string(),
password: v.string(),
default_database: v.string(), // postgres
default_schema: v.string() // public
});
export const PostgresForm: React.FC<{ export const PostgresForm: React.FC<{
dataSource?: DataSource; dataSource?: DataSource;
@ -55,20 +33,22 @@ export const PostgresForm: React.FC<{
password: credentials?.password || '', password: credentials?.password || '',
default_database: credentials?.default_database || '', default_database: credentials?.default_database || '',
default_schema: credentials?.default_schema || '', default_schema: credentials?.default_schema || '',
type: 'postgres', type: credentials?.type || 'postgres',
name: dataSource?.name || credentials?.name || '' name: dataSource?.name || credentials?.name || ''
} as Parameters<typeof createPostgresDataSource>[0], } satisfies PostgresCredentials,
onSubmit: async ({ value }) => { onSubmit: async ({ value, ...rest }) => {
await dataSourceFormSubmit({ console.log(rest);
flow, // await dataSourceFormSubmit({
dataSourceId: dataSource?.id, // flow,
onUpdate: () => updateDataSource({ id: dataSource!.id, ...value }), // dataSourceId: dataSource?.id,
onCreate: () => createDataSource(value) // onUpdate: () => updateDataSource({ id: dataSource!.id, ...value }),
}); // onCreate: () => createDataSource(value)
// });
}, },
validators: { validators: {
// onChangeAsyncDebounceMs: 1000, onChangeAsyncDebounceMs: 1000,
onChange: PostgresCredentialsSchema onChangeAsync: PostgresCredentialsSchema,
onSubmit: PostgresCredentialsSchema
} }
}); });
@ -127,37 +107,3 @@ export const PostgresForm: React.FC<{
</FormWrapper> </FormWrapper>
); );
}; };
const Test2 = () => {
const flow = 'create';
const dataSourceFormSubmit = useDataSourceFormSuccess();
const form2 = useForm({
defaultValues: {
host: 'test',
port: 5432,
username: 'test',
password: 'test',
default_database: 'test',
default_schema: 'test',
type: 'postgres',
name: 'test'
} satisfies Parameters<typeof createPostgresDataSource>[0],
onSubmit: async ({ value }) => {
await dataSourceFormSubmit({
flow,
dataSourceId: '123',
onUpdate: async () => {},
onCreate: async () => {}
});
},
validators: {
// DEMO: You can switch between schemas seamlessly
// onChange: ZodSchema,
onChange: ValibotSchema
// onChange: ArkTypeSchema,
}
});
return <div>Test2</div>;
};

View File

@ -5,6 +5,7 @@ import { InputPassword } from '../inputs/InputPassword';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { Button } from '../buttons'; import { Button } from '../buttons';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Text } from '../typography';
export const { fieldContext, useFieldContext, formContext, useFormContext } = export const { fieldContext, useFieldContext, formContext, useFormContext } =
createFormHookContexts(); createFormHookContexts();
@ -65,16 +66,33 @@ export function TextField({
type?: Parameters<typeof Input>[0]['type']; type?: Parameters<typeof Input>[0]['type'];
}) { }) {
const field = useFieldContext<string>(); const field = useFieldContext<string>();
const {
name,
state: {
value,
meta: { errors, isTouched }
}
} = field;
const error = errors?.[0]?.message;
const isFormSubmitted = field.form.state.submissionAttempts > 1;
const showError = !!error && (isFormSubmitted || isTouched);
const InputComponent = ( const InputComponent = (
<Input <div className={cn('relative flex w-full flex-col', className)}>
id={field.name} <Input
className={cn('flex-shrink', className)} id={name}
value={field.state.value} className={cn('w-full flex-shrink')}
onChange={(e) => field.handleChange(e.target.value)} value={value}
type={type} onChange={(e) => field.handleChange(e.target.value)}
placeholder={placeholder} type={type}
/> placeholder={placeholder}
/>
{showError && (
<Text className="mt-0.5 text-left" size={'sm'} variant={'danger'}>
{error}
</Text>
)}
</div>
); );
if (label === null) return InputComponent; if (label === null) return InputComponent;
@ -91,7 +109,51 @@ export function TextField({
} }
export function NumberField(props: Parameters<typeof TextField>[0]) { export function NumberField(props: Parameters<typeof TextField>[0]) {
return <TextField {...props} type="number" />; const field = useFieldContext<number>();
const isFormSubmitted = field.form.state.submissionAttempts > 1;
const {
name,
state: {
value,
meta: { errors, isTouched }
}
} = field;
const error = errors?.[0]?.message;
const showError = !!error && (isFormSubmitted || isTouched);
const InputComponent = (
<div className={cn('relative flex w-full flex-col', props.className)}>
<Input
id={name}
className={cn('w-full flex-shrink')}
value={value ?? ''}
onChange={(e) => {
const val = e.target.value === '' ? 0 : Number(e.target.value);
field.handleChange(val);
}}
type="number"
placeholder={props.placeholder}
/>
{showError && (
<Text className="mt-0.5 text-left" size={'sm'} variant={'danger'}>
{error}
</Text>
)}
</div>
);
if (props.label === null) return InputComponent;
return (
<LabelWrapper
label={props.label}
direction={props.direction}
labelClassName={props.labelClassName}
htmlFor={field.name}>
{InputComponent}
</LabelWrapper>
);
} }
export function PasswordField({ export function PasswordField({
@ -103,15 +165,32 @@ export function PasswordField({
placeholder placeholder
}: Parameters<typeof TextField>[0]) { }: Parameters<typeof TextField>[0]) {
const field = useFieldContext<string>(); const field = useFieldContext<string>();
const {
name,
state: {
value,
meta: { errors, isTouched }
}
} = field;
const error = errors?.[0]?.message;
const isFormSubmitted = field.form.state.submissionAttempts > 1;
const showError = !!error && (isFormSubmitted || isTouched);
const InputComponent = ( const InputComponent = (
<InputPassword <div className={cn('relative flex w-full flex-col', className)}>
id={field.name} <InputPassword
className={cn('flex-shrink', inputClassName)} id={name}
value={field.state.value} className={cn('flex-shrink', inputClassName)}
onChange={(e) => field.handleChange(e.target.value)} value={value}
placeholder={placeholder} onChange={(e) => field.handleChange(e.target.value)}
/> placeholder={placeholder}
/>
{showError && (
<Text className="mt-0.5 text-left" size={'sm'} variant={'danger'}>
{error}
</Text>
)}
</div>
); );
if (label === null) return InputComponent; if (label === null) return InputComponent;