mirror of
https://github.com/cosscom/coss.git
synced 2026-09-14 20:06:50 +08:00
e8ec1536b2
* add more list items * several improvements * mc * add today / next * update style * mc * remove unneeded badge * mc * format booking date * add timezone popover * tz popover * inline badges * add missing badge * mc * mc * fix inconsistencies * replace thumb icon * replace data * Update apps/examples/calcom/app/(dashboard)/booking/booking-actions.tsx Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1968 lines
50 KiB
TypeScript
1968 lines
50 KiB
TypeScript
/**
|
|
* Mock data for bookings that matches Cal.com's data structure.
|
|
* This enables easier integration with the actual Cal.com API later.
|
|
*
|
|
* Based on Cal.com's Booking model from packages/trpc/server/routers/viewer/bookings/get.handler.ts
|
|
*/
|
|
|
|
// =============================================================================
|
|
// TYPE DEFINITIONS
|
|
// =============================================================================
|
|
|
|
export type BookingStatus =
|
|
| "ACCEPTED"
|
|
| "PENDING"
|
|
| "CANCELLED"
|
|
| "REJECTED"
|
|
| "AWAITING_HOST";
|
|
|
|
export type SchedulingType = "ROUND_ROBIN" | "COLLECTIVE" | "MANAGED" | null;
|
|
|
|
export interface BookingUser {
|
|
id: number;
|
|
name: string | null;
|
|
email: string;
|
|
avatarUrl: string | null;
|
|
username: string | null;
|
|
timeZone: string;
|
|
}
|
|
|
|
export interface BookingAttendee {
|
|
id: number;
|
|
email: string;
|
|
name: string;
|
|
timeZone: string;
|
|
locale: string | null;
|
|
bookingId: number;
|
|
noShow: boolean | null;
|
|
}
|
|
|
|
export type DisableScope = "HOST_AND_ATTENDEE" | "ATTENDEE_ONLY";
|
|
|
|
export interface BookingEventType {
|
|
id: number;
|
|
slug: string;
|
|
title: string;
|
|
eventName: string | null;
|
|
price: number;
|
|
recurringEvent: {
|
|
freq: number;
|
|
count: number;
|
|
interval: number;
|
|
} | null;
|
|
currency: string;
|
|
metadata: Record<string, unknown> | null;
|
|
disableGuests: boolean;
|
|
bookingFields: unknown[] | null;
|
|
seatsPerTimeSlot: number | null;
|
|
seatsShowAttendees: boolean;
|
|
seatsShowAvailabilityCount: boolean;
|
|
eventTypeColor: {
|
|
lightEventTypeColor: string;
|
|
darkEventTypeColor: string;
|
|
} | null;
|
|
customReplyToEmail: string | null;
|
|
allowReschedulingPastBookings: boolean;
|
|
hideOrganizerEmail: boolean;
|
|
disableCancelling: boolean;
|
|
disableCancellingScope: DisableScope;
|
|
disableRescheduling: boolean;
|
|
disableReschedulingScope: DisableScope;
|
|
minimumRescheduleNotice: number;
|
|
teamId: number | null;
|
|
parentId: number | null;
|
|
schedulingType: SchedulingType;
|
|
hosts: {
|
|
userId: number;
|
|
user: {
|
|
id: number;
|
|
email: string;
|
|
};
|
|
}[];
|
|
length: number;
|
|
team: {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
} | null;
|
|
hostGroups: {
|
|
id: number;
|
|
name: string;
|
|
}[];
|
|
}
|
|
|
|
export interface BookingReference {
|
|
id: number;
|
|
type: string;
|
|
uid: string;
|
|
meetingId: string | null;
|
|
meetingPassword: string | null;
|
|
meetingUrl: string | null;
|
|
bookingId: number;
|
|
externalCalendarId: string | null;
|
|
deleted: boolean | null;
|
|
credentialId: number | null;
|
|
}
|
|
|
|
export interface BookingPayment {
|
|
paymentOption: string | null;
|
|
amount: number;
|
|
currency: string;
|
|
success: boolean;
|
|
appId: string | null;
|
|
refunded: boolean;
|
|
}
|
|
|
|
export interface BookingSeat {
|
|
referenceUid: string;
|
|
attendee: {
|
|
email: string;
|
|
};
|
|
}
|
|
|
|
export interface AssignmentReason {
|
|
id: number;
|
|
reasonString: string;
|
|
bookingId: number;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export interface BookingReport {
|
|
id: number;
|
|
reportedById: number;
|
|
reason: string;
|
|
description: string | null;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export interface Booking {
|
|
id: number;
|
|
title: string;
|
|
userPrimaryEmail: string | null;
|
|
description: string | null;
|
|
customInputs: Record<string, unknown> | null;
|
|
startTime: Date;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
endTime: Date;
|
|
metadata: Record<string, unknown> | null;
|
|
uid: string;
|
|
responses: Record<string, unknown> | null;
|
|
recurringEventId: string | null;
|
|
location: string | null;
|
|
status: BookingStatus;
|
|
paid: boolean;
|
|
fromReschedule: string | null;
|
|
rescheduled: boolean;
|
|
rescheduledBy: string | null;
|
|
cancelledBy: string | null;
|
|
isRecorded: boolean;
|
|
cancellationReason: string | null;
|
|
rejectionReason: string | null;
|
|
routedFromRoutingFormReponse: { id: number } | null;
|
|
eventType: BookingEventType | null;
|
|
references: BookingReference[];
|
|
payment: BookingPayment[];
|
|
user: BookingUser | null;
|
|
attendees: BookingAttendee[];
|
|
seatsReferences: BookingSeat[];
|
|
assignmentReason: AssignmentReason[];
|
|
report: BookingReport | null;
|
|
}
|
|
|
|
// =============================================================================
|
|
// MOCK DATA
|
|
// =============================================================================
|
|
|
|
const userPasquale: BookingUser = {
|
|
avatarUrl:
|
|
"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80",
|
|
email: "pasquale@cal.com",
|
|
id: 1,
|
|
name: "Pasquale Vitiello",
|
|
timeZone: "Europe/Rome",
|
|
username: "pasquale",
|
|
};
|
|
|
|
const userKeith: BookingUser = {
|
|
avatarUrl: null,
|
|
email: "keith@cal.com",
|
|
id: 2,
|
|
name: "Keith Williams",
|
|
timeZone: "America/Los_Angeles",
|
|
username: "keith",
|
|
};
|
|
|
|
const userPeer: BookingUser = {
|
|
avatarUrl:
|
|
"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80",
|
|
email: "peer@cal.com",
|
|
id: 3,
|
|
name: "Peer Richelsen",
|
|
timeZone: "Europe/London",
|
|
username: "peer",
|
|
};
|
|
|
|
const _userCarina: BookingUser = {
|
|
avatarUrl: null,
|
|
email: "carina@cal.com",
|
|
id: 4,
|
|
name: "Carina Wollheim",
|
|
timeZone: "Europe/Berlin",
|
|
username: "carina",
|
|
};
|
|
|
|
const _userJonathan: BookingUser = {
|
|
avatarUrl: null,
|
|
email: "jonathan@cal.com",
|
|
id: 5,
|
|
name: "Jonathan Djalo",
|
|
timeZone: "Europe/London",
|
|
username: "jonathan",
|
|
};
|
|
|
|
const _userSusan: BookingUser = {
|
|
avatarUrl: null,
|
|
email: "susan@example.com",
|
|
id: 6,
|
|
name: "Susan Moeller",
|
|
timeZone: "America/New_York",
|
|
username: "susan",
|
|
};
|
|
|
|
const _userDavid: BookingUser = {
|
|
avatarUrl: null,
|
|
email: "david@example.com",
|
|
id: 7,
|
|
name: "David Borenius",
|
|
timeZone: "Europe/Stockholm",
|
|
username: "david",
|
|
};
|
|
|
|
const defaultEventType: BookingEventType = {
|
|
allowReschedulingPastBookings: false,
|
|
bookingFields: null,
|
|
currency: "usd",
|
|
customReplyToEmail: null,
|
|
disableCancelling: false,
|
|
disableCancellingScope: "HOST_AND_ATTENDEE",
|
|
disableGuests: false,
|
|
disableRescheduling: false,
|
|
disableReschedulingScope: "HOST_AND_ATTENDEE",
|
|
eventName: null,
|
|
eventTypeColor: null,
|
|
hideOrganizerEmail: false,
|
|
hostGroups: [],
|
|
hosts: [],
|
|
id: 1,
|
|
length: 30,
|
|
metadata: null,
|
|
minimumRescheduleNotice: 0,
|
|
parentId: null,
|
|
price: 0,
|
|
recurringEvent: null,
|
|
schedulingType: null,
|
|
seatsPerTimeSlot: null,
|
|
seatsShowAttendees: false,
|
|
seatsShowAvailabilityCount: false,
|
|
slug: "30min",
|
|
team: null,
|
|
teamId: null,
|
|
title: "30 Min Meeting",
|
|
};
|
|
|
|
const defaultBookingFields = {
|
|
assignmentReason: [],
|
|
cancellationReason: null,
|
|
cancelledBy: null,
|
|
customInputs: null,
|
|
fromReschedule: null,
|
|
isRecorded: false,
|
|
metadata: null,
|
|
paid: false,
|
|
payment: [],
|
|
recurringEventId: null,
|
|
references: [],
|
|
rejectionReason: null,
|
|
report: null,
|
|
rescheduled: false,
|
|
rescheduledBy: null,
|
|
responses: null,
|
|
routedFromRoutingFormReponse: null,
|
|
seatsReferences: [],
|
|
};
|
|
|
|
export const mockPastBookings: Booking[] = [
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 1,
|
|
email: "keith@cal.com",
|
|
id: 1,
|
|
locale: "en",
|
|
name: "Keith Williams",
|
|
noShow: false,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
{
|
|
bookingId: 1,
|
|
email: "pasquale@cal.com",
|
|
id: 2,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-11-20T10:00:00"),
|
|
description: "Ship-room for the calendar sync patch and open PR queue.",
|
|
endTime: new Date("2025-11-25T15:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 10,
|
|
length: 20,
|
|
slug: "engineering-chat",
|
|
title: "Engineering Chat",
|
|
},
|
|
id: 1,
|
|
location: "integrations:daily",
|
|
rescheduled: true,
|
|
startTime: new Date("2025-11-25T14:40:00"),
|
|
status: "ACCEPTED",
|
|
title: "Release checklist sync",
|
|
uid: "abc123-booking-1",
|
|
updatedAt: new Date("2025-11-24T09:30:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 2,
|
|
email: "carina@cal.com",
|
|
id: 3,
|
|
locale: "en",
|
|
name: "Carina Wollheim",
|
|
noShow: false,
|
|
timeZone: "Europe/Berlin",
|
|
},
|
|
{
|
|
bookingId: 2,
|
|
email: "jonathan@cal.com",
|
|
id: 4,
|
|
locale: "en",
|
|
name: "Jonathan Djalo",
|
|
noShow: false,
|
|
timeZone: "Europe/London",
|
|
},
|
|
{
|
|
bookingId: 2,
|
|
email: "pasquale@cal.com",
|
|
id: 5,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-11-01T14:00:00"),
|
|
description:
|
|
"Discussion about the platform onboarding flow and upcoming improvements.",
|
|
endTime: new Date("2025-11-07T12:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#f53468",
|
|
lightEventTypeColor: "#f53468",
|
|
},
|
|
id: 11,
|
|
slug: "platform-meeting",
|
|
title: "Platform Meeting",
|
|
},
|
|
id: 2,
|
|
location: "integrations:daily",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-11-07T11:30:00"),
|
|
status: "ACCEPTED",
|
|
title: "Platform onboarding roadmap",
|
|
uid: "abc123-booking-2",
|
|
updatedAt: new Date("2025-11-01T14:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 3,
|
|
email: "keith@cal.com",
|
|
id: 6,
|
|
locale: "en",
|
|
name: "Keith Williams",
|
|
noShow: false,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
{
|
|
bookingId: 3,
|
|
email: "pasquale@cal.com",
|
|
id: 7,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-11-01T09:00:00"),
|
|
description: null,
|
|
endTime: new Date("2025-11-06T15:20:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 10,
|
|
length: 20,
|
|
slug: "engineering-chat",
|
|
title: "Engineering Chat",
|
|
},
|
|
id: 3,
|
|
location: "integrations:daily",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-11-06T15:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Auth refactor pairing",
|
|
uid: "abc123-booking-3",
|
|
updatedAt: new Date("2025-11-01T09:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 4,
|
|
email: "susan@example.com",
|
|
id: 8,
|
|
locale: "en",
|
|
name: "Elena Muro",
|
|
noShow: false,
|
|
timeZone: "America/New_York",
|
|
},
|
|
{
|
|
bookingId: 4,
|
|
email: "pasquale@cal.com",
|
|
id: 9,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-10-28T11:00:00"),
|
|
description: "Billing v2 scope — proration rules and dunning.",
|
|
endTime: new Date("2025-11-03T15:30:00"),
|
|
eventType: defaultEventType,
|
|
id: 4,
|
|
location: "integrations:google_meet",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-11-03T15:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Billing v2 scope pass",
|
|
uid: "abc123-booking-4",
|
|
updatedAt: new Date("2025-10-28T11:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 5,
|
|
email: "pasquale@cal.com",
|
|
id: 10,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
{
|
|
bookingId: 5,
|
|
email: "otto@nordvik.io",
|
|
id: 11,
|
|
locale: "en",
|
|
name: "Otto Nordvik",
|
|
noShow: false,
|
|
timeZone: "Europe/Stockholm",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-10-01T08:00:00"),
|
|
description: "Channel terms and co-marketing for Nordvik.",
|
|
endTime: new Date("2025-10-13T16:00:00"),
|
|
eventType: defaultEventType,
|
|
id: 5,
|
|
location: "integrations:google_meet",
|
|
rescheduled: true,
|
|
startTime: new Date("2025-10-13T15:30:00"),
|
|
status: "ACCEPTED",
|
|
title: "Nordvik partnership check-in",
|
|
uid: "abc123-booking-5",
|
|
updatedAt: new Date("2025-10-12T14:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 6,
|
|
email: "peer@cal.com",
|
|
id: 12,
|
|
locale: "en",
|
|
name: "Peer Richelsen",
|
|
noShow: false,
|
|
timeZone: "Europe/London",
|
|
},
|
|
{
|
|
bookingId: 6,
|
|
email: "keith@cal.com",
|
|
id: 13,
|
|
locale: "en",
|
|
name: "Keith Williams",
|
|
noShow: false,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
{
|
|
bookingId: 6,
|
|
email: "pasquale@cal.com",
|
|
id: 14,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: false,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-10-05T10:00:00"),
|
|
description: "Component library cutover plan and breaking changes.",
|
|
endTime: new Date("2025-10-10T17:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 12,
|
|
schedulingType: "COLLECTIVE",
|
|
slug: "team-meeting",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Team Meeting",
|
|
},
|
|
id: 6,
|
|
location: "integrations:google_meet",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-10-10T17:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Design system migration",
|
|
uid: "abc123-booking-6",
|
|
updatedAt: new Date("2025-10-05T10:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 7,
|
|
email: "priya@helixstudio.co",
|
|
id: 15,
|
|
locale: "en",
|
|
name: "Priya Narayan",
|
|
noShow: false,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-10-01T09:00:00"),
|
|
description: "Paid office hours — pricing page teardown.",
|
|
endTime: new Date("2025-10-08T14:45:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#fd6d06",
|
|
lightEventTypeColor: "#fd6d06",
|
|
},
|
|
id: 5,
|
|
length: 45,
|
|
price: 9900,
|
|
slug: "paid-consultation",
|
|
title: "Paid Consultation",
|
|
},
|
|
id: 7,
|
|
location: "integrations:zoom",
|
|
paid: true,
|
|
payment: [
|
|
{
|
|
amount: 9900,
|
|
appId: "stripe",
|
|
currency: "usd",
|
|
paymentOption: "ON_BOOKING",
|
|
refunded: false,
|
|
success: true,
|
|
},
|
|
],
|
|
rescheduled: false,
|
|
startTime: new Date("2025-10-08T14:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Office hours w/ Priya Narayan",
|
|
uid: "abc123-booking-7",
|
|
updatedAt: new Date("2025-10-01T09:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 8,
|
|
email: "designer@example.com",
|
|
id: 16,
|
|
locale: "en",
|
|
name: "Livia Marsh",
|
|
noShow: null,
|
|
timeZone: "America/New_York",
|
|
},
|
|
],
|
|
cancellationReason: "Schedule conflict - will reschedule for next week.",
|
|
cancelledBy: "user",
|
|
createdAt: new Date("2025-09-28T15:00:00"),
|
|
description: "Mobile nav comps — round two feedback.",
|
|
endTime: new Date("2025-10-05T10:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 13,
|
|
slug: "design-review",
|
|
title: "Design Review",
|
|
},
|
|
id: 8,
|
|
location: "integrations:daily",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-10-05T10:00:00"),
|
|
status: "CANCELLED",
|
|
title: "Mobile nav critique",
|
|
uid: "abc123-booking-8",
|
|
updatedAt: new Date("2025-10-04T08:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 9,
|
|
email: "team@cal.com",
|
|
id: 17,
|
|
locale: "en",
|
|
name: "Marco Bellini",
|
|
noShow: false,
|
|
timeZone: "Europe/London",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-09-01T10:00:00"),
|
|
description: "Standing ops review — incidents and on-call handoff.",
|
|
endTime: new Date("2025-10-02T09:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 6,
|
|
recurringEvent: {
|
|
count: 12,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
slug: "weekly-sync",
|
|
title: "Weekly Sync",
|
|
},
|
|
id: 9,
|
|
location: "integrations:daily",
|
|
recurringEventId: "recurring-abc123",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-10-02T09:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Ops weekly (series)",
|
|
uid: "abc123-booking-9",
|
|
updatedAt: new Date("2025-09-01T10:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
assignmentReason: [
|
|
{
|
|
bookingId: 10,
|
|
createdAt: new Date("2025-09-20T11:00:00"),
|
|
id: 1,
|
|
reasonString:
|
|
"Round Robin: Assigned based on availability and equal distribution",
|
|
},
|
|
],
|
|
attendees: [
|
|
{
|
|
bookingId: 10,
|
|
email: "prospect@company.com",
|
|
id: 18,
|
|
locale: "en",
|
|
name: "Sam Whitfield",
|
|
noShow: false,
|
|
timeZone: "America/Chicago",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-09-20T11:00:00"),
|
|
description: "Inbound from trade-show badge scan.",
|
|
endTime: new Date("2025-09-28T16:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#0dbf82",
|
|
lightEventTypeColor: "#0dbf82",
|
|
},
|
|
hosts: [
|
|
{
|
|
user: { email: "pasquale@cal.com", id: 1 },
|
|
userId: 1,
|
|
},
|
|
{
|
|
user: { email: "keith@cal.com", id: 2 },
|
|
userId: 2,
|
|
},
|
|
],
|
|
id: 101,
|
|
schedulingType: "ROUND_ROBIN",
|
|
slug: "sales-call",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Sales Call",
|
|
},
|
|
id: 10,
|
|
location: "integrations:zoom",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-09-28T16:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Inbound — Whitfield & Co.",
|
|
uid: "abc123-booking-10",
|
|
updatedAt: new Date("2025-09-20T11:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 11,
|
|
email: "attendee1@example.com",
|
|
id: 19,
|
|
locale: "en",
|
|
name: "Claire Dubois",
|
|
noShow: false,
|
|
timeZone: "America/New_York",
|
|
},
|
|
{
|
|
bookingId: 11,
|
|
email: "attendee2@example.com",
|
|
id: 20,
|
|
locale: "fr",
|
|
name: "Henri Laurent",
|
|
noShow: false,
|
|
timeZone: "Europe/Paris",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-09-10T10:00:00"),
|
|
description: "Live walkthrough for EU prospects.",
|
|
endTime: new Date("2025-09-25T19:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: null,
|
|
id: 7,
|
|
length: 60,
|
|
seatsPerTimeSlot: 50,
|
|
seatsShowAttendees: true,
|
|
seatsShowAvailabilityCount: true,
|
|
slug: "webinar",
|
|
title: "Webinar",
|
|
},
|
|
id: 11,
|
|
isRecorded: true,
|
|
location: "integrations:zoom",
|
|
rescheduled: false,
|
|
seatsReferences: [
|
|
{ attendee: { email: "attendee1@example.com" }, referenceUid: "seat-1" },
|
|
{ attendee: { email: "attendee2@example.com" }, referenceUid: "seat-2" },
|
|
],
|
|
startTime: new Date("2025-09-25T18:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Webinar — scheduling at scale",
|
|
uid: "abc123-booking-11",
|
|
updatedAt: new Date("2025-09-10T10:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 12,
|
|
email: "noshow@example.com",
|
|
id: 21,
|
|
locale: "en",
|
|
name: "Tyler Brooks",
|
|
noShow: true,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-09-15T09:00:00"),
|
|
description: null,
|
|
endTime: new Date("2025-09-20T11:15:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 1,
|
|
length: 15,
|
|
slug: "15min",
|
|
title: "15 Min Meeting",
|
|
},
|
|
id: 12,
|
|
location: "integrations:daily",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-09-20T11:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Intro call — Tyler Brooks",
|
|
uid: "abc123-booking-12",
|
|
updatedAt: new Date("2025-09-20T11:30:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 13,
|
|
email: "enterprise@example.com",
|
|
id: 22,
|
|
locale: "en",
|
|
name: "Renata Vogel",
|
|
noShow: false,
|
|
timeZone: "America/New_York",
|
|
},
|
|
{
|
|
bookingId: 13,
|
|
email: "sales@cal.com",
|
|
id: 23,
|
|
locale: "en",
|
|
name: "Jonah Pike",
|
|
noShow: false,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-09-01T10:00:00"),
|
|
description: "Enterprise rollout — SSO, SCIM, and seat mapping.",
|
|
endTime: new Date("2025-09-10T16:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#0dbf82",
|
|
lightEventTypeColor: "#0dbf82",
|
|
},
|
|
id: 20,
|
|
length: 60,
|
|
price: 29900,
|
|
recurringEvent: {
|
|
count: 4,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
schedulingType: "ROUND_ROBIN",
|
|
slug: "enterprise-onboarding",
|
|
team: {
|
|
id: 2,
|
|
name: "Sales Team",
|
|
slug: "sales",
|
|
},
|
|
teamId: 2,
|
|
title: "Enterprise Onboarding",
|
|
},
|
|
id: 13,
|
|
location: "integrations:zoom",
|
|
paid: true,
|
|
payment: [
|
|
{
|
|
amount: 29900,
|
|
appId: "stripe",
|
|
currency: "usd",
|
|
paymentOption: "ON_BOOKING",
|
|
refunded: false,
|
|
success: true,
|
|
},
|
|
],
|
|
recurringEventId: "recurring-stress-test",
|
|
rescheduled: false,
|
|
startTime: new Date("2025-09-10T15:00:00"),
|
|
status: "PENDING",
|
|
title: "Acme rollout — onboarding block",
|
|
uid: "abc123-booking-13",
|
|
updatedAt: new Date("2025-09-05T14:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
];
|
|
|
|
export const mockUpcomingBookings: Booking[] = [
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 100,
|
|
email: "peer@cal.com",
|
|
id: 100,
|
|
locale: "en",
|
|
name: "Peer Richelsen",
|
|
noShow: null,
|
|
timeZone: "Europe/London",
|
|
},
|
|
{
|
|
bookingId: 100,
|
|
email: "keith@cal.com",
|
|
id: 101,
|
|
locale: "en",
|
|
name: "Keith Williams",
|
|
noShow: null,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
],
|
|
createdAt: new Date("2026-01-10T10:00:00"),
|
|
description: "Q1 bets, staffing, and milestone dates.",
|
|
endTime: new Date("2026-01-15T15:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 14,
|
|
length: 60,
|
|
schedulingType: "COLLECTIVE",
|
|
slug: "product-planning",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Product Planning",
|
|
},
|
|
id: 100,
|
|
location: "integrations:google_meet",
|
|
rescheduled: false,
|
|
startTime: new Date("2026-01-15T14:00:00"),
|
|
status: "ACCEPTED",
|
|
title: "Q1 roadmap working session",
|
|
uid: "upcoming-booking-1",
|
|
updatedAt: new Date("2026-01-10T10:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 101,
|
|
email: "candidate@example.com",
|
|
id: 102,
|
|
locale: "en",
|
|
name: "Rina Okonkwo",
|
|
noShow: null,
|
|
timeZone: "America/New_York",
|
|
},
|
|
],
|
|
createdAt: new Date("2026-01-12T09:00:00"),
|
|
description: "Systems design panel — take-home debrief pending.",
|
|
endTime: new Date("2026-01-20T11:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: null,
|
|
id: 102,
|
|
length: 60,
|
|
schedulingType: "COLLECTIVE",
|
|
slug: "technical-interview",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Technical Interview",
|
|
},
|
|
id: 101,
|
|
location: "integrations:zoom",
|
|
rescheduled: false,
|
|
startTime: new Date("2026-01-20T10:00:00"),
|
|
status: "PENDING",
|
|
title: "Pending — Rina Okonkwo (backend)",
|
|
uid: "upcoming-booking-2",
|
|
updatedAt: new Date("2026-01-12T09:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 102,
|
|
email: "pasquale@cal.com",
|
|
id: 103,
|
|
locale: "en",
|
|
name: "Pasquale Vitiello",
|
|
noShow: null,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
{
|
|
bookingId: 102,
|
|
email: "carina@cal.com",
|
|
id: 104,
|
|
locale: "en",
|
|
name: "Carina Wollheim",
|
|
noShow: null,
|
|
timeZone: "Europe/Berlin",
|
|
},
|
|
],
|
|
createdAt: new Date("2026-01-18T11:00:00"),
|
|
description: "Thursday release train and hotfix queue.",
|
|
endTime: new Date("2026-01-25T16:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 10,
|
|
length: 30,
|
|
slug: "engineering-chat",
|
|
title: "Engineering Chat",
|
|
},
|
|
id: 102,
|
|
location: "integrations:daily",
|
|
rescheduled: false,
|
|
startTime: new Date("2026-01-25T15:30:00"),
|
|
status: "ACCEPTED",
|
|
title: "Release train sync",
|
|
uid: "upcoming-booking-3",
|
|
updatedAt: new Date("2026-01-18T11:00:00"),
|
|
user: userKeith,
|
|
userPrimaryEmail: "keith@cal.com",
|
|
},
|
|
{
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 103,
|
|
email: "keith@cal.com",
|
|
id: 105,
|
|
locale: "en",
|
|
name: "Keith Williams",
|
|
noShow: null,
|
|
timeZone: "America/Los_Angeles",
|
|
},
|
|
{
|
|
bookingId: 103,
|
|
email: "jonathan@cal.com",
|
|
id: 106,
|
|
locale: "en",
|
|
name: "Jonathan Djalo",
|
|
noShow: null,
|
|
timeZone: "Europe/London",
|
|
},
|
|
],
|
|
createdAt: new Date("2026-01-20T09:00:00"),
|
|
description: "Settings IA — nav density and empty states.",
|
|
endTime: new Date("2026-01-28T12:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#3b82f6",
|
|
lightEventTypeColor: "#3b82f6",
|
|
},
|
|
id: 15,
|
|
length: 45,
|
|
slug: "design-review",
|
|
title: "Design Review",
|
|
},
|
|
id: 103,
|
|
location: "integrations:google_meet",
|
|
rescheduled: false,
|
|
startTime: new Date("2026-01-28T11:15:00"),
|
|
status: "ACCEPTED",
|
|
title: "Settings IA critique",
|
|
uid: "upcoming-booking-4",
|
|
updatedAt: new Date("2026-01-20T09:00:00"),
|
|
user: userPeer,
|
|
userPrimaryEmail: "peer@cal.com",
|
|
},
|
|
];
|
|
|
|
function getMockBooking(bookings: Booking[], index: number): Booking {
|
|
const booking = bookings[index];
|
|
|
|
if (!booking) {
|
|
throw new Error(`Missing mock booking at index ${index}`);
|
|
}
|
|
|
|
return booking;
|
|
}
|
|
|
|
function getMockEventType(booking: Booking): BookingEventType {
|
|
if (!booking.eventType) {
|
|
throw new Error(`Missing event type for mock booking ${booking.id}`);
|
|
}
|
|
|
|
return booking.eventType;
|
|
}
|
|
|
|
const baseUpcomingPlanning = getMockBooking(mockUpcomingBookings, 0);
|
|
const baseUpcomingPending = getMockBooking(mockUpcomingBookings, 1);
|
|
const baseUpcomingEngineering = getMockBooking(mockUpcomingBookings, 2);
|
|
const baseCancelledDesignReview = getMockBooking(mockPastBookings, 7);
|
|
const basePastRecurring = getMockBooking(mockPastBookings, 8);
|
|
const basePastStressTest = getMockBooking(mockPastBookings, 12);
|
|
|
|
const upcomingPaidConsultation: Booking = {
|
|
...baseUpcomingPlanning,
|
|
attendees: [
|
|
{
|
|
bookingId: 104,
|
|
email: "founder@example.com",
|
|
id: 107,
|
|
locale: "en",
|
|
name: "Nadia Elmasri",
|
|
noShow: null,
|
|
timeZone: "America/New_York",
|
|
},
|
|
],
|
|
description: "Strategy block — pricing experiments still open.",
|
|
endTime: new Date("2026-02-03T17:45:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#fd6d06",
|
|
lightEventTypeColor: "#fd6d06",
|
|
},
|
|
id: 16,
|
|
length: 45,
|
|
price: 14900,
|
|
slug: "paid-strategy",
|
|
title: "Paid Strategy Session",
|
|
},
|
|
id: 104,
|
|
location: "integrations:zoom",
|
|
payment: [],
|
|
startTime: new Date("2026-02-03T17:00:00"),
|
|
title: "Strategy block w/ Nadia Elmasri",
|
|
uid: "upcoming-booking-5",
|
|
};
|
|
|
|
function createTodayDate(hours: number, minutes: number): Date {
|
|
const date = new Date();
|
|
date.setHours(hours, minutes, 0, 0);
|
|
return date;
|
|
}
|
|
|
|
const upcomingTodayMeeting: Booking = {
|
|
...baseUpcomingPlanning,
|
|
attendees: [
|
|
{
|
|
bookingId: 106,
|
|
email: "carina@cal.com",
|
|
id: 108,
|
|
locale: "en",
|
|
name: "Carina Wollheim",
|
|
noShow: null,
|
|
timeZone: "Europe/Berlin",
|
|
},
|
|
],
|
|
createdAt: createTodayDate(9, 0),
|
|
description: "Morning standup — blockers only.",
|
|
endTime: createTodayDate(11, 0),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#0dbf82",
|
|
lightEventTypeColor: "#0dbf82",
|
|
},
|
|
id: 18,
|
|
length: 60,
|
|
slug: "team-sync",
|
|
title: "Team Sync",
|
|
},
|
|
id: 106,
|
|
location: "integrations:google_meet",
|
|
startTime: createTodayDate(10, 0),
|
|
title: "Product standup",
|
|
uid: "upcoming-booking-today",
|
|
updatedAt: createTodayDate(9, 0),
|
|
};
|
|
|
|
function createRelativeDate(minutesFromNow: number): Date {
|
|
return new Date(Date.now() + minutesFromNow * 60 * 1000);
|
|
}
|
|
|
|
const upcomingMinimumNoticeBooking: Booking = {
|
|
...baseUpcomingPlanning,
|
|
attendees: [
|
|
{
|
|
bookingId: 107,
|
|
email: "guest@example.com",
|
|
id: 109,
|
|
locale: "en",
|
|
name: "Tomás Ribeiro",
|
|
noShow: null,
|
|
timeZone: "America/New_York",
|
|
},
|
|
],
|
|
description: "Investor prep — deck v4 and dry run.",
|
|
endTime: createRelativeDate(75),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 23,
|
|
length: 30,
|
|
minimumRescheduleNotice: 120,
|
|
slug: "notice-demo",
|
|
title: "Investor prep call",
|
|
},
|
|
id: 107,
|
|
location: "integrations:zoom",
|
|
startTime: createRelativeDate(45),
|
|
title: "Investor prep w/ Tomás Ribeiro",
|
|
uid: "upcoming-booking-notice",
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
const upcomingAttendeeOnlyRestrictions: Booking = {
|
|
...baseUpcomingEngineering,
|
|
attendees: [
|
|
{
|
|
bookingId: 108,
|
|
email: "client@example.com",
|
|
id: 110,
|
|
locale: "en",
|
|
name: "Gemma Walsh",
|
|
noShow: null,
|
|
timeZone: "Europe/London",
|
|
},
|
|
],
|
|
description: "MSA redlines — attendee cannot cancel without host.",
|
|
endTime: new Date("2026-03-12T11:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
disableCancelling: true,
|
|
disableCancellingScope: "ATTENDEE_ONLY",
|
|
disableRescheduling: true,
|
|
disableReschedulingScope: "ATTENDEE_ONLY",
|
|
id: 24,
|
|
slug: "host-only-actions",
|
|
title: "Contract walkthrough",
|
|
},
|
|
id: 108,
|
|
location: "integrations:google_meet",
|
|
startTime: new Date("2026-03-12T11:00:00"),
|
|
title: "MSA walkthrough — Gemma Walsh",
|
|
uid: "upcoming-booking-scope",
|
|
updatedAt: new Date("2026-03-01T09:00:00"),
|
|
};
|
|
|
|
const upcomingRescheduledRecurring: Booking = {
|
|
...baseUpcomingEngineering,
|
|
description: "Moved by the booker — conflicts with travel.",
|
|
endTime: new Date("2026-02-06T10:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#8b5cf6",
|
|
lightEventTypeColor: "#8b5cf6",
|
|
},
|
|
id: 17,
|
|
length: 30,
|
|
recurringEvent: {
|
|
count: 8,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
slug: "customer-check-in",
|
|
title: "Customer Check-in",
|
|
},
|
|
fromReschedule: "previous-booking-uid",
|
|
id: 105,
|
|
metadata: {
|
|
recurringEventsRemaining: 6,
|
|
recurringPattern: "Every week for 8 occurrences",
|
|
},
|
|
recurringEventId: "recurring-upcoming-check-in",
|
|
rescheduled: true,
|
|
rescheduledBy: "Nadia Elmasri",
|
|
startTime: new Date("2026-02-06T10:00:00"),
|
|
title: "Customer check-in (series)",
|
|
uid: "upcoming-booking-6",
|
|
};
|
|
|
|
const unconfirmedTeamBooking: Booking = {
|
|
...baseUpcomingPending,
|
|
attendees: [
|
|
{
|
|
bookingId: 106,
|
|
email: "enterprise@example.com",
|
|
id: 108,
|
|
locale: "en",
|
|
name: "Helena Gruber",
|
|
noShow: null,
|
|
timeZone: "America/Chicago",
|
|
},
|
|
],
|
|
description: "Security questionnaire + sandbox access.",
|
|
endTime: new Date("2026-02-10T16:30:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#0dbf82",
|
|
lightEventTypeColor: "#0dbf82",
|
|
},
|
|
hosts: [
|
|
{
|
|
user: { email: "pasquale@cal.com", id: 1 },
|
|
userId: 1,
|
|
},
|
|
{
|
|
user: { email: "keith@cal.com", id: 2 },
|
|
userId: 2,
|
|
},
|
|
],
|
|
id: 18,
|
|
schedulingType: "ROUND_ROBIN",
|
|
slug: "enterprise-demo",
|
|
team: {
|
|
id: 2,
|
|
name: "Sales Team",
|
|
slug: "sales",
|
|
},
|
|
teamId: 2,
|
|
title: "Enterprise Demo",
|
|
},
|
|
id: 106,
|
|
location: "integrations:teams",
|
|
startTime: new Date("2026-02-10T16:00:00"),
|
|
title: "Enterprise demo — awaiting confirm",
|
|
uid: "unconfirmed-booking-2",
|
|
};
|
|
|
|
const unconfirmedTeamEventType = getMockEventType(unconfirmedTeamBooking);
|
|
|
|
const unconfirmedPaidRecurring: Booking = {
|
|
...unconfirmedTeamBooking,
|
|
description: "Four-part onboarding series; payment not captured yet.",
|
|
endTime: new Date("2026-02-12T15:00:00"),
|
|
eventType: {
|
|
...unconfirmedTeamEventType,
|
|
price: 29900,
|
|
recurringEvent: {
|
|
count: 4,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
slug: "paid-onboarding-series",
|
|
title: "Paid Onboarding Series",
|
|
},
|
|
id: 107,
|
|
metadata: {
|
|
recurringEventsRemaining: 4,
|
|
recurringPattern: "Every week for 4 occurrences",
|
|
},
|
|
payment: [],
|
|
recurringEventId: "recurring-unconfirmed-onboarding",
|
|
startTime: new Date("2026-02-12T14:00:00"),
|
|
title: "Onboarding series — pending payment",
|
|
uid: "unconfirmed-booking-3",
|
|
};
|
|
|
|
const cancelledTeamBooking: Booking = {
|
|
...baseCancelledDesignReview,
|
|
cancellationReason: "Customer asked to move the project review.",
|
|
description: "Design critique for the analytics shell.",
|
|
endTime: new Date("2025-10-15T13:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#3b82f6",
|
|
lightEventTypeColor: "#3b82f6",
|
|
},
|
|
id: 19,
|
|
schedulingType: "COLLECTIVE",
|
|
slug: "team-design-review",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Team Design Review",
|
|
},
|
|
id: 14,
|
|
rescheduled: true,
|
|
startTime: new Date("2025-10-15T12:30:00"),
|
|
title: "Analytics shell review",
|
|
uid: "cancelled-booking-2",
|
|
};
|
|
|
|
const cancelledPaidRecurring: Booking = {
|
|
...baseCancelledDesignReview,
|
|
cancellationReason: "Payment was not completed before the deadline.",
|
|
description: "Advisory retainer — card never confirmed.",
|
|
endTime: new Date("2025-10-18T11:45:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#fd6d06",
|
|
lightEventTypeColor: "#fd6d06",
|
|
},
|
|
id: 21,
|
|
length: 45,
|
|
price: 19900,
|
|
recurringEvent: {
|
|
count: 6,
|
|
freq: 2,
|
|
interval: 2,
|
|
},
|
|
slug: "paid-advisory",
|
|
title: "Paid Advisory",
|
|
},
|
|
id: 15,
|
|
metadata: {
|
|
recurringEventsRemaining: 3,
|
|
recurringPattern: "Every 2 weeks for 6 occurrences",
|
|
},
|
|
payment: [],
|
|
recurringEventId: "recurring-cancelled-paid",
|
|
startTime: new Date("2025-10-18T11:00:00"),
|
|
title: "Advisory retainer (series)",
|
|
uid: "cancelled-booking-3",
|
|
};
|
|
|
|
const recurringYogaClass: Booking = {
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 200,
|
|
email: "pro@example.com",
|
|
id: 200,
|
|
locale: "en",
|
|
name: "Jordan Hale",
|
|
noShow: null,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
createdAt: new Date("2026-05-01T09:00:00"),
|
|
description: null,
|
|
endTime: new Date("2026-05-22T17:31:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#8b5cf6",
|
|
lightEventTypeColor: "#8b5cf6",
|
|
},
|
|
id: 200,
|
|
length: 30,
|
|
recurringEvent: {
|
|
count: 6,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
slug: "yoga-class",
|
|
title: "Yoga class",
|
|
},
|
|
id: 200,
|
|
location: "integrations:daily",
|
|
metadata: {
|
|
recurringEventsRemaining: 4,
|
|
recurringPattern: "Every week for 6 occurrences",
|
|
},
|
|
recurringEventId: "recurring-yoga-class",
|
|
rescheduled: false,
|
|
startTime: new Date("2026-05-22T17:01:00"),
|
|
status: "ACCEPTED",
|
|
title: "Yoga class",
|
|
uid: "recurring-booking-1",
|
|
updatedAt: new Date("2026-05-01T09:00:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
};
|
|
|
|
const recurringYogaEventType = getMockEventType(recurringYogaClass);
|
|
|
|
const recurringUnconfirmedTennisClass: Booking = {
|
|
...recurringYogaClass,
|
|
attendees: [
|
|
{
|
|
bookingId: 201,
|
|
email: "pro@example.com",
|
|
id: 201,
|
|
locale: "en",
|
|
name: "Jordan Hale",
|
|
noShow: null,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
endTime: new Date("2026-05-23T18:01:00"),
|
|
eventType: {
|
|
...recurringYogaEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#0dbf82",
|
|
lightEventTypeColor: "#0dbf82",
|
|
},
|
|
id: 201,
|
|
recurringEvent: {
|
|
count: 5,
|
|
freq: 2,
|
|
interval: 2,
|
|
},
|
|
slug: "tennis-class",
|
|
title: "Tennis class",
|
|
},
|
|
id: 201,
|
|
metadata: {
|
|
recurringEventsRemaining: 5,
|
|
recurringPattern: "Every 2 weeks for 5 occurrences",
|
|
},
|
|
recurringEventId: "recurring-tennis-class",
|
|
startTime: new Date("2026-05-23T17:01:00"),
|
|
status: "PENDING",
|
|
title: "Tennis class",
|
|
uid: "recurring-booking-2",
|
|
};
|
|
|
|
const recurringSeededYogaClass: Booking = {
|
|
...recurringYogaClass,
|
|
attendees: [
|
|
{
|
|
bookingId: 202,
|
|
email: "pro@example.com",
|
|
id: 202,
|
|
locale: "en",
|
|
name: "Jordan Hale",
|
|
noShow: null,
|
|
timeZone: "Europe/Rome",
|
|
},
|
|
],
|
|
description: "Fixture row for seeded recurrence metadata.",
|
|
endTime: new Date("2026-05-24T17:31:00"),
|
|
eventType: {
|
|
...recurringYogaEventType,
|
|
id: 202,
|
|
slug: "seeded-yoga-class",
|
|
title: "Sunrise flow",
|
|
},
|
|
id: 202,
|
|
metadata: {
|
|
recurringEventsRemaining: 1,
|
|
recurringPattern: "Every week for 3 occurrences",
|
|
},
|
|
recurringEventId: "recurring-seeded-yoga-class",
|
|
startTime: new Date("2026-05-24T17:01:00"),
|
|
title: "Sunrise flow (seeded)",
|
|
uid: "recurring-booking-3",
|
|
};
|
|
|
|
const recurringPaidTeamWorkshop: Booking = {
|
|
...recurringYogaClass,
|
|
attendees: [
|
|
{
|
|
bookingId: 203,
|
|
email: "ops@example.com",
|
|
id: 203,
|
|
locale: "en",
|
|
name: "Inès Moreau",
|
|
noShow: null,
|
|
timeZone: "America/New_York",
|
|
},
|
|
{
|
|
bookingId: 203,
|
|
email: "product@example.com",
|
|
id: 204,
|
|
locale: "en",
|
|
name: "Felix Hart",
|
|
noShow: null,
|
|
timeZone: "Europe/London",
|
|
},
|
|
],
|
|
description: "Recurring workshop — invoice still outstanding.",
|
|
endTime: new Date("2026-05-26T16:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#fd6d06",
|
|
lightEventTypeColor: "#fd6d06",
|
|
},
|
|
id: 203,
|
|
length: 60,
|
|
price: 49900,
|
|
recurringEvent: {
|
|
count: 10,
|
|
freq: 2,
|
|
interval: 1,
|
|
},
|
|
schedulingType: "COLLECTIVE",
|
|
slug: "paid-team-workshop",
|
|
team: {
|
|
id: 1,
|
|
name: "Cal.com",
|
|
slug: "cal",
|
|
},
|
|
teamId: 1,
|
|
title: "Facilitation workshop",
|
|
},
|
|
id: 203,
|
|
metadata: {
|
|
recurringEventsRemaining: 8,
|
|
recurringPattern: "Every week for 10 occurrences",
|
|
},
|
|
payment: [],
|
|
recurringEventId: "recurring-paid-team-workshop",
|
|
startTime: new Date("2026-05-26T15:00:00"),
|
|
title: "Facilitation workshop (series)",
|
|
uid: "recurring-booking-4",
|
|
};
|
|
|
|
const recurringRescheduledClass: Booking = {
|
|
...recurringYogaClass,
|
|
description: "Attendee moved the May slot to avoid overlap.",
|
|
endTime: new Date("2026-05-27T09:45:00"),
|
|
eventType: {
|
|
...recurringYogaEventType,
|
|
eventTypeColor: {
|
|
darkEventTypeColor: "#3b82f6",
|
|
lightEventTypeColor: "#3b82f6",
|
|
},
|
|
id: 204,
|
|
slug: "pilates-class",
|
|
title: "Pilates class",
|
|
},
|
|
fromReschedule: "previous-pilates-booking",
|
|
id: 204,
|
|
metadata: {
|
|
recurringEventsRemaining: 2,
|
|
recurringPattern: "Every month for 4 occurrences",
|
|
},
|
|
recurringEventId: "recurring-pilates-class",
|
|
rescheduled: true,
|
|
rescheduledBy: "Jordan Hale",
|
|
startTime: new Date("2026-05-27T09:15:00"),
|
|
title: "Pilates class",
|
|
uid: "recurring-booking-5",
|
|
};
|
|
|
|
const pastReportedBooking: Booking = {
|
|
...defaultBookingFields,
|
|
attendees: [
|
|
{
|
|
bookingId: 16,
|
|
email: "unknown@example.com",
|
|
id: 18,
|
|
locale: "en",
|
|
name: "M. Jensen",
|
|
noShow: false,
|
|
timeZone: "America/New_York",
|
|
},
|
|
],
|
|
createdAt: new Date("2025-10-18T08:00:00"),
|
|
description: "Walk-in via public link — flagged after the call.",
|
|
endTime: new Date("2025-10-20T11:00:00"),
|
|
eventType: {
|
|
...defaultEventType,
|
|
id: 22,
|
|
slug: "30min",
|
|
title: "30 Min Meeting",
|
|
},
|
|
id: 16,
|
|
location: "integrations:zoom",
|
|
report: {
|
|
createdAt: new Date("2025-10-20T11:05:00"),
|
|
description: "Unrecognized attendee used a generic booking link.",
|
|
id: 1,
|
|
reason: "Spam or unwanted booking",
|
|
reportedById: userPasquale.id,
|
|
},
|
|
startTime: new Date("2025-10-20T10:30:00"),
|
|
status: "ACCEPTED",
|
|
title: "Inbound — M. Jensen",
|
|
uid: "past-booking-reported",
|
|
updatedAt: new Date("2025-10-20T11:05:00"),
|
|
user: userPasquale,
|
|
userPrimaryEmail: "pasquale@cal.com",
|
|
};
|
|
|
|
export const mockPastBookingsForTab: Booking[] = [
|
|
pastReportedBooking,
|
|
...mockPastBookings.filter(
|
|
(booking) => booking.status !== "CANCELLED" && booking.status !== "PENDING",
|
|
),
|
|
];
|
|
|
|
export const mockUpcomingBookingsForTab: Booking[] = [
|
|
upcomingTodayMeeting,
|
|
upcomingMinimumNoticeBooking,
|
|
upcomingAttendeeOnlyRestrictions,
|
|
...mockUpcomingBookings,
|
|
upcomingPaidConsultation,
|
|
upcomingRescheduledRecurring,
|
|
];
|
|
|
|
export const mockUnconfirmedBookings: Booking[] = [
|
|
baseUpcomingPending,
|
|
unconfirmedTeamBooking,
|
|
unconfirmedPaidRecurring,
|
|
];
|
|
|
|
export const mockRecurringBookings: Booking[] = [
|
|
recurringYogaClass,
|
|
recurringUnconfirmedTennisClass,
|
|
recurringSeededYogaClass,
|
|
recurringPaidTeamWorkshop,
|
|
recurringRescheduledClass,
|
|
upcomingRescheduledRecurring,
|
|
unconfirmedPaidRecurring,
|
|
basePastRecurring,
|
|
basePastStressTest,
|
|
];
|
|
|
|
export const mockCancelledBookings: Booking[] = [
|
|
...mockPastBookings.filter((booking) => booking.status === "CANCELLED"),
|
|
cancelledTeamBooking,
|
|
cancelledPaidRecurring,
|
|
];
|
|
|
|
// =============================================================================
|
|
// HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
export function isBookingToday(
|
|
date: Date,
|
|
referenceDate: Date = new Date(),
|
|
): boolean {
|
|
return (
|
|
date.getFullYear() === referenceDate.getFullYear() &&
|
|
date.getMonth() === referenceDate.getMonth() &&
|
|
date.getDate() === referenceDate.getDate()
|
|
);
|
|
}
|
|
|
|
export function formatBookingDate(startTime: Date, endTime?: Date): string {
|
|
const isUpcoming = (endTime ?? startTime) >= new Date();
|
|
const bookingYear = startTime.getFullYear();
|
|
const currentYear = new Date().getFullYear();
|
|
const isDifferentYear = bookingYear !== currentYear;
|
|
const weekday = startTime.toLocaleDateString("en-US", { weekday: "short" });
|
|
const day = startTime.getDate();
|
|
const monthShort = startTime.toLocaleDateString("en-US", { month: "short" });
|
|
const monthLong = startTime.toLocaleDateString("en-US", { month: "long" });
|
|
|
|
if (isUpcoming) {
|
|
if (isDifferentYear) {
|
|
return `${weekday}, ${day} ${monthShort} ${bookingYear}`;
|
|
}
|
|
|
|
return `${weekday}, ${day} ${monthShort}`;
|
|
}
|
|
|
|
return `${day} ${monthLong} ${bookingYear}`;
|
|
}
|
|
|
|
export function formatBookingTime(startTime: Date, endTime: Date): string {
|
|
const formatTime = (d: Date) =>
|
|
d.toLocaleTimeString("en-US", {
|
|
hour: "numeric",
|
|
hour12: true,
|
|
minute: "2-digit",
|
|
});
|
|
return `${formatTime(startTime)} - ${formatTime(endTime)}`;
|
|
}
|
|
|
|
export function getBookingParticipants(booking: Booking): string {
|
|
const names = booking.attendees.map((a) => a.name);
|
|
if (names.length === 0) return "";
|
|
const firstName = names[0] ?? "";
|
|
if (names.length === 1) return firstName;
|
|
const secondName = names[1] ?? "";
|
|
if (names.length === 2) return `${firstName} and ${secondName}`;
|
|
const lastName = names[names.length - 1] ?? "";
|
|
return `${names.slice(0, -1).join(", ")} and ${lastName}`;
|
|
}
|
|
|
|
export function getLocationLabel(location: string | null): string {
|
|
if (!location) return "";
|
|
const locationMap: Record<string, string> = {
|
|
"integrations:daily": "Join Cal Video",
|
|
"integrations:google_meet": "Join Google Meet",
|
|
"integrations:teams": "Join Microsoft Teams",
|
|
"integrations:zoom": "Join Zoom",
|
|
};
|
|
return locationMap[location] || location;
|
|
}
|
|
|
|
export function getLocationIcon(
|
|
location: string | null,
|
|
): "video" | "phone" | "location" | null {
|
|
if (!location) return null;
|
|
if (location.startsWith("integrations:")) return "video";
|
|
if (location.includes("phone")) return "phone";
|
|
return "location";
|
|
}
|
|
|
|
// =============================================================================
|
|
// FILTER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
export interface BookingFilter {
|
|
categoryId: string;
|
|
selectedOptionIds: string[];
|
|
}
|
|
|
|
export function filterBookings(
|
|
bookings: Booking[],
|
|
filters: BookingFilter[],
|
|
): Booking[] {
|
|
if (filters.length === 0) return bookings;
|
|
|
|
return bookings.filter((booking) => {
|
|
return filters.every((filter) => {
|
|
const { categoryId, selectedOptionIds } = filter;
|
|
if (selectedOptionIds.length === 0) return true;
|
|
|
|
switch (categoryId) {
|
|
case "event-type": {
|
|
const eventTypeTitle = booking.eventType?.title?.toLowerCase() ?? "";
|
|
const eventTypeSlug = booking.eventType?.slug?.toLowerCase() ?? "";
|
|
return selectedOptionIds.some((optionId) => {
|
|
const optionLabel = getEventTypeLabel(optionId).toLowerCase();
|
|
return (
|
|
eventTypeTitle.includes(optionLabel) ||
|
|
eventTypeSlug.includes(optionId.replace(/-/g, ""))
|
|
);
|
|
});
|
|
}
|
|
case "member": {
|
|
const hostName = booking.user?.name?.toLowerCase() ?? "";
|
|
const hostEmail = booking.user?.email?.toLowerCase() ?? "";
|
|
return selectedOptionIds.some((optionId) => {
|
|
const memberName = optionId.replace(/-/g, " ").toLowerCase();
|
|
return (
|
|
hostName.includes(memberName) ||
|
|
hostEmail.includes(optionId.replace(/-/g, ""))
|
|
);
|
|
});
|
|
}
|
|
case "attendees-name": {
|
|
return selectedOptionIds.some((optionId) => {
|
|
const searchName = optionId.replace(/-/g, " ").toLowerCase();
|
|
return booking.attendees.some((attendee) =>
|
|
attendee.name.toLowerCase().includes(searchName),
|
|
);
|
|
});
|
|
}
|
|
case "attendee-email": {
|
|
return selectedOptionIds.some((optionId) => {
|
|
return booking.attendees.some((attendee) =>
|
|
attendee.email
|
|
.toLowerCase()
|
|
.includes(optionId.split("-")[0] ?? ""),
|
|
);
|
|
});
|
|
}
|
|
case "date-range": {
|
|
const now = new Date();
|
|
const bookingDate = new Date(booking.startTime);
|
|
return selectedOptionIds.some((optionId) => {
|
|
switch (optionId) {
|
|
case "today":
|
|
return isSameDay(bookingDate, now);
|
|
case "yesterday":
|
|
return isSameDay(bookingDate, addDays(now, -1));
|
|
case "this-week":
|
|
return isWithinWeek(bookingDate, now);
|
|
case "last-week":
|
|
return isWithinLastWeek(bookingDate, now);
|
|
case "this-month":
|
|
return isSameMonth(bookingDate, now);
|
|
case "last-month":
|
|
return isLastMonth(bookingDate, now);
|
|
default:
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
case "booking-uid": {
|
|
return selectedOptionIds.some((optionId) =>
|
|
booking.uid.toLowerCase().includes(optionId.toLowerCase()),
|
|
);
|
|
}
|
|
default:
|
|
return true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function getEventTypeLabel(optionId: string): string {
|
|
const labels: Record<string, string> = {
|
|
"15-min": "15 Min Meeting",
|
|
"30-min": "30 Min Meeting",
|
|
"60-min": "60 Min Meeting",
|
|
consultation: "Consultation",
|
|
interview: "Interview",
|
|
onboarding: "Onboarding Call",
|
|
};
|
|
return labels[optionId] ?? optionId;
|
|
}
|
|
|
|
function isSameDay(date1: Date, date2: Date): boolean {
|
|
return (
|
|
date1.getFullYear() === date2.getFullYear() &&
|
|
date1.getMonth() === date2.getMonth() &&
|
|
date1.getDate() === date2.getDate()
|
|
);
|
|
}
|
|
|
|
function addDays(date: Date, days: number): Date {
|
|
const result = new Date(date);
|
|
result.setDate(result.getDate() + days);
|
|
return result;
|
|
}
|
|
|
|
function isWithinWeek(date: Date, referenceDate: Date): boolean {
|
|
const startOfWeek = new Date(referenceDate);
|
|
startOfWeek.setDate(referenceDate.getDate() - referenceDate.getDay());
|
|
startOfWeek.setHours(0, 0, 0, 0);
|
|
const endOfWeek = new Date(startOfWeek);
|
|
endOfWeek.setDate(startOfWeek.getDate() + 7);
|
|
return date >= startOfWeek && date < endOfWeek;
|
|
}
|
|
|
|
function isWithinLastWeek(date: Date, referenceDate: Date): boolean {
|
|
const startOfLastWeek = new Date(referenceDate);
|
|
startOfLastWeek.setDate(referenceDate.getDate() - referenceDate.getDay() - 7);
|
|
startOfLastWeek.setHours(0, 0, 0, 0);
|
|
const endOfLastWeek = new Date(startOfLastWeek);
|
|
endOfLastWeek.setDate(startOfLastWeek.getDate() + 7);
|
|
return date >= startOfLastWeek && date < endOfLastWeek;
|
|
}
|
|
|
|
function isSameMonth(date: Date, referenceDate: Date): boolean {
|
|
return (
|
|
date.getFullYear() === referenceDate.getFullYear() &&
|
|
date.getMonth() === referenceDate.getMonth()
|
|
);
|
|
}
|
|
|
|
function isLastMonth(date: Date, referenceDate: Date): boolean {
|
|
const lastMonth = new Date(referenceDate);
|
|
lastMonth.setMonth(lastMonth.getMonth() - 1);
|
|
return (
|
|
date.getFullYear() === lastMonth.getFullYear() &&
|
|
date.getMonth() === lastMonth.getMonth()
|
|
);
|
|
}
|