Compare commits
4 Commits
main
...
gh-readonl
Author | SHA1 | Date | |
---|---|---|---|
|
72728a07b2 | ||
|
576b2d9876 | ||
|
30bdf55399 | ||
|
1620a6113a |
|
@ -318,6 +318,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||
<DialogClose />
|
||||
<Button
|
||||
disabled={mutation.isLoading}
|
||||
data-testid="rejection-confirm"
|
||||
onClick={() => {
|
||||
bookingConfirm(false);
|
||||
}}>
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
"react-intl": "^5.25.1",
|
||||
"react-live-chat-loader": "^2.7.3",
|
||||
"react-multi-email": "^0.5.3",
|
||||
"react-phone-input-2": "^2.15.1",
|
||||
"react-phone-number-input": "^3.2.7",
|
||||
"react-schemaorg": "^2.0.0",
|
||||
"react-select": "^5.7.0",
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { expect } from "@playwright/test";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { createHttpServer, selectFirstAvailableTimeSlotNextMonth, waitFor } from "./lib/testUtils";
|
||||
import {
|
||||
bookOptinEvent,
|
||||
createHttpServer,
|
||||
selectFirstAvailableTimeSlotNextMonth,
|
||||
waitFor,
|
||||
} from "./lib/testUtils";
|
||||
|
||||
test.afterEach(({ users }) => users.deleteAll());
|
||||
|
||||
test.describe("BOOKING_CREATED", async () => {
|
||||
test("add webhook & test that creating an event triggers a webhook call", async ({
|
||||
page,
|
||||
users,
|
||||
|
@ -129,3 +135,253 @@ test("add webhook & test that creating an event triggers a webhook call", async
|
|||
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("BOOKING_REJECTED", async () => {
|
||||
test("can book an event that requires confirmation and then that booking can be rejected by organizer", async ({
|
||||
page,
|
||||
users,
|
||||
}) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
// --- create a user
|
||||
const user = await users.create();
|
||||
|
||||
// --- visit user page
|
||||
await page.goto(`/${user.username}`);
|
||||
|
||||
// --- book the user's event
|
||||
await bookOptinEvent(page);
|
||||
|
||||
// --- login as that user
|
||||
await user.login();
|
||||
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
|
||||
await page.goto("/bookings/unconfirmed");
|
||||
await page.click('[data-testid="reject"]');
|
||||
await page.click('[data-testid="rejection-confirm"]');
|
||||
await page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm"));
|
||||
|
||||
// --- check that webhook was called
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
// body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_REJECTED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "Opt in",
|
||||
title: "Opt in between Nameless and Test Testson",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Unnamed",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
// hideCalendarNotes: false,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "Opt in",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
// metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
status: "REJECTED",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
},
|
||||
});
|
||||
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("BOOKING_REQUESTED", async () => {
|
||||
test("can book an event that requires confirmation and get a booking requested event", async ({
|
||||
page,
|
||||
users,
|
||||
}) => {
|
||||
const webhookReceiver = createHttpServer();
|
||||
// --- create a user
|
||||
const user = await users.create();
|
||||
|
||||
// --- login as that user
|
||||
await user.login();
|
||||
|
||||
await page.goto(`/settings/developer/webhooks`);
|
||||
|
||||
// --- add webhook
|
||||
await page.click('[data-testid="new_webhook"]');
|
||||
|
||||
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
|
||||
|
||||
await page.fill('[name="secret"]', "secret");
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
|
||||
|
||||
// --- visit user page
|
||||
await page.goto(`/${user.username}`);
|
||||
|
||||
// --- book the user's opt in
|
||||
await bookOptinEvent(page);
|
||||
|
||||
// --- check that webhook was called
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webhookReceiver.requestList.length).toBe(1);
|
||||
});
|
||||
const [request] = webhookReceiver.requestList;
|
||||
const body = request.body as any;
|
||||
|
||||
// remove dynamic properties that differs depending on where you run the tests
|
||||
const dynamic = "[redacted/dynamic]";
|
||||
body.createdAt = dynamic;
|
||||
body.payload.startTime = dynamic;
|
||||
body.payload.endTime = dynamic;
|
||||
body.payload.location = dynamic;
|
||||
for (const attendee of body.payload.attendees) {
|
||||
attendee.timeZone = dynamic;
|
||||
attendee.language = dynamic;
|
||||
}
|
||||
body.payload.organizer.id = dynamic;
|
||||
body.payload.organizer.email = dynamic;
|
||||
body.payload.organizer.timeZone = dynamic;
|
||||
body.payload.organizer.language = dynamic;
|
||||
body.payload.uid = dynamic;
|
||||
body.payload.bookingId = dynamic;
|
||||
body.payload.additionalInformation = dynamic;
|
||||
body.payload.requiresConfirmation = dynamic;
|
||||
body.payload.eventTypeId = dynamic;
|
||||
body.payload.videoCallData = dynamic;
|
||||
body.payload.appsStatus = dynamic;
|
||||
body.payload.metadata.videoCallUrl = dynamic;
|
||||
|
||||
expect(body).toMatchObject({
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
createdAt: "[redacted/dynamic]",
|
||||
payload: {
|
||||
type: "Opt in",
|
||||
title: "Opt in between Nameless and Test Testson",
|
||||
customInputs: {},
|
||||
startTime: "[redacted/dynamic]",
|
||||
endTime: "[redacted/dynamic]",
|
||||
organizer: {
|
||||
id: "[redacted/dynamic]",
|
||||
name: "Nameless",
|
||||
email: "[redacted/dynamic]",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
responses: {
|
||||
email: {
|
||||
value: "test@example.com",
|
||||
label: "email_address",
|
||||
},
|
||||
name: {
|
||||
value: "Test Testson",
|
||||
label: "your_name",
|
||||
},
|
||||
},
|
||||
userFieldsResponses: {},
|
||||
attendees: [
|
||||
{
|
||||
email: "test@example.com",
|
||||
name: "Test Testson",
|
||||
timeZone: "[redacted/dynamic]",
|
||||
language: "[redacted/dynamic]",
|
||||
},
|
||||
],
|
||||
location: "[redacted/dynamic]",
|
||||
destinationCalendar: null,
|
||||
requiresConfirmation: "[redacted/dynamic]",
|
||||
eventTypeId: "[redacted/dynamic]",
|
||||
uid: "[redacted/dynamic]",
|
||||
eventTitle: "Opt in",
|
||||
eventDescription: null,
|
||||
price: 0,
|
||||
currency: "usd",
|
||||
length: 30,
|
||||
bookingId: "[redacted/dynamic]",
|
||||
status: "PENDING",
|
||||
additionalInformation: "[redacted/dynamic]",
|
||||
metadata: { videoCallUrl: "[redacted/dynamic]" },
|
||||
},
|
||||
});
|
||||
|
||||
webhookReceiver.close();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -374,6 +374,8 @@
|
|||
"booking_rescheduled": "Booking Rescheduled",
|
||||
"recording_ready":"Recording Download Link Ready",
|
||||
"booking_created": "Booking Created",
|
||||
"booking_rejected":"Booking Rejected",
|
||||
"booking_requested":"Booking Requested",
|
||||
"meeting_ended": "Meeting Ended",
|
||||
"form_submitted": "Form Submitted",
|
||||
"event_triggers": "Event Triggers",
|
||||
|
|
|
@ -497,4 +497,16 @@ select:focus {
|
|||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.react-tel-input .country-list .country:hover,
|
||||
.react-tel-input .country-list .country.highlight {
|
||||
@apply !bg-emphasis;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown .selected-flag,
|
||||
.react-tel-input .flag-dropdown.open .selected-flag {
|
||||
@apply !bg-default;
|
||||
}
|
||||
|
||||
.react-tel-input .flag-dropdown {
|
||||
@apply !border-r-default !border-y-0 !border-l-0 left-0.5;
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ function NumberWidget({ value, setValue, ...remainingProps }: TextLikeComponentP
|
|||
type="number"
|
||||
labelSrOnly={remainingProps.noLabel}
|
||||
containerClassName="w-full"
|
||||
className="focus:border-brand-default bg-default dark:bg-muted border-default disabled:bg-emphasis focus:ring-brand block w-full rounded-md text-sm disabled:hover:cursor-not-allowed"
|
||||
className="bg-default border-default disabled:bg-emphasis focus:ring-brand-default dark:focus:border-emphasis block w-full rounded-md text-sm focus:border-neutral-300 disabled:hover:cursor-not-allowed"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dx": "docker compose up -d"
|
||||
"dx": "docker compose up -d || docker-compose up -d"
|
||||
},
|
||||
"dependencies": {
|
||||
"@calcom/dayjs": "*",
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
} from "@calcom/emails";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import {
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
|
@ -40,6 +41,7 @@ import {
|
|||
import { deleteScheduledEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
|
||||
import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { deleteScheduledSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser";
|
||||
|
@ -72,7 +74,6 @@ import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
|||
import type { WorkingHours, TimeRange as DateOverride } from "@calcom/types/schedule";
|
||||
|
||||
import type { EventTypeInfo } from "../../webhooks/lib/sendPayload";
|
||||
import sendPayload from "../../webhooks/lib/sendPayload";
|
||||
import getBookingResponsesSchema from "./getBookingResponsesSchema";
|
||||
|
||||
const translator = short();
|
||||
|
@ -2092,16 +2093,44 @@ async function handler(
|
|||
videoCallUrl: getVideoCallUrlFromCalEvent(evt),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: eventType.title,
|
||||
eventDescription: eventType.description,
|
||||
requiresConfirmation: requiresConfirmation || null,
|
||||
price: paymentAppData.price,
|
||||
currency: eventType.currency,
|
||||
length: eventType.length,
|
||||
};
|
||||
const webhookData = {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
bookingId: booking?.id,
|
||||
rescheduleUid,
|
||||
rescheduleStartTime: originalRescheduledBooking?.startTime
|
||||
? dayjs(originalRescheduledBooking?.startTime).utc().format()
|
||||
: undefined,
|
||||
rescheduleEndTime: originalRescheduledBooking?.endTime
|
||||
? dayjs(originalRescheduledBooking?.endTime).utc().format()
|
||||
: undefined,
|
||||
metadata: { ...metadata, ...reqBody.metadata },
|
||||
eventTypeId,
|
||||
status: "ACCEPTED",
|
||||
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||
};
|
||||
const subscriberOptions: GetSubscriberOptions = {
|
||||
userId: organizerUser.id,
|
||||
eventTypeId,
|
||||
triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
|
||||
teamId: eventType.team?.id,
|
||||
};
|
||||
|
||||
if (isConfirmedByDefault) {
|
||||
const eventTrigger: WebhookTriggerEvents = rescheduleUid
|
||||
? WebhookTriggerEvents.BOOKING_RESCHEDULED
|
||||
: WebhookTriggerEvents.BOOKING_CREATED;
|
||||
const subscriberOptions = {
|
||||
userId: organizerUser.id,
|
||||
eventTypeId,
|
||||
triggerEvent: eventTrigger,
|
||||
teamId: eventType.team?.id,
|
||||
};
|
||||
|
||||
subscriberOptions.triggerEvent = eventTrigger;
|
||||
|
||||
const subscriberOptionsMeetingEnded = {
|
||||
userId: organizerUser.id,
|
||||
|
@ -2125,48 +2154,14 @@ async function handler(
|
|||
log.error("Error while running scheduledJobs for booking", error);
|
||||
}
|
||||
|
||||
try {
|
||||
// Send Webhook call if hooked to BOOKING_CREATED & BOOKING_RESCHEDULED
|
||||
const subscribers = await getWebhooks(subscriberOptions);
|
||||
console.log("evt:", {
|
||||
...evt,
|
||||
metadata: reqBody.metadata,
|
||||
});
|
||||
const bookingId = booking?.id;
|
||||
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: eventType.title,
|
||||
eventDescription: eventType.description,
|
||||
requiresConfirmation: requiresConfirmation || null,
|
||||
price: paymentAppData.price,
|
||||
currency: eventType.currency,
|
||||
length: eventType.length,
|
||||
};
|
||||
|
||||
const promises = subscribers.map((sub) =>
|
||||
sendPayload(sub.secret, eventTrigger, new Date().toISOString(), sub, {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
rescheduleUid,
|
||||
rescheduleStartTime: originalRescheduledBooking?.startTime
|
||||
? dayjs(originalRescheduledBooking?.startTime).utc().format()
|
||||
: undefined,
|
||||
rescheduleEndTime: originalRescheduledBooking?.endTime
|
||||
? dayjs(originalRescheduledBooking?.endTime).utc().format()
|
||||
: undefined,
|
||||
metadata: { ...metadata, ...reqBody.metadata },
|
||||
eventTypeId,
|
||||
status: "ACCEPTED",
|
||||
smsReminderNumber: booking?.smsReminderNumber || undefined,
|
||||
}).catch((e) => {
|
||||
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);
|
||||
})
|
||||
);
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
log.error("Error while sending webhook", error);
|
||||
}
|
||||
await handleWebhookTrigger({ subscriberOptions, eventTrigger, webhookData });
|
||||
} else if (eventType.requiresConfirmation) {
|
||||
// if eventType requires confirmation we will trigger the BOOKING REQUESTED Webhook
|
||||
const eventTrigger: WebhookTriggerEvents = WebhookTriggerEvents.BOOKING_REQUESTED;
|
||||
subscriberOptions.triggerEvent = eventTrigger;
|
||||
webhookData.status = "PENDING";
|
||||
await handleWebhookTrigger({ subscriberOptions, eventTrigger, webhookData });
|
||||
}
|
||||
|
||||
// Avoid passing referencesToCreate with id unique constrain values
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import type { GetSubscriberOptions } from "@calcom/features/webhooks/lib/getWebhooks";
|
||||
import type { WebhookDataType } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
export async function handleWebhookTrigger(args: {
|
||||
subscriberOptions: GetSubscriberOptions;
|
||||
eventTrigger: string;
|
||||
webhookData: Omit<WebhookDataType, "createdAt" | "triggerEvent">;
|
||||
}) {
|
||||
try {
|
||||
const subscribers = await getWebhooks(args.subscriberOptions);
|
||||
|
||||
const promises = subscribers.map((sub) =>
|
||||
sendPayload(sub.secret, args.eventTrigger, new Date().toISOString(), sub, args.webhookData).catch(
|
||||
(e) => {
|
||||
console.error(
|
||||
`Error executing webhook for event: ${args.eventTrigger}, URL: ${sub.subscriberUrl}`,
|
||||
e
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
logger.error("Error while sending webhook", error);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { useRouter } from "next/navigation";
|
||||
import { useState, Suspense } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
@ -8,14 +9,7 @@ import type { RecordingItemSchema } from "@calcom/prisma/zod-utils";
|
|||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
UpgradeTeamsBadge,
|
||||
} from "@calcom/ui";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Download } from "@calcom/ui/components/icon";
|
||||
|
||||
|
@ -101,6 +95,7 @@ const useRecordingDownload = () => {
|
|||
const ViewRecordingsList = ({ roomName, hasTeamPlan }: { roomName: string; hasTeamPlan: boolean }) => {
|
||||
const { t } = useLocale();
|
||||
const { setRecordingId, isFetching, recordingId } = useRecordingDownload();
|
||||
const router = useRouter();
|
||||
|
||||
const { data: recordings } = trpc.viewer.getCalVideoRecordings.useQuery(
|
||||
{ roomName },
|
||||
|
@ -121,7 +116,7 @@ const ViewRecordingsList = ({ roomName, hasTeamPlan }: { roomName: string; hasTe
|
|||
{recordings.data.map((recording: RecordingItemSchema, index: number) => {
|
||||
return (
|
||||
<div
|
||||
className="flex w-full items-center justify-between rounded-md border px-4 py-2"
|
||||
className="border-subtle flex w-full items-center justify-between rounded-md border px-4 py-2"
|
||||
key={recording.id}>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-sm font-semibold">
|
||||
|
@ -138,7 +133,12 @@ const ViewRecordingsList = ({ roomName, hasTeamPlan }: { roomName: string; hasTe
|
|||
{t("download")}
|
||||
</Button>
|
||||
) : (
|
||||
<UpgradeTeamsBadge />
|
||||
<Button
|
||||
tooltip={t("upgrade_to_access_recordings_description")}
|
||||
className="ml-4 lg:ml-0"
|
||||
onClick={() => router.push("/teams")}>
|
||||
{t("upgrade")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import { useRouter } from "next/router";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Users } from "@calcom/ui/components/icon";
|
||||
|
||||
export default function UpgradeRecordingBanner() {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="bg-subtle flex items-start gap-2 rounded-md p-4">
|
||||
<Users className="dark:bg-gray-90 inline-block h-5 w-5" />
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-sm font-semibold">{t("upgrade_to_access_recordings_title")}</h2>
|
||||
<p className="text-sm font-normal">{t("upgrade_to_access_recordings_description")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
router.push(`${WEBAPP_URL}/teams`);
|
||||
}}>
|
||||
{t("upgrade_now")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -134,7 +134,7 @@ export const Components: Record<BookingFieldType, Component> = {
|
|||
const { t } = useLocale();
|
||||
value = value || [];
|
||||
const inputClassName =
|
||||
"dark:placeholder:text-darkgray-600 focus:border-brand-default border-subtle block w-full rounded-md border-default text-sm focus:ring-black disabled:bg-emphasis disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-subtle";
|
||||
"dark:placeholder:text-muted focus:border-emphasis border-subtle block w-full rounded-md border-default text-sm focus:ring-black disabled:bg-emphasis disabled:hover:cursor-not-allowed dark:selection:bg-green-500 disabled:dark:text-subtle bg-default";
|
||||
return (
|
||||
<>
|
||||
{value.length ? (
|
||||
|
@ -148,11 +148,11 @@ export const Components: Record<BookingFieldType, Component> = {
|
|||
<EmailField
|
||||
disabled={readOnly}
|
||||
value={value[index]}
|
||||
className={inputClassName}
|
||||
onChange={(e) => {
|
||||
value[index] = e.target.value;
|
||||
setValue(value);
|
||||
}}
|
||||
className={inputClassName}
|
||||
placeholder={placeholder}
|
||||
label={<></>}
|
||||
required
|
||||
|
@ -251,7 +251,7 @@ export const Components: Record<BookingFieldType, Component> = {
|
|||
}
|
||||
setValue(newValue);
|
||||
}}
|
||||
className="dark:bg-darkgray-300 border-subtle text-emphasis h-4 w-4 rounded focus:ring-black ltr:mr-2 rtl:ml-2"
|
||||
className="border-default dark:border-default hover:bg-subtle checked:hover:bg-brand-default checked:bg-brand-default dark:checked:bg-brand-default dark:bg-darkgray-100 dark:hover:bg-subtle dark:checked:hover:bg-brand-default h-4 w-4 cursor-pointer rounded ltr:mr-2 rtl:ml-2"
|
||||
value={option.value}
|
||||
checked={value.includes(option.value)}
|
||||
/>
|
||||
|
@ -310,7 +310,7 @@ export const Components: Record<BookingFieldType, Component> = {
|
|||
type="radio"
|
||||
disabled={readOnly}
|
||||
name={name}
|
||||
className="dark:bg-darkgray-300 border-subtle text-emphasis h-4 w-4 focus:ring-black ltr:mr-2 rtl:ml-2"
|
||||
className="dark:checked:bg-brand-default dark:bg-darkgray-100 dark:hover:bg-subtle dark:checked:hover:bg-brand-default focus:border-brand-default focus:ring-brand-default border-emphasis h-4 w-4 cursor-pointer text-[--cal-brand] focus:ring-2 ltr:mr-2 rtl:ml-2"
|
||||
value={option.value}
|
||||
onChange={(e) => {
|
||||
setValue({
|
||||
|
|
|
@ -201,7 +201,7 @@ const SettingsSidebarContainer = ({
|
|||
alt="User Avatar"
|
||||
/>
|
||||
)}
|
||||
<p className="text-sm font-medium leading-5 truncate">{t(tab.name)}</p>
|
||||
<p className="truncate text-sm font-medium leading-5">{t(tab.name)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-3 space-y-0.5">
|
||||
|
@ -226,7 +226,7 @@ const SettingsSidebarContainer = ({
|
|||
{tab && tab.icon && (
|
||||
<tab.icon className="h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0" />
|
||||
)}
|
||||
<p className="text-sm font-medium leading-5 truncate">{t(tab.name)}</p>
|
||||
<p className="truncate text-sm font-medium leading-5">{t(tab.name)}</p>
|
||||
</div>
|
||||
</Link>
|
||||
{teams &&
|
||||
|
|
|
@ -32,6 +32,8 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
|
|||
core: [
|
||||
{ value: WebhookTriggerEvents.BOOKING_CANCELLED, label: "booking_cancelled" },
|
||||
{ value: WebhookTriggerEvents.BOOKING_CREATED, label: "booking_created" },
|
||||
{ value: WebhookTriggerEvents.BOOKING_REJECTED, label: "booking_rejected"},
|
||||
{ value: WebhookTriggerEvents.BOOKING_REQUESTED, label: "booking_requested"},
|
||||
{ value: WebhookTriggerEvents.BOOKING_RESCHEDULED, label: "booking_rescheduled" },
|
||||
{ value: WebhookTriggerEvents.MEETING_ENDED, label: "meeting_ended" },
|
||||
{ value: WebhookTriggerEvents.RECORDING_READY, label: "recording_ready" },
|
||||
|
|
|
@ -8,6 +8,8 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = {
|
|||
WebhookTriggerEvents.BOOKING_CREATED,
|
||||
WebhookTriggerEvents.BOOKING_RESCHEDULED,
|
||||
WebhookTriggerEvents.MEETING_ENDED,
|
||||
WebhookTriggerEvents.BOOKING_REQUESTED,
|
||||
WebhookTriggerEvents.BOOKING_REJECTED,
|
||||
WebhookTriggerEvents.RECORDING_READY,
|
||||
] as const,
|
||||
"routing-forms": [WebhookTriggerEvents.FORM_SUBMITTED] as const,
|
||||
|
|
|
@ -16,7 +16,7 @@ export type EventTypeInfo = {
|
|||
length?: number | null;
|
||||
};
|
||||
|
||||
type WebhookDataType = CalendarEvent &
|
||||
export type WebhookDataType = CalendarEvent &
|
||||
EventTypeInfo & {
|
||||
metadata?: { [key: string]: string };
|
||||
bookingId?: number;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterEnum
|
||||
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'BOOKING_REQUESTED';
|
||||
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'BOOKING_REJECTED';
|
|
@ -495,7 +495,9 @@ enum PaymentOption {
|
|||
enum WebhookTriggerEvents {
|
||||
BOOKING_CREATED
|
||||
BOOKING_RESCHEDULED
|
||||
BOOKING_REQUESTED
|
||||
BOOKING_CANCELLED
|
||||
BOOKING_REJECTED
|
||||
FORM_SUBMITTED
|
||||
MEETING_ENDED
|
||||
RECORDING_READY
|
||||
|
|
|
@ -4,10 +4,12 @@ import appStore from "@calcom/app-store";
|
|||
import { sendDeclinedEmails } from "@calcom/emails";
|
||||
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
|
||||
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
|
||||
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
|
||||
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { getTranslation } from "@calcom/lib/server";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { BookingStatus, MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { BookingStatus, MembershipRole, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
|
||||
|
||||
|
@ -304,6 +306,31 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
|
|||
}
|
||||
|
||||
await sendDeclinedEmails(evt);
|
||||
// send BOOKING_REJECTED webhooks
|
||||
const subscriberOptions = {
|
||||
userId: booking.userId,
|
||||
eventTypeId: booking.eventTypeId,
|
||||
triggerEvent: WebhookTriggerEvents.BOOKING_REJECTED,
|
||||
teamId: booking.eventType?.teamId,
|
||||
};
|
||||
const eventTrigger: WebhookTriggerEvents = WebhookTriggerEvents.BOOKING_REJECTED;
|
||||
const eventTypeInfo: EventTypeInfo = {
|
||||
eventTitle: booking.eventType?.title,
|
||||
eventDescription: booking.eventType?.description,
|
||||
requiresConfirmation: booking.eventType?.requiresConfirmation || null,
|
||||
price: booking.eventType?.price,
|
||||
currency: booking.eventType?.currency,
|
||||
length: booking.eventType?.length,
|
||||
};
|
||||
const webhookData = {
|
||||
...evt,
|
||||
...eventTypeInfo,
|
||||
bookingId,
|
||||
eventTypeId: booking.eventType?.id,
|
||||
status: BookingStatus.REJECTED,
|
||||
smsReminderNumber: booking.smsReminderNumber || undefined,
|
||||
};
|
||||
await handleWebhookTrigger({ subscriberOptions, eventTrigger, webhookData });
|
||||
}
|
||||
|
||||
const message = "Booking " + confirmed ? "confirmed" : "rejected";
|
||||
|
|
|
@ -23,7 +23,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
|||
{...props}
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
"hover:border-emphasis border-default bg-default placeholder:text-muted text-emphasis disabled:hover:border-default disabled:bg-subtle mb-2 block h-9 rounded-md border py-2 px-3 text-sm leading-4 focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1 disabled:cursor-not-allowed",
|
||||
"hover:border-emphasis dark:focus:border-emphasis border-default bg-default placeholder:text-muted text-emphasis disabled:hover:border-default disabled:bg-subtle focus:ring-brand-default mb-2 block h-9 rounded-md border py-2 px-3 text-sm leading-4 focus:border-neutral-300 focus:outline-none focus:ring-2 disabled:cursor-not-allowed",
|
||||
isFullWidth && "w-full",
|
||||
props.className
|
||||
)}
|
||||
|
@ -129,7 +129,7 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function
|
|||
{addOnLeading || addOnSuffix ? (
|
||||
<div
|
||||
dir="ltr"
|
||||
className="group relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1">
|
||||
className="focus-within:ring-brand-default group relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2">
|
||||
{addOnLeading && (
|
||||
<Addon isFilled={addOnFilled} className={classNames("rounded-l-md border-r-0", addOnClassname)}>
|
||||
{addOnLeading}
|
||||
|
@ -280,7 +280,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(function
|
|||
ref={ref}
|
||||
{...props}
|
||||
className={classNames(
|
||||
"hover:border-emphasis border-default bg-default placeholder:text-muted text-emphasis disabled:hover:border-default disabled:bg-subtle mb-2 block w-full rounded-md border py-2 px-3 text-sm focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1 disabled:cursor-not-allowed",
|
||||
"hover:border-emphasis border-default bg-default placeholder:text-muted text-emphasis disabled:hover:border-default disabled:bg-subtle focus:ring-brand-default mb-2 block w-full rounded-md border py-2 px-3 text-sm focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-1 disabled:cursor-not-allowed",
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -51,7 +51,7 @@ export const Select = <
|
|||
dropdownIndicator: () => "text-default",
|
||||
control: (state) =>
|
||||
cx(
|
||||
"bg-default border-default !min-h-9 h-9 text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-2 focus-within:ring-emphasis hover:border-emphasis rounded-md border ",
|
||||
"bg-default border-default !min-h-9 h-9 text-sm leading-4 placeholder:text-sm placeholder:font-normal dark:focus:border-emphasis focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-default hover:border-emphasis rounded-md border",
|
||||
state.isMulti
|
||||
? variant === "checkbox"
|
||||
? "px-3 py-2 h-fit"
|
||||
|
|
|
@ -1,53 +1,69 @@
|
|||
import { isSupportedCountry } from "libphonenumber-js";
|
||||
import { useState } from "react";
|
||||
import BasePhoneInput from "react-phone-number-input";
|
||||
import type { Props, Country } from "react-phone-number-input";
|
||||
import "react-phone-number-input/style.css";
|
||||
import PhoneInput from "react-phone-input-2";
|
||||
import "react-phone-input-2/lib/style.css";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
export type PhoneInputProps = Props<{
|
||||
value: string;
|
||||
export type PhoneInputProps = {
|
||||
value?: string;
|
||||
id?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
className?: string;
|
||||
name?: string;
|
||||
}>;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
function PhoneInput({ name, className = "", onChange, ...rest }: PhoneInputProps) {
|
||||
function BasePhoneInput({ name, className = "", onChange, ...rest }: PhoneInputProps) {
|
||||
const defaultCountry = useDefaultCountry();
|
||||
|
||||
return (
|
||||
<BasePhoneInput
|
||||
<PhoneInput
|
||||
{...rest}
|
||||
flagUrl="/country-flag-icons/3x2/{XX}.svg"
|
||||
international
|
||||
defaultCountry={defaultCountry}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
countrySelectProps={{ className: "text-emphasis" }}
|
||||
numberInputProps={{
|
||||
className: "border-0 text-sm focus:ring-0 bg-default text-default",
|
||||
country={defaultCountry}
|
||||
enableSearch
|
||||
disableSearchIcon
|
||||
inputProps={{
|
||||
name: name,
|
||||
required: rest.required,
|
||||
placeholder: rest.placeholder,
|
||||
}}
|
||||
className={classNames(
|
||||
"hover:border-emphasis border-default bg-default rounded-md border py-px pl-3 focus-within:border-neutral-300 focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1 disabled:cursor-not-allowed",
|
||||
onChange={(value) => onChange(value)}
|
||||
containerClass={classNames(
|
||||
"hover:border-emphasis dark:focus:border-emphasis border-default !bg-default rounded-md border focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-default disabled:cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
inputClass="text-sm focus:ring-0 !bg-default text-default"
|
||||
buttonClass="text-emphasis !bg-default hover:!bg-emphasis"
|
||||
searchClass="!text-default !bg-default hover:!bg-emphasis"
|
||||
dropdownClass="!text-default !bg-default"
|
||||
inputStyle={{ width: "inherit", border: 0 }}
|
||||
searchStyle={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
padding: "6px 12px",
|
||||
gap: "8px",
|
||||
width: "296px",
|
||||
height: "28px",
|
||||
marginLeft: "-4px",
|
||||
}}
|
||||
dropdownStyle={{ width: "max-content" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const useDefaultCountry = () => {
|
||||
const [defaultCountry, setDefaultCountry] = useState<Country>("US");
|
||||
const [defaultCountry, setDefaultCountry] = useState("us");
|
||||
trpc.viewer.public.countryCode.useQuery(undefined, {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: false,
|
||||
onSuccess: (data) => {
|
||||
if (isSupportedCountry(data?.countryCode)) {
|
||||
setDefaultCountry(data.countryCode as Country);
|
||||
setDefaultCountry(data.countryCode);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -55,4 +71,4 @@ const useDefaultCountry = () => {
|
|||
return defaultCountry;
|
||||
};
|
||||
|
||||
export default PhoneInput;
|
||||
export default BasePhoneInput;
|
||||
|
|
|
@ -11,7 +11,7 @@ export const Radio = (props: RadioGroupPrimitive.RadioGroupItemProps & { childre
|
|||
<RadioGroupPrimitive.Item
|
||||
{...props}
|
||||
className={classNames(
|
||||
"hover:bg-subtle border-default focus:ring-emphasis mt-0.5 h-4 w-4 flex-shrink-0 rounded-full border focus:ring-2",
|
||||
"hover:bg-subtle border-default dark:checked:bg-brand-default dark:bg-darkgray-100 dark:hover:bg-subtle dark:checked:hover:bg-brand-default focus:ring-brand-default me-1.5 mt-0.5 h-4 w-4 flex-shrink-0 rounded-full border text-[--cal-brand] focus:ring-2 focus:ring-offset-1",
|
||||
props.disabled && "opacity-60"
|
||||
)}>
|
||||
{props.children}
|
||||
|
@ -21,7 +21,7 @@ export const Indicator = ({ disabled }: { disabled?: boolean }) => (
|
|||
<RadioGroupPrimitive.Indicator
|
||||
className={classNames(
|
||||
"after:bg-default dark:after:bg-inverted relative flex h-full w-full items-center justify-center rounded-full bg-black after:h-[6px] after:w-[6px] after:rounded-full after:content-['']",
|
||||
disabled ? "after:bg-muted" : "bg-black"
|
||||
disabled ? "after:bg-muted" : "bg-brand-default"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
65
yarn.lock
65
yarn.lock
|
@ -5212,6 +5212,7 @@ __metadata:
|
|||
react-intl: ^5.25.1
|
||||
react-live-chat-loader: ^2.7.3
|
||||
react-multi-email: ^0.5.3
|
||||
react-phone-input-2: ^2.15.1
|
||||
react-phone-number-input: ^3.2.7
|
||||
react-schemaorg: ^2.0.0
|
||||
react-select: ^5.7.0
|
||||
|
@ -17969,7 +17970,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classnames@npm:^2.2.5":
|
||||
"classnames@npm:^2.2.5, classnames@npm:^2.2.6":
|
||||
version: 2.3.2
|
||||
resolution: "classnames@npm:2.3.2"
|
||||
checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e
|
||||
|
@ -28194,6 +28195,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.memoize@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "lodash.memoize@npm:4.1.2"
|
||||
checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.merge@npm:^4.6.2":
|
||||
version: 4.6.2
|
||||
resolution: "lodash.merge@npm:4.6.2"
|
||||
|
@ -28215,6 +28223,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.reduce@npm:^4.6.0":
|
||||
version: 4.6.0
|
||||
resolution: "lodash.reduce@npm:4.6.0"
|
||||
checksum: 81f2a1045440554f8427f895ef479f1de5c141edd7852dde85a894879312801efae0295116e5cf830c531c1a51cdab8f3628c3ad39fa21a9874bb9158d9ea075
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.startcase@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "lodash.startcase@npm:4.4.0"
|
||||
|
@ -28222,6 +28237,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.startswith@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "lodash.startswith@npm:4.2.1"
|
||||
checksum: 1847371cbf6f32c4125a555847aff8a1ff1a977f40882b0ad4e2656a32e364793cfa7602915c9165cc0bba61fbd802e561888126d242faadcf3fc77215bab19b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.uniq@npm:4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "lodash.uniq@npm:4.5.0"
|
||||
|
@ -32045,21 +32067,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.34.0":
|
||||
version: 1.34.0
|
||||
resolution: "playwright-core@npm:1.34.0"
|
||||
checksum: 1821ab254b3c756e52c144c26cae7250dd5a1c779277cdd583bd289501d45cceaea40a8185ebf7eaa56b8291e4a042e3eef65f511da2306349c275794c9a582d
|
||||
"playwright-core@npm:1.33.0":
|
||||
version: 1.33.0
|
||||
resolution: "playwright-core@npm:1.33.0"
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 5fb7bda06a8b73b56b85b5a0b8f711211dde57a375d9379289e22239b2de879c6d93c8fdc9ba44b932bf100914ab1ca1a55697ad88440fdd0a39101fc020b77f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.31.2":
|
||||
version: 1.34.0
|
||||
resolution: "playwright@npm:1.34.0"
|
||||
version: 1.33.0
|
||||
resolution: "playwright@npm:1.33.0"
|
||||
dependencies:
|
||||
playwright-core: 1.34.0
|
||||
playwright-core: 1.33.0
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 5e5586e7c4e42f206526fe4306e35c68b7af353d6e3701c9e1045940aa3a916ac50b0b7c27a8eaeb3831ddc4f1146a367f98a5b6b841f977e9770b925e81877f
|
||||
checksum: fb3934c56ed749cf412ea35b82052f013872d7f7b7747ab7042580af473dfc1b038956a31c132ee8c0098f74150c75e2073e9c737fd4c1ed54700a23fffc079f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -33521,12 +33545,12 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"react-fast-marquee@npm:^1.3.5":
|
||||
version: 1.6.0
|
||||
resolution: "react-fast-marquee@npm:1.6.0"
|
||||
version: 1.5.2
|
||||
resolution: "react-fast-marquee@npm:1.5.2"
|
||||
peerDependencies:
|
||||
react: ">= 16.8.0 || 18.0.0"
|
||||
react-dom: ">= 16.8.0 || 18.0.0"
|
||||
checksum: f448341d30ef417292d8f7618d4931190a6d4aa353fbd276da4dcecc49bb6a7a32962f9cc74fe0af2ea9617d7b453ed3efccf7a0e8709a8b66e04ec04f4ae18c
|
||||
checksum: 2db7a61470a9e02707aa5a80792ec009a8694ef7283b01e30ec516893297794d7f466ae3230c59773bab357684803767c1ffab875175471c41b28a5055345130
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -33756,6 +33780,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-phone-input-2@npm:^2.15.1":
|
||||
version: 2.15.1
|
||||
resolution: "react-phone-input-2@npm:2.15.1"
|
||||
dependencies:
|
||||
classnames: ^2.2.6
|
||||
lodash.debounce: ^4.0.8
|
||||
lodash.memoize: ^4.1.2
|
||||
lodash.reduce: ^4.6.0
|
||||
lodash.startswith: ^4.2.1
|
||||
prop-types: ^15.7.2
|
||||
peerDependencies:
|
||||
react: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0
|
||||
react-dom: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0
|
||||
checksum: 60cee455c7044da648a23312cae0077b4108dd24e26173a954904123decefae9875a1f8225a722a6c4d74c37b18ad3038ec6c302b64857f75089e544e8d48847
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-phone-number-input@npm:^3.2.7":
|
||||
version: 3.2.7
|
||||
resolution: "react-phone-number-input@npm:3.2.7"
|
||||
|
|
Loading…
Reference in New Issue
Block a user