+ {reservations
+ .sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime())
+ .map(reservation => (
+
+
+
+
+ {reservation.title}
+
+
+ {reservation.resource.name}
+ {reservation.resource.location && ` • ${reservation.resource.location}`}
- )}
+
+
+ {reservation.status}
+
+
+
+
+ {getReservationLabel(reservation)}
+
+
+ {reservation.description && (
+
+ {reservation.description}
+
+ )}
+
+
+
+ Booked on {format(new Date(reservation.createdAt), 'MMM d, yyyy')}
+
+
+
+
+ View Calendar
+
+
))}
diff --git a/src/services/api.ts b/src/services/api.ts
deleted file mode 100644
index 3e568ae..0000000
--- a/src/services/api.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { Resource, Reservation, CreateReservationRequest } from '../types';
-
-// Mock data for development - will be replaced with real API calls
-const mockResources: Resource[] = [
- {
- id: '1',
- name: 'Conference Room A',
- description: 'Large conference room with projector and whiteboard',
- location: 'Building 1, Floor 2',
- capacity: 20,
- imageUrl: '/images/conference-room.jpg',
- amenities: ['Projector', 'Whiteboard', 'Video Conference'],
- isAvailable: true,
- },
- {
- id: '2',
- name: 'Meeting Room B',
- description: 'Small meeting room for team discussions',
- location: 'Building 1, Floor 1',
- capacity: 6,
- amenities: ['TV', 'Whiteboard'],
- isAvailable: true,
- },
- {
- id: '3',
- name: 'Training Room',
- description: 'Spacious training room with multiple screens',
- location: 'Building 2, Floor 3',
- capacity: 30,
- amenities: ['Projector', 'Sound System', 'Recording Equipment'],
- isAvailable: false,
- },
-];
-
-const mockReservations: Reservation[] = [
- {
- id: '1',
- resourceId: '1',
- resource: mockResources[0],
- userId: 'user1',
- startTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')}T10:00:00`),
- endTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')}T12:00:00`),
- title: 'Team Meeting',
- description: 'Weekly team sync',
- status: 'confirmed',
- createdAt: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate() - 2).padStart(2, '0')}T09:00:00`),
- },
- {
- id: '2',
- resourceId: '2',
- resource: mockResources[1],
- userId: 'user2',
- startTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')}T14:00:00`),
- endTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate()).padStart(2, '0')}T15:30:00`),
- title: 'Client Presentation',
- status: 'pending',
- createdAt: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate() - 1).padStart(2, '0')}T11:00:00`),
- },
- {
- id: '3',
- resourceId: '1',
- resource: mockResources[0],
- userId: 'user3',
- startTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate() + 1).padStart(2, '0')}T09:00:00`),
- endTime: new Date(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}-${String(new Date().getDate() + 1).padStart(2, '0')}T11:00:00`),
- title: 'Project Planning',
- description: 'Q1 planning session',
- status: 'pending',
- createdAt: new Date(),
- },
-];
-
-export const api = {
- // Resources
- getResources: async (): Promise
=> {
- // Simulate API delay
- await new Promise(resolve => setTimeout(resolve, 500));
- return mockResources;
- },
-
- getResource: async (id: string): Promise => {
- await new Promise(resolve => setTimeout(resolve, 300));
- return mockResources.find(r => r.id === id) || null;
- },
-
- // Reservations
- getReservations: async (resourceId?: string): Promise => {
- await new Promise(resolve => setTimeout(resolve, 500));
- if (resourceId) {
- return mockReservations.filter(r => r.resourceId === resourceId);
- }
- return mockReservations;
- },
-
- createReservation: async (data: CreateReservationRequest): Promise => {
- await new Promise(resolve => setTimeout(resolve, 800));
- const resource = mockResources.find(r => r.id === data.resourceId);
- if (!resource) {
- throw new Error('Resource not found');
- }
-
- const newReservation: Reservation = {
- id: Date.now().toString(),
- resourceId: data.resourceId,
- resource,
- userId: 'current-user', // Will come from authentication
- startTime: data.startTime,
- endTime: data.endTime,
- title: data.title,
- description: data.description,
- status: 'pending',
- createdAt: new Date(),
- };
-
- mockReservations.push(newReservation);
- console.log('New reservation created:', newReservation);
- console.log('All reservations now:', mockReservations);
- return newReservation;
- },
-
- updateReservationStatus: async (id: string, status: 'confirmed' | 'cancelled'): Promise => {
- await new Promise(resolve => setTimeout(resolve, 500));
- const reservation = mockReservations.find(r => r.id === id);
- if (!reservation) {
- throw new Error('Reservation not found');
- }
-
- reservation.status = status;
- return reservation;
- },
-};
\ No newline at end of file
diff --git a/src/services/librebooking-api.ts b/src/services/librebooking-api.ts
index 0f21471..65b54ae 100644
--- a/src/services/librebooking-api.ts
+++ b/src/services/librebooking-api.ts
@@ -1,133 +1,241 @@
-import { Resource, Reservation, CreateReservationRequest } from '../types';
+import { Resource, CreateReservationRequest, Reservation } from '../types';
-export interface LibreBookingAPIConfig {
- baseURL: string;
- apiKey?: string;
- timeout?: number;
-}
-
-class LibreBookingAPIClient {
- private config: LibreBookingAPIConfig;
+export class SimpleLibreBookingClient {
private baseUrl: string;
+ private sessionToken: string | null = null;
+ private userId: string | null = null;
- constructor(config: LibreBookingAPIConfig) {
- this.config = config;
- this.baseUrl = config.baseURL.replace(/\/$/, ''); // Remove trailing slash
+ constructor(baseUrl?: string) {
+ this.baseUrl = baseUrl || process.env.REACT_APP_LIBREBOOKING_API_URL || 'http://localhost:8080/Web';
}
- private async makeRequest(
- endpoint: string,
- options: RequestInit = {}
- ): Promise {
- const url = `${this.baseUrl}${endpoint}`;
+ async authenticate(username: string, password: string): Promise {
+ try {
+ console.log('Attempting authentication with:', { username, baseUrl: this.baseUrl });
+
+ const response = await fetch(`${this.baseUrl}/Services/index.php/Authentication/Authenticate`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ username, password }),
+ });
+
+ console.log('Authentication response status:', response.status);
+ const data = await response.json();
+ console.log('Authentication response data:', data);
+
+ if (response.ok && data.isAuthenticated) {
+ this.sessionToken = data.sessionToken;
+ this.userId = data.userId?.toString();
+ console.log('Authentication successful:', { sessionToken: this.sessionToken, userId: this.userId });
+ return true;
+ } else {
+ console.log('Authentication failed:', { status: response.status, data });
+ return false;
+ }
+ } catch (error) {
+ console.error('Authentication error:', error);
+ return false;
+ }
+ }
+
+ async getResources(): Promise {
+ if (!this.sessionToken) {
+ const username = process.env.REACT_APP_LIBREBOOKING_USERNAME;
+ const password = process.env.REACT_APP_LIBREBOOKING_PASSWORD;
+
+ if (username && password) {
+ const authenticated = await this.authenticate(username, password);
+ if (!authenticated) {
+ throw new Error('Authentication failed');
+ }
+ }
+ }
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (this.sessionToken && this.userId) {
+ headers['X-Booked-SessionToken'] = this.sessionToken;
+ headers['X-Booked-UserId'] = this.userId;
+ }
+
+ const response = await fetch(`${this.baseUrl}/Services/index.php/Resources/`, {
+ headers,
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log('Resources response:', data);
+ return data.resources || [];
+ }
+
+ async getResource(id: string): Promise {
+ if (!this.sessionToken) {
+ const username = process.env.REACT_APP_LIBREBOOKING_USERNAME;
+ const password = process.env.REACT_APP_LIBREBOOKING_PASSWORD;
+
+ if (username && password) {
+ const authenticated = await this.authenticate(username, password);
+ if (!authenticated) {
+ throw new Error('Authentication failed');
+ }
+ }
+ }
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (this.sessionToken && this.userId) {
+ headers['X-Booked-SessionToken'] = this.sessionToken;
+ headers['X-Booked-UserId'] = this.userId;
+ }
+
+ const response = await fetch(`${this.baseUrl}/Services/index.php/Resources/${id}`, {
+ headers,
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log('Resource response:', data);
+ return data.resource || null;
+ }
+
+ async getReservations(resourceId?: string): Promise {
+ if (!this.sessionToken) {
+ const username = process.env.REACT_APP_LIBREBOOKING_USERNAME;
+ const password = process.env.REACT_APP_LIBREBOOKING_PASSWORD;
+
+ if (username && password) {
+ const authenticated = await this.authenticate(username, password);
+ if (!authenticated) {
+ throw new Error('Authentication failed');
+ }
+ }
+ }
+
+ const url = resourceId
+ ? `${this.baseUrl}/Services/index.php/Reservations/?resourceId=${resourceId}`
+ : `${this.baseUrl}/Services/index.php/Reservations/`;
const headers: Record = {
'Content-Type': 'application/json',
- ...(options.headers as Record || {}),
};
-
- if (this.config.apiKey) {
- headers['Authorization'] = `Bearer ${this.config.apiKey}`;
+
+ if (this.sessionToken && this.userId) {
+ headers['X-Booked-SessionToken'] = this.sessionToken;
+ headers['X-Booked-UserId'] = this.userId;
}
- try {
- const response = await fetch(url, {
- ...options,
- headers,
- signal: AbortSignal.timeout(this.config.timeout || 10000),
- });
+ const response = await fetch(url, {
+ headers,
+ });
- if (!response.ok) {
- throw new Error(`API Error: ${response.status} ${response.statusText}`);
- }
-
- const contentType = response.headers.get('content-type');
- if (contentType && contentType.includes('application/json')) {
- return await response.json();
- } else {
- return response.text() as unknown as T;
- }
- } catch (error) {
- if (error instanceof Error) {
- throw new Error(`Network error: ${error.message}`);
- }
- throw error;
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
}
- }
- // Resources
- async getResources(): Promise {
- return this.makeRequest('/api/resources');
- }
-
- async getResource(id: string): Promise {
- return this.makeRequest(`/api/resources/${id}`);
- }
-
- // Reservations
- async getReservations(resourceId?: string): Promise {
- const params = resourceId ? `?resourceId=${resourceId}` : '';
- return this.makeRequest(`/api/reservations${params}`);
- }
-
- async getReservation(id: string): Promise {
- return this.makeRequest(`/api/reservations/${id}`);
+ const data = await response.json();
+ console.log('Reservations response:', data);
+ return data.reservations || [];
}
async createReservation(data: CreateReservationRequest): Promise {
- return this.makeRequest('/api/reservations', {
+ if (!this.sessionToken) {
+ const username = process.env.REACT_APP_LIBREBOOKING_USERNAME;
+ const password = process.env.REACT_APP_LIBREBOOKING_PASSWORD;
+
+ if (username && password) {
+ const authenticated = await this.authenticate(username, password);
+ if (!authenticated) {
+ throw new Error('Authentication failed');
+ }
+ }
+ }
+
+ // Use the exact field names expected by LibreBooking API
+ const requestData = {
+ resourceId: data.resourceId,
+ startDateTime: data.startDateTime,
+ endDateTime: data.endDateTime,
+ title: data.title,
+ description: data.description || '',
+ userId: this.getUserId() || undefined,
+ allowParticipation: true,
+ termsAccepted: true
+ };
+
+ console.log('Creating reservation with data:', requestData);
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (this.sessionToken && this.userId) {
+ headers['X-Booked-SessionToken'] = this.sessionToken;
+ headers['X-Booked-UserId'] = this.userId;
+ }
+
+ const response = await fetch(`${this.baseUrl}/Services/index.php/Reservations/`, {
method: 'POST',
- body: JSON.stringify(data),
+ headers,
+ body: JSON.stringify(requestData),
});
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const reservation = await response.json();
+ return reservation;
}
- async updateReservationStatus(
- id: string,
- status: 'confirmed' | 'cancelled'
- ): Promise {
- return this.makeRequest(`/api/reservations/${id}/status`, {
- method: 'PATCH',
+ async updateReservationStatus(id: string, status: 'confirmed' | 'cancelled'): Promise {
+ if (!this.sessionToken) {
+ const username = process.env.REACT_APP_LIBREBOOKING_USERNAME;
+ const password = process.env.REACT_APP_LIBREBOOKING_PASSWORD;
+
+ if (username && password) {
+ const authenticated = await this.authenticate(username, password);
+ if (!authenticated) {
+ throw new Error('Authentication failed');
+ }
+ }
+ }
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (this.sessionToken && this.userId) {
+ headers['X-Booked-SessionToken'] = this.sessionToken;
+ headers['X-Booked-UserId'] = this.userId;
+ }
+
+ const response = await fetch(`${this.baseUrl}/Services/index.php/Reservations/${id}`, {
+ method: 'PUT',
+ headers,
body: JSON.stringify({ status }),
});
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const reservation = await response.json();
+ return reservation;
}
- async cancelReservation(id: string): Promise {
- return this.makeRequest(`/api/reservations/${id}/cancel`, {
- method: 'POST',
- });
+ getUserId(): string | null {
+ return this.userId || null;
}
-
- // Users (if needed for admin functions)
- async getUsers(): Promise {
- return this.makeRequest('/api/users');
- }
-
- async getUserReservations(userId: string): Promise {
- return this.makeRequest(`/api/users/${userId}/reservations`);
- }
-}
-
-// Factory function to create API client with environment variables
-export function createLibreBookingClient(
- config?: Partial
-): LibreBookingAPIClient {
- const defaultConfig: LibreBookingAPIConfig = {
- baseURL: process.env.REACT_APP_LIBREBOOKING_API_URL || 'http://localhost:8080',
- apiKey: process.env.REACT_APP_LIBREBOOKING_API_KEY,
- timeout: 10000,
- };
-
- const finalConfig = { ...defaultConfig, ...config };
- return new LibreBookingAPIClient(finalConfig);
-}
-
-// Example of how to integrate with the existing mock API
-export function integrateWithMockAPI(mockAPI: any) {
- return {
- getResources: mockAPI.getResources,
- getResource: mockAPI.getResource,
- getReservations: mockAPI.getReservations,
- createReservation: mockAPI.createReservation,
- updateReservationStatus: mockAPI.updateReservationStatus,
- };
}
\ No newline at end of file
diff --git a/src/types/index.ts b/src/types/index.ts
index 452af51..b97f035 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -31,8 +31,11 @@ export interface User {
export interface CreateReservationRequest {
resourceId: string;
- startTime: Date;
- endTime: Date;
+ startDateTime: string; // Match field name from LibreBooking API
+ endDateTime: string; // Match field name from LibreBooking API
title: string;
description?: string;
+ userId?: string; // Allow null initially
+ allowParticipation?: boolean;
+ termsAccepted?: boolean;
}
\ No newline at end of file
diff --git a/test-api.js b/test-api.js
new file mode 100644
index 0000000..cb116ab
--- /dev/null
+++ b/test-api.js
@@ -0,0 +1,37 @@
+// Test script to verify API connection
+const baseUrl = 'http://localhost:8080/Web';
+
+async function testAuth() {
+ console.log('Testing authentication...');
+
+ const response = await fetch(`${baseUrl}/Services/index.php/Authentication/Authenticate`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ username: 'admin',
+ password: 'password'
+ }),
+ });
+
+ const data = await response.json();
+ console.log('Auth response:', data);
+
+ if (data.isAuthenticated) {
+ console.log('Testing resources with session token...');
+
+ const resourcesResponse = await fetch(`${baseUrl}/Services/index.php/Resources/`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Booked-SessionToken': data.sessionToken,
+ 'X-Booked-UserId': data.userId,
+ },
+ });
+
+ const resourcesData = await resourcesResponse.json();
+ console.log('Resources response:', resourcesData);
+ }
+}
+
+testAuth().catch(console.error);
\ No newline at end of file