Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8f5bd6fe9 | ||
|
|
a1c725bb35 | ||
|
|
e4eeb11a61 | ||
|
|
3b0bb25251 | ||
|
|
d27490e6f2 | ||
|
|
b3c191b553 | ||
|
|
0b977e615d | ||
|
|
979e1fd28b | ||
|
|
3437c4331d |
@@ -117,8 +117,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
// A booking that has been rescheduled to a new booking will also have a status of CANCELLED
|
||||
const isDisabledRescheduling = booking.eventType?.disableRescheduling;
|
||||
// This comes from query param and thus is considered forced
|
||||
const canRescheduleCancelledBooking =
|
||||
isForcedRescheduleForCancelledBooking || booking.eventType?.allowReschedulingCancelledBookings;
|
||||
const canBookThroughCancelledBookingRescheduleLink = booking.eventType?.allowReschedulingCancelledBookings;
|
||||
const isNonRescheduleableBooking =
|
||||
booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED;
|
||||
|
||||
@@ -131,8 +130,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
if (isNonRescheduleableBooking) {
|
||||
const canReschedule = booking.status === BookingStatus.CANCELLED && canRescheduleCancelledBooking;
|
||||
if (isNonRescheduleableBooking && !isForcedRescheduleForCancelledBooking) {
|
||||
const canReschedule =
|
||||
booking.status === BookingStatus.CANCELLED && canBookThroughCancelledBookingRescheduleLink;
|
||||
return {
|
||||
redirect: {
|
||||
destination: canReschedule ? eventUrl : `/booking/${uid}`,
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function Router({ form, message, errorMessage }: inferSSRProps<ty
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{form.name} | Cal.com Forms</title>
|
||||
<title>{form?.name} | Cal.com Forms</title>
|
||||
</Head>
|
||||
<div className="mx-auto my-0 max-w-3xl md:my-24">
|
||||
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
import { wrapGetServerSidePropsWithSentry } from "@sentry/nextjs";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getRoutedUrl } from "@calcom/lib/server/getRoutedUrl";
|
||||
import { getRoutedUrl, hasEmbedPath } from "@calcom/lib/server/getRoutedUrl";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
export const getServerSideProps = wrapGetServerSidePropsWithSentry(async function getServerSideProps(
|
||||
context: GetServerSidePropsContext
|
||||
) {
|
||||
return await getRoutedUrl(context);
|
||||
try {
|
||||
return await getRoutedUrl(context);
|
||||
} catch (error) {
|
||||
if (error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS") {
|
||||
context.res.statusCode = 429;
|
||||
const isEmbed = hasEmbedPath(context.req.url || "");
|
||||
|
||||
return {
|
||||
props: {
|
||||
isEmbed,
|
||||
form: null,
|
||||
message: null,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
"/router");
|
||||
|
||||
@@ -155,6 +155,18 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
|
||||
};
|
||||
};
|
||||
|
||||
// Zoom agenda field has a 2000 character limit; we set maxLength to 1900 to leave a safety buffer
|
||||
const truncateAgenda = (description?: string | null) => {
|
||||
if (!description) return description;
|
||||
|
||||
const maxLength = 1900;
|
||||
const trimmed = description.trimEnd();
|
||||
if (trimmed.length > maxLength) {
|
||||
return `${trimmed.substring(0, maxLength).trimEnd()}...`;
|
||||
}
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const userSettings = await getUserSettings();
|
||||
const recurrence = getRecurrence(event);
|
||||
// Documentation at: https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingcreate
|
||||
@@ -166,7 +178,7 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
|
||||
//schedule_for: "string", TODO: Used when scheduling the meeting for someone else (needed?)
|
||||
timezone: event.organizer.timeZone,
|
||||
password: userSettings?.schedule_meeting?.default_password_for_scheduled_meetings ?? undefined,
|
||||
agenda: event.description,
|
||||
agenda: truncateAgenda(event.description),
|
||||
settings: {
|
||||
host_video: true,
|
||||
participant_video: true,
|
||||
|
||||
@@ -157,6 +157,8 @@ export async function handler(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (message?.length && message?.length > 0 && sendTo) {
|
||||
const smsMessageWithoutOptOut = message;
|
||||
|
||||
if (process.env.TWILIO_OPT_OUT_ENABLED === "true") {
|
||||
message = await WorkflowOptOutService.addOptOutMessage(message, locale || "en");
|
||||
}
|
||||
@@ -167,6 +169,7 @@ export async function handler(req: NextRequest) {
|
||||
body: message,
|
||||
scheduledDate: reminder.scheduledDate,
|
||||
sender: senderID,
|
||||
bodyWithoutOptOut: smsMessageWithoutOptOut,
|
||||
bookingUid: reminder.booking.uid,
|
||||
userId,
|
||||
teamId,
|
||||
|
||||
@@ -14,6 +14,7 @@ export async function sendSmsOrFallbackEmail(props: {
|
||||
phoneNumber: string;
|
||||
body: string;
|
||||
sender: string;
|
||||
bodyWithoutOptOut?: string;
|
||||
bookingUid?: string | null;
|
||||
userId?: number | null;
|
||||
teamId?: number | null;
|
||||
@@ -40,7 +41,7 @@ export async function sendSmsOrFallbackEmail(props: {
|
||||
await sendOrScheduleWorkflowEmails({
|
||||
to: [fallbackData.email],
|
||||
subject: fallbackData.t("notification_about_your_booking"),
|
||||
html: twilioData.body,
|
||||
html: twilioData.bodyWithoutOptOut || twilioData.body,
|
||||
replyTo: fallbackData.replyTo,
|
||||
});
|
||||
}
|
||||
@@ -60,6 +61,7 @@ export async function scheduleSmsOrFallbackEmail(props: {
|
||||
body: string;
|
||||
scheduledDate: Date;
|
||||
sender: string;
|
||||
bodyWithoutOptOut?: string;
|
||||
bookingUid?: string | null;
|
||||
userId?: number | null;
|
||||
teamId?: number | null;
|
||||
@@ -96,7 +98,7 @@ export async function scheduleSmsOrFallbackEmail(props: {
|
||||
await sendOrScheduleWorkflowEmails({
|
||||
to: [fallbackData.email],
|
||||
subject: fallbackData.t("notification_about_your_booking"),
|
||||
html: twilioData.body,
|
||||
html: twilioData.bodyWithoutOptOut || twilioData.body,
|
||||
replyTo: fallbackData.replyTo,
|
||||
sendAt: twilioData.scheduledDate,
|
||||
referenceUid: reminder.uuid || undefined,
|
||||
|
||||
@@ -176,6 +176,8 @@ export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
|
||||
}
|
||||
|
||||
if (smsMessage.length > 0) {
|
||||
const smsMessageWithoutOptOut = smsMessage;
|
||||
|
||||
if (process.env.TWILIO_OPT_OUT_ENABLED === "true") {
|
||||
smsMessage = await WorkflowOptOutService.addOptOutMessage(
|
||||
smsMessage,
|
||||
@@ -196,6 +198,7 @@ export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => {
|
||||
phoneNumber: reminderPhone,
|
||||
body: smsMessage,
|
||||
sender: senderID,
|
||||
bodyWithoutOptOut: smsMessageWithoutOptOut,
|
||||
bookingUid: evt.uid,
|
||||
userId,
|
||||
teamId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "@calcom/lib/__mocks__/logger";
|
||||
|
||||
import { createHash } from "crypto";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
@@ -12,12 +13,14 @@ import { substituteVariables } from "@calcom/app-store/routing-forms/lib/substit
|
||||
import { getUrlSearchParamsToForward } from "@calcom/app-store/routing-forms/pages/routing-link/getUrlSearchParamsToForward";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { isAuthorizedToViewFormOnOrgDomain } from "@calcom/features/routing-forms/lib/isAuthorizedToViewForm";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
|
||||
import { getRoutedUrl } from "./getRoutedUrl";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@calcom/lib/checkRateLimitAndThrowError");
|
||||
vi.mock("@calcom/app-store/routing-forms/lib/handleResponse");
|
||||
vi.mock("@calcom/lib/server/repository/routingForm");
|
||||
vi.mock("@calcom/lib/server/repository/user");
|
||||
@@ -112,6 +115,7 @@ describe("getRoutedUrl", () => {
|
||||
it("should return notFound if form is not found", async () => {
|
||||
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(null);
|
||||
const context = mockContext({});
|
||||
|
||||
const result = await getRoutedUrl(context);
|
||||
expect(result).toEqual({ notFound: true });
|
||||
expect(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).toHaveBeenCalledWith("form-id");
|
||||
@@ -259,6 +263,19 @@ describe("getRoutedUrl", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error if rate limit is exceeded", async () => {
|
||||
vi.mocked(checkRateLimitAndThrowError).mockRejectedValue(new Error("Rate limit exceeded"));
|
||||
const context = mockContext({ email: "test@cal.com" });
|
||||
const expectedHash = createHash("sha256")
|
||||
.update(JSON.stringify({ email: "test@cal.com" }))
|
||||
.digest("hex");
|
||||
|
||||
await expect(getRoutedUrl(context)).rejects.toThrow("Rate limit exceeded");
|
||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||
identifier: `form:form-id:hash:${expectedHash}`,
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dry Run", () => {
|
||||
it("should call handleResponse with isPreview:true", async () => {
|
||||
vi.mocked(RoutingFormRepository.findFormByIdIncludeUserTeamAndOrg).mockResolvedValue(mockForm as never);
|
||||
|
||||
@@ -44,7 +44,7 @@ const getDeterministicHashForResponse = (fieldsResponses: Record<string, unknown
|
||||
return hash;
|
||||
};
|
||||
|
||||
function hasEmbedPath(pathWithQuery: string) {
|
||||
export function hasEmbedPath(pathWithQuery: string) {
|
||||
const onlyPath = pathWithQuery.split("?")[0];
|
||||
return onlyPath.endsWith("/embed") || onlyPath.endsWith("/embed/");
|
||||
}
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
## 1.0.108
|
||||
|
||||
## 1.0.110
|
||||
## 1.0.114
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#22107](https://github.com/calcom/cal.com/pull/22107) [`88a2bd3`](https://github.com/calcom/cal.com/commit/88a2bd3317a2cfaf6b367524e2736445544dbd63) Thanks [@supalarry](https://github.com/supalarry)! - test log can ignore
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#22103](https://github.com/calcom/cal.com/pull/22103) [`61274bc`](https://github.com/calcom/cal.com/commit/61274bc7efc67b162d46b59cd75bd376ad515c51) Thanks [@supalarry](https://github.com/supalarry)! - testing changesets - can ignore this
|
||||
|
||||
## 1.0.107
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#22095](https://github.com/calcom/cal.com/pull/22095) [`3c43ce4`](https://github.com/calcom/cal.com/commit/3c43ce4165bce1da29b203d5f2eb62090c5fddd2) Thanks [@supalarry](https://github.com/supalarry)! - test by updating changelog
|
||||
|
||||
- [#21864](https://github.com/calcom/cal.com/pull/21864) [`540bf86`](https://github.com/calcom/cal.com/commit/540bf868b5b60a98d9d3aeb565e2089f15c3dfd3) Thanks [@supalarry](https://github.com/supalarry)! - fix saving event type settings
|
||||
- [#22148](https://github.com/calcom/cal.com/pull/22148) [`d27490e`](https://github.com/calcom/cal.com/commit/d27490e6f2438d353dfcf3e793c8886e723307f3) Thanks [@supalarry](https://github.com/supalarry)! - test release
|
||||
|
||||
## 1.0.63
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ Currently supports React 18, React 19, Next 14 and Next 15.
|
||||
3. Some of the versions are suffixed e.g. `1.0.102-framer` and are intended for isolated use cases, so you most probably
|
||||
want to use version without any suffix e.g. `1.0.103`.
|
||||
|
||||
### Documentation
|
||||
### Documentation
|
||||
Documentation on how to get started with platform solution is [here](https://cal.com/docs/platform/quickstart) and list of atoms can be viewed
|
||||
[here](https://cal.com/docs/platform/atoms/cal-provider)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"description": "Customizable UI components to integrate scheduling into your product.",
|
||||
"authors": "Cal.com, Inc.",
|
||||
"version": "1.0.110",
|
||||
"version": "1.0.114",
|
||||
"scripts": {
|
||||
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
|
||||
"build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
|
||||
@@ -37,11 +37,9 @@
|
||||
"dist",
|
||||
"globals.min.css"
|
||||
],
|
||||
"main": "./dist/cal-atoms.umd.cjs",
|
||||
"module": "./dist/cal-atoms.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./dist/cal-atoms.umd.cjs",
|
||||
"import": "./dist/cal-atoms.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
@@ -83,7 +81,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-awesome-query-builder": "^5.1.2",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,32 @@
|
||||
"../../types/@wojtekmaj__react-daterange-picker.d.ts",
|
||||
"../../types/business-days-plugin.d.ts",
|
||||
"../../types/window.d.ts",
|
||||
"../../lib",
|
||||
"../../features",
|
||||
"../types",
|
||||
"../constants",
|
||||
"../utils",
|
||||
"../libraries",
|
||||
"../../prisma",
|
||||
"../../dayjs",
|
||||
"../../trpc",
|
||||
"../../app-store"
|
||||
"../../prisma/zod-utils.ts",
|
||||
"../../prisma/zod",
|
||||
"../../trpc/react",
|
||||
"../../trpc/server/routers/viewer/slots",
|
||||
"../../trpc/server/types",
|
||||
"../../features/eventtypes",
|
||||
"../../features/schedules",
|
||||
"../../features/bookings/Booker",
|
||||
"../../features/bookings/Booker",
|
||||
"../../features/instant-meeting",
|
||||
"../../features/bookings/handleMarkNoShow.ts",
|
||||
"../../features/bookings/types.ts",
|
||||
"../../features/bookings/lib",
|
||||
"../../features/ee/workflows",
|
||||
"../../features/ee/payments",
|
||||
"../../features/ee/round-robin",
|
||||
"../../features/ee/billing",
|
||||
"../../features/shell",
|
||||
"../../features/components",
|
||||
"../../features/form-builder",
|
||||
"../../features/data-table"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
|
||||
@@ -3,7 +3,6 @@ import path from "path";
|
||||
import { resolve } from "path";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import Inspect from "vite-plugin-inspect";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), ""); // .env inside of packages/platform/atoms
|
||||
@@ -20,7 +19,7 @@ export default defineConfig(({ mode }) => {
|
||||
"@calcom/platform-utils",
|
||||
],
|
||||
},
|
||||
plugins: [Inspect(), react(), dts({ insertTypesEntry: true })],
|
||||
plugins: [react(), dts({ insertTypesEntry: true })],
|
||||
define: {
|
||||
"process.env.NEXT_PUBLIC_WEBAPP_URL": `"${webAppUrl}"`,
|
||||
},
|
||||
@@ -28,17 +27,16 @@ export default defineConfig(({ mode }) => {
|
||||
noExternal: ["turndown"], // Example if you want to disable SSR for your library
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
include: [/@calcom\/lib/, /@calcom\/features/, /node_modules/],
|
||||
},
|
||||
lib: {
|
||||
entry: [resolve(__dirname, "index.ts")],
|
||||
name: "CalAtoms",
|
||||
fileName: "cal-atoms",
|
||||
formats: ["es"],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ["react", "fs", "path", "os", "react-dom", "react-awesome-query-builder"],
|
||||
external: ["react", "fs", "path", "os", "react/jsx-runtime", "react-dom", "react-dom/client"],
|
||||
output: {
|
||||
format: "esm", // Force ESM output
|
||||
globals: {
|
||||
react: "React",
|
||||
"react-dom": "ReactDOM",
|
||||
@@ -58,6 +56,7 @@ export default defineConfig(({ mode }) => {
|
||||
"@radix-ui/react-dialog": path.resolve(__dirname, "./src/components/ui/dialog.tsx"),
|
||||
".prisma/client": path.resolve(__dirname, "../../prisma-client"),
|
||||
"@prisma/client": path.resolve(__dirname, "../../prisma-client"),
|
||||
"@calcom/prisma/client": path.resolve(__dirname, "../../prisma-client"),
|
||||
"@calcom/prisma": path.resolve(__dirname, "../../prisma"),
|
||||
"@calcom/dayjs": path.resolve(__dirname, "../../dayjs"),
|
||||
"@calcom/platform-constants": path.resolve(__dirname, "../constants/index.ts"),
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
"cache": false,
|
||||
"dependsOn": []
|
||||
},
|
||||
"@calcom/atoms#build": {
|
||||
"outputs": ["dist/**", "globals.min.css"]
|
||||
},
|
||||
"@calcom/api-v2#build": {
|
||||
"dependsOn": ["^build"],
|
||||
"env": [
|
||||
|
||||
Reference in New Issue
Block a user