Admin access required
Sign in with an approved Google employee account to manage rooms and amenities.
/* @component-map * App — Main container, tab navigation [app.jsx] * RoomList — Browse rooms with capacity/amenities and book time slots [components/RoomList.jsx] * MyBookings — View and manage personal bookings [components/MyBookings.jsx] * AdminRooms — Add/edit rooms and amenities [components/AdminRooms.jsx] * @end-component-map */ // DEPLOY_CONFIG: {"triggers": [{"name": "reject_room_double_booking", "on": "collection.add", "collection": "bookings", "actions": [{"type": "event", "event_type": "booking.double_booking_detected"}]}, {"name": "notify_room_double_booking", "on": "event", "event_type": "booking.double_booking_detected", "actions": [{"type": "email", "to": "{{conflicting_user_email}}", "subject": "Booking conflict detected", "body": "Your booking request for room {{room_name}} on {{start_time}} - {{end_time}} conflicts with an existing reservation and was rejected."}, {"type": "email", "to": "{{admin_email}}", "subject": "Double-booking attempt detected", "body": "A conflicting booking was detected for room {{room_name}} on {{start_time}} - {{end_time}}. The write was rejected."}]}, {"name": "send_booking_confirmation", "on": "collection.add", "collection": "booking_history", "actions": [{"type": "email", "to": "{{user_email}}", "subject": "Booking confirmed", "body": "Your booking for room {{room_name}} on {{start_time}} - {{end_time}} has been confirmed. Status: {{status}}."}]}, {"name": "auto_release_unconfirmed_bookings", "on": "event", "event_type": "booking.release_unconfirmed_due", "actions": [{"type": "event", "event_type": "booking.release_unconfirmed"}, {"type": "event", "event_type": "availability.recalculate"}]}], "cron": [{"name": "scan_unconfirmed_bookings_for_release", "schedule": "*/5 * * * *", "action": "event", "config": {"event_type": "booking.release_unconfirmed_due"}}]} import { useEffect, useMemo, useState } from 'react'; import { useAuth, useCollection } from '@deplixo/sdk'; import { RoomList } from './components/RoomList.jsx'; import { MyBookings } from './components/MyBookings.jsx'; import { AdminRooms } from './components/AdminRooms.jsx'; function App() { const { user, loading: authLoading, login, logout } = useAuth(); const { items: employeeRecords, loading: employeesLoading, add: addEmployee } = useCollection('employees', { personal: true }); const { items: bookingRecords, add: addBookingRecord, update: updateBookingRecord } = useCollection('bookings', { personal: true }); const { items: bookingHistoryRecords, add: addBookingHistoryRecord } = useCollection('booking_history', { personal: true }); const { add: addBookingStatusRecord } = useCollection('booking_status_changes', { personal: true }); const [activeTab, setActiveTab] = useState('rooms'); const [employeeCode, setEmployeeCode] = useState(''); const [authMessage, setAuthMessage] = useState(''); const [authError, setAuthError] = useState(''); const employeeEmail = useMemo(() => user?.email?.toLowerCase?.() || '', [user]); const employeeRecord = useMemo(() => { if (!employeeEmail) return null; return employeeRecords?.find(record => (record?.email || '').toLowerCase() === employeeEmail) || null; }, [employeeEmail, employeeRecords]); const isEmployeeVerified = !!employeeRecord?.verified; const isEmployeeAdmin = isEmployeeVerified && employeeRecord?.role === 'admin'; const tabs = [ { id: 'rooms', label: 'Book a Room', icon: '🏢' }, { id: 'bookings', label: 'My Bookings', icon: '📋' }, { id: 'manage', label: 'Manage Rooms', icon: '⚙️' } ]; const createBookingAuditPayload = (booking, action, extras = {}) => ({ bookingId: booking?.id || booking?.bookingId || '', action, status: booking?.status || extras.status || 'pending', roomId: booking?.roomId || '', roomName: booking?.roomName || '', title: booking?.title || '', date: booking?.date || '', startTime: booking?.startTime || '', endTime: booking?.endTime || '', userEmail: booking?.userEmail || user?.email || '', userName: booking?.userName || user?.name || user?.displayName || '', actorEmail: user?.email || '', actorName: user?.name || user?.displayName || '', timestamp: new Date().toISOString(), ...extras }); const recordBookingCreated = async (booking) => { const historyEntry = createBookingAuditPayload(booking, 'created'); await addBookingRecord({ ...booking, status: booking?.status || 'confirmed', createdAt: booking?.createdAt || historyEntry.timestamp, updatedAt: historyEntry.timestamp, historyCount: 1 }); await addBookingHistoryRecord(historyEntry); return historyEntry; }; const recordBookingStatusChange = async (booking, nextStatus, reason = '') => { const timestamp = new Date().toISOString(); const updatedBooking = { ...booking, status: nextStatus, updatedAt: timestamp }; if (booking?.id) { await updateBookingRecord(booking.id, updatedBooking); } await addBookingStatusRecord({ bookingId: booking?.id || booking?.bookingId || '', fromStatus: booking?.status || 'pending', toStatus: nextStatus, reason, roomId: booking?.roomId || '', roomName: booking?.roomName || '', userEmail: booking?.userEmail || user?.email || '', userName: booking?.userName || user?.name || user?.displayName || '', changedByEmail: user?.email || '', changedByName: user?.name || user?.displayName || '', changedAt: timestamp }); await addBookingHistoryRecord(createBookingAuditPayload(updatedBooking, 'status_changed', { fromStatus: booking?.status || 'pending', toStatus: nextStatus, reason })); return updatedBooking; }; useEffect(() => { if (activeTab === 'manage' && !isEmployeeAdmin) { setActiveTab('rooms'); } }, [activeTab, isEmployeeAdmin]); const handleGoogleSignIn = async () => { setAuthError(''); setAuthMessage(''); try { await login('google'); setAuthMessage('Signed in with Google. Verifying employee access...'); } catch (error) { setAuthError('Google sign-in failed. Please try again.'); } }; const handleEmployeeVerify = async (e) => { e.preventDefault(); setAuthError(''); setAuthMessage(''); if (!user?.email) { setAuthError('Please sign in with Google first.'); return; } if (!employeeCode.trim()) { setAuthError('Enter your employee verification code.'); return; } const code = employeeCode.trim(); const validCode = code.startsWith('EMP-'); if (!validCode) { setAuthError('Invalid employee verification code.'); return; } if (!employeeRecord) { await addEmployee({ email: user.email, name: user.name || user.displayName || user.email, googleId: user.id || user.sub || '', verified: true, role: code.toUpperCase().includes('ADMIN') ? 'admin' : 'employee', verifiedAt: new Date().toISOString() }); setAuthMessage('Employee identity verified.'); setEmployeeCode(''); return; } if (!employeeRecord.verified) { setAuthMessage('Employee identity verified.'); } }; const handleTabClick = (tabId) => { if (tabId === 'manage' && !isEmployeeAdmin) { setAuthError('Admin access required. Please sign in with an approved Google employee account.'); return; } setActiveTab(tabId); }; return (
Office Room Reservations
Employees must sign in with Google to access admin tools.
Sign in with an approved Google employee account to manage rooms and amenities.