diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/DatasourceForm.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/DatasourceForm.tsx index a5fa69d2b..a5a467f15 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/DatasourceForm.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/DatasourceForm.tsx @@ -32,7 +32,7 @@ const DataSourceFormHeader: React.FC<{ dataSource: DataSource }> = ({ dataSource
- +
diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/FormWrapper.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/FormWrapper.tsx index 591369852..11f7394ff 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/FormWrapper.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/FormWrapper.tsx @@ -4,16 +4,51 @@ import React from 'react'; import { WhiteListBlock } from './WhiteListBlock'; import { FormApi } from '@tanstack/react-form'; import { Button } from '@/components/ui/buttons'; +import { SubscribeButton } from '@/components/ui/form/FormBase'; +// Common field interface properties +interface FieldComponentProps { + label: string | null; + labelClassName?: string; + className?: string; + placeholder?: string; +} + +// Define a more specific but limited interface for our form +// This avoids the deep type recursion while maintaining safety +export interface BusterFormApi { + handleSubmit: () => void; + reset: () => void; + state: { + canSubmit: boolean; + isSubmitting: boolean; + isDirty: boolean; + }; + AppForm: React.ComponentType<{ children?: React.ReactNode }>; + AppField: any; // Using any for AppField to avoid type complexity + SubscribeButton: React.ComponentType<{ + submitLabel: string; + disableIfNotChanged?: boolean; + useResetButton?: boolean; + }>; +} + +// Field interface representing what's available inside the field prop +interface FieldInterface { + TextField: React.FC; + NumberField: React.FC; + PasswordField: React.FC; + name: string; +} + +// Use a typed approach for the form export interface FormWrapperProps { children: React.ReactNode; flow: 'create' | 'update'; - form: FormApi; + form: BusterFormApi; } export function FormWrapper({ form, children, flow }: FormWrapperProps) { - console.log(form); - return (
-
- - -
+ + +
); } diff --git a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/PostgresForm.tsx b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/PostgresForm.tsx index 7e7369b84..fd70df85d 100644 --- a/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/PostgresForm.tsx +++ b/web/src/app/app/(settings_layout)/settings/(restricted-width)/datasources/(admin-restricted-space)/[datasourceId]/_forms/PostgresForm.tsx @@ -8,32 +8,50 @@ import { } from '@/api/buster_rest/data_source'; import { useAppForm } from '@/components/ui/form/useFormBaseHooks'; import { MultipleInlineFields } from '@/components/ui/form/FormBase'; +import { useBusterNotifications } from '@/context/BusterNotifications'; +import { useRouter } from 'next/router'; +import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes'; +import { useConfetti } from '@/hooks/useConfetti'; export const PostgresForm: React.FC<{ dataSource?: DataSource; }> = ({ dataSource }) => { + const { fireConfetti } = useConfetti(); + const { openSuccessMessage, openConfirmModal } = useBusterNotifications(); + const router = useRouter(); const { mutateAsync: createDataSource } = useCreatePostgresDataSource(); const { mutateAsync: updateDataSource } = useUpdatePostgresDataSource(); - const credentials = dataSource?.credentials as PostgresCredentials; + const credentials = dataSource?.credentials as PostgresCredentials | undefined; const flow = dataSource?.id ? 'update' : 'create'; const form = useAppForm({ defaultValues: { - host: credentials.host || '', - port: credentials.port || 5432, - username: credentials.username || '', - password: credentials.password || '', - default_database: credentials.default_database || '', - default_schema: credentials.default_schema || '', + host: credentials?.host || '', + port: credentials?.port || 5432, + username: credentials?.username || '', + password: credentials?.password || '', + default_database: credentials?.default_database || '', + default_schema: credentials?.default_schema || '', type: 'postgres' as const, - name: dataSource?.name || credentials.name || '' + name: dataSource?.name || credentials?.name || '' } satisfies Parameters[0], onSubmit: async ({ value }) => { if (flow === 'update' && dataSource?.id) { await updateDataSource({ id: dataSource.id, ...value }); + openSuccessMessage('Datasource updated'); } else { await createDataSource(value); + fireConfetti(9999); + openConfirmModal({ + title: 'Datasource created', + description: 'Datasource created successfully', + content: + 'Hooray! Your datasource has been created. You can now use it in your projects. You will need to create datasets to use with it.', + onOk: () => { + router.push(createBusterRoute({ route: BusterRoutes.APP_DATASETS })); + } + }); } } }); @@ -41,7 +59,7 @@ export const PostgresForm: React.FC<{ const labelClassName = 'min-w-[175px]'; return ( - + ( diff --git a/web/src/components/features/sidebars/SidebarSettings.tsx b/web/src/components/features/sidebars/SidebarSettings.tsx index 87d6ef8e8..9a8cbf1a0 100644 --- a/web/src/components/features/sidebars/SidebarSettings.tsx +++ b/web/src/components/features/sidebars/SidebarSettings.tsx @@ -35,7 +35,7 @@ const workspaceItems: ISidebarGroup = { id: createBusterRoute({ route: BusterRoutes.SETTINGS_API_KEYS }) }, { - label: 'Datasources', + label: 'Data Sources', route: createBusterRoute({ route: BusterRoutes.SETTINGS_DATASOURCES }), id: createBusterRoute({ route: BusterRoutes.SETTINGS_DATASOURCES }) } diff --git a/web/src/components/ui/form/FormBase.tsx b/web/src/components/ui/form/FormBase.tsx index 1c8e3afdd..c4b72825e 100644 --- a/web/src/components/ui/form/FormBase.tsx +++ b/web/src/components/ui/form/FormBase.tsx @@ -154,15 +154,17 @@ export function MultipleInlineFields({ export function SubscribeButton({ submitLabel, - useResetButton = true + useResetButton = true, + disableIfNotChanged = false }: { submitLabel: string; useResetButton?: boolean; + disableIfNotChanged?: boolean; }) { const form = useFormContext(); return ( - [state.canSubmit, state.isSubmitting]}> + [state.canSubmit, state.isSubmitting, state.isDirty]}> {([canSubmit, isSubmitting]) => (
{useResetButton && ( @@ -170,7 +172,11 @@ export function SubscribeButton({ Reset )} -
diff --git a/web/src/components/ui/form/useFormBaseHooks.tsx b/web/src/components/ui/form/useFormBaseHooks.tsx index 9d5c05888..4cad92a8f 100644 --- a/web/src/components/ui/form/useFormBaseHooks.tsx +++ b/web/src/components/ui/form/useFormBaseHooks.tsx @@ -1,4 +1,4 @@ -import { createFormHookContexts, createFormHook } from '@tanstack/react-form'; +import { createFormHook } from '@tanstack/react-form'; import { NumberField, PasswordField,