From c26ad756996cefc8230fe308b63ab72a7196a75a Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 14:37:06 -0500 Subject: [PATCH 01/16] refactor: add new env vars, new nx target --- .gitignore | 1 + apps/recnet/project.json | 12 ++++++++++++ apps/recnet/src/clientEnv.ts | 12 +----------- apps/recnet/src/serverEnv.ts | 18 ++++++++++++++++++ apps/recnet/src/utils/resolveBaseUrl.ts | 11 +++++++++++ 5 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 apps/recnet/src/utils/resolveBaseUrl.ts diff --git a/.gitignore b/.gitignore index ad63f2e6..6ecca4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ Thumbs.db # Next.js .next +certificates # env .env diff --git a/apps/recnet/project.json b/apps/recnet/project.json index efb6fa04..32fdac87 100644 --- a/apps/recnet/project.json +++ b/apps/recnet/project.json @@ -39,6 +39,18 @@ ], "cwd": "apps/recnet" } + }, + "dev:ssl": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "next dev --experimental-https", + "forwardAllArgs": true + } + ], + "cwd": "apps/recnet" + } } }, "tags": ["type:app"] diff --git a/apps/recnet/src/clientEnv.ts b/apps/recnet/src/clientEnv.ts index 6f5986cf..68721d01 100644 --- a/apps/recnet/src/clientEnv.ts +++ b/apps/recnet/src/clientEnv.ts @@ -1,16 +1,6 @@ import { z } from "zod"; -function resolveBaseUrl(env: string | undefined) { - /** - * If the environment is preview, we need to use the Vercel branch URL. - * Otherwise, we use the base URL. - * Ref: https://vercel.com/docs/projects/environment-variables/framework-environment-variables#NEXT_PUBLIC_VERCEL_BRANCH_URL - */ - if (env === "preview") { - return `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`; - } - return process.env.NEXT_PUBLIC_BASE_URL; -} +import { resolveBaseUrl } from "./utils/resolveBaseUrl"; export const clientEnvSchema = z.object({ NEXT_PUBLIC_FIREBASE_API_KEY: z.string(), diff --git a/apps/recnet/src/serverEnv.ts b/apps/recnet/src/serverEnv.ts index d2d64511..e7d28f06 100644 --- a/apps/recnet/src/serverEnv.ts +++ b/apps/recnet/src/serverEnv.ts @@ -1,5 +1,15 @@ import { z } from "zod"; +import { resolveBaseUrl } from "./utils/resolveBaseUrl"; + +function resolveSlackRedirectUri(env: string | undefined) { + const baseUrl = resolveBaseUrl(env); + if (!baseUrl) { + return undefined; + } + return baseUrl + process.env.SLACK_OAUTH_REDIRECT_URI; +} + const serverConfigSchema = z.object({ USE_SECURE_COOKIES: z.coerce.boolean(), COOKIE_SIGNATURE_KEY: z.string(), @@ -9,6 +19,9 @@ const serverConfigSchema = z.object({ NEXT_PUBLIC_FIREBASE_API_KEY: z.string(), NEXT_PUBLIC_FIREBASE_PROJECT_ID: z.string(), RECNET_API_ENDPOINT: z.string(), + SLACK_APP_CLIENT_ID: z.string(), + SLACK_OAUTH_APP_SCOPES: z.string(), + SLACK_OAUTH_REDIRECT_URI: z.string(), }); const serverConfigRes = serverConfigSchema.safeParse({ @@ -20,6 +33,11 @@ const serverConfigRes = serverConfigSchema.safeParse({ NEXT_PUBLIC_FIREBASE_API_KEY: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, NEXT_PUBLIC_FIREBASE_PROJECT_ID: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, RECNET_API_ENDPOINT: process.env.RECNET_API_ENDPOINT, + SLACK_APP_CLIENT_ID: process.env.SLACK_APP_CLIENT_ID, + SLACK_OAUTH_APP_SCOPES: process.env.SLACK_OAUTH_APP_SCOPES, + SLACK_OAUTH_REDIRECT_URI: resolveSlackRedirectUri( + process.env.NEXT_PUBLIC_VERCEL_ENV + ), }); if (!serverConfigRes.success) { diff --git a/apps/recnet/src/utils/resolveBaseUrl.ts b/apps/recnet/src/utils/resolveBaseUrl.ts new file mode 100644 index 00000000..f262d23f --- /dev/null +++ b/apps/recnet/src/utils/resolveBaseUrl.ts @@ -0,0 +1,11 @@ +export function resolveBaseUrl(env: string | undefined) { + /** + * If the environment is preview, we need to use the Vercel branch URL. + * Otherwise, we use the base URL. + * Ref: https://vercel.com/docs/projects/environment-variables/framework-environment-variables#NEXT_PUBLIC_VERCEL_BRANCH_URL + */ + if (env === "preview") { + return `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`; + } + return process.env.NEXT_PUBLIC_BASE_URL; +} From a73f68fae2d2a5297c6add197c6790e93aac87f6 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 16:56:24 -0500 Subject: [PATCH 02/16] chore: finish route handlers for slack oauth --- apps/recnet/.env.local.sample | 3 +++ .../src/app/api/slack/oauth/callback/route.ts | 19 +++++++++++++++++++ .../src/app/api/slack/oauth/install/route.ts | 7 +++++++ .../api/slack/oauth/slackAppInstallHelper.ts | 5 +++++ .../subscription/SubscriptionSetting.tsx | 8 ++++++++ 5 files changed, 42 insertions(+) create mode 100644 apps/recnet/src/app/api/slack/oauth/callback/route.ts create mode 100644 apps/recnet/src/app/api/slack/oauth/install/route.ts create mode 100644 apps/recnet/src/app/api/slack/oauth/slackAppInstallHelper.ts diff --git a/apps/recnet/.env.local.sample b/apps/recnet/.env.local.sample index 1a534a55..f55eda99 100644 --- a/apps/recnet/.env.local.sample +++ b/apps/recnet/.env.local.sample @@ -14,3 +14,6 @@ FIREBASE_PRIVATE_KEY= FIREBASE_CLIENT_EMAIL= CRON_SECRET= RECNET_API_ENDPOINT="http://localhost:4000" +SLACK_APP_CLIENT_ID="" +SLACK_OAUTH_APP_SCOPES="" +SLACK_OAUTH_REDIRECT_URI="" diff --git a/apps/recnet/src/app/api/slack/oauth/callback/route.ts b/apps/recnet/src/app/api/slack/oauth/callback/route.ts new file mode 100644 index 00000000..d23e76a9 --- /dev/null +++ b/apps/recnet/src/app/api/slack/oauth/callback/route.ts @@ -0,0 +1,19 @@ +import { redirect } from "next/navigation"; +import { type NextRequest } from "next/server"; + +export async function GET(req: NextRequest) { + const searchParams = req.nextUrl.searchParams; + const code = searchParams.get("code"); + console.log("req: ", req); + console.log("code", code); + + if (!code) { + redirect("/feeds?slackOAuthStatus=error"); + } + try { + // hit trpc api to forward the code to api server + redirect("/feeds?slackOAuthStatus=success"); + } catch (e) { + redirect("/feeds?slackOAuthStatus=error"); + } +} diff --git a/apps/recnet/src/app/api/slack/oauth/install/route.ts b/apps/recnet/src/app/api/slack/oauth/install/route.ts new file mode 100644 index 00000000..16b9e7e4 --- /dev/null +++ b/apps/recnet/src/app/api/slack/oauth/install/route.ts @@ -0,0 +1,7 @@ +import { redirect } from "next/navigation"; + +import { generateOAuthLink } from "../slackAppInstallHelper"; + +export async function GET(req: Request) { + redirect(generateOAuthLink()); +} diff --git a/apps/recnet/src/app/api/slack/oauth/slackAppInstallHelper.ts b/apps/recnet/src/app/api/slack/oauth/slackAppInstallHelper.ts new file mode 100644 index 00000000..5ebeaab6 --- /dev/null +++ b/apps/recnet/src/app/api/slack/oauth/slackAppInstallHelper.ts @@ -0,0 +1,5 @@ +import { serverEnv } from "@recnet/recnet-web/serverEnv"; + +export function generateOAuthLink(): string { + return `https://slack.com/oauth/v2/authorize?scope=${serverEnv.SLACK_OAUTH_APP_SCOPES}&client_id=${serverEnv.SLACK_APP_CLIENT_ID}&redirect_uri=${serverEnv.SLACK_OAUTH_REDIRECT_URI}`; +} diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index 6293d31b..3aa66ffd 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -18,6 +18,7 @@ import { toast } from "sonner"; import { z } from "zod"; import { trpc } from "@recnet/recnet-web/app/_trpc/client"; +import { RecNetLink } from "../../Link"; import { LoadingBox } from "@recnet/recnet-web/components/LoadingBox"; import { cn } from "@recnet/recnet-web/utils/cn"; @@ -232,6 +233,13 @@ export function SubscriptionSetting() { })} )} + + + Slack Integration + + + + ); } From 87a94133202e9c256246b1c2a8626b2e2666889a Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 16:56:58 -0500 Subject: [PATCH 03/16] chore: fix import --- .../src/components/setting/subscription/SubscriptionSetting.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index 3aa66ffd..e135f036 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -18,7 +18,7 @@ import { toast } from "sonner"; import { z } from "zod"; import { trpc } from "@recnet/recnet-web/app/_trpc/client"; -import { RecNetLink } from "../../Link"; +import { RecNetLink } from "@recnet/recnet-web/components/Link"; import { LoadingBox } from "@recnet/recnet-web/components/LoadingBox"; import { cn } from "@recnet/recnet-web/utils/cn"; From 71ddc42edbeab3aa06ca32e97b1a82caf797f7f4 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 17:09:18 -0500 Subject: [PATCH 04/16] feat: finish route handler --- .../recnet/src/app/api/slack/oauth/callback/route.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/recnet/src/app/api/slack/oauth/callback/route.ts b/apps/recnet/src/app/api/slack/oauth/callback/route.ts index d23e76a9..64007310 100644 --- a/apps/recnet/src/app/api/slack/oauth/callback/route.ts +++ b/apps/recnet/src/app/api/slack/oauth/callback/route.ts @@ -1,19 +1,21 @@ import { redirect } from "next/navigation"; import { type NextRequest } from "next/server"; +import { serverClient } from "@recnet/recnet-web/app/_trpc/serverClient"; + export async function GET(req: NextRequest) { const searchParams = req.nextUrl.searchParams; const code = searchParams.get("code"); - console.log("req: ", req); - console.log("code", code); if (!code) { redirect("/feeds?slackOAuthStatus=error"); } + // hit trpc api to forward the code to api server + let isSuccess = true; try { - // hit trpc api to forward the code to api server - redirect("/feeds?slackOAuthStatus=success"); + await serverClient.slackOAuth2FA({ code }); } catch (e) { - redirect("/feeds?slackOAuthStatus=error"); + isSuccess = false; } + redirect(`/feeds?slackOAuthStatus=${isSuccess ? "success" : "error"}`); } From c6aa4e42b92ac3490fa8e350f02327a5a23b1132 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 17:20:28 -0500 Subject: [PATCH 05/16] feat: add new api model and trpc procedures --- .../recnet/src/server/routers/subscription.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/recnet/src/server/routers/subscription.ts b/apps/recnet/src/server/routers/subscription.ts index 6e7da4b6..d0b5abc3 100644 --- a/apps/recnet/src/server/routers/subscription.ts +++ b/apps/recnet/src/server/routers/subscription.ts @@ -1,7 +1,9 @@ import { getUsersSubscriptionsResponseSchema, + getUsersSubscriptionsSlackOAuthResponseSchema, postUsersSubscriptionsRequestSchema, postUsersSubscriptionsResponseSchema, + postUsersSubscriptionsSlackOAuthRequestSchema, } from "@recnet/recnet-api-model"; import { checkRecnetJWTProcedure } from "./middleware"; @@ -27,4 +29,27 @@ export const subscriptionRouter = router({ }); return postUsersSubscriptionsResponseSchema.parse(data); }), + slackOAuth2FA: checkRecnetJWTProcedure + .input(postUsersSubscriptionsSlackOAuthRequestSchema) + .mutation(async (opts) => { + // const { code } = opts.input; + // const { recnetApi } = opts.ctx; + + // TODO: uncomment this when the API is ready + // const { data } = await recnetApi.post("/users/subscriptions/slack/oauth"); + // return data; + + console.log("Slack OAuth 2FA success"); + return; + }), + getSlackOAuthStatus: checkRecnetJWTProcedure + .output(getUsersSubscriptionsSlackOAuthResponseSchema) + .query(async (opts) => { + // const { recnetApi } = opts.ctx; + // const { data } = await recnetApi.get("/users/subscriptions/slack/oauth"); + // return getUsersSubscriptionsSlackOAuthResponseSchema.parse(data); + return getUsersSubscriptionsSlackOAuthResponseSchema.parse({ + workspaceName: "Slack Mock Workspace", + }); + }), }); From aead2350138e9d33a1734db77cde464edcca84b7 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 17:41:07 -0500 Subject: [PATCH 06/16] feat: add slack oauth flow result dialog --- apps/recnet/src/app/feeds/SlackOAuthModal.tsx | 75 +++++++++++++++++++ apps/recnet/src/app/feeds/page.tsx | 3 + .../recnet/src/server/routers/subscription.ts | 5 +- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 apps/recnet/src/app/feeds/SlackOAuthModal.tsx diff --git a/apps/recnet/src/app/feeds/SlackOAuthModal.tsx b/apps/recnet/src/app/feeds/SlackOAuthModal.tsx new file mode 100644 index 00000000..c0ec97f3 --- /dev/null +++ b/apps/recnet/src/app/feeds/SlackOAuthModal.tsx @@ -0,0 +1,75 @@ +"use client"; +import { Button, Dialog } from "@radix-ui/themes"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; +import { useEffect, useState } from "react"; + +/** + * Modal to display the result of slack OAuth flow + */ +export function SlackOAuthModal() { + const [shouldShow, setShouldShow] = useState(false); + const [oauthStatus, setOAuthStatus] = useState<"success" | "error" | null>( + null + ); + const pathname = usePathname(); + const router = useRouter(); + + const searchParams = useSearchParams(); + + useEffect(() => { + const status = searchParams.get("slackOAuthStatus"); + if (status) { + setShouldShow(true); + setOAuthStatus(status as "success" | "error"); + } + }, [searchParams]); + + if (!shouldShow || !oauthStatus) { + return null; + } + + return ( + { + // when closed, remove the search param + if (!open) { + router.replace(pathname); + } + setShouldShow(open); + }} + > + +
+ + {oauthStatus === "success" + ? "✅ You are all set!" + : "❌ Slack OAuth flow failed"} + + + {oauthStatus === "success" + ? "Successfully installed the Slack app! You can now receive message from us in your workspace." + : "Slack OAuth flow failed. Please try again or contact us."} + +
+ +
+
+
+
+ ); +} diff --git a/apps/recnet/src/app/feeds/page.tsx b/apps/recnet/src/app/feeds/page.tsx index 755d3bb8..1f7748c5 100644 --- a/apps/recnet/src/app/feeds/page.tsx +++ b/apps/recnet/src/app/feeds/page.tsx @@ -19,6 +19,8 @@ import { formatDate, } from "@recnet/recnet-date-fns"; +import { SlackOAuthModal } from "./SlackOAuthModal"; + import { trpc } from "../_trpc/client"; import { OnboardingDialog } from "../onboard/OnboardingDialog"; @@ -124,6 +126,7 @@ export default function FeedPage({ "md:py-12" )} > + {Object.keys(recsGroupByTitle).length > 0 ? ( <> diff --git a/apps/recnet/src/server/routers/subscription.ts b/apps/recnet/src/server/routers/subscription.ts index d0b5abc3..1b21fb01 100644 --- a/apps/recnet/src/server/routers/subscription.ts +++ b/apps/recnet/src/server/routers/subscription.ts @@ -10,6 +10,8 @@ import { checkRecnetJWTProcedure } from "./middleware"; import { router } from "../trpc"; +let workspaceName: string | undefined = undefined; + export const subscriptionRouter = router({ getSubscriptions: checkRecnetJWTProcedure .output(getUsersSubscriptionsResponseSchema) @@ -40,6 +42,7 @@ export const subscriptionRouter = router({ // return data; console.log("Slack OAuth 2FA success"); + workspaceName = "Slack Mock Workspace"; return; }), getSlackOAuthStatus: checkRecnetJWTProcedure @@ -49,7 +52,7 @@ export const subscriptionRouter = router({ // const { data } = await recnetApi.get("/users/subscriptions/slack/oauth"); // return getUsersSubscriptionsSlackOAuthResponseSchema.parse(data); return getUsersSubscriptionsSlackOAuthResponseSchema.parse({ - workspaceName: "Slack Mock Workspace", + workspaceName, }); }), }); From 245171205af7b42be481aacac819fd330beff295 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 17:54:25 -0500 Subject: [PATCH 07/16] feat: finish changes in subscription setting --- .../subscription/SubscriptionSetting.tsx | 21 ++++++++++++++++--- .../recnet/src/server/routers/subscription.ts | 6 ++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index e135f036..49534b05 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -195,6 +195,11 @@ function SubscriptionTypeCard(props: { export function SubscriptionSetting() { const { data, isFetching } = trpc.getSubscriptions.useQuery(); + const { data: slackOAuthData, isFetching: isFetchingSlackOAuthData } = + trpc.getSlackOAuthStatus.useQuery(); + + console.log(slackOAuthData); + const [openedType, setOpenType] = useState( undefined ); @@ -237,9 +242,19 @@ export function SubscriptionSetting() { Slack Integration - - - + {isFetchingSlackOAuthData ? ( + + ) : slackOAuthData?.workspaceName === null ? ( + + + + ) : ( + + Currently integrated with workspace: {slackOAuthData?.workspaceName} + + )} ); } diff --git a/apps/recnet/src/server/routers/subscription.ts b/apps/recnet/src/server/routers/subscription.ts index 1b21fb01..5bbeb920 100644 --- a/apps/recnet/src/server/routers/subscription.ts +++ b/apps/recnet/src/server/routers/subscription.ts @@ -10,8 +10,6 @@ import { checkRecnetJWTProcedure } from "./middleware"; import { router } from "../trpc"; -let workspaceName: string | undefined = undefined; - export const subscriptionRouter = router({ getSubscriptions: checkRecnetJWTProcedure .output(getUsersSubscriptionsResponseSchema) @@ -42,7 +40,6 @@ export const subscriptionRouter = router({ // return data; console.log("Slack OAuth 2FA success"); - workspaceName = "Slack Mock Workspace"; return; }), getSlackOAuthStatus: checkRecnetJWTProcedure @@ -51,8 +48,9 @@ export const subscriptionRouter = router({ // const { recnetApi } = opts.ctx; // const { data } = await recnetApi.get("/users/subscriptions/slack/oauth"); // return getUsersSubscriptionsSlackOAuthResponseSchema.parse(data); + return getUsersSubscriptionsSlackOAuthResponseSchema.parse({ - workspaceName, + workspaceName: null, }); }), }); From be75ad61382ce7b0c31f97f22391241687fd8392 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Thu, 21 Nov 2024 18:06:57 -0500 Subject: [PATCH 08/16] feat: forward error message from slack --- apps/recnet/src/app/api/slack/oauth/callback/route.ts | 10 ++++++++-- apps/recnet/src/app/feeds/SlackOAuthModal.tsx | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/recnet/src/app/api/slack/oauth/callback/route.ts b/apps/recnet/src/app/api/slack/oauth/callback/route.ts index 64007310..4c3734c8 100644 --- a/apps/recnet/src/app/api/slack/oauth/callback/route.ts +++ b/apps/recnet/src/app/api/slack/oauth/callback/route.ts @@ -6,9 +6,13 @@ import { serverClient } from "@recnet/recnet-web/app/_trpc/serverClient"; export async function GET(req: NextRequest) { const searchParams = req.nextUrl.searchParams; const code = searchParams.get("code"); + const errorDesc = searchParams.get("error_description"); + console.log("errorDesc: ", errorDesc); if (!code) { - redirect("/feeds?slackOAuthStatus=error"); + redirect( + `/feeds?slackOAuthStatus=error${errorDesc ? `&error_description=${errorDesc}` : ""}` + ); } // hit trpc api to forward the code to api server let isSuccess = true; @@ -17,5 +21,7 @@ export async function GET(req: NextRequest) { } catch (e) { isSuccess = false; } - redirect(`/feeds?slackOAuthStatus=${isSuccess ? "success" : "error"}`); + redirect( + `/feeds?slackOAuthStatus=${isSuccess ? "success" : "error"}${errorDesc ? `&error_description=${errorDesc}` : ""}` + ); } diff --git a/apps/recnet/src/app/feeds/SlackOAuthModal.tsx b/apps/recnet/src/app/feeds/SlackOAuthModal.tsx index c0ec97f3..97204603 100644 --- a/apps/recnet/src/app/feeds/SlackOAuthModal.tsx +++ b/apps/recnet/src/app/feeds/SlackOAuthModal.tsx @@ -55,7 +55,8 @@ export function SlackOAuthModal() { {oauthStatus === "success" ? "Successfully installed the Slack app! You can now receive message from us in your workspace." - : "Slack OAuth flow failed. Please try again or contact us."} + : searchParams.get("error_description") || + "Slack OAuth flow failed. Please try again or contact us."}
+ ) : ( - - Currently integrated with workspace: {slackOAuthData?.workspaceName} - +
+ + ✅ Currently installed in workspace:{" "} + {workspaceName} + +
)}
); From 2a0806e762301920e85bc104759bb5c6143bdd1b Mon Sep 17 00:00:00 2001 From: swh00tw Date: Fri, 22 Nov 2024 21:55:01 -0500 Subject: [PATCH 11/16] feat: refactor ui --- .../subscription/SubscriptionSetting.tsx | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index 6e51e58b..7a8691a3 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -61,6 +61,7 @@ function SubscriptionTypeCard(props: { const { isDirty } = useFormState({ control }); const updateSubscriptionMutation = trpc.updateSubscription.useMutation(); + const { data: slackOAuthData } = trpc.getSlackOAuthStatus.useQuery(); return ( @@ -93,9 +94,11 @@ function SubscriptionTypeCard(props: { onSubmit={handleSubmit( async (data, e) => { setIsSubmitting(true); - // handle special case for WEEKLY DIGEST - // for weekly digest, at least one channel must be selected - // if no, then show error message + /** + * Special case 1: WEEKLY_DIGEST + * For weekly digest, at least one channel must be selected + * if no, then show error message + */ if (type === "WEEKLY_DIGEST" && data.channels.length === 0) { setError("channels", { type: "manual", @@ -105,6 +108,24 @@ function SubscriptionTypeCard(props: { setIsSubmitting(false); return; } + /* + * Special case 2: SLACK distribution channel + * When user selects slack channel, we need to check if the user has completed slack integration oauth flow or not + * If not, then show error message and ask user to complete slack integration + */ + if ( + slackOAuthData?.workspaceName === null && + data.channels.includes(subscriptionChannelSchema.enum.SLACK) + ) { + setError("channels", { + type: "manual", + message: + "To enable slack distribution channel, you need to complete slack integration first. See 'Slack Integration' below to learn more", + }); + setIsSubmitting(false); + return; + } + await updateSubscriptionMutation.mutateAsync({ type, channels: data.channels, @@ -152,16 +173,6 @@ function SubscriptionTypeCard(props: { }} /> - - - BETA - - - Distribute by Slack is currently in beta version. Only people in - Cornell-NLP slack workspace can use this feature. And the email - account of the slack account must match the RecNet account. - - ) : ( -
+
✅ Currently installed in{" "} {workspaceName} + { + await deleteSlackOAuthInfoMutation.mutateAsync(); + utils.getSlackOAuthStatus.invalidate(); + }} + title="Are you sure?" + description="This will uninstall the slack integration and you will not be able to distribute subscription through slack anymore. You can go through the OAuth process to install again." + > + +
)}
diff --git a/apps/recnet/src/server/routers/subscription.ts b/apps/recnet/src/server/routers/subscription.ts index e8ea554f..4b7d2077 100644 --- a/apps/recnet/src/server/routers/subscription.ts +++ b/apps/recnet/src/server/routers/subscription.ts @@ -52,4 +52,8 @@ export const subscriptionRouter = router({ const { data } = await recnetApi.get("/users/subscriptions/slack/oauth"); return getUsersSubscriptionsSlackOauthResponseSchema.parse(data); }), + deleteSlackOAuthInfo: checkRecnetJWTProcedure.mutation(async (opts) => { + const { recnetApi } = opts.ctx; + await recnetApi.delete("/users/subscriptions/slack/oauth"); + }), }); From 4fcf84d25ea030a11d3c59807e395156147e96be Mon Sep 17 00:00:00 2001 From: swh00tw Date: Mon, 25 Nov 2024 19:45:58 -0500 Subject: [PATCH 15/16] chore: change slack install button color --- .../setting/subscription/SubscriptionSetting.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index b6c4ab0c..4f1c3cb3 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -10,7 +10,7 @@ import { CheckboxCards, Button, } from "@radix-ui/themes"; -import { ChevronDown } from "lucide-react"; +import { ChevronDown, Slack as SlackIcon } from "lucide-react"; import { useState } from "react"; import { Controller, useForm, useFormState } from "react-hook-form"; import { toast } from "sonner"; @@ -262,7 +262,14 @@ export function SubscriptionSetting() { ) : workspaceName === null ? ( - + ) : (
From 87a1e673a481460f5f8ae09cf4086f579443bc04 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Mon, 2 Dec 2024 14:16:17 -0800 Subject: [PATCH 16/16] chore: modify wording for removing slack integration --- .../src/components/DoubleConfirmButton.tsx | 2 +- .../subscription/SubscriptionSetting.tsx | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/recnet/src/components/DoubleConfirmButton.tsx b/apps/recnet/src/components/DoubleConfirmButton.tsx index b3989b05..cb65fd3f 100644 --- a/apps/recnet/src/components/DoubleConfirmButton.tsx +++ b/apps/recnet/src/components/DoubleConfirmButton.tsx @@ -9,7 +9,7 @@ interface DoubleConfirmButtonProps { onConfirm: () => Promise; children: React.ReactNode; title: string; - description: string; + description: string | React.ReactNode; cancelButtonProps?: React.ComponentProps; confirmButtonProps?: React.ComponentProps; } diff --git a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx index 4f1c3cb3..0f3eb5d7 100644 --- a/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx +++ b/apps/recnet/src/components/setting/subscription/SubscriptionSetting.tsx @@ -283,7 +283,32 @@ export function SubscriptionSetting() { utils.getSlackOAuthStatus.invalidate(); }} title="Are you sure?" - description="This will uninstall the slack integration and you will not be able to distribute subscription through slack anymore. You can go through the OAuth process to install again." + description={ +
+ {[ + "We will disconnect and will not be able to distribute subscription through slack.", + "But the slack app will still be installed in your workspace.", + "To remove it from your workspace, follow the instructions ", + ].map((text, index) => ( + + {text} + + ))} + + here + + . +
+ } >