Merge branch 'main' into cal/automaticOTP

pull/12128/head
Siddharth Movaliya 2023-10-31 22:23:41 +05:30 committed by GitHub
commit 9b1becc7a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 136 additions and 45 deletions

View File

@ -141,17 +141,6 @@ function BookingListItem(booking: BookingItemProps) {
: []),
];
const showRecordingActions: ActionType[] = [
{
id: "view_recordings",
label: t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
disabled: mutation.isLoading,
},
];
let bookedActions: ActionType[] = [
{
id: "cancel",
@ -270,11 +259,21 @@ function BookingListItem(booking: BookingItemProps) {
const bookingLink = buildBookingLink();
const title = booking.title;
// To be used after we run query on legacy bookings
// const showRecordingsButtons = booking.isRecorded && isPast && isConfirmed;
const showRecordingsButtons =
(booking.location === "integrations:daily" || booking?.location?.trim() === "") && isPast && isConfirmed;
const showRecordingsButtons = !!(booking.isRecorded && isPast && isConfirmed);
const checkForRecordingsButton =
!showRecordingsButtons && (booking.location === "integrations:daily" || booking?.location?.trim() === "");
const showRecordingActions: ActionType[] = [
{
id: checkForRecordingsButton ? "check_for_recordings" : "view_recordings",
label: checkForRecordingsButton ? t("check_for_recordings") : t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
disabled: mutation.isLoading,
},
];
return (
<>
@ -299,7 +298,7 @@ function BookingListItem(booking: BookingItemProps) {
paymentCurrency={booking.payment[0].currency}
/>
)}
{showRecordingsButtons && (
{(showRecordingsButtons || checkForRecordingsButton) && (
<ViewRecordingsDialog
booking={booking}
isOpenDialog={viewRecordingsDialogIsOpen}
@ -469,7 +468,9 @@ function BookingListItem(booking: BookingItemProps) {
</>
) : null}
{isPast && isPending && !isConfirmed ? <TableActions actions={bookedActions} /> : null}
{showRecordingsButtons && <TableActions actions={showRecordingActions} />}
{(showRecordingsButtons || checkForRecordingsButton) && (
<TableActions actions={showRecordingActions} />
)}
{isCancelled && booking.rescheduled && (
<div className="hidden h-full items-center md:flex">
<RequestSentMessage />

View File

@ -62,6 +62,46 @@ const triggerWebhook = async ({
await Promise.all(promises);
};
const checkIfUserIsPartOfTheSameTeam = async (
teamId: number | undefined | null,
userId: number,
userEmail: string | undefined | null
) => {
if (!teamId) return false;
const getUserQuery = () => {
if (!!userEmail) {
return {
OR: [
{
id: userId,
},
{
email: userEmail,
},
],
};
} else {
return {
id: userId,
};
}
};
const team = await prisma.team.findFirst({
where: {
id: teamId,
members: {
some: {
user: getUserQuery(),
},
},
},
});
return !!team;
};
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
return res.status(405).json({ message: "No SendGrid API key or email" });
@ -137,12 +177,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const isUserAttendeeOrOrganiser =
booking?.user?.id === session.user.id ||
attendeesList.find((attendee) => attendee.id === session.user.id);
attendeesList.find(
(attendee) => attendee.id === session.user.id || attendee.email === session.user.email
);
if (!isUserAttendeeOrOrganiser) {
return res.status(403).send({
message: "Unauthorised",
});
const isUserMemberOfTheTeam = checkIfUserIsPartOfTheSameTeam(
booking?.eventType?.teamId,
session.user.id,
session.user.email
);
if (!isUserMemberOfTheTeam) {
return res.status(403).send({
message: "Unauthorised",
});
}
}
await prisma.booking.update({
@ -202,7 +252,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return res.status(403).json({ message: "User does not have team plan to send out emails" });
} catch (err) {
console.warn("something_went_wrong", err);
console.warn("Error in /recorded-daily-video", err);
return res.status(500).json({ message: "something went wrong" });
}
}

View File

@ -164,14 +164,13 @@ const PasswordView = ({ user }: PasswordViewProps) => {
<>
<Meta title={t("password")} description={t("password_description")} borderInShellHeader={true} />
{user && user.identityProvider !== IdentityProvider.CAL ? (
<div>
<div className="mt-6">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
{t("account_managed_by_identity_provider", {
provider: identityProviderNameMap[user.identityProvider],
})}
</h2>
</div>
<div className="border-subtle rounded-b-xl border border-t-0 px-4 py-6 sm:px-6">
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
{t("account_managed_by_identity_provider", {
provider: identityProviderNameMap[user.identityProvider],
})}
</h2>
<p className="text-subtle mt-1 text-sm">
{t("account_managed_by_identity_provider_description", {
provider: identityProviderNameMap[user.identityProvider],
@ -180,7 +179,7 @@ const PasswordView = ({ user }: PasswordViewProps) => {
</div>
) : (
<Form form={formMethods} handleSubmit={handleSubmit}>
<div className="border-x px-4 py-6 sm:px-6">
<div className="border-subtle border-x px-4 py-6 sm:px-6">
{formMethods.formState.errors.apiError && (
<div className="pb-6">
<Alert severity="error" message={formMethods.formState.errors.apiError?.message} />

View File

@ -1,5 +1,7 @@
import { expect, type Page } from "@playwright/test";
import dayjs from "@calcom/dayjs";
import type { createUsersFixture } from "./users";
const reschedulePlaceholderText = "Let others know why you need to reschedule";
@ -38,6 +40,12 @@ type fillAndConfirmBookingParams = {
type UserFixture = ReturnType<typeof createUsersFixture>;
function isLastDayOfMonth(): boolean {
const today = dayjs();
const endOfMonth = today.endOf("month");
return today.isSame(endOfMonth, "day");
}
const fillQuestion = async (eventTypePage: Page, questionType: string, customLocators: customLocators) => {
const questionActions: QuestionActions = {
phone: async () => {
@ -114,6 +122,17 @@ export async function loginUser(users: UserFixture) {
await pro.apiLogin();
}
const goToNextMonthIfNoAvailabilities = async (eventTypePage: Page) => {
try {
if (isLastDayOfMonth()) {
await eventTypePage.getByTestId("view_next_month").waitFor({ timeout: 6000 });
await eventTypePage.getByTestId("view_next_month").click();
}
} catch (err) {
console.info("No need to click on view next month button");
}
};
export function createBookingPageFixture(page: Page) {
return {
goToEventType: async (eventType: string) => {
@ -154,19 +173,13 @@ export function createBookingPageFixture(page: Page) {
return eventtypePromise;
},
selectTimeSlot: async (eventTypePage: Page) => {
while (await eventTypePage.getByRole("button", { name: "View next" }).isVisible()) {
await eventTypePage.getByRole("button", { name: "View next" }).click();
}
await goToNextMonthIfNoAvailabilities(eventTypePage);
await eventTypePage.getByTestId("time").first().click();
},
clickReschedule: async () => {
await page.getByText("Reschedule").click();
},
navigateToAvailableTimeSlot: async () => {
while (await page.getByRole("button", { name: "View next" }).isVisible()) {
await page.getByRole("button", { name: "View next" }).click();
}
},
selectFirstAvailableTime: async () => {
await page.getByTestId("time").first().click();
},
@ -186,6 +199,7 @@ export function createBookingPageFixture(page: Page) {
},
rescheduleBooking: async (eventTypePage: Page) => {
await goToNextMonthIfNoAvailabilities(eventTypePage);
await eventTypePage.getByText("Reschedule").click();
while (await eventTypePage.getByRole("button", { name: "View next" }).isVisible()) {
await eventTypePage.getByRole("button", { name: "View next" }).click();

View File

@ -1296,6 +1296,7 @@
"select_calendars": "Select which calendars you want to check for conflicts to prevent double bookings.",
"check_for_conflicts": "Check for conflicts",
"view_recordings": "View recordings",
"check_for_recordings":"Check for recordings",
"adding_events_to": "Adding events to",
"follow_system_preferences": "Follow system preferences",
"custom_brand_colors": "Custom brand colors",

View File

@ -50,8 +50,13 @@ export default class OrganizerDailyVideoDownloadRecordingEmail extends BaseEmail
return this.getFormattedRecipientTime({ time: this.calEvent.endTime, format });
}
protected getLocale(): string {
return this.calEvent.organizer.language.locale;
}
protected getFormattedDate() {
const organizerTimeFormat = this.calEvent.organizer.timeFormat || TimeFormat.TWELVE_HOUR;
return `${this.getOrganizerStart(organizerTimeFormat)} - ${this.getOrganizerEnd(
organizerTimeFormat
)}, ${this.t(this.getOrganizerStart("dddd").toLowerCase())}, ${this.t(

View File

@ -95,7 +95,7 @@ const NoAvailabilityOverlay = ({
return (
<div className="bg-muted border-subtle absolute left-1/2 top-40 -mt-10 w-max -translate-x-1/2 -translate-y-1/2 transform rounded-md border p-8 shadow-sm">
<h4 className="text-emphasis mb-4 font-medium">{t("no_availability_in_month", { month: month })}</h4>
<Button onClick={nextMonthButton} color="primary" EndIcon={ArrowRight}>
<Button onClick={nextMonthButton} color="primary" EndIcon={ArrowRight} data-testid="view_next_month">
{t("view_next_month")}
</Button>
</div>

View File

@ -1,5 +1,6 @@
import { describe, expect, test, vi } from "vitest";
import dayjs from "@calcom/dayjs";
import { getAvailableDatesInMonth } from "@calcom/features/calendars/lib/getAvailableDatesInMonth";
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
@ -63,5 +64,22 @@ describe("Test Suite: Date Picker", () => {
vi.setSystemTime(vi.getRealSystemTime());
vi.useRealTimers();
});
test("it returns the correct responses end of month", () => {
// test a date at one minute past midnight, end of month.
// we use dayjs() as the system timezone can still modify the Date.
vi.useFakeTimers().setSystemTime(dayjs().endOf("month").startOf("day").add(1, "second").toDate());
const currentDate = new Date();
const result = getAvailableDatesInMonth({
browsingDate: currentDate,
});
expect(result).toHaveLength(1);
// Undo the forced time we applied earlier, reset to system default.
vi.setSystemTime(vi.getRealSystemTime());
vi.useRealTimers();
});
});
});

View File

@ -1,3 +1,4 @@
import dayjs from "@calcom/dayjs";
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
// calculate the available dates in the month:
@ -21,7 +22,9 @@ export function getAvailableDatesInMonth({
);
for (
let date = browsingDate > minDate ? browsingDate : minDate;
date <= lastDateOfMonth;
// Check if date is before the last date of the month
// or is the same day, in the same month, in the same year.
date < lastDateOfMonth || dayjs(date).isSame(lastDateOfMonth, "day");
date = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)
) {
// intersect included dates

View File

@ -194,7 +194,7 @@ const OrgAppearanceView = ({
/>
</div>
) : (
<div className="border-subtle rounded-md border p-5">
<div className="py-5">
<span className="text-default text-sm">{t("only_owner_change")}</span>
</div>
)}

View File

@ -113,7 +113,7 @@ const OrgProfileView = () => {
{isOrgAdminOrOwner ? (
<OrgProfileForm defaultValues={defaultValues} />
) : (
<div className="flex">
<div className="border-subtle flex rounded-b-md border border-t-0 px-4 py-8 sm:px-6">
<div className="flex-grow">
<div>
<Label className="text-emphasis">{t("org_name")}</Label>

View File

@ -147,7 +147,7 @@ const PaymentForm = (props: Props) => {
formatParams: { amount: { currency: props.payment.currency } },
})}
onChange={(e) => setHoldAcknowledged(e.target.checked)}
descriptionClassName="text-blue-900 font-semibold"
descriptionClassName="text-info font-semibold"
/>
</div>
)}

View File

@ -174,7 +174,7 @@ export const ViewRecordingsDialog = (props: IViewRecordingsDialog) => {
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
<DialogContent>
<DialogContent enableOverflow>
<DialogHeader title={t("recordings_title")} subtitle={subtitle} />
{roomName ? (
<LicenseRequired>