diff --git a/apps/web/app/(use-page-wrapper)/apps/installed/[category]/page.tsx b/apps/web/app/(use-page-wrapper)/apps/installed/[category]/page.tsx index e723fd1756a3ef..11a44be7601b46 100644 --- a/apps/web/app/(use-page-wrapper)/apps/installed/[category]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/apps/installed/[category]/page.tsx @@ -1,9 +1,12 @@ +import { createRouterCaller } from "app/_trpc/context"; import type { PageProps } from "app/_types"; import { _generateMetadata } from "app/_utils"; import { redirect } from "next/navigation"; import { z } from "zod"; import { AppCategories } from "@calcom/prisma/enums"; +import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router"; +import { calendarsRouter } from "@calcom/trpc/server/routers/viewer/calendars/_router"; import InstalledApps from "~/apps/installed/[category]/installed-category-view"; @@ -28,7 +31,24 @@ const InstalledAppsWrapper = async ({ params }: PageProps) => { redirect("/apps/installed/calendar"); } - return ; + const [calendarsCaller, appsCaller] = await Promise.all([ + createRouterCaller(calendarsRouter), + createRouterCaller(appsRouter), + ]); + + const connectedCalendars = await calendarsCaller.connectedCalendars(); + const installedCalendars = await appsCaller.integrations({ + variant: "calendar", + onlyInstalled: true, + }); + + return ( + + ); }; export default InstalledAppsWrapper; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/actions.ts b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/actions.ts new file mode 100644 index 00000000000000..3b59bc1cb40612 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/actions.ts @@ -0,0 +1,7 @@ +"use server"; + +import { revalidatePath } from "next/cache"; + +export async function revalidateSettingsAppearance() { + revalidatePath("/settings/my-account/appearance"); +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/loading.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/loading.tsx new file mode 100644 index 00000000000000..1956bedffceadd --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/loading.tsx @@ -0,0 +1,5 @@ +import { SkeletonLoader } from "~/settings/my-account/appearance-skeleton"; + +export default function Loading() { + return ; +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/page.tsx index dbaf07c7c8a7a6..ae32bf8f9752f8 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/page.tsx @@ -1,7 +1,15 @@ +import { createRouterCaller } from "app/_trpc/context"; import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; +import { cookies, headers } from "next/headers"; +import { redirect } from "next/navigation"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { IS_SELF_HOSTED } from "@calcom/lib/constants"; +import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; +import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router"; +import { getCachedHasTeamPlan } from "@calcom/web/app/cache/membership"; + +import { buildLegacyRequest } from "@lib/buildLegacyCtx"; import AppearancePage from "~/settings/my-account/appearance-view"; @@ -15,13 +23,29 @@ export const generateMetadata = async () => ); const Page = async () => { - const t = await getTranslate(); + const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); + const userId = session?.user?.id; + const redirectUrl = "/auth/login?callbackUrl=/settings/my-account/appearance"; - return ( - - - - ); + if (!userId) { + redirect(redirectUrl); + } + + const [meCaller, hasTeamPlan] = await Promise.all([ + createRouterCaller(meRouter), + getCachedHasTeamPlan(userId), + ]); + + const user = await meCaller.get(); + + if (!user) { + redirect(redirectUrl); + } + const isCurrentUsernamePremium = + user && hasKeyInMetadata(user, "isPremium") ? !!user.metadata.isPremium : false; + const hasPaidPlan = IS_SELF_HOSTED ? true : hasTeamPlan?.hasTeamPlan || isCurrentUsernamePremium; + + return ; }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/loading.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/loading.tsx new file mode 100644 index 00000000000000..c02d75216389a9 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/loading.tsx @@ -0,0 +1,5 @@ +import { CalendarListContainerSkeletonLoader } from "@components/apps/CalendarListContainer"; + +export default function Loading() { + return ; +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/page.tsx index 1cc3aa315d9cbe..f39140fc8c6316 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/calendars/page.tsx @@ -1,8 +1,8 @@ +import { createRouterCaller } from "app/_trpc/context"; import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; -import { Button } from "@calcom/ui/components/button"; +import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router"; +import { calendarsRouter } from "@calcom/trpc/server/routers/viewer/calendars/_router"; import { CalendarListContainer } from "@components/apps/CalendarListContainer"; @@ -16,25 +16,18 @@ export const generateMetadata = async () => ); const Page = async () => { - const t = await getTranslate(); - - const AddCalendarButton = () => { - return ( - <> - - {t("add_calendar")} - - > - ); - }; + const [calendarsCaller, appsCaller] = await Promise.all([ + createRouterCaller(calendarsRouter), + createRouterCaller(appsRouter), + ]); + const connectedCalendars = await calendarsCaller.connectedCalendars(); + const installedCalendars = await appsCaller.integrations({ + variant: "calendar", + onlyInstalled: true, + }); return ( - }> - - + ); }; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/conferencing/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/conferencing/page.tsx index 1c12ced9ec8bbb..300fa17c15eb1a 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/conferencing/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/conferencing/page.tsx @@ -1,5 +1,4 @@ import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; import { ConferencingAppsViewWebWrapper } from "@calcom/atoms/connect/conferencing-apps/ConferencingAppsViewWebWrapper"; @@ -13,15 +12,7 @@ export const generateMetadata = async () => ); const Page = async () => { - const t = await getTranslate(); - - return ( - - ); + return ; }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/actions.ts b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/actions.ts new file mode 100644 index 00000000000000..469b615c476c67 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/actions.ts @@ -0,0 +1,7 @@ +"use server"; + +import { revalidatePath } from "next/cache"; + +export async function revalidateSettingsGeneral() { + revalidatePath("/settings/my-account/general"); +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/loading.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/loading.tsx new file mode 100644 index 00000000000000..7b7f2bcd4ce543 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/loading.tsx @@ -0,0 +1,5 @@ +import { SkeletonLoader } from "~/settings/my-account/general-skeleton"; + +export default function Loading() { + return ; +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/page.tsx index 03cbc8ecb67201..c6667a6478a2e5 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/page.tsx @@ -1,10 +1,15 @@ +import { createRouterCaller } from "app/_trpc/context"; import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; -import { revalidatePath } from "next/cache"; +import { cookies, headers } from "next/headers"; +import { redirect } from "next/navigation"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router"; +import { getTravelSchedule } from "@calcom/web/app/cache/travelSchedule"; -import GeneralQueryView from "~/settings/my-account/general-view"; +import { buildLegacyRequest } from "@lib/buildLegacyCtx"; + +import GeneralView from "~/settings/my-account/general-view"; export const generateMetadata = async () => await _generateMetadata( @@ -16,17 +21,20 @@ export const generateMetadata = async () => ); const Page = async () => { - const t = await getTranslate(); - const revalidatePage = async () => { - "use server"; - revalidatePath("settings/my-account/general"); - }; - - return ( - - - - ); + const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); + const userId = session?.user?.id; + const redirectUrl = "/auth/login?callbackUrl=/settings/my-account/general"; + + if (!userId) { + return redirect(redirectUrl); + } + + const meCaller = await createRouterCaller(meRouter); + const [user, travelSchedules] = await Promise.all([meCaller.get(), getTravelSchedule(userId)]); + if (!user) { + redirect(redirectUrl); + } + return ; }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/out-of-office/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/out-of-office/page.tsx index 95d428038e15d7..c80a4bec3c626e 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/out-of-office/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/out-of-office/page.tsx @@ -1,10 +1,6 @@ import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; -import CreateNewOutOfOfficeEntryButton from "@calcom/features/settings/outOfOffice/CreateNewOutOfOfficeEntryButton"; import OutOfOfficeEntriesList from "@calcom/features/settings/outOfOffice/OutOfOfficeEntriesList"; -import { OutOfOfficeToggleGroup } from "@calcom/features/settings/outOfOffice/OutOfOfficeToggleGroup"; export const generateMetadata = async () => await _generateMetadata( @@ -15,22 +11,8 @@ export const generateMetadata = async () => "/settings/my-account/out-of-office" ); -const Page = async () => { - const t = await getTranslate(); - - return ( - - - - - }> - - - ); +const Page = () => { + return ; }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/loading.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/loading.tsx new file mode 100644 index 00000000000000..bb23b4f89b12f8 --- /dev/null +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/loading.tsx @@ -0,0 +1,5 @@ +import { SkeletonLoader } from "~/settings/my-account/profile-skeleton"; + +export default function Loading() { + return ; +} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/page.tsx index 5f4870f2038d95..f4e764a8abf778 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/profile/page.tsx @@ -1,8 +1,8 @@ +import { createRouterCaller } from "app/_trpc/context"; import { _generateMetadata } from "app/_utils"; -import { getTranslate } from "app/_utils"; +import { redirect } from "next/navigation"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; -import { APP_NAME } from "@calcom/lib/constants"; +import { meRouter } from "@calcom/trpc/server/routers/viewer/me/_router"; import ProfileView from "~/settings/my-account/profile-view"; @@ -16,16 +16,13 @@ export const generateMetadata = async () => ); const Page = async () => { - const t = await getTranslate(); + const meCaller = await createRouterCaller(meRouter); + const user = await meCaller.get({ includePasswordAdded: true }); + if (!user) { + redirect("/auth/login"); + } - return ( - - - - ); + return ; }; export default Page; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/push-notifications/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/push-notifications/page.tsx index 4a84df38cb927c..7f82baf7efa3fd 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/push-notifications/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/push-notifications/page.tsx @@ -1,8 +1,5 @@ -import { getTranslate } from "app/_utils"; import { _generateMetadata } from "app/_utils"; -import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; - import PushNotificationsView from "~/settings/my-account/push-notifications-view"; export const generateMetadata = async () => @@ -14,17 +11,8 @@ export const generateMetadata = async () => "/settings/my-account/push-notifications" ); -const Page = async () => { - const t = await getTranslate(); - - return ( - - - - ); +const Page = () => { + return ; }; export default Page; diff --git a/apps/web/app/cache/membership.ts b/apps/web/app/cache/membership.ts new file mode 100644 index 00000000000000..ef40178d438def --- /dev/null +++ b/apps/web/app/cache/membership.ts @@ -0,0 +1,27 @@ +"use server"; + +import { revalidateTag, unstable_cache } from "next/cache"; + +import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants"; +import { MembershipRepository } from "@calcom/lib/server/repository/membership"; + +const CACHE_TAGS = { + HAS_TEAM_PLAN: "MembershipRepository.findFirstAcceptedMembershipByUserId", +} as const; + +export const getCachedHasTeamPlan = unstable_cache( + async (userId: number) => { + const hasTeamPlan = await MembershipRepository.findFirstAcceptedMembershipByUserId(userId); + + return { hasTeamPlan: !!hasTeamPlan }; + }, + ["getCachedHasTeamPlan"], + { + revalidate: NEXTJS_CACHE_TTL, + tags: [CACHE_TAGS.HAS_TEAM_PLAN], + } +); + +export const revalidateHasTeamPlan = async () => { + revalidateTag(CACHE_TAGS.HAS_TEAM_PLAN); +}; diff --git a/apps/web/app/cache/path/settings/my-account/index.ts b/apps/web/app/cache/path/settings/my-account/index.ts new file mode 100644 index 00000000000000..ebf5d155b03ae0 --- /dev/null +++ b/apps/web/app/cache/path/settings/my-account/index.ts @@ -0,0 +1,11 @@ +"use server"; + +import { revalidatePath } from "next/cache"; + +export async function revalidateSettingsProfile() { + revalidatePath("/settings/my-account/profile"); +} + +export async function revalidateSettingsCalendars() { + revalidatePath("/settings/my-account/calendars"); +} diff --git a/apps/web/app/cache/travelSchedule.ts b/apps/web/app/cache/travelSchedule.ts new file mode 100644 index 00000000000000..87888c95e76afb --- /dev/null +++ b/apps/web/app/cache/travelSchedule.ts @@ -0,0 +1,26 @@ +"use server"; + +import { revalidateTag } from "next/cache"; + +import { NEXTJS_CACHE_TTL } from "@calcom/lib/constants"; +import { TravelScheduleRepository } from "@calcom/lib/server/repository/travelSchedule"; +import { unstable_cache } from "@calcom/lib/unstable_cache"; + +const CACHE_TAGS = { + TRAVEL_SCHEDULES: "TravelRepository.findTravelSchedulesByUserId", +} as const; + +export const getTravelSchedule = unstable_cache( + async (userId: number) => { + return await TravelScheduleRepository.findTravelSchedulesByUserId(userId); + }, + ["getTravelSchedule"], + { + revalidate: NEXTJS_CACHE_TTL, + tags: [CACHE_TAGS.TRAVEL_SCHEDULES], + } +); + +export const revalidateTravelSchedules = async () => { + revalidateTag(CACHE_TAGS.TRAVEL_SCHEDULES); +}; diff --git a/apps/web/components/apps/CalendarListContainer.tsx b/apps/web/components/apps/CalendarListContainer.tsx index 04c500a696b871..1f7aa64eeaccdc 100644 --- a/apps/web/components/apps/CalendarListContainer.tsx +++ b/apps/web/components/apps/CalendarListContainer.tsx @@ -7,13 +7,16 @@ import { DestinationCalendarSettingsWebWrapper } from "@calcom/atoms/destination import { SelectedCalendarsSettingsWebWrapper } from "@calcom/atoms/selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper"; import AppListCard from "@calcom/features/apps/components/AppListCard"; import { SkeletonLoader } from "@calcom/features/apps/components/SkeletonLoader"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { Button } from "@calcom/ui/components/button"; import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import { ShellSubHeading } from "@calcom/ui/components/layout"; import { List } from "@calcom/ui/components/list"; import { showToast } from "@calcom/ui/components/toast"; +import { revalidateSettingsCalendars } from "@calcom/web/app/cache/path/settings/my-account"; import { QueryCell } from "@lib/QueryCell"; import useRouterQuery from "@lib/hooks/useRouterQuery"; @@ -63,9 +66,43 @@ function CalendarList(props: Props) { ); } -export function CalendarListContainer(props: { heading?: boolean; fromOnboarding?: boolean }) { +const AddCalendarButton = () => { + const { t } = useLocale(); + return ( + <> + + {t("add_calendar")} + + > + ); +}; + +export const CalendarListContainerSkeletonLoader = () => { + const { t } = useLocale(); + return ( + }> + + + ); +}; + +type CalendarListContainerProps = { + connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"]; + installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"]; + heading?: boolean; + fromOnboarding?: boolean; +}; + +export function CalendarListContainer({ + connectedCalendars: data, + installedCalendars, + heading = true, + fromOnboarding, +}: CalendarListContainerProps) { const { t } = useLocale(); - const { heading = true, fromOnboarding } = props; const { error, setQuery: setError } = useRouterQuery("error"); useEffect(() => { @@ -85,70 +122,61 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding } ), utils.viewer.calendars.connectedCalendars.invalidate(), + revalidateSettingsCalendars(), ]); - const query = trpc.viewer.calendars.connectedCalendars.useQuery(); - const installedCalendars = trpc.viewer.apps.integrations.useQuery({ - variant: "calendar", - onlyInstalled: true, - }); + const mutation = trpc.viewer.calendars.setDestinationCalendar.useMutation({ onSuccess: () => { utils.viewer.calendars.connectedCalendars.invalidate(); + revalidateSettingsCalendars(); }, }); + return ( - } - success={({ data }) => { - return ( - <> - {!!data.connectedCalendars.length || !!installedCalendars.data?.items.length ? ( - <> - {heading && ( - <> - - }> - - - > - )} - > - ) : fromOnboarding ? ( - <> - {!!query.data?.connectedCalendars.length && ( - } - /> - )} - - > - ) : ( - - {t(`connect_calendar_apps`)} - - } - /> - )} - > - ); - }} - /> + }> + {!!data.connectedCalendars.length || !!installedCalendars?.items.length ? ( + <> + {heading && ( + <> + + }> + + + > + )} + > + ) : fromOnboarding ? ( + <> + {!!data?.connectedCalendars.length && ( + } + /> + )} + + > + ) : ( + + {t(`connect_calendar_apps`)} + + } + /> + )} + ); } diff --git a/apps/web/modules/apps/installed/[category]/installed-category-view.tsx b/apps/web/modules/apps/installed/[category]/installed-category-view.tsx index 8ed7ccdf1b75cd..4932ee9cfb4a57 100644 --- a/apps/web/modules/apps/installed/[category]/installed-category-view.tsx +++ b/apps/web/modules/apps/installed/[category]/installed-category-view.tsx @@ -12,6 +12,7 @@ import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/Bul import { useLocale } from "@calcom/lib/hooks/useLocale"; import { AppCategories } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; +import type { RouterOutputs } from "@calcom/trpc/react"; import { Button } from "@calcom/ui/components/button"; import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import type { Icon } from "@calcom/ui/components/icon"; @@ -181,9 +182,11 @@ type ModalState = { type PageProps = { category: AppCategories; + connectedCalendars: RouterOutputs["viewer"]["calendars"]["connectedCalendars"]; + installedCalendars: RouterOutputs["viewer"]["apps"]["integrations"]; }; -export default function InstalledApps({ category }: PageProps) { +export default function InstalledApps({ category, connectedCalendars, installedCalendars }: PageProps) { const { t } = useLocale(); const utils = trpc.useUtils(); const categoryList: AppCategories[] = Object.values(AppCategories).filter((category) => { @@ -233,7 +236,12 @@ export default function InstalledApps({ category }: PageProps) { {categoryList.includes(category) && ( )} - {category === "calendar" && } + {category === "calendar" && ( + + )} {category === "other" && ( { + const { t } = useLocale(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/web/modules/settings/my-account/appearance-view.tsx b/apps/web/modules/settings/my-account/appearance-view.tsx index 98e78b93cc9865..de9ac9d96ef33e 100644 --- a/apps/web/modules/settings/my-account/appearance-view.tsx +++ b/apps/web/modules/settings/my-account/appearance-view.tsx @@ -1,5 +1,7 @@ "use client"; +import { revalidateSettingsAppearance } from "app/(use-page-wrapper)/settings/(settings-layout)/my-account/appearance/actions"; +import { revalidateHasTeamPlan } from "app/cache/membership"; import { useSession } from "next-auth/react"; import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; @@ -8,11 +10,11 @@ import type { z } from "zod"; import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import ThemeLabel from "@calcom/features/settings/ThemeLabel"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { APP_NAME } from "@calcom/lib/constants"; import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants"; import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours"; import useGetBrandingColours from "@calcom/lib/getBrandColours"; -import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts"; @@ -23,38 +25,9 @@ import { Alert } from "@calcom/ui/components/alert"; import { UpgradeTeamsBadge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { SettingsToggle, ColorPicker, Form } from "@calcom/ui/components/form"; -import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; import { useCalcomTheme } from "@calcom/ui/styles"; -const SkeletonLoader = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - const useBrandColors = ( currentTheme: string | null, { @@ -158,6 +131,8 @@ const AppearanceView = ({ const mutation = trpc.viewer.me.updateProfile.useMutation({ onSuccess: async (data) => { await utils.viewer.me.invalidate(); + revalidateSettingsAppearance(); + revalidateHasTeamPlan(); showToast(t("settings_updated_successfully"), "success"); resetBrandColorsThemeReset({ brandColor: data.brandColor, darkBrandColor: data.darkBrandColor }); resetBookerLayoutThemeReset({ metadata: data.metadata }); @@ -173,11 +148,13 @@ const AppearanceView = ({ }, onSettled: async () => { await utils.viewer.me.invalidate(); + revalidateSettingsAppearance(); + revalidateHasTeamPlan(); }, }); return ( - + {t("app_theme")} @@ -425,17 +402,8 @@ const AppearanceView = ({ /> > )} - + ); }; -const AppearancePage = () => { - const { data: user, isPending } = trpc.viewer.me.get.useQuery(); - const { isPending: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan(); - - if (isPending || isTeamPlanStatusLoading || !user) return ; - - return ; -}; - -export default AppearancePage; +export default AppearanceView; diff --git a/apps/web/modules/settings/my-account/general-skeleton.tsx b/apps/web/modules/settings/my-account/general-skeleton.tsx new file mode 100644 index 00000000000000..7ca42e5d5de8b2 --- /dev/null +++ b/apps/web/modules/settings/my-account/general-skeleton.tsx @@ -0,0 +1,23 @@ +"use client"; + +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; + +export const SkeletonLoader = () => { + const { t } = useLocale(); + return ( + + + + + + + + + + + + + ); +}; diff --git a/apps/web/modules/settings/my-account/general-view.tsx b/apps/web/modules/settings/my-account/general-view.tsx index f4fe33c01199bf..eb099bbdfd07d0 100644 --- a/apps/web/modules/settings/my-account/general-view.tsx +++ b/apps/web/modules/settings/my-account/general-view.tsx @@ -1,11 +1,13 @@ "use client"; +import { revalidateSettingsGeneral } from "app/(use-page-wrapper)/settings/(settings-layout)/my-account/general/actions"; import { useSession } from "next-auth/react"; import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { TimezoneSelect } from "@calcom/features/components/timezone-select"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { formatLocalizedDateTime } from "@calcom/lib/dayjs"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { localeOptions } from "@calcom/lib/i18n"; @@ -18,8 +20,8 @@ import { Form } from "@calcom/ui/components/form"; import { Label } from "@calcom/ui/components/form"; import { Select } from "@calcom/ui/components/form"; import { SettingsToggle } from "@calcom/ui/components/form"; -import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; +import { revalidateTravelSchedules } from "@calcom/web/app/cache/travelSchedule"; import TravelScheduleModal from "@components/settings/TravelScheduleModal"; @@ -45,55 +47,13 @@ export type FormValues = { }[]; }; -const SkeletonLoader = () => { - return ( - - - - - - - - - - - ); -}; - interface GeneralViewProps { - localeProp: string; user: RouterOutputs["viewer"]["me"]["get"]; travelSchedules: RouterOutputs["viewer"]["travelSchedules"]["get"]; - revalidatePage: GeneralQueryViewProps["revalidatePage"]; } -type GeneralQueryViewProps = { - revalidatePage: () => Promise; -}; - -const GeneralQueryView = ({ revalidatePage }: GeneralQueryViewProps) => { - const { t } = useLocale(); - - const { data: user, isPending } = trpc.viewer.me.get.useQuery(); - - const { data: travelSchedules, isPending: isPendingTravelSchedules } = - trpc.viewer.travelSchedules.get.useQuery(); - - if (isPending || isPendingTravelSchedules) return ; - if (!user) { - throw new Error(t("something_went_wrong")); - } - return ( - - ); -}; - -const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: GeneralViewProps) => { +const GeneralView = ({ user, travelSchedules }: GeneralViewProps) => { + const localeProp = user.locale ?? "en"; const utils = trpc.useContext(); const { t, @@ -106,6 +66,8 @@ const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: Gene const mutation = trpc.viewer.me.updateProfile.useMutation({ onSuccess: async (res) => { await utils.viewer.me.invalidate(); + revalidateSettingsGeneral(); + revalidateTravelSchedules(); reset(getValues()); showToast(t("settings_updated_successfully"), "success"); await update(res); @@ -114,13 +76,14 @@ const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: Gene window.calNewLocale = res.locale; document.cookie = `calNewLocale=${res.locale}; path=/`; } - await revalidatePage(); }, onError: () => { showToast(t("error_updating_settings"), "error"); }, onSettled: async () => { await utils.viewer.me.invalidate(); + revalidateSettingsGeneral(); + revalidateTravelSchedules(); setIsUpdateBtnLoading(false); }, }); @@ -188,215 +151,217 @@ const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: Gene const watchedTzSchedules = formMethods.watch("travelSchedules"); return ( - - { - setIsUpdateBtnLoading(true); - mutation.mutate({ - ...values, - locale: values.locale.value, - timeFormat: values.timeFormat.value, - weekStart: values.weekStart.value, - }); - }}> - - ( - <> - - <>{t("language")}> - - - className="capitalize" - options={localeOptions} - value={value} - onChange={onChange} - /> - > - )} - /> - ( - <> - - <>{t("timezone")}> - - { - if (event) formMethods.setValue("timeZone", event.value, { shouldDirty: true }); - }} - /> - > - )} - /> - {!watchedTzSchedules.length ? ( - setIsTZScheduleOpen(true)}> - {t("schedule_timezone_change")} - - ) : ( - - {t("travel_schedule")} - - {watchedTzSchedules.map((schedule, index) => { - return ( - - - {`${formatLocalizedDateTime( - schedule.startDate, - { day: "numeric", month: "long" }, - language - )} ${ - schedule.endDate - ? `- ${formatLocalizedDateTime( - schedule.endDate, - { day: "numeric", month: "long" }, - language - )}` - : `` - }`} - {schedule.timeZone.replace(/_/g, " ")} - - { - const updatedSchedules = watchedTzSchedules.filter( - (s, filterIndex) => filterIndex !== index - ); - formMethods.setValue("travelSchedules", updatedSchedules, { shouldDirty: true }); - }} - /> - - ); - })} - + + + { + setIsUpdateBtnLoading(true); + mutation.mutate({ + ...values, + locale: values.locale.value, + timeFormat: values.timeFormat.value, + weekStart: values.weekStart.value, + }); + }}> + + ( + <> + + <>{t("language")}> + + + className="capitalize" + options={localeOptions} + value={value} + onChange={onChange} + /> + > + )} + /> + ( + <> + + <>{t("timezone")}> + + { + if (event) formMethods.setValue("timeZone", event.value, { shouldDirty: true }); + }} + /> + > + )} + /> + {!watchedTzSchedules.length ? ( setIsTZScheduleOpen(true)}> - {t("add")} + {t("schedule_timezone_change")} - - )} - - ( - <> - - <>{t("time_format")}> - - { - if (event) formMethods.setValue("timeFormat", { ...event }, { shouldDirty: true }); - }} - /> - > + ) : ( + + {t("travel_schedule")} + + {watchedTzSchedules.map((schedule, index) => { + return ( + + + {`${formatLocalizedDateTime( + schedule.startDate, + { day: "numeric", month: "long" }, + language + )} ${ + schedule.endDate + ? `- ${formatLocalizedDateTime( + schedule.endDate, + { day: "numeric", month: "long" }, + language + )}` + : `` + }`} + {schedule.timeZone.replace(/_/g, " ")} + + { + const updatedSchedules = watchedTzSchedules.filter( + (s, filterIndex) => filterIndex !== index + ); + formMethods.setValue("travelSchedules", updatedSchedules, { shouldDirty: true }); + }} + /> + + ); + })} + + setIsTZScheduleOpen(true)}> + {t("add")} + + )} - /> - - {t("timeformat_profile_hint")} + + ( + <> + + <>{t("time_format")}> + + { + if (event) formMethods.setValue("timeFormat", { ...event }, { shouldDirty: true }); + }} + /> + > + )} + /> + + {t("timeformat_profile_hint")} + + ( + <> + + <>{t("start_of_week")}> + + { + if (event) formMethods.setValue("weekStart", { ...event }, { shouldDirty: true }); + }} + /> + > + )} + /> - ( - <> - - <>{t("start_of_week")}> - - { - if (event) formMethods.setValue("weekStart", { ...event }, { shouldDirty: true }); - }} - /> - > - )} - /> - - - - <>{t("update")}> - - - + + + <>{t("update")}> + + + - { - setIsAllowDynamicBookingChecked(checked); - mutation.mutate({ allowDynamicBooking: checked }); - }} - switchContainerClassName="mt-6" - /> + { + setIsAllowDynamicBookingChecked(checked); + mutation.mutate({ allowDynamicBooking: checked }); + }} + switchContainerClassName="mt-6" + /> - { - setIsAllowSEOIndexingChecked(checked); - mutation.mutate({ allowSEOIndexing: checked }); - }} - switchContainerClassName="mt-6" - /> + { + setIsAllowSEOIndexingChecked(checked); + mutation.mutate({ allowSEOIndexing: checked }); + }} + switchContainerClassName="mt-6" + /> - { - setIsReceiveMonthlyDigestEmailChecked(checked); - mutation.mutate({ receiveMonthlyDigestEmail: checked }); - }} - switchContainerClassName="mt-6" - /> - setIsTZScheduleOpen(false)} - setValue={formMethods.setValue} - existingSchedules={formMethods.getValues("travelSchedules") ?? []} - /> - + { + setIsReceiveMonthlyDigestEmailChecked(checked); + mutation.mutate({ receiveMonthlyDigestEmail: checked }); + }} + switchContainerClassName="mt-6" + /> + setIsTZScheduleOpen(false)} + setValue={formMethods.setValue} + existingSchedules={formMethods.getValues("travelSchedules") ?? []} + /> + + ); }; -export default GeneralQueryView; +export default GeneralView; diff --git a/apps/web/modules/settings/my-account/profile-skeleton.tsx b/apps/web/modules/settings/my-account/profile-skeleton.tsx new file mode 100644 index 00000000000000..a135c5a77913ca --- /dev/null +++ b/apps/web/modules/settings/my-account/profile-skeleton.tsx @@ -0,0 +1,35 @@ +"use client"; + +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; +import { APP_NAME } from "@calcom/lib/constants"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { + SkeletonButton, + SkeletonAvatar, + SkeletonContainer, + SkeletonText, +} from "@calcom/ui/components/skeleton"; + +export const SkeletonLoader = () => { + const { t } = useLocale(); + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/web/modules/settings/my-account/profile-view.tsx b/apps/web/modules/settings/my-account/profile-view.tsx index edd7606482d4a9..a85c1b1b7cc08d 100644 --- a/apps/web/modules/settings/my-account/profile-view.tsx +++ b/apps/web/modules/settings/my-account/profile-view.tsx @@ -1,6 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; +import { revalidateSettingsProfile } from "app/cache/path/settings/my-account"; // eslint-disable-next-line no-restricted-imports import { get, pick } from "lodash"; import { signOut, useSession } from "next-auth/react"; @@ -12,6 +13,7 @@ import { z } from "zod"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; import { Dialog } from "@calcom/features/components/controlled-dialog"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { DisplayInfo } from "@calcom/features/users/components/UserTable/EditSheet/DisplayInfo"; import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; import { emailSchema } from "@calcom/lib/emailSchema"; @@ -34,12 +36,6 @@ import { Label } from "@calcom/ui/components/form"; import { TextField } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { ImageUploader } from "@calcom/ui/components/image-uploader"; -import { - SkeletonButton, - SkeletonContainer, - SkeletonText, - SkeletonAvatar, -} from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; import TwoFactor from "@components/auth/TwoFactor"; @@ -50,24 +46,6 @@ import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; import type { TRPCClientErrorLike } from "@trpc/client"; -const SkeletonLoader = () => { - return ( - - - - - - - - - - - - - - ); -}; - interface DeleteAccountValues { totpCode: string; } @@ -87,18 +65,20 @@ export type FormValues = { bio: string; secondaryEmails: Email[]; }; +type Props = { + user: RouterOutputs["viewer"]["me"]["get"]; +}; -const ProfileView = () => { +const ProfileView = ({ user }: Props) => { const { t } = useLocale(); const utils = trpc.useUtils(); const { update } = useSession(); - const { data: user, isPending } = trpc.viewer.me.get.useQuery({ includePasswordAdded: true }); - const updateProfileMutation = trpc.viewer.me.updateProfile.useMutation({ onSuccess: async (res) => { await update(res); utils.viewer.me.invalidate(); utils.viewer.me.shouldVerifyEmail.invalidate(); + revalidateSettingsProfile(); if (res.hasEmailBeenChanged && res.sendEmailVerification) { showToast(t("change_of_email_toast", { email: tempFormValues?.email }), "success"); @@ -125,6 +105,7 @@ const ProfileView = () => { onSuccess: async (res) => { showToast(t(res.message), "success"); utils.viewer.me.invalidate(); + revalidateSettingsProfile(); }, onError: (e) => { showToast(t(e.message), "error"); @@ -136,6 +117,7 @@ const ProfileView = () => { setShowSecondaryEmailModalOpen(false); setNewlyAddedSecondaryEmail(res?.data?.email); utils.viewer.me.invalidate(); + revalidateSettingsProfile(); }, onError: (error) => { setSecondaryEmailAddErrorMessage(error?.message || ""); @@ -160,6 +142,8 @@ const ProfileView = () => { const onDeleteMeSuccessMutation = async () => { await utils.viewer.me.invalidate(); + revalidateSettingsProfile(); + showToast(t("Your account was deleted"), "success"); setHasDeleteErrors(false); // dismiss any open errors @@ -189,6 +173,7 @@ const ProfileView = () => { onError: onDeleteMeErrorMutation, async onSettled() { await utils.viewer.me.invalidate(); + revalidateSettingsProfile(); }, }); const deleteMeWithoutPasswordMutation = trpc.viewer.me.deleteMeWithoutPassword.useMutation({ @@ -196,6 +181,7 @@ const ProfileView = () => { onError: onDeleteMeErrorMutation, async onSettled() { await utils.viewer.me.invalidate(); + revalidateSettingsProfile(); }, }); @@ -241,10 +227,6 @@ const ProfileView = () => { [ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"), }; - if (isPending || !user) { - return ; - } - const userEmail = user.email || ""; const defaultValues = { username: user.username || "", @@ -269,7 +251,10 @@ const ProfileView = () => { }; return ( - <> + { onSuccessMutation={async () => { showToast(t("settings_updated_successfully"), "success"); await utils.viewer.me.invalidate(); + revalidateSettingsProfile(); }} onErrorMutation={() => { showToast(t("error_updating_settings"), "error"); @@ -473,7 +459,7 @@ const ProfileView = () => { onCancel={() => setNewlyAddedSecondaryEmail(undefined)} /> )} - > + ); }; diff --git a/apps/web/modules/settings/my-account/push-notifications-view.tsx b/apps/web/modules/settings/my-account/push-notifications-view.tsx index 281b00109c6c4a..9de28006f0ada2 100644 --- a/apps/web/modules/settings/my-account/push-notifications-view.tsx +++ b/apps/web/modules/settings/my-account/push-notifications-view.tsx @@ -1,6 +1,7 @@ "use client"; import { useWebPush } from "@calcom/features/notifications/WebPushContext"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button } from "@calcom/ui/components/button"; @@ -9,11 +10,16 @@ const PushNotificationsView = () => { const { subscribe, unsubscribe, isSubscribed, isLoading } = useWebPush(); return ( - - - {isSubscribed ? t("disable_browser_notifications") : t("allow_browser_notifications")} - - + + + + {isSubscribed ? t("disable_browser_notifications") : t("allow_browser_notifications")} + + + ); }; diff --git a/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx b/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx index 9d76ad6a8e6831..60eb2409afe7a2 100644 --- a/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx +++ b/packages/features/settings/outOfOffice/OutOfOfficeEntriesList.tsx @@ -23,6 +23,7 @@ import { DataTableSegment, } from "@calcom/features/data-table"; import { useSegments } from "@calcom/features/data-table/hooks/useSegments"; +import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import ServerTrans from "@calcom/lib/components/ServerTrans"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; @@ -39,7 +40,7 @@ import { Tooltip } from "@calcom/ui/components/tooltip"; import CreateNewOutOfOfficeEntryButton from "./CreateNewOutOfOfficeEntryButton"; import { CreateOrEditOutOfOfficeEntryModal } from "./CreateOrEditOutOfOfficeModal"; import type { BookingRedirectForm } from "./CreateOrEditOutOfOfficeModal"; -import { OutOfOfficeTab } from "./OutOfOfficeToggleGroup"; +import { OutOfOfficeTab, OutOfOfficeToggleGroup } from "./OutOfOfficeToggleGroup"; interface OutOfOfficeEntry { id: number; @@ -63,10 +64,22 @@ interface OutOfOfficeEntry { } export default function OutOfOfficeEntriesList() { + const { t } = useLocale(); + return ( - - - + + + + + }> + + + + ); } diff --git a/packages/lib/server/repository/membership.ts b/packages/lib/server/repository/membership.ts index 01b4021d827626..2a7c8d29edf1a8 100644 --- a/packages/lib/server/repository/membership.ts +++ b/packages/lib/server/repository/membership.ts @@ -110,6 +110,20 @@ export class MembershipRepository { }); } + static async findFirstAcceptedMembershipByUserId(userId: number) { + return await prisma.membership.findFirst({ + where: { + accepted: true, + userId, + team: { + slug: { + not: null, + }, + }, + }, + }); + } + static async createMany(data: IMembership[]) { return await prisma.membership.createMany({ data: data.map((item) => ({ diff --git a/packages/lib/unstable_cache/index.ts b/packages/lib/unstable_cache/index.ts new file mode 100644 index 00000000000000..fa1666fcea44a5 --- /dev/null +++ b/packages/lib/unstable_cache/index.ts @@ -0,0 +1,3 @@ +import { cache as unstable_cache } from "./unstable_cache"; + +export { unstable_cache }; diff --git a/packages/lib/unstable_cache/unstable_cache.ts b/packages/lib/unstable_cache/unstable_cache.ts new file mode 100644 index 00000000000000..8ae6b99bb29a8c --- /dev/null +++ b/packages/lib/unstable_cache/unstable_cache.ts @@ -0,0 +1,24 @@ +/** + * This implementation is adapted from https://github.com/vercel/next.js/issues/51613#issuecomment-1892644565. + * It is a wrapper around `unstable_cache` that adds serialization and deserialization + */ +import { unstable_cache } from "next/cache"; +import { parse, stringify } from "superjson"; + +export const cache = ( + fn: (...params: P) => Promise, + keys: Parameters[1], + opts: Parameters[2] +) => { + const wrap = async (params: unknown[]): Promise => { + const result = await fn(...(params as P)); + return stringify(result); + }; + + const cachedFn = unstable_cache(wrap, keys, opts); + + return async (...params: P): Promise => { + const result = await cachedFn(params); + return parse(result); + }; +}; diff --git a/packages/platform/atoms/connect/conferencing-apps/ConferencingAppsViewWebWrapper.tsx b/packages/platform/atoms/connect/conferencing-apps/ConferencingAppsViewWebWrapper.tsx index f3016d9aabec44..6da033c0eb05d8 100644 --- a/packages/platform/atoms/connect/conferencing-apps/ConferencingAppsViewWebWrapper.tsx +++ b/packages/platform/atoms/connect/conferencing-apps/ConferencingAppsViewWebWrapper.tsx @@ -12,12 +12,6 @@ import { EmptyScreen } from "@calcom/ui/components/empty-screen"; import { SkeletonText, SkeletonContainer } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; -type ConferencingAppsViewWebWrapperProps = { - title: string; - description: string; - add: string; -}; - export type UpdateUsersDefaultConferencingAppParams = { appSlug: string; appLink?: string; @@ -175,11 +169,17 @@ const useDisconnectIntegrationModalController = () => { }; }; -export const ConferencingAppsViewWebWrapper = ({ - title, - description, - add, -}: ConferencingAppsViewWebWrapperProps) => { +const AddConferencingButton = () => { + const { t } = useLocale(); + + return ( + + {t("add")} + + ); +}; + +export const ConferencingAppsViewWebWrapper = () => { const { t } = useLocale(); const utils = trpc.useUtils(); @@ -203,20 +203,12 @@ export const ConferencingAppsViewWebWrapper = ({ ); }; - const AddConferencingButton = () => { - return ( - - {add} - - ); - }; - const disconnectIntegrationModalCtrl = useDisconnectIntegrationModalController(); return ( } borderInShellHeader={true}> <> diff --git a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts index 08296a58412ea9..161fd0e2a8c9df 100644 --- a/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/hasTeamPlan.handler.ts @@ -1,4 +1,5 @@ -import { prisma } from "@calcom/prisma"; +import { MembershipRepository } from "@calcom/lib/server/repository/membership"; +import type { TrpcSessionUser } from "@calcom/trpc/server/types"; type HasTeamPlanOptions = { ctx: { @@ -6,18 +7,11 @@ type HasTeamPlanOptions = { }; }; -export const hasTeamPlanHandler = async ({ ctx: { user } }: HasTeamPlanOptions) => { - const hasTeamPlan = await prisma.membership.findFirst({ - where: { - accepted: true, - userId: user.id, - team: { - slug: { - not: null, - }, - }, - }, - }); +export const hasTeamPlanHandler = async ({ ctx }: HasTeamPlanOptions) => { + const userId = ctx.user.id; + + const hasTeamPlan = await MembershipRepository.findFirstAcceptedMembershipByUserId(userId); + return { hasTeamPlan: !!hasTeamPlan }; };
{t("app_theme")}