From 105e7d3e283f0f105ceed94aa3db54cfb8d2b92a Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Fri, 6 Sep 2024 22:38:37 +0300 Subject: [PATCH] chore: booker atom username / teams props enabled queries (#16514) --- .../provider/outputs/verify-client.output.ts | 12 ++++- .../v2/src/ee/provider/provider.controller.ts | 5 ++ apps/api/v2/swagger/documentation.json | 25 ++++++++- .../atoms/booker/BookerPlatformWrapper.tsx | 52 +++++++++++++------ .../atoms/cal-provider/BaseCalProvider.tsx | 8 ++- .../atoms/cal-provider/CalProvider.tsx | 5 +- .../hooks/event-types/public/useEventType.ts | 13 ++--- .../event-types/public/useTeamEventType.ts | 9 +--- .../platform/atoms/hooks/useAvailableSlots.ts | 16 +++++- .../platform/atoms/hooks/useOAuthClient.ts | 12 +++-- .../examples/base/src/pages/booking.tsx | 6 +-- packages/platform/types/slots.ts | 1 + 12 files changed, 118 insertions(+), 46 deletions(-) diff --git a/apps/api/v2/src/ee/provider/outputs/verify-client.output.ts b/apps/api/v2/src/ee/provider/outputs/verify-client.output.ts index 24d96283d1..e11ee13d18 100644 --- a/apps/api/v2/src/ee/provider/outputs/verify-client.output.ts +++ b/apps/api/v2/src/ee/provider/outputs/verify-client.output.ts @@ -1,10 +1,20 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsEnum } from "class-validator"; +import { IsEnum, IsNumber, IsString } from "class-validator"; import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants"; +export class ProviderVerifyClientData { + @IsString() + clientId!: string; + @IsNumber() + organizationId!: number; + @IsString() + name!: string; +} export class ProviderVerifyClientOutput { @ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] }) @IsEnum([SUCCESS_STATUS, ERROR_STATUS]) status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS; + + data!: ProviderVerifyClientData; } diff --git a/apps/api/v2/src/ee/provider/provider.controller.ts b/apps/api/v2/src/ee/provider/provider.controller.ts index 5df78e7273..9ba0bfb4a5 100644 --- a/apps/api/v2/src/ee/provider/provider.controller.ts +++ b/apps/api/v2/src/ee/provider/provider.controller.ts @@ -40,6 +40,11 @@ export class CalProviderController { return { status: SUCCESS_STATUS, + data: { + clientId: oAuthClient.id, + organizationId: oAuthClient.organizationId, + name: oAuthClient.name, + }, }; } diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index fb4fbc1086..219408b271 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -9313,6 +9313,25 @@ "status" ] }, + "ProviderVerifyClientData": { + "type": "object", + "properties": { + "clientId": { + "type": "string" + }, + "organizationId": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required": [ + "clientId", + "organizationId", + "name" + ] + }, "ProviderVerifyClientOutput": { "type": "object", "properties": { @@ -9323,10 +9342,14 @@ "success", "error" ] + }, + "data": { + "$ref": "#/components/schemas/ProviderVerifyClientData" } }, "required": [ - "status" + "status", + "data" ] }, "ProviderVerifyAccessTokenOutput": { diff --git a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx index 9a0a59e813..361b504c92 100644 --- a/packages/platform/atoms/booker/BookerPlatformWrapper.tsx +++ b/packages/platform/atoms/booker/BookerPlatformWrapper.tsx @@ -45,11 +45,13 @@ import { useMe } from "../hooks/useMe"; import { useSlots } from "../hooks/useSlots"; import { AtomsWrapper } from "../src/components/atoms-wrapper"; -export type BookerPlatformWrapperAtomProps = Omit & { +export type BookerPlatformWrapperAtomProps = Omit< + BookerProps, + "username" | "entity" | "isTeamEvent" | "teamId" +> & { rescheduleUid?: string; rescheduledBy?: string; bookingUid?: string; - username: string | string[] | undefined; entity?: BookerProps["entity"]; // values for the booking form and booking fields defaultFormValues?: { @@ -73,11 +75,24 @@ export type BookerPlatformWrapperAtomProps = Omit void; onDeleteSlotError?: (data: ApiErrorResponse) => void; locationUrl?: string; - teamId?: number; }; -export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) => { +type BookerPlatformWrapperAtomPropsForIndividual = BookerPlatformWrapperAtomProps & { + username: string | string[]; + isTeamEvent?: false; +}; + +type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomProps & { + username?: string | string[]; + isTeamEvent: true; + teamId: number; +}; + +export const BookerPlatformWrapper = ( + props: BookerPlatformWrapperAtomPropsForIndividual | BookerPlatformWrapperAtomPropsForTeam +) => { const { clientId } = useAtomsContext(); + const teamId: number | undefined = props.isTeamEvent ? props.teamId : undefined; const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow); const setSelectedDate = useBookerStore((state) => state.setSelectedDate); const setSelectedDuration = useBookerStore((state) => state.setSelectedDuration); @@ -112,18 +127,18 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) => isSuccess: isTeamSuccess, isError: isTeamError, isPending: isTeamPending, - data: teamData, - } = useTeamEventType(props.teamId, props.eventSlug, props.isTeamEvent); + data: teamEventTypeData, + } = useTeamEventType(teamId, props.eventSlug, props.isTeamEvent); const event = useMemo(() => { - if (props.isTeamEvent) { + if (props.isTeamEvent && !isTeamPending && teamId && teamEventTypeData && teamEventTypeData.length > 0) { return { isSuccess: isTeamSuccess, isError: isTeamError, isPending: isTeamPending, data: - teamData && teamData.length > 0 - ? transformApiTeamEventTypeForAtom(teamData[0], props.entity) + teamEventTypeData && teamEventTypeData.length > 0 + ? transformApiTeamEventTypeForAtom(teamEventTypeData[0], props.entity) : undefined, }; } @@ -136,15 +151,16 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) => }; }, [ props.isTeamEvent, + teamId, props.entity, + teamEventTypeData, isSuccess, isError, isPending, data, + isTeamPending, isTeamSuccess, isTeamError, - isTeamPending, - teamData, ]); if (isDynamic && props.duration && event.data) { @@ -212,19 +228,25 @@ export const BookerPlatformWrapper = (props: BookerPlatformWrapperAtomProps) => }); const schedule = useAvailableSlots({ - usernameList: getUsernameList(username ?? ""), + usernameList: getUsernameList(username), eventTypeId: event?.data?.id ?? 0, startTime, endTime, timeZone: session?.data?.timeZone, duration: selectedDuration ?? undefined, rescheduleUid: props.rescheduleUid, + ...(props.isTeamEvent + ? { + isTeamEvent: props.isTeamEvent, + teamId: teamId, + } + : {}), enabled: - Boolean(username) && + Boolean(teamId || username) && Boolean(month) && Boolean(timezone) && - // Should only wait for one or the other, not both. - (Boolean(eventSlug) || Boolean(event?.data?.id) || event?.data?.id === 0), + (props.isTeamEvent ? !isTeamPending : !isPending) && + Boolean(event?.data?.id), orgSlug: props.entity?.orgSlug ?? undefined, eventTypeSlug: isDynamic ? "dynamic" : eventSlug || "", }); diff --git a/packages/platform/atoms/cal-provider/BaseCalProvider.tsx b/packages/platform/atoms/cal-provider/BaseCalProvider.tsx index 44480f384b..2dfa79d7ee 100644 --- a/packages/platform/atoms/cal-provider/BaseCalProvider.tsx +++ b/packages/platform/atoms/cal-provider/BaseCalProvider.tsx @@ -37,9 +37,12 @@ export function BaseCalProvider({ labels, autoUpdateTimezone, language = EN, + organizationId, onTimezoneChange, }: CalProviderProps) { const [error, setError] = useState(""); + const [stateOrgId, setOrganizationId] = useState(0); + const { data: me } = useMe(); const { mutateAsync } = useUpdateUserTimezone(); @@ -64,8 +67,9 @@ export function BaseCalProvider({ apiUrl: options.apiUrl, refreshUrl: options.refreshUrl, onError: setError, - onSuccess: () => { + onSuccess: (data) => { setError(""); + setOrganizationId(data.organizationId); }, }); @@ -120,7 +124,7 @@ export function BaseCalProvider({ isInit: isInit, isValidClient: Boolean(!error && clientId && isInit), isAuth: Boolean(isInit && !error && clientId && currentAccessToken && http.getAuthorizationHeader()), - organizationId: me?.data.organizationId || 0, + organizationId: organizationId || stateOrgId || me?.data.organizationId || 0, ...translations, }}> {children} diff --git a/packages/platform/atoms/cal-provider/CalProvider.tsx b/packages/platform/atoms/cal-provider/CalProvider.tsx index 2a206b0b7b..76df7cdc1f 100644 --- a/packages/platform/atoms/cal-provider/CalProvider.tsx +++ b/packages/platform/atoms/cal-provider/CalProvider.tsx @@ -71,6 +71,7 @@ export type CalProviderProps = { autoUpdateTimezone?: boolean; onTimezoneChange?: () => void; version?: API_VERSIONS_ENUM; + organizationId?: number; } & i18nProps; /** @@ -97,6 +98,7 @@ export function CalProvider({ language = "en", onTimezoneChange, version = VERSION_2024_06_14, + organizationId, }: CalProviderProps) { useEffect(() => { http.setVersionHeader(version); @@ -118,7 +120,8 @@ export function CalProvider({ options={options} version={version} labels={labels as Record} - language={language}> + language={language} + organizationId={organizationId}> {children} diff --git a/packages/platform/atoms/hooks/event-types/public/useEventType.ts b/packages/platform/atoms/hooks/event-types/public/useEventType.ts index 8cc49b0044..33dae8a851 100644 --- a/packages/platform/atoms/hooks/event-types/public/useEventType.ts +++ b/packages/platform/atoms/hooks/event-types/public/useEventType.ts @@ -1,8 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; -import { shallow } from "zustand/shallow"; -import { useBookerStore } from "@calcom/features/bookings/Booker/store"; import { getUsernameList } from "@calcom/lib/defaultEvents"; import { SUCCESS_STATUS, V2_ENDPOINTS } from "@calcom/platform-constants"; import type { EventTypeOutput_2024_06_14 } from "@calcom/platform-types"; @@ -14,20 +12,17 @@ export const QUERY_KEY = "use-event-type"; export type UsePublicEventReturnType = ReturnType; export const useEventType = (username: string, eventSlug: string, isTeamEvent: boolean | undefined) => { - const [stateUsername, stateEventSlug] = useBookerStore( - (state) => [state.username, state.eventSlug], - shallow - ); - const requestUsername = stateUsername ?? username; - const requestEventSlug = stateEventSlug ?? eventSlug; + + const requestUsername = username; + const requestEventSlug = eventSlug; const isDynamic = useMemo(() => { return getUsernameList(requestUsername ?? "").length > 1; }, [requestUsername]); const event = useQuery({ - queryKey: [QUERY_KEY, stateUsername ?? username, stateEventSlug ?? eventSlug], + queryKey: [QUERY_KEY, username, eventSlug], queryFn: async () => { if (isTeamEvent) { return; diff --git a/packages/platform/atoms/hooks/event-types/public/useTeamEventType.ts b/packages/platform/atoms/hooks/event-types/public/useTeamEventType.ts index 0750b2af44..f5b7947cbe 100644 --- a/packages/platform/atoms/hooks/event-types/public/useTeamEventType.ts +++ b/packages/platform/atoms/hooks/event-types/public/useTeamEventType.ts @@ -1,7 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { shallow } from "zustand/shallow"; -import { useBookerStore } from "@calcom/features/bookings/Booker/store"; import { SUCCESS_STATUS } from "@calcom/platform-constants"; import type { ApiSuccessResponse, TeamEventTypeOutput_2024_06_14 } from "@calcom/platform-types"; import type { ApiResponse } from "@calcom/platform-types"; @@ -14,12 +12,8 @@ export const QUERY_KEY = "use-team-event-type"; export const useTeamEventType = (teamId: number | undefined, eventSlug: string, isTeamEvent: boolean | undefined) => { const { organizationId } = useAtomsContext(); - const [stateEventSlug] = useBookerStore( - (state) => [state.eventSlug], - shallow - ); - const requestEventSlug = stateEventSlug ?? eventSlug; + const requestEventSlug = eventSlug; const pathname = `/organizations/${organizationId}/teams/${teamId}/event-types?eventSlug=${requestEventSlug}`; @@ -35,6 +29,7 @@ export const useTeamEventType = (teamId: number | undefined, eventSlug: string, }); } }, + enabled: Boolean(organizationId) && Boolean(teamId) && Boolean(eventSlug) && Boolean(isTeamEvent), }); return event; diff --git a/packages/platform/atoms/hooks/useAvailableSlots.ts b/packages/platform/atoms/hooks/useAvailableSlots.ts index 50eb3b43ca..dc5e1ab9d6 100644 --- a/packages/platform/atoms/hooks/useAvailableSlots.ts +++ b/packages/platform/atoms/hooks/useAvailableSlots.ts @@ -8,9 +8,21 @@ import http from "../lib/http"; export const QUERY_KEY = "get-available-slots"; -export const useAvailableSlots = ({ enabled, ...rest }: GetAvailableSlotsInput & { enabled: boolean }) => { +export const useAvailableSlots = ({ + enabled, + ...rest +}: GetAvailableSlotsInput & { enabled: boolean; isTeamEvent?: boolean; teamId?: number }) => { const availableSlots = useQuery({ - queryKey: [QUERY_KEY, rest.startTime, rest.endTime, rest.eventTypeId, rest.eventTypeSlug], + queryKey: [ + QUERY_KEY, + rest.startTime, + rest.endTime, + rest.eventTypeId, + rest.eventTypeSlug, + rest.isTeamEvent ?? false, + rest.teamId ?? false, + rest.usernameList, + ], queryFn: () => { return http .get>("/slots/available", { diff --git a/packages/platform/atoms/hooks/useOAuthClient.ts b/packages/platform/atoms/hooks/useOAuthClient.ts index c2516dade2..70fa9c5787 100644 --- a/packages/platform/atoms/hooks/useOAuthClient.ts +++ b/packages/platform/atoms/hooks/useOAuthClient.ts @@ -2,6 +2,7 @@ import type { AxiosError } from "axios"; import { useState, useEffect } from "react"; import { usePrevious } from "react-use"; +import { SUCCESS_STATUS } from "@calcom/platform-constants"; import type { ApiResponse } from "@calcom/platform-types"; import http from "../lib/http"; @@ -11,12 +12,11 @@ export interface useOAuthClientProps { apiUrl?: string; refreshUrl?: string; onError: (error: string) => void; - onSuccess: () => void; + onSuccess: (data: { client: string; organizationId: number; name: string }) => void; } export const useOAuthClient = ({ clientId, apiUrl, refreshUrl, onError, onSuccess }: useOAuthClientProps) => { const prevClientId = usePrevious(clientId); const [isInit, setIsInit] = useState(false); - useEffect(() => { if (apiUrl && http.getUrl() !== apiUrl) { http.setUrl(apiUrl); @@ -31,9 +31,11 @@ export const useOAuthClient = ({ clientId, apiUrl, refreshUrl, onError, onSucces if (clientId && http.getUrl() && prevClientId !== clientId) { try { http - .get(`/provider/${clientId}`) - .then(() => { - onSuccess(); + .get>(`/provider/${clientId}`) + .then((response) => { + if (response.data.status === SUCCESS_STATUS) { + onSuccess(response.data.data); + } http.setClientIdHeader(clientId); }) .catch((err: AxiosError) => { diff --git a/packages/platform/examples/base/src/pages/booking.tsx b/packages/platform/examples/base/src/pages/booking.tsx index 0245bb9f67..84c290bc22 100644 --- a/packages/platform/examples/base/src/pages/booking.tsx +++ b/packages/platform/examples/base/src/pages/booking.tsx @@ -92,13 +92,10 @@ export default function Bookings(props: { calUsername: string; calEmail: string

{eventTypeSlug}

{ setBookingTitle(data.data.title ?? ""); router.push(`/${data.data.uid}`); }} - teamId={teams?.[0]?.id || 0} - isTeamEvent={isTeamEvent} duration={eventTypeDuration} customClassNames={{ bookerContainer: "!bg-[#F5F2FE] [&_button:!rounded-full] border-subtle border", @@ -113,6 +110,9 @@ export default function Bookings(props: { calUsername: string; calEmail: string availableTimes: "!bg-[#D7CEF5]", }, }} + {...(isTeamEvent + ? { isTeamEvent: true, teamId: teams?.[0]?.id || 0 } + : { username: props.calUsername })} /> )} diff --git a/packages/platform/types/slots.ts b/packages/platform/types/slots.ts index 490822cb3c..d8844f6bbb 100644 --- a/packages/platform/types/slots.ts +++ b/packages/platform/types/slots.ts @@ -19,6 +19,7 @@ export class GetAvailableSlotsInput { @IsArray() @IsString({ each: true }) + @IsOptional() usernameList?: string[]; @IsBoolean()