fix: Booker fix for team + user with same event + event slug (#9654)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Jeroen Reumkens 2023-06-21 09:31:55 +02:00 committed by GitHub
parent b070cfb227
commit ab6781cd72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 86 additions and 22 deletions

View File

@ -14,7 +14,7 @@ import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<BookerSeo
@ -29,6 +29,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
rescheduleBooking={booking}
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent={isTeamEvent}
/>
</main>
);
@ -57,6 +58,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
username: true,
},
},
team: {
select: {
id: true,
},
},
},
},
},
@ -96,9 +102,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
}
const isTeamEvent = !!hashedLink.eventType?.team?.id;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
if (!eventData) {
return {
@ -114,6 +122,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
slug,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
// Sending the team event from the server, because this template file
// is reused for both team and user events.
isTeamEvent,
},
};
}

View File

@ -30,6 +30,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
rescheduleBooking={booking}
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent
/>
</main>
);
@ -71,7 +72,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
const eventData = await ssr.viewer.public.event.fetch({
username: teamSlug,
eventSlug: meetingSlug,
isTeamEvent: true,
});
if (!eventData) {
return {

View File

@ -36,6 +36,7 @@ const BookerComponent = ({
month,
rescheduleBooking,
hideBranding = false,
isTeamEvent,
}: BookerProps) => {
const isMobile = useMediaQuery("(max-width: 768px)");
const isTablet = useMediaQuery("(max-width: 1024px)");
@ -81,6 +82,7 @@ const BookerComponent = ({
rescheduleUid,
rescheduleBooking,
layout: defaultLayout,
isTeamEvent,
});
useEffect(() => {

View File

@ -22,6 +22,7 @@ type StoreInitializeType = {
rescheduleUid: string | null;
rescheduleBooking: GetBookingType | null | undefined;
layout: BookerLayout;
isTeamEvent?: boolean;
};
export type BookerStore = {
@ -89,6 +90,12 @@ export type BookerStore = {
*/
formValues: Record<string, any>;
setFormValues: (values: Record<string, any>) => void;
/**
* Force event being a team event, so we only query for team events instead
* of also include 'user' events and return the first event that matches with
* both the slug and the event slug.
*/
isTeamEvent: boolean;
};
/**
@ -137,6 +144,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
updateQueryParam("month", month ?? "");
get().setSelectedDate(null);
},
isTeamEvent: false,
initialize: ({
username,
eventSlug,
@ -145,6 +153,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
rescheduleUid = null,
rescheduleBooking = null,
layout,
isTeamEvent,
}: StoreInitializeType) => {
const selectedDateInStore = get().selectedDate;
@ -165,6 +174,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
rescheduleUid,
rescheduleBooking,
layout: layout || BookerLayouts.MONTH_VIEW,
isTeamEvent: isTeamEvent || false,
// Preselect today's date in week / column view, since they use this to show the week title.
selectedDate:
selectedDateInStore ||
@ -207,9 +217,29 @@ export const useInitializeBookerStore = ({
rescheduleUid = null,
rescheduleBooking = null,
layout,
isTeamEvent,
}: StoreInitializeType) => {
const initializeStore = useBookerStore((state) => state.initialize);
useEffect(() => {
initializeStore({ username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking, layout });
}, [initializeStore, username, eventSlug, month, eventId, rescheduleUid, rescheduleBooking, layout]);
initializeStore({
username,
eventSlug,
month,
eventId,
rescheduleUid,
rescheduleBooking,
layout,
isTeamEvent,
});
}, [
initializeStore,
username,
eventSlug,
month,
eventId,
rescheduleUid,
rescheduleBooking,
layout,
isTeamEvent,
]);
};

View File

@ -41,6 +41,13 @@ export interface BookerProps {
* within the atom (i.e. without a server side component).
*/
rescheduleBooking?: GetBookingType;
/**
* If this boolean is passed, we will only check team events with this slug and event slug.
* If it's not passed, we will first query a generic user event, and only if that doesn't exist
* fetch the team event. In case there's both a team + user with the same slug AND same event slug,
* that will always result in the user event being returned.
*/
isTeamEvent?: boolean;
}
export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking";

View File

@ -16,9 +16,10 @@ import { useBookerStore } from "../store";
*/
export const useEvent = () => {
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
const isTeamEvent = useBookerStore((state) => state.isTeamEvent);
return trpc.viewer.public.event.useQuery(
{ username: username ?? "", eventSlug: eventSlug ?? "" },
{ username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent },
{ refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) }
);
};

View File

@ -76,7 +76,12 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
hidden: true,
});
export const getPublicEvent = async (username: string, eventSlug: string, prisma: PrismaClient) => {
export const getPublicEvent = async (
username: string,
eventSlug: string,
isTeamEvent: boolean | undefined,
prisma: PrismaClient
) => {
const usernameList = username.split("+");
// In case of dynamic group event, we fetch user's data and use the default event.
@ -141,24 +146,26 @@ export const getPublicEvent = async (username: string, eventSlug: string, prisma
};
}
const usersOrTeamQuery = isTeamEvent
? {
team: {
slug: username,
},
}
: {
users: {
some: {
username,
},
},
team: null,
};
// In case it's not a group event, it's either a single user or a team, and we query that data.
const event = await prisma.eventType.findFirst({
where: {
slug: eventSlug,
OR: [
{
users: {
some: {
username,
},
},
},
{
team: {
slug: username,
},
},
],
...usersOrTeamQuery,
},
select: publicEventSelect,
});

View File

@ -10,6 +10,6 @@ interface EventHandlerOptions {
}
export const eventHandler = async ({ ctx, input }: EventHandlerOptions) => {
const event = await getPublicEvent(input.username, input.eventSlug, ctx.prisma);
const event = await getPublicEvent(input.username, input.eventSlug, input.isTeamEvent, ctx.prisma);
return event;
};

View File

@ -3,6 +3,7 @@ import z from "zod";
export const ZEventInputSchema = z.object({
username: z.string(),
eventSlug: z.string(),
isTeamEvent: z.boolean().optional(),
});
export type TEventInputSchema = z.infer<typeof ZEventInputSchema>;