Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/assets/image/icons/connections/Notion.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/assets/image/icons/connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ export { default as AnthropicIcon } from "@assets/image/icons/connections/Anthro
export { default as RedditIcon } from "@assets/image/icons/connections/Reddit.svg?react";
// Pipedrive icon https://drive.google.com/drive/folders/1Tc48Ffe7BwE7tKv239aA_ENXnG9cG3v0 + https://www.pipedrive.com/en/newsroom/press-kit
export { default as PipedriveIcon } from "@assets/image/icons/connections/Pipedrive.svg?react";
// Notion icon based on MIT licensed sources generated by Cursor
export { default as NotionIcon } from "@assets/image/icons/connections/Notion.svg?react";
4 changes: 4 additions & 0 deletions src/components/organisms/connections/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ export {
PipedriveIntegrationAddForm,
PipedriveIntegrationEditForm,
} from "@components/organisms/connections/integrations/pipedrive";
export {
NotionIntegrationAddForm,
NotionIntegrationEditForm,
} from "@components/organisms/connections/integrations/notion";
105 changes: 105 additions & 0 deletions src/components/organisms/connections/integrations/notion/add.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from "react";

import { useTranslation } from "react-i18next";
import { SingleValue } from "react-select";

import { formsPerIntegrationsMapping } from "@src/constants";
import { notionIntegrationAuthMethods } from "@src/constants/lists/connections";
import { ConnectionAuthType } from "@src/enums";
import { Integrations } from "@src/enums/components";
import { useConnectionForm } from "@src/hooks";
import { SelectOption } from "@src/interfaces/components";
import { notionApiKeyIntegrationSchema, oauthSchema } from "@validations";

import { Select } from "@components/molecules";

