add success messages

This commit is contained in:
Nate Kelley 2025-03-24 23:22:06 -06:00
parent 0d2132f021
commit f06e675e21
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 83 additions and 30 deletions

View File

@ -32,7 +32,7 @@ const DataSourceFormHeader: React.FC<{ dataSource: DataSource }> = ({ dataSource
<div className="flex justify-between space-x-2">
<div className="flex items-center space-x-4">
<div className="text-icon-color text-4xl">
<AppDataSourceIcon size={55} type={dataSource.type} />
<AppDataSourceIcon size={32} type={dataSource.type} />
</div>
<div className="flex flex-col space-y-1">

View File

@ -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<FieldComponentProps>;
NumberField: React.FC<FieldComponentProps>;
PasswordField: React.FC<FieldComponentProps>;
name: string;
}
// Use a typed approach for the form
export interface FormWrapperProps {
children: React.ReactNode;
flow: 'create' | 'update';
form: FormApi<any, any, any, any, any, any, any, any, any, any>;
form: BusterFormApi;
}
export function FormWrapper({ form, children, flow }: FormWrapperProps) {
console.log(form);
return (
<form
className="[&_.label-wrapper]:border-b-border flex flex-col space-y-4 [&_.label-wrapper]:border-b [&_.label-wrapper]:pb-4"
@ -25,18 +60,12 @@ export function FormWrapper({ form, children, flow }: FormWrapperProps) {
<WhiteListBlock />
<div className="flex w-full justify-end space-x-2">
<Button variant="ghost" type="reset" onClick={() => form.reset()}>
Reset
</Button>
<Button
variant="black"
type="submit"
disabled={!form.state.canSubmit}
loading={form.state.isSubmitting}>
{flow === 'create' ? 'Create' : 'Update'}
</Button>
</div>
<form.AppForm>
<form.SubscribeButton
submitLabel={flow === 'create' ? 'Create' : 'Update'}
disableIfNotChanged={flow === 'update'}
/>
</form.AppForm>
</form>
);
}

View File

@ -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<typeof createPostgresDataSource>[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 (
<FormWrapper form={form} flow={dataSource?.id ? 'update' : 'create'}>
<FormWrapper form={form} flow={flow}>
<form.AppField
name="name"
children={(field) => (

View File

@ -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 })
}

View File

@ -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 (
<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}>
<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting, state.isDirty]}>
{([canSubmit, isSubmitting]) => (
<div className="flex w-full justify-end space-x-2">
{useResetButton && (
@ -170,7 +172,11 @@ export function SubscribeButton({
Reset
</Button>
)}
<Button variant="black" type="submit" disabled={!canSubmit} loading={isSubmitting}>
<Button
variant="black"
type="submit"
disabled={!canSubmit || (disableIfNotChanged && !form.state.isDirty)}
loading={isSubmitting}>
{submitLabel}
</Button>
</div>

View File

@ -1,4 +1,4 @@
import { createFormHookContexts, createFormHook } from '@tanstack/react-form';
import { createFormHook } from '@tanstack/react-form';
import {
NumberField,
PasswordField,