Address issue with timezones and improve embed
This commit is contained in:
parent
2da8984a8b
commit
4043ec775b
6 changed files with 42 additions and 80 deletions
9
api/package-lock.json
generated
9
api/package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"graphile-worker": "^0.16.6",
|
||||
"hono": "^4.7.7",
|
||||
"hono-sessions": "^0.7.2",
|
||||
"luxon": "^3.7.2",
|
||||
"multer": "^2.0.2",
|
||||
"nodemailer": "^7.0.4",
|
||||
"stream-chat": "^9.8.0",
|
||||
|
|
@ -3905,6 +3906,14 @@
|
|||
"get-func-name": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"graphile-worker": "^0.16.6",
|
||||
"hono": "^4.7.7",
|
||||
"hono-sessions": "^0.7.2",
|
||||
"luxon": "^3.7.2",
|
||||
"multer": "^2.0.2",
|
||||
"nodemailer": "^7.0.4",
|
||||
"stream-chat": "^9.8.0",
|
||||
|
|
@ -41,5 +42,5 @@
|
|||
},
|
||||
"overrides": {
|
||||
"linkifyjs": "^4.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
type EventTypeConfig,
|
||||
type Exception,
|
||||
generateTimeSlots,
|
||||
getDateString,
|
||||
getDateStringCET,
|
||||
getDayOfWeek,
|
||||
type WeeklyAvailability,
|
||||
} from "../slots.js";
|
||||
|
|
@ -734,8 +734,10 @@ describe("generateTimeSlots", () => {
|
|||
|
||||
expect(slot09_00?.available, "09:00 should be available").to.be.true;
|
||||
expect(slot09_30?.available, "09:30 should not be available").to.be.false;
|
||||
expect(slot10_00?.available, "10:00 should not be unavailable").to.be.false; // Within buffered time
|
||||
expect(slot10_30?.available, "10:30 should not be unavailable").to.be.false; // Within buffered time
|
||||
expect(slot10_00?.available, "10:00 should not be unavailable").to.be
|
||||
.false; // Within buffered time
|
||||
expect(slot10_30?.available, "10:30 should not be unavailable").to.be
|
||||
.false; // Within buffered time
|
||||
expect(slot11_00?.available, "11:00 should be available").to.be.true; // After buffered time
|
||||
});
|
||||
|
||||
|
|
@ -1143,8 +1145,12 @@ describe("generateTimeSlots", () => {
|
|||
});
|
||||
|
||||
it("should format date strings correctly", () => {
|
||||
expect(getDateString(new Date("2024-01-15T10:30:00Z"))).to.equal("2024-01-15");
|
||||
expect(getDateString(new Date("2024-12-31T23:59:59Z"))).to.equal("2024-12-31");
|
||||
expect(getDateStringCET(new Date("2024-01-15T10:30:00Z"))).to.equal(
|
||||
"2024-01-15"
|
||||
);
|
||||
expect(getDateStringCET(new Date("2024-12-31T23:59:59Z"))).to.equal(
|
||||
"2025-01-01"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,40 +6,12 @@ import {
|
|||
type EventTypeConfig,
|
||||
type Exception,
|
||||
generateTimeSlots,
|
||||
getDateString,
|
||||
getDateStringCET,
|
||||
getDayOfWeek,
|
||||
type TimeSlot,
|
||||
type WeeklyAvailability,
|
||||
} from "./slots.js";
|
||||
|
||||
// Helper function to get current time in CET
|
||||
function getCETTime(): Date {
|
||||
const utcNow = new Date();
|
||||
|
||||
// Use Intl.DateTimeFormat to get the correct CET/CEST time
|
||||
const formatter = new Intl.DateTimeFormat("en", {
|
||||
timeZone: "Europe/Paris",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(utcNow);
|
||||
const year = parseInt(parts.find((p) => p.type === "year")?.value || "0");
|
||||
const month =
|
||||
parseInt(parts.find((p) => p.type === "month")?.value || "0") - 1; // Month is 0-indexed
|
||||
const day = parseInt(parts.find((p) => p.type === "day")?.value || "0");
|
||||
const hour = parseInt(parts.find((p) => p.type === "hour")?.value || "0");
|
||||
const minute = parseInt(parts.find((p) => p.type === "minute")?.value || "0");
|
||||
const second = parseInt(parts.find((p) => p.type === "second")?.value || "0");
|
||||
|
||||
return new Date(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
export const publicRouter = new Hono<{
|
||||
Variables: {
|
||||
supabase: SupabaseClient;
|
||||
|
|
@ -102,7 +74,7 @@ publicRouter.get("/slots/:shortUserId/:standardName", async (c) => {
|
|||
|
||||
// Get existing events for the next month
|
||||
// Use CET time for availability calculations
|
||||
const now = getCETTime();
|
||||
const now = new Date();
|
||||
const nextMonth = new Date(now);
|
||||
nextMonth.setMonth(now.getMonth() + 2);
|
||||
|
||||
|
|
@ -110,8 +82,8 @@ publicRouter.get("/slots/:shortUserId/:standardName", async (c) => {
|
|||
.from("events")
|
||||
.select("*")
|
||||
.eq("created_by", user.id)
|
||||
.gte("start_date", getDateString(now))
|
||||
.lte("start_date", getDateString(nextMonth))
|
||||
.gte("start_date", getDateStringCET(now))
|
||||
.lte("start_date", getDateStringCET(nextMonth))
|
||||
.is("deleted_at", null);
|
||||
|
||||
if (eventsError) {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,6 @@
|
|||
import type { Tables } from "./database.types.js";
|
||||
|
||||
// Helper function to convert UTC date to CET
|
||||
function convertToCET(utcDate: Date): Date {
|
||||
// Use Intl.DateTimeFormat to get the correct CET/CEST offset
|
||||
const formatter = new Intl.DateTimeFormat("en", {
|
||||
timeZone: "Europe/Paris",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(utcDate);
|
||||
const year = parseInt(parts.find((p) => p.type === "year")?.value || "0");
|
||||
const month =
|
||||
parseInt(parts.find((p) => p.type === "month")?.value || "0") - 1; // Month is 0-indexed
|
||||
const day = parseInt(parts.find((p) => p.type === "day")?.value || "0");
|
||||
const hour = parseInt(parts.find((p) => p.type === "hour")?.value || "0");
|
||||
const minute = parseInt(parts.find((p) => p.type === "minute")?.value || "0");
|
||||
const second = parseInt(parts.find((p) => p.type === "second")?.value || "0");
|
||||
|
||||
return new Date(year, month, day, hour, minute, second);
|
||||
}
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
// Types for availability calculation
|
||||
type TimeRange = {
|
||||
|
|
@ -139,39 +115,38 @@ function getMinAdvanceBookingDate(
|
|||
config: EventTypeConfig,
|
||||
currentDate: Date
|
||||
): { date: string; time: string } {
|
||||
// Convert current UTC date to CET
|
||||
const cetCurrentDate = convertToCET(currentDate);
|
||||
|
||||
if (!config.minAdvanceBooking) {
|
||||
return {
|
||||
date: getDateString(cetCurrentDate),
|
||||
time: formatTime(cetCurrentDate.getHours(), cetCurrentDate.getMinutes()),
|
||||
date: getDateStringCET(currentDate),
|
||||
time: formatTime(currentDate.getHours(), currentDate.getMinutes()),
|
||||
};
|
||||
}
|
||||
|
||||
const { value, unit } = config.minAdvanceBooking;
|
||||
const advanceDate = new Date(cetCurrentDate);
|
||||
const advanceDate = new Date(currentDate);
|
||||
|
||||
switch (unit) {
|
||||
case "minutes":
|
||||
advanceDate.setMinutes(cetCurrentDate.getMinutes() + value);
|
||||
advanceDate.setMinutes(currentDate.getMinutes() + value);
|
||||
break;
|
||||
case "hours":
|
||||
advanceDate.setHours(cetCurrentDate.getHours() + value);
|
||||
advanceDate.setHours(currentDate.getHours() + value);
|
||||
break;
|
||||
case "days":
|
||||
advanceDate.setDate(cetCurrentDate.getDate() + value);
|
||||
advanceDate.setDate(currentDate.getDate() + value);
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
date: getDateString(advanceDate),
|
||||
date: getDateStringCET(advanceDate),
|
||||
time: formatTime(advanceDate.getHours(), advanceDate.getMinutes()),
|
||||
};
|
||||
}
|
||||
|
||||
export function getDateString(date: Date): string {
|
||||
return date.toISOString().split("T")[0];
|
||||
export function getDateStringCET(date: Date): string {
|
||||
return DateTime.fromJSDate(date)
|
||||
.setZone("Europe/Paris")
|
||||
.toFormat("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
export function generateTimeSlots(
|
||||
|
|
@ -182,14 +157,13 @@ export function generateTimeSlots(
|
|||
exceptions: Exception[],
|
||||
existingEvents: Tables<"events">[]
|
||||
): TimeSlot[] {
|
||||
const dateStr = getDateString(date);
|
||||
const dateStr = getDateStringCET(date);
|
||||
const slots: TimeSlot[] = [];
|
||||
|
||||
// Check if this date has an exception
|
||||
const exception = exceptions.find((e) => {
|
||||
const exceptionDate = new Date(e.date);
|
||||
const exceptionDateCET = convertToCET(exceptionDate);
|
||||
const exceptionDateStr = getDateString(exceptionDateCET);
|
||||
const exceptionDateStr = getDateStringCET(exceptionDate);
|
||||
return exceptionDateStr === dateStr;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -435,8 +435,8 @@ export function EmbeddedBookingPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="w-[1130px] h-[700px] m-6 bg-transparent overflow-hidden">
|
||||
<div className="h-full bg-white dark:bg-gray-800 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-700 flex overflow-hidden">
|
||||
<div className="w-[1130px] h-[700px] px-6 mt-6 mb-4 bg-transparent overflow-hidden">
|
||||
<div className="h-full bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 flex overflow-hidden">
|
||||
{/* Left Side - Event Details */}
|
||||
<div
|
||||
className={twMerge(
|
||||
|
|
|
|||
Loading…
Reference in a new issue