From e24ff187b6301b72b11acc21410f0cf238eb0f56 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Sat, 31 Aug 2024 14:36:03 -0400 Subject: [PATCH 1/4] preparePageMetadata: improve logic for title --- apps/web/lib/metadata.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/metadata.ts b/apps/web/lib/metadata.ts index b3b576ce6d4d7f..bf98f6560c8831 100644 --- a/apps/web/lib/metadata.ts +++ b/apps/web/lib/metadata.ts @@ -67,10 +67,11 @@ export const prepareRootMetadata = (recipe: RootMetadataRecipe): Metadata => ({ }); export const preparePageMetadata = (recipe: PageMetadataRecipe): Metadata => { + const { title, description } = recipe; const titleSuffix = `| ${APP_NAME}`; - const { description } = recipe; + return { - title: recipe.title.includes(titleSuffix) ? recipe.title : `${recipe.title} ${titleSuffix}`, + title: title.length === 0 ? APP_NAME : title.includes(titleSuffix) ? title : `${title} ${titleSuffix}`, description, alternates: { canonical: recipe.canonical, @@ -80,7 +81,7 @@ export const preparePageMetadata = (recipe: PageMetadataRecipe): Metadata => { url: recipe.canonical, type: "website", siteName: recipe.siteName, - title: recipe.title, + title, images: [recipe.image], }, metadataBase: recipe.metadataBase, From 9bfaeca12d1a52eb2be1e425b8ea5c74385f9703 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Sat, 31 Aug 2024 14:37:09 -0400 Subject: [PATCH 2/4] add missing pages: oauth2 / platform --- .../app/future/auth/oauth2/authorize/page.tsx | 16 ++ .../future/auth/platform/authorize/page.tsx | 16 ++ .../modules/auth/oauth2/authorize-view.tsx | 177 +++++++++++++++++ .../modules/auth/platform/authorize-view.tsx | 126 ++++++++++++ apps/web/pages/auth/oauth2/authorize.tsx | 180 +----------------- apps/web/pages/auth/platform/authorize.tsx | 129 +------------ 6 files changed, 343 insertions(+), 301 deletions(-) create mode 100644 apps/web/app/future/auth/oauth2/authorize/page.tsx create mode 100644 apps/web/app/future/auth/platform/authorize/page.tsx create mode 100644 apps/web/modules/auth/oauth2/authorize-view.tsx create mode 100644 apps/web/modules/auth/platform/authorize-view.tsx diff --git a/apps/web/app/future/auth/oauth2/authorize/page.tsx b/apps/web/app/future/auth/oauth2/authorize/page.tsx new file mode 100644 index 00000000000000..3fa8dca2358437 --- /dev/null +++ b/apps/web/app/future/auth/oauth2/authorize/page.tsx @@ -0,0 +1,16 @@ +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import Page from "~/auth/oauth2/authorize-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => "Authorize", + () => "" + ); +}; + +export default WithLayout({ + getLayout: null, + Page, +}); diff --git a/apps/web/app/future/auth/platform/authorize/page.tsx b/apps/web/app/future/auth/platform/authorize/page.tsx new file mode 100644 index 00000000000000..4f263ae4c03d54 --- /dev/null +++ b/apps/web/app/future/auth/platform/authorize/page.tsx @@ -0,0 +1,16 @@ +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import Page from "~/auth/platform/authorize-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => "Authorize", + () => "" + ); +}; + +export default WithLayout({ + getLayout: null, + Page, +}); diff --git a/apps/web/modules/auth/oauth2/authorize-view.tsx b/apps/web/modules/auth/oauth2/authorize-view.tsx new file mode 100644 index 00000000000000..61efe7eec78f1e --- /dev/null +++ b/apps/web/modules/auth/oauth2/authorize-view.tsx @@ -0,0 +1,177 @@ +"use client"; + +/* eslint-disable react-hooks/exhaustive-deps */ +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; + +import { APP_NAME } from "@calcom/lib/constants"; +import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import { Avatar, Button, Icon, Select } from "@calcom/ui"; + +export default function Authorize() { + const { t } = useLocale(); + const { status } = useSession(); + + const router = useRouter(); + const searchParams = useCompatSearchParams(); + + const client_id = (searchParams?.get("client_id") as string) || ""; + const state = searchParams?.get("state") as string; + const scope = searchParams?.get("scope") as string; + + const queryString = searchParams?.toString(); + + const [selectedAccount, setSelectedAccount] = useState<{ value: string; label: string } | null>(); + const scopes = scope ? scope.toString().split(",") : []; + + const { data: client, isPending: isPendingGetClient } = trpc.viewer.oAuth.getClient.useQuery( + { + clientId: client_id as string, + }, + { + enabled: status !== "loading", + } + ); + + const { data, isPending: isPendingProfiles } = trpc.viewer.teamsAndUserProfilesQuery.useQuery(); + + const generateAuthCodeMutation = trpc.viewer.oAuth.generateAuthCode.useMutation({ + onSuccess: (data) => { + window.location.href = `${client?.redirectUri}?code=${data.authorizationCode}&state=${state}`; + }, + }); + + const mappedProfiles = data + ? data + .filter((profile) => !profile.readOnly) + .map((profile) => ({ + label: profile.name || profile.slug || "", + value: profile.slug || "", + })) + : []; + + useEffect(() => { + if (mappedProfiles.length > 0) { + setSelectedAccount(mappedProfiles[0]); + } + }, [isPendingProfiles]); + + useEffect(() => { + if (status === "unauthenticated") { + const urlSearchParams = new URLSearchParams({ + callbackUrl: `auth/oauth2/authorize?${queryString}`, + }); + router.replace(`/auth/login?${urlSearchParams.toString()}`); + } + }, [status]); + + const isPending = isPendingGetClient || isPendingProfiles || status !== "authenticated"; + + if (isPending) { + return <>; + } + + if (!client) { + return
{t("unauthorized")}
; + } + + return ( +
+
+
+ } + className="items-center" + imageSrc={client.logo} + size="lg" + /> +
+
+
+ Logo +
+
+
+
+

+ {t("access_cal_account", { clientName: client.name, appName: APP_NAME })} +

+
{t("select_account_team")}
+ { - setSelectedAccount(value); - }} - className="w-52" - defaultValue={selectedAccount || mappedProfiles[0]} - options={mappedProfiles} - /> -
{t("allow_client_to", { clientName: client.name })}
-
    -
  • - {" "} - {t("associate_with_cal_account", { clientName: client.name })} -
  • -
  • - {t("see_personal_info")} -
  • -
  • - {t("see_primary_email_address")} -
  • -
  • - {t("connect_installed_apps")} -
  • -
  • - {t("access_event_type")} -
  • -
  • - {t("access_availability")} -
  • -
  • - {t("access_bookings")} -
  • -
-
-
- -
-
-
- {t("allow_client_to_do", { clientName: client.name })} -
-
{t("oauth_access_information", { appName: APP_NAME })}
{" "} -
-
-
-
- - -
-
-
- ); -} +import Authorize from "~/auth/oauth2/authorize-view"; -Authorize.PageWrapper = PageWrapper; +const Page = () => ; +Page.PageWrapper = PageWrapper; +export default Page; diff --git a/apps/web/pages/auth/platform/authorize.tsx b/apps/web/pages/auth/platform/authorize.tsx index ca877f05c62968..fd68c83625a1b4 100644 --- a/apps/web/pages/auth/platform/authorize.tsx +++ b/apps/web/pages/auth/platform/authorize.tsx @@ -1,128 +1,7 @@ -import { useRouter } from "next/navigation"; - -import { APP_NAME } from "@calcom/lib/constants"; -import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Avatar, Button, Icon } from "@calcom/ui"; - import PageWrapper from "@components/PageWrapper"; -import { PERMISSIONS_GROUPED_MAP } from "../../../../../packages/platform/constants/permissions"; -import { hasPermission } from "../../../../../packages/platform/utils/permissions"; - -export default function Authorize() { - const { t } = useLocale(); - const router = useRouter(); - - const searchParams = useCompatSearchParams(); - const queryString = searchParams?.toString(); - - // const { isLoading, error, data: client } = useOAuthClient(queryString); - - const client: { - name: string; - logo?: string; - redirect_uris: string[]; - permissions: number; - } = { - name: "Acme.com", - redirect_uris: ["", ""], - permissions: 7, - }; - - console.log("These are the search params:", queryString); - - const permissions = Object.values(PERMISSIONS_GROUPED_MAP).map((value) => { - let permissionsMessage = ""; - const hasReadPermission = hasPermission(client.permissions, value.read); - const hasWritePermission = hasPermission(client.permissions, value.write); - - if (hasReadPermission || hasWritePermission) { - permissionsMessage = hasReadPermission ? "Read" : "Write"; - } - - if (hasReadPermission && hasWritePermission) { - permissionsMessage = "Read, write"; - } - - return ( - !!permissionsMessage && ( -
  • - - {permissionsMessage} your {`${value.label}s`.toLocaleLowerCase()} -
  • - ) - ); - }); - - return ( -
    -
    -
    - {/* - below is where the client logo will be displayed - first we check if the client has a logo property and display logo if present - else we take logo from user profile pic - */} - {client.logo ? ( - } - className="items-center" - imageSrc={client.logo} - size="lg" - /> - ) : ( - } - className="items-center" - imageSrc="/cal-com-icon.svg" - size="lg" - /> - )} -
    -
    -
    - Logo -
    -
    -
    -
    -

    - {t("access_cal_account", { clientName: client.name, appName: APP_NAME })} -

    -
    - {t("allow_client_to", { clientName: client.name })} -
    -
      {permissions}
    -
    -
    - -
    -
    -
    - {t("allow_client_to_do", { clientName: client.name })} -
    -
    {t("oauth_access_information", { appName: APP_NAME })}
    {" "} -
    -
    -
    -
    - - -
    -
    -
    - ); -} +import Authorize from "~/auth/platform/authorize-view"; -Authorize.PageWrapper = PageWrapper; +const Page = () => ; +Page.PageWrapper = PageWrapper; +export default Page; From 03437de904194936f7fa2151c08d3a48dc2a44a8 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Sat, 31 Aug 2024 14:44:12 -0400 Subject: [PATCH 3/4] auth/error: add to app router and extract to /modules --- apps/web/app/future/auth/error/page.tsx | 19 +++++++ apps/web/modules/auth/error/error-view.tsx | 40 +++++++++++++ apps/web/pages/auth/error.tsx | 56 ++----------------- .../server/lib/auth/error/getStaticProps.ts | 13 +++++ 4 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 apps/web/app/future/auth/error/page.tsx create mode 100644 apps/web/modules/auth/error/error-view.tsx create mode 100644 apps/web/server/lib/auth/error/getStaticProps.ts diff --git a/apps/web/app/future/auth/error/page.tsx b/apps/web/app/future/auth/error/page.tsx new file mode 100644 index 00000000000000..25b885e454b553 --- /dev/null +++ b/apps/web/app/future/auth/error/page.tsx @@ -0,0 +1,19 @@ +import { withAppDirSsg } from "app/WithAppDirSsg"; +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import { getStaticProps } from "@server/lib/auth/error/getStaticProps"; + +import Page from "~/auth/error/error-view"; + +export const generateMetadata = async () => { + return await _generateMetadata( + () => "Error", + () => "" + ); +}; + +const getData = withAppDirSsg(getStaticProps); + +export default WithLayout({ getData, Page, getLayout: null })<"P">; +export const dynamic = "force-static"; diff --git a/apps/web/modules/auth/error/error-view.tsx b/apps/web/modules/auth/error/error-view.tsx new file mode 100644 index 00000000000000..967bc20e41fab9 --- /dev/null +++ b/apps/web/modules/auth/error/error-view.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; +import z from "zod"; + +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Button, Icon } from "@calcom/ui"; + +import AuthContainer from "@components/ui/AuthContainer"; + +const querySchema = z.object({ + error: z.string().optional(), +}); + +export default function Error() { + const { t } = useLocale(); + const searchParams = useSearchParams(); + const { error } = querySchema.parse({ error: searchParams?.get("error") || undefined }); + const errorMsg = error || t("error_during_login"); + return ( + +
    +
    + +
    +
    + +
    +
    +
    + + + +
    +
    + ); +} diff --git a/apps/web/pages/auth/error.tsx b/apps/web/pages/auth/error.tsx index 0a849c3661cd23..96b7f6ca09e299 100644 --- a/apps/web/pages/auth/error.tsx +++ b/apps/web/pages/auth/error.tsx @@ -1,54 +1,8 @@ -import type { GetStaticPropsContext } from "next"; -import Link from "next/link"; -import { useSearchParams } from "next/navigation"; -import z from "zod"; - -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button, Icon } from "@calcom/ui"; - import PageWrapper from "@components/PageWrapper"; -import AuthContainer from "@components/ui/AuthContainer"; - -import { getTranslations } from "@server/lib/getTranslations"; - -const querySchema = z.object({ - error: z.string().optional(), -}); - -export default function Error() { - const { t } = useLocale(); - const searchParams = useSearchParams(); - const { error } = querySchema.parse({ error: searchParams?.get("error") || undefined }); - const errorMsg = error || t("error_during_login"); - return ( - -
    -
    - -
    -
    - -
    -
    -
    - - - -
    -
    - ); -} - -Error.PageWrapper = PageWrapper; -export const getStaticProps = async (context: GetStaticPropsContext) => { - const i18n = await getTranslations(context); +import Error from "~/auth/error/error-view"; - return { - props: { - i18n, - }, - }; -}; +const Page = () => ; +Page.PageWrapper = PageWrapper; +export default Page; +export { getStaticProps } from "@server/lib/auth/error/getStaticProps"; diff --git a/apps/web/server/lib/auth/error/getStaticProps.ts b/apps/web/server/lib/auth/error/getStaticProps.ts new file mode 100644 index 00000000000000..92eab092f5f3d7 --- /dev/null +++ b/apps/web/server/lib/auth/error/getStaticProps.ts @@ -0,0 +1,13 @@ +import type { GetStaticPropsContext } from "next"; + +import { getTranslations } from "@server/lib/getTranslations"; + +export const getStaticProps = async (context: GetStaticPropsContext) => { + const i18n = await getTranslations(context); + + return { + props: { + i18n, + }, + }; +}; From b3aab87916f0a8c8ff8d3344b999225ea8d7be92 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Mon, 2 Sep 2024 06:48:28 -0400 Subject: [PATCH 4/4] improve typing --- apps/web/modules/auth/error/error-view.tsx | 4 +++- apps/web/pages/auth/error.tsx | 7 +++++-- apps/web/server/lib/auth/error/getStaticProps.ts | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/web/modules/auth/error/error-view.tsx b/apps/web/modules/auth/error/error-view.tsx index 967bc20e41fab9..2d034a53a0d3d5 100644 --- a/apps/web/modules/auth/error/error-view.tsx +++ b/apps/web/modules/auth/error/error-view.tsx @@ -9,11 +9,13 @@ import { Button, Icon } from "@calcom/ui"; import AuthContainer from "@components/ui/AuthContainer"; +import type { PageProps } from "@server/lib/auth/error/getStaticProps"; + const querySchema = z.object({ error: z.string().optional(), }); -export default function Error() { +export default function Error(props: PageProps) { const { t } = useLocale(); const searchParams = useSearchParams(); const { error } = querySchema.parse({ error: searchParams?.get("error") || undefined }); diff --git a/apps/web/pages/auth/error.tsx b/apps/web/pages/auth/error.tsx index 96b7f6ca09e299..2995d07aafb97b 100644 --- a/apps/web/pages/auth/error.tsx +++ b/apps/web/pages/auth/error.tsx @@ -1,8 +1,11 @@ import PageWrapper from "@components/PageWrapper"; +import type { PageProps } from "@server/lib/auth/error/getStaticProps"; +import { getStaticProps } from "@server/lib/auth/error/getStaticProps"; + import Error from "~/auth/error/error-view"; -const Page = () => ; +const Page = (props: PageProps) => ; Page.PageWrapper = PageWrapper; export default Page; -export { getStaticProps } from "@server/lib/auth/error/getStaticProps"; +export { getStaticProps }; diff --git a/apps/web/server/lib/auth/error/getStaticProps.ts b/apps/web/server/lib/auth/error/getStaticProps.ts index 92eab092f5f3d7..cc917a12da0ef6 100644 --- a/apps/web/server/lib/auth/error/getStaticProps.ts +++ b/apps/web/server/lib/auth/error/getStaticProps.ts @@ -1,7 +1,8 @@ -import type { GetStaticPropsContext } from "next"; +import type { GetStaticPropsContext, InferGetStaticPropsType } from "next"; import { getTranslations } from "@server/lib/getTranslations"; +export type PageProps = InferGetStaticPropsType; export const getStaticProps = async (context: GetStaticPropsContext) => { const i18n = await getTranslations(context);