Skip to content

Commit bffe9e0

Browse files
authored
Merge branch 'main' into dub-folders
2 parents 2b78200 + 46add2b commit bffe9e0

File tree

54 files changed

+616
-802
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+616
-802
lines changed

.yarn/versions/b7d8df6e.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
undecided:
2+
- calcom-monorepo
3+
- "@calcom/app-store-cli"
4+
- "@calcom/platform-constants"
5+
- "@calcom/platform-enums"
6+
- "@calcom/platform-types"
7+
- "@calcom/platform-utils"
8+
- "@calcom/prisma"

apps/api/v2/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@axiomhq/winston": "^1.2.0",
3131
"@calcom/platform-constants": "*",
3232
"@calcom/platform-enums": "*",
33-
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.95",
33+
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.99",
3434
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
3535
"@calcom/platform-types": "*",
3636
"@calcom/platform-utils": "*",

apps/api/v2/src/ee/bookings/2024-04-15/inputs/create-booking.input.ts

+15
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,19 @@ export class CreateBookingInput_2024_04_15 {
169169
label?: string | undefined;
170170
}
171171
>;
172+
173+
@IsString()
174+
@IsOptional()
175+
@ApiPropertyOptional()
176+
teamMemberEmail?: string;
177+
178+
@IsString()
179+
@IsOptional()
180+
@ApiPropertyOptional()
181+
crmAppSlug?: string;
182+
183+
@IsString()
184+
@IsOptional()
185+
@ApiPropertyOptional()
186+
crmOwnerRecordType?: string;
172187
}

apps/api/v2/src/modules/router/controllers/router.controller.ts

+50-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
2+
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
23
import { Controller, Req, NotFoundException, Param, Post, Body } from "@nestjs/common";
34
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
45
import { Request } from "express";
56

6-
import { getRoutedUrl } from "@calcom/platform-libraries";
7+
import {
8+
getRoutedUrl,
9+
getTeamMemberEmailForResponseOrContactUsingUrlQuery,
10+
} from "@calcom/platform-libraries";
711
import { ApiResponse } from "@calcom/platform-types";
812

