Files
twenty_crm/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx
Paul Rastoin 4a4e65fe4a [REFACTOR] Twenty UI multi barrel (#11301)
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/591
Same than for `twenty-shared` made in
https://github.com/twentyhq/twenty/pull/11083.

## TODO
- [x] Manual migrate twenty-website twenty-ui imports

## What's next:
- Generate barrel and migration script factorization within own package
+ tests
- Refactoring using preconstruct ? TimeBox
- Lint circular dependencies
- Lint import from barrel and forbid them

### Preconstruct
We need custom rollup plugins addition, but preconstruct does not expose
its rollup configuration. It might be possible to handle this using the
babel overrides. But was a big tunnel.
We could give it a try afterwards ! ( allowing cjs interop and stuff
like that )
Stuck to vite lib app

Closed related PRs:
- https://github.com/twentyhq/twenty/pull/11294
- https://github.com/twentyhq/twenty/pull/11203
2025-04-03 09:47:55 +00:00

189 lines
6.2 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { z } from 'zod';
import { useCreateOneDatabaseConnection } from '@/databases/hooks/useCreateOneDatabaseConnection';
import { getForeignDataWrapperType } from '@/databases/utils/getForeignDataWrapperType';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import {
SettingsIntegrationDatabaseConnectionForm,
settingsIntegrationPostgreSQLConnectionFormSchema,
settingsIntegrationStripeConnectionFormSchema,
} from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled';
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { CreateRemoteServerInput } from '~/generated-metadata/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const createRemoteServerInputPostgresSchema =
settingsIntegrationPostgreSQLConnectionFormSchema.transform<CreateRemoteServerInput>(
(values) => ({
foreignDataWrapperType: 'postgres_fdw',
foreignDataWrapperOptions: {
dbname: values.dbname,
host: values.host,
port: values.port,
},
userMappingOptions: {
password: values.password,
user: values.user,
},
schema: values.schema,
label: values.label,
}),
);
type SettingsIntegrationNewConnectionPostgresFormValues = z.infer<
typeof createRemoteServerInputPostgresSchema
>;
const createRemoteServerInputStripeSchema =
settingsIntegrationStripeConnectionFormSchema.transform<CreateRemoteServerInput>(
(values) => ({
foreignDataWrapperType: 'stripe_fdw',
foreignDataWrapperOptions: {
api_key: values.api_key,
},
label: values.label,
}),
);
type SettingsIntegrationNewConnectionStripeFormValues = z.infer<
typeof createRemoteServerInputStripeSchema
>;
type SettingsIntegrationNewConnectionFormValues =
| SettingsIntegrationNewConnectionPostgresFormValues
| SettingsIntegrationNewConnectionStripeFormValues;
export const SettingsIntegrationNewDatabaseConnection = () => {
const { databaseKey = '' } = useParams();
const navigate = useNavigateSettings();
const navigateApp = useNavigateApp();
const [integrationCategoryAll] = useSettingsIntegrationCategories();
const integration = integrationCategoryAll.integrations.find(
({ from: { key } }) => key === databaseKey,
);
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
const { enqueueSnackBar } = useSnackBar();
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
const isIntegrationAvailable = !!integration && isIntegrationEnabled;
useEffect(() => {
if (!isIntegrationAvailable) {
navigateApp(AppPath.NotFound);
}
}, [integration, databaseKey, navigateApp, isIntegrationAvailable]);
const newConnectionSchema =
databaseKey === 'postgresql'
? createRemoteServerInputPostgresSchema
: createRemoteServerInputStripeSchema;
const formConfig = useForm<SettingsIntegrationNewConnectionFormValues>({
mode: 'onTouched',
resolver: zodResolver(newConnectionSchema),
});
if (!isIntegrationAvailable) return null;
const settingsIntegrationsPagePath = getSettingsPath(
SettingsPath.Integrations,
);
const canSave = formConfig.formState.isValid;
const handleSave = async () => {
const formValues = formConfig.getValues();
try {
const createdConnection = await createOneDatabaseConnection(
newConnectionSchema.parse({
...formValues,
foreignDataWrapperType: getForeignDataWrapperType(databaseKey),
}),
);
const connectionId = createdConnection.data?.createOneRemoteServer.id;
if (!connectionId) {
throw new Error('Failed to create connection');
}
navigate(SettingsPath.IntegrationDatabaseConnection, {
databaseKey,
connectionId,
});
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
});
}
};
return (
<SubMenuTopBarContainer
title="New"
links={[
{
children: 'Workspace',
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Integrations',
href: settingsIntegrationsPagePath,
},
{
children: integration.text,
href: `${settingsIntegrationsPagePath}/${databaseKey}`,
},
{ children: 'New' },
]}
actionButton={
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() =>
navigate(SettingsPath.IntegrationDatabase, {
databaseKey,
})
}
onSave={handleSave}
/>
}
>
<SettingsPageContainer>
<FormProvider
// eslint-disable-next-line react/jsx-props-no-spreading
{...formConfig}
>
<Section>
<H2Title
title="Connect a new database"
description="Provide the information to connect your database"
/>
<SettingsIntegrationDatabaseConnectionForm
databaseKey={databaseKey}
/>
</Section>
</FormProvider>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};