feat: round robin: show avatar and name of person on success page (#14245)
* feat: round robin: show avatar and name of person on success page * fix: add background border * fix: e2e tests * fix: remove avatar on select, use proper border variables * fix: bookings-list e2e round robin title * chore: change name --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
co-authored by
Peer Richelsen
Udit Takkar
Udit Takkar
parent
0eae0bb8fe
commit
68568e5a25
@@ -72,6 +72,7 @@ export const getEventTypesFromDB = async (id: number) => {
|
||||
seatsPerTimeSlot: true,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
schedulingType: true,
|
||||
periodStartDate: true,
|
||||
periodEndDate: true,
|
||||
},
|
||||
|
||||
@@ -44,11 +44,12 @@ import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
Alert,
|
||||
Avatar,
|
||||
Badge,
|
||||
Button,
|
||||
EmailInput,
|
||||
@@ -225,6 +226,7 @@ export default function Success(props: PageProps) {
|
||||
|
||||
const giphyAppData = getEventTypeAppData(eventType, "giphy");
|
||||
const giphyImage = giphyAppData?.thankYouPage;
|
||||
const isRoundRobin = eventType.schedulingType === SchedulingType.ROUND_ROBIN;
|
||||
|
||||
const eventName = getEventName(eventNameObject, true);
|
||||
// Confirmation can be needed in two cases as of now
|
||||
@@ -294,6 +296,7 @@ export default function Success(props: PageProps) {
|
||||
|
||||
function getTitle(): string {
|
||||
const titleSuffix = props.recurringBookings ? "_recurring" : "";
|
||||
const titlePrefix = isRoundRobin ? "round_robin_" : "";
|
||||
if (isCancelled) {
|
||||
return "";
|
||||
}
|
||||
@@ -305,6 +308,11 @@ export default function Success(props: PageProps) {
|
||||
}
|
||||
return t(`needs_to_be_confirmed_or_rejected${titleSuffix}`);
|
||||
}
|
||||
if (bookingInfo.user) {
|
||||
return t(`${titlePrefix}emailed_you_and_attendees${titleSuffix}`, {
|
||||
user: bookingInfo.user.name || bookingInfo.user.email,
|
||||
});
|
||||
}
|
||||
return t(`emailed_you_and_attendees${titleSuffix}`);
|
||||
}
|
||||
|
||||
@@ -396,27 +404,36 @@ export default function Success(props: PageProps) {
|
||||
{!isFeedbackMode && (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
"mx-auto flex items-center justify-center",
|
||||
!giphyImage && !isCancelled && !needsConfirmation
|
||||
? "bg-success h-12 w-12 rounded-full"
|
||||
: "",
|
||||
!giphyImage && !isCancelled && needsConfirmation
|
||||
? "bg-subtle h-12 w-12 rounded-full"
|
||||
: "",
|
||||
isCancelled ? "bg-error h-12 w-12 rounded-full " : ""
|
||||
)}>
|
||||
{giphyImage && !needsConfirmation && !isCancelled && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={giphyImage} alt="Gif from Giphy" />
|
||||
className={classNames(isRoundRobin && "min-w-32 min-h-24 relative mx-auto h-24 w-32")}>
|
||||
{isRoundRobin && bookingInfo.user && (
|
||||
<Avatar
|
||||
className="mx-auto flex items-center justify-center"
|
||||
alt={bookingInfo.user.name || bookingInfo.user.email}
|
||||
size="xl"
|
||||
imageSrc={`${bookingInfo.user.avatarUrl}`}
|
||||
/>
|
||||
)}
|
||||
{!giphyImage && !needsConfirmation && !isCancelled && (
|
||||
<Icon name="check" className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
)}
|
||||
{needsConfirmation && !isCancelled && (
|
||||
<Icon name="calendar" className="text-emphasis h-5 w-5" />
|
||||
)}
|
||||
{isCancelled && <Icon name="x" className="h-5 w-5 text-red-600 dark:text-red-200" />}
|
||||
<div
|
||||
className={classNames(
|
||||
"mx-auto flex h-12 w-12 items-center justify-center rounded-full",
|
||||
isRoundRobin &&
|
||||
"border-cal-bg dark:border-cal-bg-muted absolute bottom-0 right-0 z-10 h-12 w-12 border-8",
|
||||
!giphyImage && !isCancelled && !needsConfirmation ? "bg-success" : "",
|
||||
!giphyImage && !isCancelled && needsConfirmation ? "bg-subtle" : "",
|
||||
isCancelled ? "bg-error" : ""
|
||||
)}>
|
||||
{giphyImage && !needsConfirmation && !isCancelled && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={giphyImage} alt="Gif from Giphy" />
|
||||
)}
|
||||
{!giphyImage && !needsConfirmation && !isCancelled && (
|
||||
<Icon name="check" className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
)}
|
||||
{needsConfirmation && !isCancelled && (
|
||||
<Icon name="calendar" className="text-emphasis h-5 w-5" />
|
||||
)}
|
||||
{isCancelled && <Icon name="x" className="h-5 w-5 text-red-600 dark:text-red-200" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8 mt-6 text-center last:mb-0">
|
||||
<h3
|
||||
@@ -463,7 +480,7 @@ export default function Success(props: PageProps) {
|
||||
)}
|
||||
<div className="font-medium">{t("what")}</div>
|
||||
<div className="col-span-2 mb-6 last:mb-0" data-testid="booking-title">
|
||||
{eventName}
|
||||
{isRoundRobin ? bookingInfo.title : eventName}
|
||||
</div>
|
||||
<div className="font-medium">{t("when")}</div>
|
||||
<div className="col-span-2 mb-6 last:mb-0">
|
||||
|
||||
@@ -103,7 +103,7 @@ test.describe("Bookings", () => {
|
||||
page,
|
||||
},
|
||||
async () => {
|
||||
await bookTeamEvent({ page, team, event: teamEvent });
|
||||
await bookTeamEvent({ page, team, event: teamEvent, teamMatesObj });
|
||||
|
||||
// Since all the users have the same leastRecentlyBooked value
|
||||
// Anyone of the teammates could be the Host of the booking.
|
||||
@@ -421,13 +421,15 @@ async function bookTeamEvent({
|
||||
page,
|
||||
team,
|
||||
event,
|
||||
teamMatesObj,
|
||||
}: {
|
||||
page: Page;
|
||||
team: {
|
||||
slug: string | null;
|
||||
name: string | null;
|
||||
};
|
||||
event: { slug: string; title: string };
|
||||
event: { slug: string; title: string; schedulingType: SchedulingType | null };
|
||||
teamMatesObj?: { name: string }[];
|
||||
}) {
|
||||
// Note that even though the default way to access a team booking in an organization is to not use /team in the URL, but it isn't testable with playwright as the rewrite is taken care of by Next.js config which can't handle on the fly org slug's handling
|
||||
// So, we are using /team in the URL to access the team booking
|
||||
@@ -440,8 +442,19 @@ async function bookTeamEvent({
|
||||
await expect(page.getByTestId("success-page")).toBeVisible();
|
||||
|
||||
// The title of the booking
|
||||
const BookingTitle = `${event.title} between ${team.name} and ${testName}`;
|
||||
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
|
||||
if (event.schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
const bookingTitle = await page.getByTestId("booking-title").textContent();
|
||||
|
||||
expect(
|
||||
teamMatesObj?.some((teamMate) => {
|
||||
const BookingTitle = `${event.title} between ${teamMate.name} and ${testName}`;
|
||||
return BookingTitle === bookingTitle;
|
||||
})
|
||||
).toBe(true);
|
||||
} else {
|
||||
const BookingTitle = `${event.title} between ${team.name} and ${testName}`;
|
||||
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
|
||||
}
|
||||
// The booker should be in the attendee list
|
||||
await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
|
||||
}
|
||||
|
||||
@@ -147,8 +147,13 @@ testBothFutureAndLegacyRoutes.describe("Teams - NonOrg", (routeVariant) => {
|
||||
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName);
|
||||
|
||||
// The title of the booking
|
||||
const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`;
|
||||
await expect(page.locator("[data-testid=booking-title]")).toHaveText(BookingTitle);
|
||||
const bookingTitle = await page.getByTestId("booking-title").textContent();
|
||||
expect(
|
||||
teamMatesObj?.some((teamMate) => {
|
||||
const BookingTitle = `${teamEventTitle} between ${teamMate.name} and ${testName}`;
|
||||
return BookingTitle === bookingTitle;
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
// Since all the users have the same leastRecentlyBooked value
|
||||
// Anyone of the teammates could be the Host of the booking.
|
||||
|
||||
@@ -326,8 +326,10 @@
|
||||
"add_another_calendar": "Add another calendar",
|
||||
"other": "Other",
|
||||
"email_sign_in_subject": "Your sign-in link for {{appName}}",
|
||||
"round_robin_emailed_you_and_attendees": "You are meeting with {{user}}. We sent an email with a calendar invitation with the details to everyone.",
|
||||
"emailed_you_and_attendees": "We sent an email with a calendar invitation with the details to everyone.",
|
||||
"emailed_you_and_attendees_recurring": "We sent an email with a calendar invitation with the details to everyone for the first of these recurring events.",
|
||||
"round_robin_emailed_you_and_attendees_recurring": "You are meeting with {{user}}.We sent an email with a calendar invitation with the details to everyone for the first of these recurring events.",
|
||||
"emailed_you_and_any_other_attendees": "We sent an email to everyone with this information.",
|
||||
"needs_to_be_confirmed_or_rejected": "Your booking still needs to be confirmed or rejected.",
|
||||
"needs_to_be_confirmed_or_rejected_recurring": "Your recurring meeting still needs to be confirmed or rejected.",
|
||||
|
||||
@@ -67,6 +67,8 @@ module.exports = {
|
||||
booker: `var(--cal-border-booker, ${subtleColor})`,
|
||||
error: "var(--cal-border-error, #AA2E26)",
|
||||
focus: "var(--cal-border-focus, #1A1A1A)",
|
||||
"cal-bg": "var(--cal-bg, white)",
|
||||
"cal-bg-muted": "var(--cal-bg-muted)",
|
||||
},
|
||||
textColor: {
|
||||
emphasis: "var(--cal-text-emphasis, #111827)",
|
||||
|
||||
@@ -6,7 +6,7 @@ import z from "zod";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, Icon } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
interface Props {
|
||||
samlTenantID: string;
|
||||
|
||||
-1
@@ -9,7 +9,6 @@ import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
EmptyScreen,
|
||||
Icon,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemTitle,
|
||||
|
||||
@@ -29,6 +29,7 @@ const getUserBooking = async (uid: string) => {
|
||||
email: true,
|
||||
username: true,
|
||||
timeZone: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
attendees: {
|
||||
@@ -44,6 +45,7 @@ const getUserBooking = async (uid: string) => {
|
||||
eventName: true,
|
||||
slug: true,
|
||||
timeZone: true,
|
||||
schedulingType: true,
|
||||
},
|
||||
},
|
||||
seatsReferences: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { Button, ButtonGroup, Icon } from "@calcom/ui";
|
||||
import { Button, ButtonGroup } from "@calcom/ui";
|
||||
|
||||
import { useCalendarStore } from "../../state/store";
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Directory } from "@boxyhq/saml-jackson";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, showToast, Label, Tooltip } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
const DirectoryInfo = ({ directory }: { directory: Directory }) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
Tooltip,
|
||||
UserAvatar,
|
||||
} from "@calcom/ui";
|
||||
@@ -82,12 +81,7 @@ export default function MemberListItem(props: Props) {
|
||||
<div className="flex md:hidden">
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="icon"
|
||||
color="minimal"
|
||||
StartIcon="ellipsis"
|
||||
/>
|
||||
<Button type="button" variant="icon" color="minimal" StartIcon="ellipsis" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { classNames } from "@calcom/lib";
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { Avatar, Badge, Button, ButtonGroup, Icon, Select, Switch, Tooltip } from "@calcom/ui";
|
||||
import { Avatar, Badge, Button, ButtonGroup, Select, Switch, Tooltip } from "@calcom/ui";
|
||||
|
||||
export type ChildrenEventType = {
|
||||
value: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useFilterContext } from "@calcom/features/insights/context/provider";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Button, Dropdown, DropdownItem, DropdownMenuContent, DropdownMenuTrigger, Icon } from "@calcom/ui";
|
||||
import { Button, Dropdown, DropdownItem, DropdownMenuContent, DropdownMenuTrigger } from "@calcom/ui";
|
||||
|
||||
const Download = () => {
|
||||
const { filter } = useFilterContext();
|
||||
|
||||
@@ -2,7 +2,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import type { TimeRange, WorkingHours } from "@calcom/types/schedule";
|
||||
import { Button, DialogTrigger, Icon, Tooltip } from "@calcom/ui";
|
||||
import { Button, DialogTrigger, Tooltip } from "@calcom/ui";
|
||||
|
||||
import DateOverrideInputDialog from "./DateOverrideInputDialog";
|
||||
|
||||
|
||||
@@ -71,11 +71,7 @@ const DateOverride = ({ workingHours, disabled }: { workingHours: WorkingHours[]
|
||||
excludedDates={excludedDates}
|
||||
onChange={(ranges) => ranges.forEach((range) => append({ ranges: [range] }))}
|
||||
Trigger={
|
||||
<Button
|
||||
color="secondary"
|
||||
StartIcon="plus"
|
||||
data-testid="add-override"
|
||||
disabled={disabled}>
|
||||
<Button color="secondary" StartIcon="plus" data-testid="add-override" disabled={disabled}>
|
||||
{t("add_an_override")}
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||
import { Button, ButtonGroup, DataTable, Icon, UserAvatar } from "@calcom/ui";
|
||||
import { Button, ButtonGroup, DataTable, UserAvatar } from "@calcom/ui";
|
||||
|
||||
import { UpgradeTip } from "../../tips/UpgradeTip";
|
||||
import { createTimezoneBuddyStore, TBContext } from "../store";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, ButtonGroup, Icon } from "@calcom/ui";
|
||||
import { Button, ButtonGroup } from "@calcom/ui";
|
||||
|
||||
import { useTroubleshooterStore } from "../store";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ZodError } from "zod";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { ZTestTriggerInputSchema } from "@calcom/trpc/server/routers/viewer/webhook/testTrigger.schema";
|
||||
import { Badge, Button, Icon, showToast } from "@calcom/ui";
|
||||
import { Badge, Button, showToast } from "@calcom/ui";
|
||||
|
||||
export default function WebhookTestDisclosure() {
|
||||
const [subscriberUrl, webhookSecret]: [string, string] = useWatch({ name: ["subscriberUrl", "secret"] });
|
||||
|
||||
@@ -183,10 +183,7 @@ const DateOverride = ({
|
||||
onChange={(ranges) => ranges.forEach((range) => append({ ranges: [range] }))}
|
||||
userTimeFormat={userTimeFormat}
|
||||
Trigger={
|
||||
<Button
|
||||
color="secondary"
|
||||
StartIcon="plus"
|
||||
data-testid="add-override">
|
||||
<Button color="secondary" StartIcon="plus" data-testid="add-override">
|
||||
{t("add_an_override")}
|
||||
</Button>
|
||||
}
|
||||
@@ -330,11 +327,7 @@ export function AvailabilitySettings({
|
||||
openSidebar ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
|
||||
)}>
|
||||
<div className="flex flex-row items-center pt-5">
|
||||
<Button
|
||||
StartIcon="arrow-left"
|
||||
color="minimal"
|
||||
onClick={() => setOpenSidebar(false)}
|
||||
/>
|
||||
<Button StartIcon="arrow-left" color="minimal" onClick={() => setOpenSidebar(false)} />
|
||||
<p className="-ml-2">{t("availability_settings")}</p>
|
||||
<DeleteDialogButton
|
||||
buttonClassName="ml-16 inline"
|
||||
|
||||
Reference in New Issue
Block a user