Skip to content

Commit c265955

Browse files
committed
feat(portal): add prominent Scheduled Sessions command center with local+ET times and inline 1-click rebooking
1 parent cb09ee8 commit c265955

3 files changed

Lines changed: 1043 additions & 260 deletions

File tree

‎apps/site/src/lib/google-calendar.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function syncPrivateShadowCalendarBlocks(opts: {
4040
`Room: ${studioOrMeet}`,
4141
`Guest: ${opts.guestName} (${opts.company})`,
4242
...(opts.portalUrl
43-
? [`Portal & Asset Vault: ${opts.portalUrl}#vault`]
43+
? [`Guest Studio Portal: ${opts.portalUrl}`]
4444
: []),
4545
].join("\n"),
4646
location: studioOrMeet,
@@ -606,7 +606,7 @@ export async function createGoogleCalendarBookingEvent(opts: {
606606
? `Recording Studio (StreamYard): ${STREAMYARD_STUDIO_URL}`
607607
: "Status: Calendar Hold Reserved. Once Alex confirms your session, this calendar invite will automatically update with your StreamYard studio link.",
608608
...(opts.portalUrl
609-
? [`Guest Portal & Sanity Media Vault: ${opts.portalUrl}#vault`]
609+
? [`Guest Studio Portal & Reschedule Link: ${opts.portalUrl}`]
610610
: []),
611611
];
612612
const description = descriptionLines.join("\n");
@@ -621,7 +621,7 @@ export async function createGoogleCalendarBookingEvent(opts: {
621621
`CodingCat.dev Podcast Recording (${durationMinutes} min)`,
622622
`Guest: ${opts.guestName} (${opts.company})`,
623623
...(opts.portalUrl
624-
? [`Deal Room & Asset Vault: ${opts.portalUrl}#vault`]
624+
? [`Guest Studio Portal & Reschedule Link: ${opts.portalUrl}`]
625625
: []),
626626
].join("\n"),
627627
});
@@ -684,7 +684,7 @@ export async function createGoogleCalendarBookingEvent(opts: {
684684
: isApprovedRecording
685685
? STREAMYARD_STUDIO_URL
686686
: opts.portalUrl
687-
? `${opts.portalUrl}#vault`
687+
? opts.portalUrl
688688
: "CodingCat.dev Studio (StreamYard link sent upon approval)",
689689
start: { dateTime: start.toISOString() },
690690
end: { dateTime: end.toISOString() },
@@ -811,7 +811,7 @@ export async function approveGoogleCalendarRecordingEvent(opts: {
811811
`Company: ${opts.company}`,
812812
`Topics: ${opts.topics}`,
813813
...(opts.portalUrl
814-
? [`Deal Room & Sanity Media Vault: ${opts.portalUrl}#vault`]
814+
? [`Guest Studio Portal & Reschedule Link: ${opts.portalUrl}`]
815815
: []),
816816
].join("\n");
817817

‎apps/site/src/pages/api/booking.ts‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
sendCodingCatEmailDetailed,
88
} from "@/lib/email";
99
import {
10+
cancelGoogleCalendarBookingEvent,
1011
createGoogleCalendarBookingEvent,
1112
STREAMYARD_STUDIO_URL,
1213
} from "@/lib/google-calendar";
@@ -232,10 +233,34 @@ export const POST: APIRoute = async ({ request, url }) => {
232233
}
233234
}
234235

236+
const wasAlreadyApproved =
237+
existingBookingDoc?.status === "approved" ||
238+
existingBookingDoc?.status === "confirmed" ||
239+
existingBookingDoc?.status === "scheduled";
240+
241+
// If rescheduling an existing 15m Pre-Meeting or 45m Recording, cancel the previous Google Calendar event first
242+
// so the old time slot is freed and the guest receives a clean replacement invite.
243+
if (isKickoff && existingBookingDoc?.preMeetingGoogleEventId) {
244+
await cancelGoogleCalendarBookingEvent({
245+
eventId: existingBookingDoc.preMeetingGoogleEventId,
246+
startAt: existingBookingDoc.preMeetingSlotStartAt,
247+
durationMinutes: 15,
248+
guestName,
249+
}).catch(() => null);
250+
} else if (!isKickoff && existingBookingDoc?.googleCalendarEventId) {
251+
await cancelGoogleCalendarBookingEvent({
252+
eventId: existingBookingDoc.googleCalendarEventId,
253+
startAt: existingBookingDoc.slotStartAt,
254+
durationMinutes: 60,
255+
guestName,
256+
}).catch(() => null);
257+
}
258+
235259
// Create live Google Calendar event:
236260
// - 15m Pre-Meetings get a confirmed Google Meet invite immediately.
237261
// - 45m Recording slots create an OPAQUE (Busy) placeholder hold on Alex's calendars
238-
// so nothing can jump them; StreamYard (https://streamyard.com/c4kv26is93) is revealed once approved.
262+
// so nothing can jump them; StreamYard (https://streamyard.com/c4kv26is93) is revealed once approved
263+
// (or kept revealed if rescheduling an already-approved recording).
239264
const calendarEvent = await createGoogleCalendarBookingEvent({
240265
guestName,
241266
guestEmail,
@@ -246,7 +271,7 @@ export const POST: APIRoute = async ({ request, url }) => {
246271
topics,
247272
portalUrl,
248273
isPreMeeting: isKickoff,
249-
autoApproveRecording: false,
274+
autoApproveRecording: !isKickoff && wasAlreadyApproved,
250275
});
251276

252277
if (calendarEvent.conflictDetected) {
@@ -297,9 +322,14 @@ export const POST: APIRoute = async ({ request, url }) => {
297322

298323
if (canWrite) {
299324
try {
300-
if (data.preMeetingForBookingId && isKickoff) {
325+
const targetPreMeetingDocId =
326+
data.preMeetingForBookingId ||
327+
(isKickoff && existingBookingDoc?._id
328+
? existingBookingDoc._id
329+
: undefined);
330+
if (targetPreMeetingDocId && isKickoff) {
301331
await client
302-
.patch(data.preMeetingForBookingId)
332+
.patch(targetPreMeetingDocId)
303333
.set({
304334
preMeetingStatus: "scheduled",
305335
preMeetingSlotStartAt: slotStartAt,
@@ -321,7 +351,11 @@ export const POST: APIRoute = async ({ request, url }) => {
321351
...(socialUrl ? { socialUrl } : {}),
322352
track,
323353
sessionType,
324-
status: isKickoff ? "scheduled" : "pending",
354+
status: isKickoff
355+
? "scheduled"
356+
: wasAlreadyApproved
357+
? existingBookingDoc.status
358+
: "pending",
325359
slotStartAt,
326360
recordingUrl: STREAMYARD_STUDIO_URL,
327361
topics,

0 commit comments

Comments
 (0)