Skip to content

Add room booking system for members#45

Merged
wearsshoes merged 10 commits intomainfrom
claude/room-bookings
Jan 29, 2026
Merged

Add room booking system for members#45
wearsshoes merged 10 commits intomainfrom
claude/room-bookings

Conversation

@wearsshoes
Copy link
Collaborator

Summary

  • Add room booking feature allowing logged-in members to reserve meeting rooms and call booths marked as "bookable" in Airtable
  • Created new "Room Bookings" table in Airtable with fields: Room (link), Booked By (link), Start, End, Purpose, Status
  • Multi-room calendar view showing all bookable rooms side-by-side with hourly slots (8am-10pm)
  • Conflict detection prevents double-booking
  • Users can view and cancel their own bookings

Changes

  • app/lib/airtable.ts - Added Rooms and RoomBookings to Tables constant
  • app/lib/room-bookings.ts - New library with booking helper functions
  • app/portal/api/rooms/route.ts - GET endpoint for bookable rooms
  • app/portal/api/room-bookings/route.ts - GET/POST for listing and creating bookings
  • app/portal/api/room-bookings/[id]/route.ts - DELETE for cancelling bookings
  • app/portal/book-room/ - New booking UI with form and day calendar view
  • app/portal/page.tsx - Added link to book-room section

Test plan

  • Navigate to /portal/book-room when logged in
  • Select a room and time slot, create a booking
  • Verify booking appears in calendar grid (marked with x)
  • Verify booking appears in "your bookings" list
  • Try to double-book same slot - should show conflict error
  • Cancel a booking - should remove from list

🤖 Generated with Claude Code

wearsshoes and others added 6 commits January 28, 2026 19:28
- Create Room Bookings table in Airtable via API
- Add room-bookings.ts library with booking/conflict logic
- Add API routes for rooms and bookings (GET, POST, DELETE)
- Add /portal/book-room UI with hourly time slots
- Add link to book rooms from portal main page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shows hourly time slots for the selected room and date:
- Green highlight for user's current selection
- Red highlight for already booked slots
- Updates in real-time when booking is created

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Airtable's ARRAYJOIN function doesn't work on linked record fields
in formulas. Changed to fetch all matching bookings and filter by
room/user ID client-side instead.

Also updated day calendar to show all rooms side-by-side.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
mox-sf Ready Ready Preview, Comment Jan 29, 2026 7:53am

Request Review

- Move date field before room selector
- Prefill start/end times to nearest hour
- Switch table orientation: rooms as rows, times as columns
- Group rooms by floor in calendar view

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 29, 2026

Code Review

Found 1 issue during review:

Race Condition: Time-Of-Check to Time-Of-Use (TOCTOU) in Booking Creation

Location: app/portal/api/room-bookings/route.ts lines 90-135

Issue: This endpoint has a race condition that could allow double-bookings for the same room and time slot.

The code checks for conflicts with findConflicts() (lines 90-103), then creates the booking in a separate operation (lines 127-135). Between these two operations, another concurrent request could pass the same conflict check, and both requests would proceed to create overlapping bookings.

Why This Is Significant:
The gap between check and create includes multiple network round-trips:

  • Airtable query for conflict detection
  • Airtable query for getRoom() (lines 106-113)
  • Potential query to fetch user details (lines 119-124)
  • Airtable create operation

This creates a realistic window for race conditions, especially when multiple users are booking popular rooms simultaneously (e.g., at the start of a workday).

Code Reference:

// Check for conflicts
const conflicts = await findConflicts(roomId, start, end)
if (conflicts.length > 0) {
return NextResponse.json(
{
error: 'Time slot is already booked',
conflicts: conflicts.map((c) => ({
startDate: c.startDate,
endDate: c.endDate,
})),
},
{ status: 409 }
)
}
// Get room details
const room = await getRoom(roomId)
if (!room) {
return NextResponse.json({ error: 'Room not found' }, { status: 404 })
}
if (!room.bookable) {
return NextResponse.json({ error: 'Room is not bookable' }, { status: 400 })
}
// Get user details
const userId = session.viewingAsUserId || session.userId
const userName = session.viewingAsName || session.name || ''
// If we don't have the name from session, fetch it
let finalUserName = userName
if (!finalUserName) {
const userRecord = await getRecord<PersonFields>(Tables.People, userId)
finalUserName = userRecord?.fields.Name || 'Unknown'
}
// Create the booking
const booking = await createBooking({
roomId,
roomName: room.name,
userId,
userName: finalUserName,
startDate: start,
endDate: end,
purpose,
})

Suggested Fixes:

  1. Optimistic concurrency control: After creating the booking, re-check for conflicts and delete the booking if conflicts appeared
  2. Unique constraint: Use Airtable automations/scripting to enforce atomicity
  3. Distributed locking: Implement locking around the check-and-create sequence
  4. Accept the risk: Document that double-bookings are possible and provide admin tools to resolve them

The first option (optimistic concurrency) is likely the most practical for Airtable-based systems.

- Sort floors in descending order (higher floors on top)
- If after 9pm, default to next day at 8am instead of empty times
- Before 8am, default to today at 8am

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Jan 29, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Use local time formatting instead of toISOString() which converts
to UTC. At 11pm PST, toISOString() returns the next day in UTC,
causing the wrong date to be displayed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After creating a booking, re-check for conflicts (excluding the new
booking). If another booking was created concurrently for the same
slot, cancel our booking and return an error asking user to retry.

This prevents double-bookings that could occur when two users try
to book the same slot simultaneously.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wearsshoes wearsshoes merged commit 94fc16d into main Jan 29, 2026
2 of 3 checks passed
@wearsshoes wearsshoes deleted the claude/room-bookings branch January 29, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant