Skip to content

Commit

Permalink
Merge branch 'main' into feat/org-team-app-install-2
Browse files Browse the repository at this point in the history
  • Loading branch information
SomayChauhan committed Jul 9, 2024
2 parents 5268ee0 + 5f8d090 commit 1f5cee7
Show file tree
Hide file tree
Showing 94 changed files with 4,707 additions and 2,159 deletions.
69 changes: 68 additions & 1 deletion apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,69 @@ const GroupedAttendees = (groupedAttendeeProps: GroupedAttendeeProps) => {
</Dropdown>
);
};
const GroupedGuests = ({ guests }: { guests: AttendeeProps[] }) => {
const [openDropdown, setOpenDropdown] = useState(false);
const { t } = useLocale();
const { copyToClipboard, isCopied } = useCopy();
const [selectedEmail, setSelectedEmail] = useState("");

return (
<Dropdown
open={openDropdown}
onOpenChange={(value) => {
setOpenDropdown(value);
setSelectedEmail("");
}}>
<DropdownMenuTrigger asChild>
<button
onClick={(e) => e.stopPropagation()}
className="radix-state-open:text-blue-500 hover:text-blue-500 focus:outline-none">
{t("plus_more", { count: guests.length - 1 })}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel className="text-xs font-medium uppercase">{t("guests")}</DropdownMenuLabel>
{guests.slice(1).map((guest) => (
<DropdownMenuItem key={guest.id}>
<DropdownItem
className="pr-6 focus:outline-none"
StartIcon={selectedEmail === guest.email ? "circle-check" : undefined}
onClick={(e) => {
e.preventDefault();
setSelectedEmail(guest.email);
}}>
<span className={`${selectedEmail !== guest.email ? "pl-6" : ""}`}>{guest.email}</span>
</DropdownItem>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<div className=" flex justify-end space-x-2 p-2">
<Link href={`mailto:${selectedEmail}`}>
<Button
color="secondary"
disabled={selectedEmail.length === 0}
onClick={(e) => {
setOpenDropdown(false);
e.stopPropagation();
}}>
{t("email")}
</Button>
</Link>
<Button
color="secondary"
disabled={selectedEmail.length === 0}
onClick={(e) => {
e.preventDefault();
copyToClipboard(selectedEmail);
showToast(t("email_copied"), "success");
}}>
{!isCopied ? t("copy") : t("copied")}
</Button>
</div>
</DropdownMenuContent>
</Dropdown>
);
};

const DisplayAttendees = ({
attendees,
Expand Down Expand Up @@ -911,7 +974,11 @@ const DisplayAttendees = ({
<Attendee {...attendee} bookingUid={bookingUid} isBookingInPast={isBookingInPast} />
</p>
))}>
{isBookingInPast && <GroupedAttendees attendees={attendees} bookingUid={bookingUid} />}
{isBookingInPast ? (
<GroupedAttendees attendees={attendees} bookingUid={bookingUid} />
) : (
<GroupedGuests guests={attendees} />
)}
</Tooltip>
) : (
<Attendee {...attendees[1]} bookingUid={bookingUid} isBookingInPast={isBookingInPast} />
Expand Down
6 changes: 2 additions & 4 deletions apps/web/lib/booking.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
import { bookingResponsesDbSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import { workflowSelect } from "@calcom/features/ee/workflows/lib/getAllWorkflows";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
Expand Down Expand Up @@ -61,10 +62,7 @@ export const getEventTypesFromDB = async (id: number) => {
workflows: {
select: {
workflow: {
select: {
id: true,
steps: true,
},
select: workflowSelect,
},
},
},
Expand Down
48 changes: 33 additions & 15 deletions apps/web/modules/event-types/views/event-types-single-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { z } from "zod";
import checkForMultiplePaymentApps from "@calcom/app-store/_utils/payments/checkForMultiplePaymentApps";
import { getEventLocationType } from "@calcom/app-store/locations";
import { validateCustomEventName } from "@calcom/core/event";
import type { Workflow } from "@calcom/features/ee/workflows/lib/types";
import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { validateIntervalLimitOrder } from "@calcom/lib";
Expand Down Expand Up @@ -208,7 +209,7 @@ export const locationsResolver = (t: TFunction) => {
.optional();
};

const EventTypePage = (props: EventTypeSetupProps) => {
const EventTypePage = (props: EventTypeSetupProps & { allActiveWorkflows?: Workflow[] }) => {
const { t } = useLocale();
const utils = trpc.useUtils();
const telemetry = useTelemetry();
Expand Down Expand Up @@ -466,11 +467,10 @@ const EventTypePage = (props: EventTypeSetupProps) => {
instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />,
recurring: <EventRecurringTab eventType={eventType} />,
apps: <EventAppsTab eventType={{ ...eventType, URL: permalink }} />,
workflows: (
<EventWorkflowsTab
eventType={eventType}
workflows={eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow)}
/>
workflows: props.allActiveWorkflows ? (
<EventWorkflowsTab eventType={eventType} workflows={props.allActiveWorkflows} />
) : (
<></>
),
webhooks: <EventWebhooksTab eventType={eventType} />,
ai: <EventAITab eventType={eventType} isTeamEvent={!!team} />,
Expand Down Expand Up @@ -707,7 +707,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
<EventTypeSingleLayout
enabledAppsNumber={numberOfActiveApps}
installedAppsNumber={eventTypeApps?.items.length || 0}
enabledWorkflowsNumber={eventType.workflows.length}
enabledWorkflowsNumber={props.allActiveWorkflows ? props.allActiveWorkflows.length : 0}
eventType={eventType}
activeWebhooksNumber={eventType.webhooks.filter((webhook) => webhook.active).length}
team={team}
Expand Down Expand Up @@ -838,13 +838,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}}
/>
) : null}
<AssignmentWarningDialog
isOpenAssignmentWarnDialog={isOpenAssignmentWarnDialog}
setIsOpenAssignmentWarnDialog={setIsOpenAssignmentWarnDialog}
pendingRoute={pendingRoute}
leaveWithoutAssigningHosts={leaveWithoutAssigningHosts}
id={eventType.id}
/>
{/*<AssignmentWarningDialog*/}
{/* isOpenAssignmentWarnDialog={isOpenAssignmentWarnDialog}*/}
{/* setIsOpenAssignmentWarnDialog={setIsOpenAssignmentWarnDialog}*/}
{/* pendingRoute={pendingRoute}*/}
{/* leaveWithoutAssigningHosts={leaveWithoutAssigningHosts}*/}
{/* id={eventType.id}*/}
{/*/>*/}
</>
);
};
Expand All @@ -855,7 +855,25 @@ const EventTypePageWrapper: React.FC<PageProps> & {
const { data } = trpc.viewer.eventTypes.get.useQuery({ id: props.type });

if (!data) return null;
return <EventTypePage {...(data as EventTypeSetupProps)} />;

const eventType = data.eventType;

const { data: workflows } = trpc.viewer.workflows.getAllActiveWorkflows.useQuery({
eventType: {
workflows: eventType.workflows,
teamId: eventType.teamId,
userId: eventType.userId,
parent: eventType.parent,
metadata: eventType.metadata,
},
});

const propsData = {
...(data as EventTypeSetupProps),
allActiveWorkflows: workflows,
};

return <EventTypePage {...propsData} />;
};

export default EventTypePageWrapper;
9 changes: 8 additions & 1 deletion apps/web/pages/api/auth/verify-email.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";

import stripe from "@calcom/app-store/stripepayment/lib/server";
import dayjs from "@calcom/dayjs";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { IS_STRIPE_ENABLED } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
import { userMetadata } from "@calcom/prisma/zod-utils";

Expand Down Expand Up @@ -66,7 +68,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
id: true,
},
});

if (existingUser) {
return res.status(401).json({ message: USER_ALREADY_EXISTING_MESSAGE });
}
Expand Down Expand Up @@ -101,6 +102,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});

if (IS_STRIPE_ENABLED && userMetadataParsed.stripeCustomerId) {
await stripe.customers.update(userMetadataParsed.stripeCustomerId, {
email: updatedEmail,
});
}

// The user is trying to update the email to an already existing unverified secondary email of his
// so we swap the emails and its verified status
if (existingSecondaryUser?.userId === user.id) {
Expand Down
136 changes: 35 additions & 101 deletions apps/web/playwright/apps/analytics/analyticsApps.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,107 +5,41 @@ const ALL_APPS = ["fathom", "matomo", "plausible", "ga4", "gtm", "metapixel"];
test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());

test.describe("Check analytics Apps", () => {
test("Check analytics Apps by skipping the configure step", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
await page.goto("/apps/");
await appsPage.goToAppsCategory("analytics");
for (const app of ALL_APPS) {
await appsPage.installAnalyticsAppSkipConfigure(app);
await appsPage.goBackToAppsPage();
}
await page.goto("/event-types");
await appsPage.goToEventType("30 min");
await appsPage.goToAppsTab();
await appsPage.verifyAppsInfo(0);
for (const app of ALL_APPS) {
await appsPage.activeApp(app);
}
await appsPage.verifyAppsInfo(6);
test.describe("check analytics Apps", () => {
test.describe("check analytics apps by skipping the configure step", () => {
ALL_APPS.forEach((app) => {
test(`check analytics app: ${app} by skipping the configure step`, async ({
appsPage,
page,
users,
}) => {
const user = await users.create();
await user.apiLogin();
await page.goto("apps/categories/analytics");
await appsPage.installAnalyticsAppSkipConfigure(app);

await page.goto("/event-types");
await appsPage.goToEventType("30 min");
await appsPage.goToAppsTab();
await appsPage.verifyAppsInfo(0);
await appsPage.activeApp(app);
await appsPage.verifyAppsInfo(1);
});
});
});

test("Check fathom App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("fathom", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("fathom", id);
}
});

test("Check matomo App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("matomo", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("matomo", id);
}
});

test("Check plausible App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("plausible", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("plausible", id);
}
});

test("Check ga4 App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("ga4", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("ga4", id);
}
});

test("Check gtm App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("gtm", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("gtm", id);
}
});

test("Check metapixel App using the new flow", async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);

await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp("metapixel", eventTypesIds);

for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew("metapixel", id);
}
test.describe("check analytics apps using the new flow", () => {
ALL_APPS.forEach((app) => {
test(`check analytics app: ${app}`, async ({ appsPage, page, users }) => {
const user = await users.create();
await user.apiLogin();
const eventTypes = await user.getUserEventsAsOwner();
const eventTypesIds = eventTypes.map((item) => item.id);
await page.goto("/apps/categories/analytics");
await appsPage.installAnalyticsApp(app, eventTypesIds);
for (const id of eventTypesIds) {
await appsPage.verifyAppsInfoNew(app, id);
}
});
});
});
});
Loading

0 comments on commit 1f5cee7

Please sign in to comment.