chore: booker atom username / teams props enabled queries (#16514)

This commit is contained in:
Morgan
2024-09-06 12:38:37 -07:00
committed by GitHub
parent 21ef1900e9
commit 105e7d3e28
12 changed files with 118 additions and 46 deletions
@@ -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;
}
@@ -40,6 +40,11 @@ export class CalProviderController {
return {
status: SUCCESS_STATUS,
data: {
clientId: oAuthClient.id,
organizationId: oAuthClient.organizationId,
name: oAuthClient.name,
},
};
}
+24 -1
View File
@@ -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": {
@@ -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<BookerProps, "username" | "entity"> & {
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<BookerProps, "username" | "ent
onDeleteSlotSuccess?: (data: ApiSuccessResponseWithoutData) => 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 || "",
});
@@ -37,9 +37,12 @@ export function BaseCalProvider({
labels,
autoUpdateTimezone,
language = EN,
organizationId,
onTimezoneChange,
}: CalProviderProps) {
const [error, setError] = useState<string>("");
const [stateOrgId, setOrganizationId] = useState<number>(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,
}}>
<TooltipProvider>{children}</TooltipProvider>
@@ -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<translationKeys, string>}
language={language}>
language={language}
organizationId={organizationId}>
{children}
</BaseCalProvider>
</QueryClientProvider>
@@ -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<typeof useEventType>;
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;
@@ -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;
@@ -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<ApiResponse<AvailableSlotsType>>("/slots/available", {
@@ -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<boolean>(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<ApiResponse>(`/provider/${clientId}`)
.then(() => {
onSuccess();
.get<ApiResponse<{ client: string; organizationId: number; name: string }>>(`/provider/${clientId}`)
.then((response) => {
if (response.data.status === SUCCESS_STATUS) {
onSuccess(response.data.data);
}
http.setClientIdHeader(clientId);
})
.catch((err: AxiosError) => {
@@ -92,13 +92,10 @@ export default function Bookings(props: { calUsername: string; calEmail: string
<h1>{eventTypeSlug}</h1>
<Booker
eventSlug={eventTypeSlug}
username={props.calUsername ?? ""}
onCreateBookingSuccess={(data) => {
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 })}
/>
</>
)}
+1
View File
@@ -19,6 +19,7 @@ export class GetAvailableSlotsInput {
@IsArray()
@IsString({ each: true })
@IsOptional()
usernameList?: string[];
@IsBoolean()