From a33470435aa53bacb49c31493660a399347466f4 Mon Sep 17 00:00:00 2001 From: Tero Halla-aho Date: Sun, 21 Dec 2025 22:29:33 +0200 Subject: [PATCH 1/3] Show single-month availability calendar with nav controls --- app/api/listings/[id]/availability/route.ts | 4 +- app/components/AvailabilityCalendar.tsx | 85 ++++++++++----------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/app/api/listings/[id]/availability/route.ts b/app/api/listings/[id]/availability/route.ts index 90a6539..366b696 100644 --- a/app/api/listings/[id]/availability/route.ts +++ b/app/api/listings/[id]/availability/route.ts @@ -5,12 +5,12 @@ import { expandBlockedDates, getCalendarRanges } from '../../../../../lib/calend export async function GET(_: Request, { params }: { params: { id: string } }) { const monthParam = Number(new URL(_.url).searchParams.get('month') ?? new Date().getUTCMonth()); const yearParam = Number(new URL(_.url).searchParams.get('year') ?? new Date().getUTCFullYear()); - const monthsParam = Math.min(Number(new URL(_.url).searchParams.get('months') ?? 2), 12); + const monthsParam = Math.min(Number(new URL(_.url).searchParams.get('months') ?? 1), 12); const forceRefresh = new URL(_.url).searchParams.get('refresh') === '1'; const month = Number.isFinite(monthParam) ? monthParam : new Date().getUTCMonth(); const year = Number.isFinite(yearParam) ? yearParam : new Date().getUTCFullYear(); - const months = Number.isFinite(monthsParam) && monthsParam > 0 ? monthsParam : 2; + const months = Number.isFinite(monthsParam) && monthsParam > 0 ? monthsParam : 1; const listing = await prisma.listing.findUnique({ where: { id: params.id }, select: { calendarUrls: true } }); if (!listing) { diff --git a/app/components/AvailabilityCalendar.tsx b/app/components/AvailabilityCalendar.tsx index 9a151c4..5789805 100644 --- a/app/components/AvailabilityCalendar.tsx +++ b/app/components/AvailabilityCalendar.tsx @@ -41,7 +41,7 @@ function buildMonths(monthCount: number, blocked: Set, startYear: number type AvailabilityResponse = { blockedDates: string[] }; -export default function AvailabilityCalendar({ listingId, hasCalendar, months = 2 }: { listingId: string; hasCalendar: boolean; months?: number }) { +export default function AvailabilityCalendar({ listingId, hasCalendar }: { listingId: string; hasCalendar: boolean }) { const { t } = useI18n(); const today = useMemo(() => new Date(), []); const [month, setMonth] = useState(today.getUTCMonth()); @@ -49,8 +49,9 @@ export default function AvailabilityCalendar({ listingId, hasCalendar, months = const [blockedDates, setBlockedDates] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const monthCount = 1; const blockedSet = useMemo(() => new Set(blockedDates), [blockedDates]); - const monthViews = useMemo(() => buildMonths(months, blockedSet, year, month), [months, blockedSet, year, month]); + const monthViews = useMemo(() => buildMonths(monthCount, blockedSet, year, month), [monthCount, blockedSet, year, month]); useEffect(() => { if (!hasCalendar) return; @@ -60,7 +61,7 @@ export default function AvailabilityCalendar({ listingId, hasCalendar, months = const params = new URLSearchParams({ month: String(month), year: String(year), - months: String(months), + months: String(monthCount), refresh: '1', }); fetch(`/api/listings/${listingId}/availability?${params.toString()}`, { cache: 'no-store', signal: controller.signal }) @@ -79,7 +80,7 @@ export default function AvailabilityCalendar({ listingId, hasCalendar, months = .finally(() => setLoading(false)); return () => controller.abort(); - }, [listingId, hasCalendar, month, year, months]); + }, [listingId, hasCalendar, month, year, monthCount]); function shiftMonth(delta: number) { const next = new Date(Date.UTC(year, month, 1)); @@ -108,7 +109,7 @@ export default function AvailabilityCalendar({ listingId, hasCalendar, months = {t('availabilityTitle')} {t('availabilityLegendBooked')} -
+
@@ -132,45 +133,43 @@ export default function AvailabilityCalendar({ listingId, hasCalendar, months =
{error ?
{error}
: null} -
- {monthViews.map((month) => ( -
-
{month.label}
-
- {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d) => ( -
- {d} -
- ))} - {month.days.map((day, idx) => ( -
- {day.label} -
- ))} -
+ {monthViews.map((monthView) => ( +
+
{monthView.label}
+
+ {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d) => ( +
+ {d} +
+ ))} + {monthView.days.map((day, idx) => ( +
+ {day.label} +
+ ))}
- ))} -
+
+ ))}
); } -- 2.45.3 From cfd6dec9d0f57031b611af7deaeccb67b4fcb20c Mon Sep 17 00:00:00 2001 From: Tero Halla-aho Date: Sun, 21 Dec 2025 22:36:27 +0200 Subject: [PATCH 2/3] Embed month/year selects inside calendar header --- app/components/AvailabilityCalendar.tsx | 33 +++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/app/components/AvailabilityCalendar.tsx b/app/components/AvailabilityCalendar.tsx index 5789805..5bdc524 100644 --- a/app/components/AvailabilityCalendar.tsx +++ b/app/components/AvailabilityCalendar.tsx @@ -109,24 +109,10 @@ export default function AvailabilityCalendar({ listingId, hasCalendar }: { listi {t('availabilityTitle')} {t('availabilityLegendBooked')} -
+
- - @@ -135,7 +121,22 @@ export default function AvailabilityCalendar({ listingId, hasCalendar }: { listi {error ?
{error}
: null} {monthViews.map((monthView) => (
-
{monthView.label}
+
+ + +
Date: Sun, 21 Dec 2025 22:43:33 +0200 Subject: [PATCH 3/3] Localize month names and tighten calendar header --- app/components/AvailabilityCalendar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/components/AvailabilityCalendar.tsx b/app/components/AvailabilityCalendar.tsx index 5bdc524..856eecf 100644 --- a/app/components/AvailabilityCalendar.tsx +++ b/app/components/AvailabilityCalendar.tsx @@ -42,7 +42,7 @@ function buildMonths(monthCount: number, blocked: Set, startYear: number type AvailabilityResponse = { blockedDates: string[] }; export default function AvailabilityCalendar({ listingId, hasCalendar }: { listingId: string; hasCalendar: boolean }) { - const { t } = useI18n(); + const { t, locale } = useI18n(); const today = useMemo(() => new Date(), []); const [month, setMonth] = useState(today.getUTCMonth()); const [year, setYear] = useState(today.getUTCFullYear()); @@ -93,9 +93,9 @@ export default function AvailabilityCalendar({ listingId, hasCalendar }: { listi () => Array.from({ length: 12 }, (_, m) => ({ value: m, - label: new Date(Date.UTC(2020, m, 1)).toLocaleString(undefined, { month: 'long' }), + label: new Date(Date.UTC(2020, m, 1)).toLocaleString(locale, { month: 'long' }), })), - [], + [locale], ); const yearOptions = useMemo(() => { const current = today.getUTCFullYear(); @@ -107,9 +107,9 @@ export default function AvailabilityCalendar({ listingId, hasCalendar }: { listi
{t('availabilityTitle')} - {t('availabilityLegendBooked')}
-
+
+ {t('availabilityLegendBooked')} @@ -129,7 +129,7 @@ export default function AvailabilityCalendar({ listingId, hasCalendar }: { listi ))} - setYear(Number(e.target.value))} disabled={!hasCalendar || loading} style={{ width: 96 }}> {yearOptions.map((y) => (