913
@Controller({
@@ -13,6 +17,8 @@ import { ApiResponse } from "@calcom/platform-types";
1317
@DocsTags("Router controller")
1418
@DocsExcludeController(true)
1519
export class RouterController {
20+
constructor(private readonly teamsEventTypesRepository: TeamsEventTypesRepository) {}
21+
1622
@Post("/forms/:formId/submit")
1723
async getRoutingFormResponse(
1824
@Req() request: Request,
@@ -27,13 +33,54 @@ export class RouterController {
2733
}
2834

2935
if (routedUrlData?.redirect?.destination) {
30-
return { status: "success", data: routedUrlData?.redirect?.destination, redirect: true };
36+
return this.handleRedirect(routedUrlData.redirect.destination);
3137
}
3238

3339
if (routedUrlData?.props) {
34-
return { status: "success", data: { message: routedUrlData?.props?.message ?? "" }, redirect: false };
40+
return { status: "success", data: { message: routedUrlData.props.message ?? "" }, redirect: false };
3541
}
3642

3743
return { status: "success", data: { message: "No Route nor custom message found." }, redirect: false };
3844
}
45+
46+
private async handleRedirect(destination: string): Promise<ApiResponse<unknown> & { redirect: boolean }> {
47+
const routingUrl = new URL(destination);
48+
const routingSearchParams = routingUrl.searchParams;
49+
if (
50+
routingSearchParams.get("cal.action") === "eventTypeRedirectUrl" &&
51+
routingSearchParams.has("email") &&
52+
routingSearchParams.has("cal.teamId") &&
53+
!routingSearchParams.has("cal.skipContactOwner")
54+
) {
55+
return this.handleRedirectWithContactOwner(routingUrl, routingSearchParams);
56+
}
57+
58+
return { status: "success", data: destination, redirect: true };
59+
}
60+
61+
private async handleRedirectWithContactOwner(
62+
routingUrl: URL,
63+
routingSearchParams: URLSearchParams
64+
): Promise<ApiResponse<unknown> & { redirect: boolean }> {
65+
const pathNameParams = routingUrl.pathname.split("/");
66+
const eventTypeSlug = pathNameParams[pathNameParams.length - 1];
67+
const teamId = Number(routingSearchParams.get("cal.teamId"));
68+
const eventTypeData = this.teamsEventTypesRepository.getTeamEventTypeBySlug(teamId, eventTypeSlug, 3);
69+
70+
// get the salesforce record owner email for the email given as a form response.
71+
const {
72+
email: teamMemberEmail,
73+
recordType: crmOwnerRecordType,
74+
crmAppSlug,
75+
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
76+
query: Object.fromEntries(routingSearchParams),
77+
eventData: eventTypeData,
78+
});
79+
80+
Boolean(teamMemberEmail) && routingUrl.searchParams.set("cal.teamMemberEmail", teamMemberEmail);
81+
Boolean(crmOwnerRecordType) && routingUrl.searchParams.set("cal.crmOwnerRecordType", crmOwnerRecordType);
82+
Boolean(crmAppSlug) && routingUrl.searchParams.set("cal.crmAppSlug", crmAppSlug);
83+
84+
return { status: "success", data: routingUrl.toString(), redirect: true };
85+
}
3986
}

apps/api/v2/src/modules/router/router.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { PrismaModule } from "@/modules/prisma/prisma.module";
22
import { RouterController } from "@/modules/router/controllers/router.controller";
3+
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
34
import { Module } from "@nestjs/common";
45

56
@Module({
67
imports: [PrismaModule],
7-
providers: [],
8+
providers: [TeamsEventTypesRepository],
89
exports: [],
910
controllers: [RouterController],
1011
})

apps/api/v2/swagger/documentation.json

+8
Original file line numberDiff line numberDiff line change
@@ -5593,6 +5593,14 @@
55935593
],
55945594
"type": "string"
55955595
}
5596+
},
5597+
{
5598+
"name": "teamMemberEmail",
5599+
"required": false,
5600+
"in": "query",
5601+
"schema": {
5602+
"type": "string"
5603+
}
55965604
}
55975605
],
55985606
"responses": {

apps/web/app/(use-page-wrapper)/auth/error/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const ServerPage = async ({ searchParams }: PageProps) => {
2323
const { error } = querySchema.parse({ error: searchParams?.error || undefined });
2424
const errorMsg = t("error_during_login") + (error ? ` Error code: ${error}` : "");
2525
return (
26-
<AuthContainer title="" description="" isAppDir={true}>
26+
<AuthContainer>
2727
<div>
2828
<div className="bg-error mx-auto flex h-12 w-12 items-center justify-center rounded-full">
2929
<Icon name="x" className="h-6 w-6 text-red-600" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import withEmbedSsrAppDir from "app/WithEmbedSSR";
2+
import type { PageProps as ServerPageProps } from "app/_types";
3+
import { cookies, headers } from "next/headers";
4+
5+
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
6+
7+
import OldPage from "~/bookings/views/bookings-single-view";
8+
import {
9+
getServerSideProps,
10+
type PageProps as ClientPageProps,
11+
} from "~/bookings/views/bookings-single-view.getServerSideProps";
12+
13+
const getEmbedData = withEmbedSsrAppDir<ClientPageProps>(getServerSideProps);
14+
15+
const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
16+
const context = buildLegacyCtx(headers(), cookies(), params, searchParams);
17+
const props = await getEmbedData(context);
18+
return <OldPage {...props} />;
19+
};
20+
21+
export default ServerPage;

apps/web/app/d/[link]/[slug]/page.tsx apps/web/app/(use-page-wrapper)/d/[link]/[slug]/page.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { withAppDirSsr } from "app/WithAppDirSsr";
22
import type { PageProps as _PageProps } from "app/_types";
33
import { _generateMetadata } from "app/_utils";
4-
import { WithLayout } from "app/layoutHOC";
54
import { cookies, headers } from "next/headers";
65

76
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
@@ -27,4 +26,11 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
2726
};
2827

2928
const getData = withAppDirSsr<PageProps>(getServerSideProps);
30-
export default WithLayout({ getLayout: null, Page: Type, getData })<"P">;
29+
const ServerPage = async ({ params, searchParams }: _PageProps) => {
30+
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
31+
const pageProps = await getData(legacyCtx);
32+
33+
return <Type {...pageProps} />;
34+
};
35+
36+
export default ServerPage;

apps/web/app/api/plain-hash/route.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { createHmac } from "crypto";
2-
import type { NextRequest } from "next/server";
2+
import { cookies, headers } from "next/headers";
33
import { NextResponse } from "next/server";
44
import { z } from "zod";
55

66
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
77
import { apiRouteMiddleware } from "@calcom/lib/server/apiRouteMiddleware";
88

9+
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
10+
911
const responseSchema = z.object({
1012
hash: z.string(),
1113
email: z.string().email(),
@@ -15,9 +17,8 @@ const responseSchema = z.object({
1517
chatAvatarUrl: z.string(),
1618
});
1719

18-
async function handler(request: NextRequest) {
19-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20-
const session = await getServerSession({ req: request as any });
20+
async function handler() {
21+
const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) });
2122
if (!session?.user?.email) {
2223
return NextResponse.json({ error: "Unauthorized - No session email found" }, { status: 401 });
2324
}

apps/web/app/booking/[uid]/embed/page.tsx

-9
This file was deleted.

apps/web/app/providers.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { TrpcProvider } from "app/_trpc/trpc-provider";
44
import { SessionProvider } from "next-auth/react";
55
import CacheProvider from "react-inlinesvg/provider";
66

7+
import PlainChat from "@lib/plain/dynamicProvider";
8+
79
export function Providers({ children }: { children: React.ReactNode }) {
810
return (
911
<SessionProvider>
1012
<TrpcProvider>
13+
<PlainChat />
1114
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
1215
<CacheProvider>{children}</CacheProvider>
1316
</TrpcProvider>

apps/web/components/booking/BookingListItem.tsx

+54-51
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
DropdownMenuLabel,
3939
DropdownMenuSeparator,
4040
DropdownMenuTrigger,
41+
DropdownMenuPortal,
4142
Icon,
4243
MeetingTimeInTimezones,
4344
showToast,
@@ -909,71 +910,73 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
909910
onClick={(e) => e.stopPropagation()}
910911
className="radix-state-open:text-blue-500 transition hover:text-blue-500">
911912
{noShow ? (
912-
<s>
913+
<>
913914
{name || email} <Icon name="eye-off" className="inline h-4" />
914-
</s>
915+
</>
915916
) : (
916917
<>{name || email}</>
917918
)}
918919
</button>
919920
</DropdownMenuTrigger>
920-
<DropdownMenuContent>
921-
{!isSmsCalEmail(email) && (
921+
<DropdownMenuPortal>
922+
<DropdownMenuContent>
923+
{!isSmsCalEmail(email) && (
924+
<DropdownMenuItem className="focus:outline-none">
925+
<DropdownItem
926+
StartIcon="mail"
927+
href={`mailto:${email}`}
928+
onClick={(e) => {
929+
setOpenDropdown(false);
930+
e.stopPropagation();
931+
}}>
932+
<a href={`mailto:${email}`}>{t("email")}</a>
933+
</DropdownItem>
934+
</DropdownMenuItem>
935+
)}
936+
922937
<DropdownMenuItem className="focus:outline-none">
923938
<DropdownItem
924-
StartIcon="mail"
925-
href={`mailto:${email}`}
939+
StartIcon={isCopied ? "clipboard-check" : "clipboard"}
926940
onClick={(e) => {
941+
e.preventDefault();
942+
const isEmailCopied = isSmsCalEmail(email);
943+
copyToClipboard(isEmailCopied ? email : phoneNumber ?? "");
927944
setOpenDropdown(false);
928-
e.stopPropagation();
945+
showToast(isEmailCopied ? t("email_copied") : t("phone_number_copied"), "success");
929946
}}>
930-
<a href={`mailto:${email}`}>{t("email")}</a>
947+
{!isCopied ? t("copy") : t("copied")}
931948
</DropdownItem>
932949
</DropdownMenuItem>
933-
)}
934950

935-
<DropdownMenuItem className="focus:outline-none">
936-
<DropdownItem
937-
StartIcon={isCopied ? "clipboard-check" : "clipboard"}
938-
onClick={(e) => {
939-
e.preventDefault();
940-
const isEmailCopied = isSmsCalEmail(email);
941-
copyToClipboard(isEmailCopied ? email : phoneNumber ?? "");
942-
setOpenDropdown(false);
943-
showToast(isEmailCopied ? t("email_copied") : t("phone_number_copied"), "success");
944-
}}>
945-
{!isCopied ? t("copy") : t("copied")}
946-
</DropdownItem>
947-
</DropdownMenuItem>
948-
949-
{isBookingInPast && (
950-
<DropdownMenuItem className="focus:outline-none">
951-
{noShow ? (
952-
<DropdownItem
953-
data-testid="unmark-no-show"
954-
onClick={(e) => {
955-
e.preventDefault();
956-
setOpenDropdown(false);
957-
toggleNoShow({ attendee: { noShow: false, email }, bookingUid });
958-
}}
959-
StartIcon="eye">
960-
{t("unmark_as_no_show")}
961-
</DropdownItem>
962-
) : (
963-
<DropdownItem
964-
data-testid="mark-no-show"
965-
onClick={(e) => {
966-
e.preventDefault();
967-
setOpenDropdown(false);
968-
toggleNoShow({ attendee: { noShow: true, email }, bookingUid });
969-
}}
970-
StartIcon="eye-off">
971-
{t("mark_as_no_show")}
972-
</DropdownItem>
973-
)}
974-
</DropdownMenuItem>
975-
)}
976-
</DropdownMenuContent>
951+
{isBookingInPast && (
952+
<DropdownMenuItem className="focus:outline-none">
953+
{noShow ? (
954+
<DropdownItem
955+
data-testid="unmark-no-show"
956+
onClick={(e) => {
957+
e.preventDefault();
958+
setOpenDropdown(false);
959+
toggleNoShow({ attendee: { noShow: false, email }, bookingUid });
960+
}}
961+
StartIcon="eye">
962+
{t("unmark_as_no_show")}
963+
</DropdownItem>
964+
) : (
965+
<DropdownItem
966+
data-testid="mark-no-show"
967+
onClick={(e) => {
968+
e.preventDefault();
969+
setOpenDropdown(false);
970+
toggleNoShow({ attendee: { noShow: true, email }, bookingUid });
971+
}}
972+
StartIcon="eye-off">
973+
{t("mark_as_no_show")}
974+
</DropdownItem>
975+
)}
976+
</DropdownMenuItem>
977+
)}
978+
</DropdownMenuContent>
979+
</DropdownMenuPortal>
977980
</Dropdown>
978981
);
979982
};

0 commit comments

Comments
 (0)