diff --git a/apps/api/src/reservations/reservations.service.ts b/apps/api/src/reservations/reservations.service.ts
index 2aef008..20c405a 100644
--- a/apps/api/src/reservations/reservations.service.ts
+++ b/apps/api/src/reservations/reservations.service.ts
@@ -31,6 +31,17 @@ export class ReservationsService {
throw new BadRequestException('Start time must be before end time');
}
+ const now = new Date();
+ if (space.type === SpaceType.DESK) {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ if (start < today) {
+ throw new BadRequestException('Cannot book desks in the past');
+ }
+ } else if (start < now) {
+ throw new BadRequestException('Cannot book in the past');
+ }
+
// Rules
if (space.type === SpaceType.DESK) {
// Desks: Full day (setting to midnight of start and end of that day)
@@ -204,6 +215,17 @@ export class ReservationsService {
throw new BadRequestException('Start time must be before end time');
}
+ const now = new Date();
+ if (space.type === SpaceType.DESK) {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ if (start < today) {
+ throw new BadRequestException('Cannot update desk reservation to the past');
+ }
+ } else if (start < now) {
+ throw new BadRequestException('Cannot update reservation to the past');
+ }
+
// Rules
if (space.type === SpaceType.DESK) {
start.setHours(0, 0, 0, 0);
diff --git a/apps/web/src/lib/components/BookingModal.svelte b/apps/web/src/lib/components/BookingModal.svelte
index d3fae87..d8ce122 100644
--- a/apps/web/src/lib/components/BookingModal.svelte
+++ b/apps/web/src/lib/components/BookingModal.svelte
@@ -72,6 +72,14 @@
start.setHours(0, 0, 0, 0);
end = new Date(bookingDate);
end.setHours(23, 59, 59, 999);
+
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ if (start < today) {
+ error = $t('booking.errorPast');
+ loading = false;
+ return;
+ }
} else {
const [sh, sm] = startTime.split(':').map(Number);
const [eh, em] = endTime.split(':').map(Number);
@@ -82,6 +90,12 @@
end = new Date(bookingDate);
end.setHours(eh, em, 0, 0);
+ if (start < new Date()) {
+ error = $t('booking.errorPast');
+ loading = false;
+ return;
+ }
+
if (end <= start) {
error = $t('booking.errorTime');
loading = false;
@@ -143,6 +157,10 @@
const slotStart = new Date(bookingDate);
slotStart.setHours(h, m, 0, 0);
+ if (slotStart < new Date()) {
+ return { isPast: true };
+ }
+
return dayReservations.find(r => {
const rStart = new Date(r.startTime);
const rEnd = new Date(r.endTime);
@@ -287,12 +305,13 @@
diff --git a/apps/web/src/lib/i18n/locales/en.json b/apps/web/src/lib/i18n/locales/en.json
index 20dd97c..c34fadd 100644
--- a/apps/web/src/lib/i18n/locales/en.json
+++ b/apps/web/src/lib/i18n/locales/en.json
@@ -77,6 +77,8 @@
"errorCreating": "Failed to create booking",
"errorDeleting": "Failed to delete booking",
"errorTime": "End time must be after start time",
+ "errorPast": "Cannot book in the past",
+ "past": "This time has already passed",
"spaceType": {
"DESK": "Desk",
"MEETING_ROOM": "Meeting Room",
diff --git a/apps/web/src/lib/i18n/locales/es-ES.json b/apps/web/src/lib/i18n/locales/es-ES.json
index cc08f65..0c7947f 100644
--- a/apps/web/src/lib/i18n/locales/es-ES.json
+++ b/apps/web/src/lib/i18n/locales/es-ES.json
@@ -77,6 +77,8 @@
"errorCreating": "Error al crear la reserva",
"errorDeleting": "Error al eliminar la reserva",
"errorTime": "La hora de finalización debe ser posterior a la de inicio",
+ "errorPast": "No se puede reservar en el pasado",
+ "past": "Esta hora ya ha pasado",
"spaceType": {
"DESK": "Puesto",
"MEETING_ROOM": "Sala de reuniones",
diff --git a/apps/web/src/routes/dashboard/building/[id]/+page.svelte b/apps/web/src/routes/dashboard/building/[id]/+page.svelte
index dededd5..b22ecef 100644
--- a/apps/web/src/routes/dashboard/building/[id]/+page.svelte
+++ b/apps/web/src/routes/dashboard/building/[id]/+page.svelte
@@ -108,6 +108,7 @@
id="date-select"
type="date"
bind:value={selectedDateString}
+ min={new Date().toISOString().split('T')[0]}
class="absolute inset-0 opacity-0 cursor-pointer z-10 w-full h-full"
on:click={(e) => e.currentTarget.showPicker()}
/>
diff --git a/apps/web/src/routes/dashboard/floor/[id]/+page.svelte b/apps/web/src/routes/dashboard/floor/[id]/+page.svelte
index e559c20..82fc302 100644
--- a/apps/web/src/routes/dashboard/floor/[id]/+page.svelte
+++ b/apps/web/src/routes/dashboard/floor/[id]/+page.svelte
@@ -69,6 +69,7 @@
id="date-select"
type="date"
bind:value={selectedDateString}
+ min={new Date().toLocaleDateString('en-CA')}
class="absolute inset-0 opacity-0 cursor-pointer z-10 w-full h-full"
on:click={(e) => e.currentTarget.showPicker()}
/>
diff --git a/apps/web/src/routes/dashboard/my-bookings/+page.svelte b/apps/web/src/routes/dashboard/my-bookings/+page.svelte
index 163b83f..ce7ef2b 100644
--- a/apps/web/src/routes/dashboard/my-bookings/+page.svelte
+++ b/apps/web/src/routes/dashboard/my-bookings/+page.svelte
@@ -74,6 +74,21 @@
const start = new Date(`${editStartTimeDate}T${editStartTimeTime}`);
const end = new Date(`${editEndTimeDate}T${editEndTimeTime}`);
+ const now = new Date();
+ if (editingReservation.space.type === 'DESK') {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ if (start < today) {
+ alert($t('booking.errorPast'));
+ saving = false;
+ return;
+ }
+ } else if (start < now) {
+ alert($t('booking.errorPast'));
+ saving = false;
+ return;
+ }
+
await apiFetch(`/reservations/${editingReservation.id}`, {
method: "PATCH",
body: JSON.stringify({
@@ -268,6 +283,7 @@
e.currentTarget.showPicker()}
/>