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,