export const NotionIntegrationAddForm = ({
connectionId,
triggerParentFormSubmit,
}: {
connectionId?: string;
triggerParentFormSubmit: () => void;
}) => {
const { t } = useTranslation("integrations");
const {
control,
copyToClipboard,
errors,
handleOAuth,
handleSubmit,
isLoading,
register,
setValidationSchema,
setValue,
createConnection,
} = useConnectionForm(oauthSchema, "create");

const [connectionType, setConnectionType] = useState<SingleValue<SelectOption>>();

const configureConnection = async (connectionId: string) => {
switch (connectionType?.value) {
case ConnectionAuthType.OauthDefault:
await handleOAuth(connectionId, Integrations.notion);
break;
case ConnectionAuthType.ApiKey:
await createConnection(connectionId, ConnectionAuthType.ApiKey, Integrations.notion);
break;
default:
break;
}
};

useEffect(() => {
if (!connectionType?.value) {
return;
}
if (connectionType.value === ConnectionAuthType.OauthDefault) {
setValidationSchema(oauthSchema);

return;
}
if (connectionType.value === ConnectionAuthType.ApiKey) {
setValidationSchema(notionApiKeyIntegrationSchema);

return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connectionType]);

useEffect(() => {
if (connectionId) {
configureConnection(connectionId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connectionId]);

const ConnectionTypeComponent =
formsPerIntegrationsMapping[Integrations.notion]?.[connectionType?.value as ConnectionAuthType];

return (
<>
<Select
aria-label={t("placeholders.selectConnectionType")}
disabled={isLoading}
label={t("placeholders.connectionType")}
onChange={(option) => setConnectionType(option)}
options={notionIntegrationAuthMethods}
placeholder={t("placeholders.selectConnectionType")}
value={connectionType}
/>
<form className="mt-6 flex flex-col gap-6" onSubmit={handleSubmit(triggerParentFormSubmit)}>
{ConnectionTypeComponent ? (
<ConnectionTypeComponent
control={control}
copyToClipboard={copyToClipboard}
errors={errors}
isLoading={isLoading}
mode="create"
register={register}
setValue={setValue}
/>
) : null}
</form>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from "react";

import { FieldErrors, UseFormRegister, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";

import { Button, ErrorMessage, Input, SecretInput, Spinner } from "@components/atoms";

import { FloppyDiskIcon } from "@assets/image/icons";

export const NotionApiKeyForm = ({
control,
errors,
isLoading,
mode,
register,
setValue,
}: {
control: any;
errors: FieldErrors<any>;
isLoading: boolean;
mode: "create" | "edit";
register: UseFormRegister<{ [x: string]: any }>;
setValue: any;
}) => {
const { t } = useTranslation("integrations");
const [lockState, setLockState] = useState<{ apiKey: boolean }>({
apiKey: true,
});

const apiKey = useWatch({ control, name: "api_key" });
const isEditMode = mode === "edit";

return (
<>
<div className="relative">
{isEditMode ? (
<SecretInput
type="password"
{...register("api_key")}
aria-label={t("notion.placeholders.apiKey")}
disabled={isLoading}
handleInputChange={(newValue) => setValue("api_key", newValue)}
handleLockAction={(newLockState) =>
setLockState((prevState) => ({ ...prevState, apiKey: newLockState }))
}
isError={!!errors.api_key}
isLocked={lockState.apiKey}
isRequired
label={t("notion.placeholders.apiKey")}
value={apiKey}
/>
) : (
<Input
{...register("api_key")}
aria-label={t("notion.placeholders.apiKey")}
disabled={isLoading}
isError={!!errors.api_key}
isRequired
label={t("notion.placeholders.apiKey")}
value={apiKey}
/>
)}

<ErrorMessage>{errors.api_key?.message as string}</ErrorMessage>
</div>

<Button
aria-label={t("buttons.saveConnection")}
className="ml-auto w-fit border-white px-3 font-medium text-white hover:bg-black"
disabled={isLoading}
type="submit"
variant="outline"
>
{isLoading ? <Spinner /> : <FloppyDiskIcon className="size-5 fill-white transition" />}
{t("buttons.saveConnection")}
</Button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { NotionOauthForm } from "@components/organisms/connections/integrations/notion/authMethods/oauth";
export { NotionApiKeyForm } from "@components/organisms/connections/integrations/notion/authMethods/apiKey";
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";

import { useTranslation } from "react-i18next";

import { Button, Spinner } from "@components/atoms";

import { ExternalLinkIcon } from "@assets/image/icons";

export const NotionOauthForm = ({ isLoading }: { isLoading: boolean }) => {
const { t } = useTranslation("integrations");

return (
<Button
aria-label={t("buttons.startOAuthFlow")}
className="ml-auto w-fit border-white px-3 font-medium text-white hover:bg-black"
disabled={isLoading}
type="submit"
variant="outline"
>
{isLoading ? <Spinner /> : <ExternalLinkIcon className="size-4 fill-white transition" />}
{t("buttons.startOAuthFlow")}
</Button>
);
};
21 changes: 21 additions & 0 deletions src/components/organisms/connections/integrations/notion/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";

import { notionIntegrationAuthMethods } from "@src/constants/lists/connections";
import { ConnectionAuthType } from "@src/enums";
import { Integrations } from "@src/enums/components";
import { notionApiKeyIntegrationSchema, oauthSchema } from "@validations";

import { IntegrationEditForm } from "@components/organisms/connections/integrations";

export const NotionIntegrationEditForm = () => {
return (
<IntegrationEditForm
integrationType={Integrations.notion}
schemas={{
[ConnectionAuthType.ApiKey]: notionApiKeyIntegrationSchema,
[ConnectionAuthType.OauthDefault]: oauthSchema,
}}
selectOptions={notionIntegrationAuthMethods}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { NotionIntegrationAddForm } from "@components/organisms/connections/integrations/notion/add";
export { NotionIntegrationEditForm } from "@components/organisms/connections/integrations/notion/edit";
2 changes: 2 additions & 0 deletions src/constants/connections/addComponentsMapping.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
KubernetesIntegrationAddForm,
RedditIntegrationAddForm,
PipedriveIntegrationAddForm,
NotionIntegrationAddForm,
} from "@components/organisms/connections/integrations";
import { MicrosoftTeamsIntegrationAddForm } from "@components/organisms/connections/integrations/microsoft/teams";

Expand Down Expand Up @@ -58,4 +59,5 @@ export const integrationAddFormComponents: Partial<Record<keyof typeof Integrati
kubernetes: KubernetesIntegrationAddForm,
reddit: RedditIntegrationAddForm,
pipedrive: PipedriveIntegrationAddForm,
notion: NotionIntegrationAddForm,
};
2 changes: 2 additions & 0 deletions src/constants/connections/editComponentsMapping.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
KubernetesIntegrationEditForm,
RedditIntegrationEditForm,
PipedriveIntegrationEditForm,
NotionIntegrationEditForm,
} from "@components/organisms/connections/integrations";
import { MicrosoftTeamsIntegrationEditForm } from "@components/organisms/connections/integrations/microsoft/teams";

Expand Down Expand Up @@ -56,4 +57,5 @@ export const integrationToEditComponent: Partial<Record<keyof typeof Integration
[Integrations.kubernetes]: KubernetesIntegrationEditForm,
[Integrations.reddit]: RedditIntegrationEditForm,
[Integrations.pipedrive]: PipedriveIntegrationEditForm,
[Integrations.notion]: NotionIntegrationEditForm,
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
MicrosoftTeamsOauthPrivateForm,
MicrosoftTeamsDaemonForm,
} from "@components/organisms/connections/integrations/microsoft/teams";
import { NotionOauthForm, NotionApiKeyForm } from "@components/organisms/connections/integrations/notion/authMethods";
import {
SalesforceOauthPrivateForm,
SalesforceOauthForm,
Expand Down Expand Up @@ -135,4 +136,8 @@ export const formsPerIntegrationsMapping: Partial<
[ConnectionAuthType.Oauth]: OauthGoogleYoutubeForm,
[ConnectionAuthType.JsonKey]: JsonKeyGoogleYoutubeForm,
},
[Integrations.notion]: {
[ConnectionAuthType.OauthDefault]: NotionOauthForm,
[ConnectionAuthType.ApiKey]: NotionApiKeyForm,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,7 @@ export const integrationVariablesMapping = {
username: "username",
password: "password",
},
[Integrations.notion]: {
apiKey: "api_key",
},
};
1 change: 1 addition & 0 deletions src/constants/featureFlags.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const featureFlags = {
salesforceHideDefaultOAuth: import.meta.env.VITE_SALESFORCE_HIDE_DEFAULT_OAUTH,
zoomHideDefaultOAuth: import.meta.env.VITE_ZOOM_HIDE_DEFAULT_OAUTH,
microsoftHideDefaultOAuth: import.meta.env.VITE_MICROSOFT_HIDE_INTEGRATION,
notionHideDefaultOAuth: import.meta.env.VITE_NOTION_HIDE_DEFAULT_OAUTH,
telegramHideIntegration: import.meta.env.VITE_HIDE_TELEGRAM_CONN,
pipedriveHideIntegration: import.meta.env.VITE_PIPEDRIVE_HIDE_INTEGRATION,
sendDotEmptyTriggerFilter: import.meta.env.VITE_SEND_DOT_EMPTY_TRIGGER_FILTER,
Expand Down
1 change: 1 addition & 0 deletions src/constants/lists/connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export {
selectIntegrationLinearActor,
microsoftTeamsIntegrationAuthMethods,
salesforceIntegrationAuthMethods,
notionIntegrationAuthMethods,
} from "@constants/lists/connections/options.constants";
9 changes: 9 additions & 0 deletions src/constants/lists/connections/options.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,12 @@ export const selectIntegrationConfluence: SelectOption[] = [
{ label: "OAuth 2.0 App", value: ConnectionAuthType.Oauth },
{ label: "User API Token / PAT", value: ConnectionAuthType.ApiToken },
];

const notionDisplayOAuth = featureFlags.notionHideDefaultOAuth
? []
: [{ label: "OAuth v2 - Default app", value: ConnectionAuthType.OauthDefault }];

export const notionIntegrationAuthMethods: SelectOption[] = [
...notionDisplayOAuth,
{ label: "API Key", value: ConnectionAuthType.ApiKey },
];
7 changes: 7 additions & 0 deletions src/enums/components/connection.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
AnthropicIcon,
RedditIcon,
PipedriveIcon,
NotionIcon,
} from "@assets/image/icons/connections";

export enum ConnectionStatus {
Expand Down Expand Up @@ -75,6 +76,7 @@ export enum Integrations {
kubernetes = "kubernetes",
reddit = "reddit",
pipedrive = "pipedrive",
notion = "notion",
}

export const defaultGoogleConnectionName = "google";
Expand Down Expand Up @@ -259,6 +261,11 @@ export const IntegrationsMap: Record<Integrations, IntegrationSelectOption> = {
label: "Pipedrive",
value: Integrations.pipedrive,
},
notion: {
icon: NotionIcon,
label: "Notion",
value: Integrations.notion,
},
};

const shouldHideIntegration: Partial<Record<Integrations, boolean>> = {
Expand Down
5 changes: 5 additions & 0 deletions src/locales/en/integrations/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,5 +283,10 @@
"authentication": "Pipedrive API authentication",
"apiKey": "How to find your personal API key"
}
},
"notion": {
"placeholders": {
"apiKey": "Internal Integration Secret"
}
}
}
3 changes: 3 additions & 0 deletions src/validations/connection.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ export const pipedriveIntegrationSchema = z.object({
api_key: z.string().min(1, "API Key is required"),
company_domain: z.string().min(1, "Company domain is required").url({ message: "Invalid url" }),
});
export const notionApiKeyIntegrationSchema = z.object({
api_key: z.string().min(1, "Internal Integration Secret is required"),
});

export const oauthSchema = z.object({});

Expand Down
1 change: 1 addition & 0 deletions src/validations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export {
kubernetesIntegrationSchema,
redditPrivateAuthIntegrationSchema,
pipedriveIntegrationSchema,
notionApiKeyIntegrationSchema,
} from "@validations/connection.schema";
export { codeAssetsSchema } from "@validations/coseAndAssets.schema";
export { validateManualRun } from "@validations/manualRun.schema";
Expand Down
Loading