Skip to content

Commit 5aa4298

Browse files
authored
feat(UI-1887): notion connection (#1362)
## Description ## Linear Ticket ## What type of PR is this? (check all applicable) - [ ] πŸ’‘ (feat) - A new feature (non-breaking change which adds functionality) - [ ] πŸ”„ (refactor) - Code Refactoring - A code change that neither fixes a bug nor adds a feature - [ ] 🐞 (fix) - Bug Fix (non-breaking change which fixes an issue) - [ ] 🏎 (perf) - Optimization - [ ] πŸ“„ (docs) - Documentation - Documentation only changes - [ ] πŸ“„ (test) - Tests - Adding missing tests or correcting existing tests - [ ] βš™οΈ (ci) - Continuous Integrations - Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) - [ ] β˜‘οΈ (chore) - Chores - Other changes that don't modify src or test files - [ ] ↩️ (revert) - Reverts - Reverts a previous commit(s). <!-- For a timely review/response, please avoid force-pushing additional commits if your PR already received reviews or comments. Before submitting a Pull Request, please ensure you've done the following: - πŸ‘·β€β™€οΈ Create small PRs. In most cases this will be possible. - βœ… Provide tests for your changes. - πŸ“ Use descriptive commit messages (as described below). - πŸ“— Update any related documentation and include any relevant screenshots. Commit Message Structure (all lower-case): <type>(optional ticket number): <description> [optional body] -->
1 parent a4b1948 commit 5aa4298

File tree

22 files changed

+284
-0
lines changed

22 files changed

+284
-0
lines changed
Lines changed: 4 additions & 0 deletions
Loading

β€Žsrc/assets/image/icons/connections/index.tsβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ export { default as AnthropicIcon } from "@assets/image/icons/connections/Anthro
4646
export { default as RedditIcon } from "@assets/image/icons/connections/Reddit.svg?react";
4747
// Pipedrive icon https://drive.google.com/drive/folders/1Tc48Ffe7BwE7tKv239aA_ENXnG9cG3v0 + https://www.pipedrive.com/en/newsroom/press-kit
4848
export { default as PipedriveIcon } from "@assets/image/icons/connections/Pipedrive.svg?react";
49+
// Notion icon based on MIT licensed sources generated by Cursor
50+
export { default as NotionIcon } from "@assets/image/icons/connections/Notion.svg?react";

β€Žsrc/components/organisms/connections/integrations/index.tsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@ export {
7272
PipedriveIntegrationAddForm,
7373
PipedriveIntegrationEditForm,
7474
} from "@components/organisms/connections/integrations/pipedrive";
75+
export {
76+
NotionIntegrationAddForm,
77+
NotionIntegrationEditForm,
78+
} from "@components/organisms/connections/integrations/notion";
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
import { useTranslation } from "react-i18next";
4+
import { SingleValue } from "react-select";
5+
6+
import { formsPerIntegrationsMapping } from "@src/constants";
7+
import { notionIntegrationAuthMethods } from "@src/constants/lists/connections";
8+
import { ConnectionAuthType } from "@src/enums";
9+
import { Integrations } from "@src/enums/components";
10+
import { useConnectionForm } from "@src/hooks";
11+
import { SelectOption } from "@src/interfaces/components";
12+
import { notionApiKeyIntegrationSchema, oauthSchema } from "@validations";
13+
14+
import { Select } from "@components/molecules";
15+
16+
export const NotionIntegrationAddForm = ({
17+
connectionId,
18+
triggerParentFormSubmit,
19+
}: {
20+
connectionId?: string;
21+
triggerParentFormSubmit: () => void;
22+
}) => {
23+
const { t } = useTranslation("integrations");
24+
const {
25+
control,
26+
copyToClipboard,
27+
errors,
28+
handleOAuth,
29+
handleSubmit,
30+
isLoading,
31+
register,
32+
setValidationSchema,
33+
setValue,
34+
createConnection,
35+
} = useConnectionForm(oauthSchema, "create");
36+
37+
const [connectionType, setConnectionType] = useState<SingleValue<SelectOption>>();
38+
39+
const configureConnection = async (connectionId: string) => {
40+
switch (connectionType?.value) {
41+
case ConnectionAuthType.OauthDefault:
42+
await handleOAuth(connectionId, Integrations.notion);
43+
break;
44+
case ConnectionAuthType.ApiKey:
45+
await createConnection(connectionId, ConnectionAuthType.ApiKey, Integrations.notion);
46+
break;
47+
default:
48+
break;
49+
}
50+
};
51+
52+
useEffect(() => {
53+
if (!connectionType?.value) {
54+
return;
55+
}
56+
if (connectionType.value === ConnectionAuthType.OauthDefault) {
57+
setValidationSchema(oauthSchema);
58+
59+
return;
60+
}
61+
if (connectionType.value === ConnectionAuthType.ApiKey) {
62+
setValidationSchema(notionApiKeyIntegrationSchema);
63+
64+
return;
65+
}
66+
// eslint-disable-next-line react-hooks/exhaustive-deps
67+
}, [connectionType]);
68+
69+
useEffect(() => {
70+
if (connectionId) {
71+
configureConnection(connectionId);
72+
}
73+
// eslint-disable-next-line react-hooks/exhaustive-deps
74+
}, [connectionId]);
75+
76+
const ConnectionTypeComponent =
77+
formsPerIntegrationsMapping[Integrations.notion]?.[connectionType?.value as ConnectionAuthType];
78+
79+
return (
80+
<>
81+
<Select
82+
aria-label={t("placeholders.selectConnectionType")}
83+
disabled={isLoading}
84+
label={t("placeholders.connectionType")}
85+
onChange={(option) => setConnectionType(option)}
86+
options={notionIntegrationAuthMethods}
87+
placeholder={t("placeholders.selectConnectionType")}
88+
value={connectionType}
89+
/>
90+
<form className="mt-6 flex flex-col gap-6" onSubmit={handleSubmit(triggerParentFormSubmit)}>
91+
{ConnectionTypeComponent ? (
92+
<ConnectionTypeComponent
93+
control={control}
94+
copyToClipboard={copyToClipboard}
95+
errors={errors}
96+
isLoading={isLoading}
97+
mode="create"
98+
register={register}
99+
setValue={setValue}
100+
/>
101+
) : null}
102+
</form>
103+
</>
104+
);
105+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useState } from "react";
2+
3+
import { FieldErrors, UseFormRegister, useWatch } from "react-hook-form";
4+
import { useTranslation } from "react-i18next";
5+
6+
import { Button, ErrorMessage, Input, SecretInput, Spinner } from "@components/atoms";
7+
8+
import { FloppyDiskIcon } from "@assets/image/icons";
9+
10+
export const NotionApiKeyForm = ({
11+
control,
12+
errors,
13+
isLoading,
14+
mode,
15+
register,
16+
setValue,
17+
}: {
18+
control: any;
19+
errors: FieldErrors<any>;
20+
isLoading: boolean;
21+
mode: "create" | "edit";
22+
register: UseFormRegister<{ [x: string]: any }>;
23+
setValue: any;
24+
}) => {
25+
const { t } = useTranslation("integrations");
26+
const [lockState, setLockState] = useState<{ apiKey: boolean }>({
27+
apiKey: true,
28+
});
29+
30+
const apiKey = useWatch({ control, name: "api_key" });
31+
const isEditMode = mode === "edit";
32+
33+
return (
34+
<>
35+
<div className="relative">
36+
{isEditMode ? (
37+
<SecretInput
38+
type="password"
39+
{...register("api_key")}
40+
aria-label={t("notion.placeholders.apiKey")}
41+
disabled={isLoading}
42+
handleInputChange={(newValue) => setValue("api_key", newValue)}
43+
handleLockAction={(newLockState) =>
44+
setLockState((prevState) => ({ ...prevState, apiKey: newLockState }))
45+
}
46+
isError={!!errors.api_key}
47+
isLocked={lockState.apiKey}
48+
isRequired
49+
label={t("notion.placeholders.apiKey")}
50+
value={apiKey}
51+
/>
52+
) : (
53+
<Input
54+
{...register("api_key")}
55+
aria-label={t("notion.placeholders.apiKey")}
56+
disabled={isLoading}
57+
isError={!!errors.api_key}
58+
isRequired
59+
label={t("notion.placeholders.apiKey")}
60+
value={apiKey}
61+
/>
62+
)}
63+
64+
<ErrorMessage>{errors.api_key?.message as string}</ErrorMessage>
65+
</div>
66+
67+
<Button
68+
aria-label={t("buttons.saveConnection")}
69+
className="ml-auto w-fit border-white px-3 font-medium text-white hover:bg-black"
70+
disabled={isLoading}
71+
type="submit"
72+
variant="outline"
73+
>
74+
{isLoading ? <Spinner /> : <FloppyDiskIcon className="size-5 fill-white transition" />}
75+
{t("buttons.saveConnection")}
76+
</Button>
77+
</>
78+
);
79+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { NotionOauthForm } from "@components/organisms/connections/integrations/notion/authMethods/oauth";
2+
export { NotionApiKeyForm } from "@components/organisms/connections/integrations/notion/authMethods/apiKey";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
3+
import { useTranslation } from "react-i18next";
4+
5+
import { Button, Spinner } from "@components/atoms";
6+
7+
import { ExternalLinkIcon } from "@assets/image/icons";
8+
9+
export const NotionOauthForm = ({ isLoading }: { isLoading: boolean }) => {
10+
const { t } = useTranslation("integrations");
11+
12+
return (
13+
<Button
14+
aria-label={t("buttons.startOAuthFlow")}
15+
className="ml-auto w-fit border-white px-3 font-medium text-white hover:bg-black"
16+
disabled={isLoading}
17+
type="submit"
18+
variant="outline"
19+
>
20+
{isLoading ? <Spinner /> : <ExternalLinkIcon className="size-4 fill-white transition" />}
21+
{t("buttons.startOAuthFlow")}
22+
</Button>
23+
);
24+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
3+
import { notionIntegrationAuthMethods } from "@src/constants/lists/connections";
4+
import { ConnectionAuthType } from "@src/enums";
5+
import { Integrations } from "@src/enums/components";
6+
import { notionApiKeyIntegrationSchema, oauthSchema } from "@validations";
7+
8+
import { IntegrationEditForm } from "@components/organisms/connections/integrations";
9+
10+
export const NotionIntegrationEditForm = () => {
11+
return (
12+
<IntegrationEditForm
13+
integrationType={Integrations.notion}
14+
schemas={{
15+
[ConnectionAuthType.ApiKey]: notionApiKeyIntegrationSchema,
16+
[ConnectionAuthType.OauthDefault]: oauthSchema,
17+
}}
18+
selectOptions={notionIntegrationAuthMethods}
19+
/>
20+
);
21+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { NotionIntegrationAddForm } from "@components/organisms/connections/integrations/notion/add";
2+
export { NotionIntegrationEditForm } from "@components/organisms/connections/integrations/notion/edit";

β€Žsrc/constants/connections/addComponentsMapping.constants.tsβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
KubernetesIntegrationAddForm,
2727
RedditIntegrationAddForm,
2828
PipedriveIntegrationAddForm,
29+
NotionIntegrationAddForm,
2930
} from "@components/organisms/connections/integrations";
3031
import { MicrosoftTeamsIntegrationAddForm } from "@components/organisms/connections/integrations/microsoft/teams";
3132

@@ -58,4 +59,5 @@ export const integrationAddFormComponents: Partial<Record<keyof typeof Integrati
5859
kubernetes: KubernetesIntegrationAddForm,
5960
reddit: RedditIntegrationAddForm,
6061
pipedrive: PipedriveIntegrationAddForm,
62+
notion: NotionIntegrationAddForm,
6163
};

0 commit comments

Comments
Β (0)