Skip to content

Commit 559d528

Browse files
Address full-day start time review feedback
1 parent 114b5b6 commit 559d528

6 files changed

Lines changed: 40 additions & 42 deletions

File tree

src/app/api/events/[slug]/ics/route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe("GET /api/events/[slug]/ics", () => {
110110
eventType: "full_day",
111111
timezone: "Europe/Vienna",
112112
fullDayStartMinutes: 18 * 60,
113-
meetingDurationMinutes: 60,
113+
meetingDurationMinutes: 180,
114114
status: "CLOSED",
115115
finalizedSlot: {
116116
slotStart: "2026-04-01T22:00:00.000Z",

src/app/api/events/[slug]/ics/route.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextResponse } from "next/server";
22

33
import { getPublicEventSnapshot } from "@/lib/event-service";
4-
import { buildSlotStart } from "@/lib/availability";
4+
import { buildTimedFullDaySlotWindow } from "@/lib/availability";
55
import { createI18n, getLocaleFromRequest } from "@/lib/i18n/server";
66
import { buildEventCalendarFile } from "@/lib/ics";
77
import { PUBLIC_NO_STORE_HEADERS, mergeHeaders } from "@/lib/security";
@@ -34,18 +34,13 @@ export async function GET(request: Request, { params }: Context) {
3434
event.snapshot.eventType === "full_day" &&
3535
event.snapshot.fullDayStartMinutes !== null &&
3636
event.snapshot.fullDayStartMinutes !== undefined;
37-
const slotStart = isTimedFullDayEvent
38-
? buildSlotStart(
39-
event.snapshot.finalizedSlot.dateKey,
40-
event.snapshot.fullDayStartMinutes ?? 0,
41-
event.snapshot.timezone,
42-
)
43-
: event.snapshot.finalizedSlot.slotStart;
44-
const slotEnd = isTimedFullDayEvent
45-
? new Date(
46-
new Date(slotStart).getTime() + event.snapshot.meetingDurationMinutes * 60 * 1000,
47-
).toISOString()
48-
: event.snapshot.finalizedSlot.slotEnd;
37+
const timedFullDaySlotWindow = isTimedFullDayEvent
38+
? buildTimedFullDaySlotWindow({
39+
dateKey: event.snapshot.finalizedSlot.dateKey,
40+
fullDayStartMinutes: event.snapshot.fullDayStartMinutes ?? 0,
41+
timezone: event.snapshot.timezone,
42+
})
43+
: null;
4944

5045
const body = buildEventCalendarFile({
5146
slug: event.snapshot.slug,
@@ -54,8 +49,8 @@ export async function GET(request: Request, { params }: Context) {
5449
isOnlineMeeting: event.snapshot.isOnlineMeeting,
5550
meetingLink: event.snapshot.meetingLink,
5651
timezone: event.snapshot.timezone,
57-
slotStart,
58-
slotEnd,
52+
slotStart: timedFullDaySlotWindow?.slotStart ?? event.snapshot.finalizedSlot.slotStart,
53+
slotEnd: timedFullDaySlotWindow?.slotEnd ?? event.snapshot.finalizedSlot.slotEnd,
5954
allDayDateKey:
6055
event.snapshot.eventType === "full_day" && !isTimedFullDayEvent
6156
? event.snapshot.finalizedSlot.dateKey

src/components/full-day-availability.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
2727
import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control";
2828
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
2929
import type { BoardMode } from "@/components/event-heatmap";
30-
import { minutesToLabel } from "@/lib/availability";
30+
import { formatFullDayDateLabel, minutesToLabel } from "@/lib/availability";
31+
import type { AppLocale } from "@/lib/i18n/locale";
3132
import { useI18n } from "@/lib/i18n/context";
3233
import type { PublicEventSnapshot, SnapshotParticipant, SnapshotSlot } from "@/lib/types";
3334
import { cn } from "@/lib/utils";
@@ -181,12 +182,13 @@ function getDisabledDayStyle(): CSSProperties {
181182
};
182183
}
183184

184-
function getDayLabel(snapshot: PublicEventSnapshot, slot: SnapshotSlot) {
185-
const dateLabel = snapshot.dates.find((date) => date.dateKey === slot.dateKey)?.label ?? slot.dateKey;
186-
187-
return snapshot.fullDayStartMinutes === null || snapshot.fullDayStartMinutes === undefined
188-
? dateLabel
189-
: `${dateLabel} · ${minutesToLabel(snapshot.fullDayStartMinutes)}`;
185+
function getDayLabel(snapshot: PublicEventSnapshot, slot: SnapshotSlot, locale: AppLocale) {
186+
return formatFullDayDateLabel({
187+
dateKey: slot.dateKey,
188+
fullDayStartMinutes: snapshot.fullDayStartMinutes,
189+
timezone: snapshot.timezone,
190+
locale,
191+
});
190192
}
191193

192194
function getDayElementFromPoint(clientX: number, clientY: number) {
@@ -273,7 +275,7 @@ export function FullDayAvailability({
273275
onActiveParticipantChange,
274276
description,
275277
}: FullDayAvailabilityProps) {
276-
const { messages, format, plural, intlLocale } = useI18n();
278+
const { messages, format, plural, intlLocale, locale } = useI18n();
277279
const [activeSlotStart, setActiveSlotStart] = useState<string | null>(null);
278280
const [internalActiveParticipantId, setInternalActiveParticipantId] = useState<string | null>(
279281
null,
@@ -348,7 +350,7 @@ export function FullDayAvailability({
348350
const activeSlotDetails = activeSlot
349351
? {
350352
slot: activeSlot,
351-
dateLabel: getDayLabel(snapshot, activeSlot),
353+
dateLabel: getDayLabel(snapshot, activeSlot, locale),
352354
availableParticipants: snapshot.participants.filter((participant) =>
353355
activeSlot.participantIds.includes(participant.id),
354356
),
@@ -377,7 +379,7 @@ export function FullDayAvailability({
377379
}, []);
378380

379381
function getAvailabilityTitle(slot: SnapshotSlot) {
380-
const dateLabel = getDayLabel(snapshot, slot);
382+
const dateLabel = getDayLabel(snapshot, slot, locale);
381383
const availableNames = slot.participantIds
382384
.map((participantId) => participantNamesById.get(participantId))
383385
.filter(Boolean) as string[];

src/components/ui/segmented-control.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ export function SegmentedControlItem({
2222
active,
2323
className,
2424
...props
25-
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
25+
}: Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "aria-pressed" | "type"> & {
2626
active?: boolean;
2727
}) {
2828
return (
2929
<button
30+
{...props}
3031
type="button"
3132
data-slot="segmented-control-item"
3233
data-active={active ? "true" : undefined}
@@ -37,7 +38,6 @@ export function SegmentedControlItem({
3738
className,
3839
)}
3940
aria-pressed={active}
40-
{...props}
4141
/>
4242
);
4343
}

src/lib/availability.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ describe("availability helpers", () => {
217217
fullDayStartMinutes: 18 * 60,
218218
status: "OPEN",
219219
slotMinutes: 30,
220-
meetingDurationMinutes: 60,
220+
meetingDurationMinutes: 180,
221221
dayStartMinutes: 9 * 60,
222222
dayEndMinutes: 11 * 60,
223223
dates: ["2026-04-10"],

src/lib/availability.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,26 @@ function buildFullDaySlotEnd(dateKey: string, timezone: string) {
121121
return buildFullDaySlotStart(addDaysToDateKey(dateKey, 1), timezone);
122122
}
123123

124-
function buildTimedFullDaySlotEnd({
124+
const timedFullDayCalendarDurationMinutes = 60;
125+
126+
export function buildTimedFullDaySlotWindow({
125127
dateKey,
126128
fullDayStartMinutes,
127-
meetingDurationMinutes,
128129
timezone,
129130
}: {
130131
dateKey: string;
131132
fullDayStartMinutes: number;
132-
meetingDurationMinutes: number;
133133
timezone: string;
134134
}) {
135135
const slotStart = buildSlotStart(dateKey, fullDayStartMinutes, timezone);
136+
const slotEnd = new Date(
137+
new Date(slotStart).getTime() + timedFullDayCalendarDurationMinutes * 60 * 1000,
138+
).toISOString();
136139

137-
return new Date(new Date(slotStart).getTime() + meetingDurationMinutes * 60 * 1000).toISOString();
140+
return {
141+
slotStart,
142+
slotEnd,
143+
};
138144
}
139145

140146
function getDateKeyInTimezone(date: Date, timezone: string) {
@@ -592,12 +598,11 @@ export function buildFinalizedSlot({
592598
slotEnd:
593599
fullDayStartMinutes === null || fullDayStartMinutes === undefined
594600
? buildFullDaySlotEnd(slot.dateKey, timezone)
595-
: buildTimedFullDaySlotEnd({
601+
: buildTimedFullDaySlotWindow({
596602
dateKey: slot.dateKey,
597603
fullDayStartMinutes,
598-
meetingDurationMinutes,
599604
timezone,
600-
}),
605+
}).slotEnd,
601606
dateKey: slot.dateKey,
602607
label: formatFullDayDateLabel({
603608
dateKey: slot.dateKey,
@@ -730,7 +735,6 @@ export function buildSnapshot({
730735
locale,
731736
timezone,
732737
fullDayStartMinutes,
733-
meetingDurationMinutes,
734738
slots,
735739
})
736740
: rankBestSuggestions({
@@ -794,14 +798,12 @@ export function buildSnapshot({
794798
function rankBestFullDaySuggestions({
795799
dates,
796800
fullDayStartMinutes,
797-
meetingDurationMinutes,
798801
locale,
799802
timezone,
800803
slots,
801804
}: {
802805
dates: string[];
803806
fullDayStartMinutes?: number | null;
804-
meetingDurationMinutes: number;
805807
locale: AppLocale;
806808
timezone: string;
807809
slots: SnapshotSlot[];
@@ -821,12 +823,11 @@ function rankBestFullDaySuggestions({
821823
slotEnd:
822824
fullDayStartMinutes === null || fullDayStartMinutes === undefined
823825
? buildFullDaySlotEnd(slot.dateKey, timezone)
824-
: buildTimedFullDaySlotEnd({
826+
: buildTimedFullDaySlotWindow({
825827
dateKey: slot.dateKey,
826828
fullDayStartMinutes,
827-
meetingDurationMinutes,
828829
timezone,
829-
}),
830+
}).slotEnd,
830831
dateKey: slot.dateKey,
831832
label: formatFullDayDateLabel({
832833
dateKey: slot.dateKey,

0 commit comments

Comments
 (0)