Phoenix21 commited on
Commit
a321c65
·
verified ·
1 Parent(s): d8d5eab

🔧 Update backend with static frontend

Browse files
backend/src/main/resources/static/app.js ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // API Base URL - will use relative URLs in production
2
+ const API_BASE = '/api';
3
+
4
+ // Current user state
5
+ let currentUser = null;
6
+ let allItems = [];
7
+ let categories = [];
8
+
9
+ // Initialize app
10
+ document.addEventListener('DOMContentLoaded', function() {
11
+ // Check if user is logged in
12
+ const token = localStorage.getItem('token');
13
+ const user = localStorage.getItem('user');
14
+
15
+ if (token && user) {
16
+ currentUser = JSON.parse(user);
17
+ updateNavigation();
18
+ }
19
+
20
+ // Load initial data
21
+ loadCategories();
22
+
23
+ // Show home page by default
24
+ const hash = window.location.hash.slice(1);
25
+ if (hash) {
26
+ showPage(hash);
27
+ }
28
+ });
29
+
30
+ // Navigation
31
+ function showPage(pageName) {
32
+ // Hide all pages
33
+ document.querySelectorAll('.page').forEach(page => {
34
+ page.classList.remove('active');
35
+ });
36
+
37
+ // Show requested page
38
+ const page = document.getElementById(pageName + 'Page');
39
+ if (page) {
40
+ page.classList.add('active');
41
+
42
+ // Load page-specific data
43
+ if (pageName === 'browse') {
44
+ loadItems();
45
+ } else if (pageName === 'my-items') {
46
+ if (!currentUser) {
47
+ showPage('login');
48
+ return;
49
+ }
50
+ loadMyItems();
51
+ } else if (pageName === 'my-bookings') {
52
+ if (!currentUser) {
53
+ showPage('login');
54
+ return;
55
+ }
56
+ loadMyBookings();
57
+ } else if (pageName === 'add-item') {
58
+ if (!currentUser) {
59
+ showPage('login');
60
+ return;
61
+ }
62
+ loadCategoriesForForm();
63
+ }
64
+
65
+ // Update URL hash
66
+ window.location.hash = pageName;
67
+ }
68
+ }
69
+
70
+ function updateNavigation() {
71
+ if (currentUser) {
72
+ document.getElementById('loginLink').style.display = 'none';
73
+ document.getElementById('registerLink').style.display = 'none';
74
+ document.getElementById('logoutLink').style.display = 'block';
75
+ document.getElementById('myItemsLink').style.display = 'block';
76
+ document.getElementById('myBookingsLink').style.display = 'block';
77
+ document.getElementById('heroLoginBtn').style.display = 'none';
78
+ } else {
79
+ document.getElementById('loginLink').style.display = 'block';
80
+ document.getElementById('registerLink').style.display = 'block';
81
+ document.getElementById('logoutLink').style.display = 'none';
82
+ document.getElementById('myItemsLink').style.display = 'none';
83
+ document.getElementById('myBookingsLink').style.display = 'none';
84
+ document.getElementById('heroLoginBtn').style.display = 'inline-block';
85
+ }
86
+ }
87
+
88
+ // API Helper Functions
89
+ async function apiCall(endpoint, options = {}) {
90
+ const token = localStorage.getItem('token');
91
+ const headers = {
92
+ 'Content-Type': 'application/json',
93
+ ...options.headers
94
+ };
95
+
96
+ if (token) {
97
+ headers['Authorization'] = `Bearer ${token}`;
98
+ }
99
+
100
+ try {
101
+ const response = await fetch(API_BASE + endpoint, {
102
+ ...options,
103
+ headers
104
+ });
105
+
106
+ if (response.status === 401) {
107
+ logout();
108
+ throw new Error('Session expired. Please login again.');
109
+ }
110
+
111
+ if (response.status === 204) {
112
+ return null;
113
+ }
114
+
115
+ const data = await response.json();
116
+
117
+ if (!response.ok) {
118
+ throw new Error(data.message || data.error || 'Request failed');
119
+ }
120
+
121
+ return data;
122
+ } catch (error) {
123
+ console.error('API Error:', error);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ // Auth Functions
129
+ async function handleLogin(event) {
130
+ event.preventDefault();
131
+
132
+ const username = document.getElementById('loginUsername').value;
133
+ const password = document.getElementById('loginPassword').value;
134
+ const errorDiv = document.getElementById('loginError');
135
+
136
+ try {
137
+ const response = await apiCall('/auth/login', {
138
+ method: 'POST',
139
+ body: JSON.stringify({ username, password })
140
+ });
141
+
142
+ localStorage.setItem('token', response.token);
143
+ localStorage.setItem('user', JSON.stringify(response.user));
144
+ currentUser = response.user;
145
+
146
+ updateNavigation();
147
+ showPage('browse');
148
+
149
+ // Clear form
150
+ document.getElementById('loginForm').reset();
151
+ errorDiv.classList.remove('show');
152
+ } catch (error) {
153
+ errorDiv.textContent = error.message;
154
+ errorDiv.classList.add('show');
155
+ }
156
+ }
157
+
158
+ async function handleRegister(event) {
159
+ event.preventDefault();
160
+
161
+ const username = document.getElementById('registerUsername').value;
162
+ const email = document.getElementById('registerEmail').value;
163
+ const name = document.getElementById('registerName').value;
164
+ const phone = document.getElementById('registerPhone').value;
165
+ const address = document.getElementById('registerAddress').value;
166
+ const password = document.getElementById('registerPassword').value;
167
+ const errorDiv = document.getElementById('registerError');
168
+
169
+ try {
170
+ await apiCall('/auth/register', {
171
+ method: 'POST',
172
+ body: JSON.stringify({
173
+ username,
174
+ email,
175
+ name,
176
+ phoneNumber: phone,
177
+ address,
178
+ password
179
+ })
180
+ });
181
+
182
+ // Auto login after registration
183
+ const loginResponse = await apiCall('/auth/login', {
184
+ method: 'POST',
185
+ body: JSON.stringify({ username, password })
186
+ });
187
+
188
+ localStorage.setItem('token', loginResponse.token);
189
+ localStorage.setItem('user', JSON.stringify(loginResponse.user));
190
+ currentUser = loginResponse.user;
191
+
192
+ updateNavigation();
193
+ showPage('browse');
194
+
195
+ // Clear form
196
+ document.getElementById('registerForm').reset();
197
+ errorDiv.classList.remove('show');
198
+ } catch (error) {
199
+ errorDiv.textContent = error.message;
200
+ errorDiv.classList.add('show');
201
+ }
202
+ }
203
+
204
+ function logout() {
205
+ localStorage.removeItem('token');
206
+ localStorage.removeItem('user');
207
+ currentUser = null;
208
+ updateNavigation();
209
+ showPage('home');
210
+ }
211
+
212
+ // Category Functions
213
+ async function loadCategories() {
214
+ try {
215
+ categories = await apiCall('/categories');
216
+
217
+ // Update category filter
218
+ const categoryFilter = document.getElementById('categoryFilter');
219
+ if (categoryFilter) {
220
+ categoryFilter.innerHTML = '<option value="">All Categories</option>';
221
+ categories.forEach(cat => {
222
+ const option = document.createElement('option');
223
+ option.value = cat.id;
224
+ option.textContent = cat.name;
225
+ categoryFilter.appendChild(option);
226
+ });
227
+ }
228
+ } catch (error) {
229
+ console.error('Failed to load categories:', error);
230
+ }
231
+ }
232
+
233
+ function loadCategoriesForForm() {
234
+ const itemCategory = document.getElementById('itemCategory');
235
+ if (itemCategory) {
236
+ itemCategory.innerHTML = '<option value="">Select a category</option>';
237
+ categories.forEach(cat => {
238
+ const option = document.createElement('option');
239
+ option.value = cat.id;
240
+ option.textContent = cat.name;
241
+ itemCategory.appendChild(option);
242
+ });
243
+ }
244
+ }
245
+
246
+ // Items Functions
247
+ async function loadItems() {
248
+ const grid = document.getElementById('itemsGrid');
249
+ grid.innerHTML = '<div class="loading">Loading items...</div>';
250
+
251
+ try {
252
+ const categoryId = document.getElementById('categoryFilter').value;
253
+ let endpoint = '/items';
254
+ if (categoryId) {
255
+ endpoint += `/category/${categoryId}`;
256
+ }
257
+
258
+ allItems = await apiCall(endpoint);
259
+ displayItems(allItems, grid);
260
+ } catch (error) {
261
+ grid.innerHTML = '<div class="loading">Failed to load items</div>';
262
+ console.error('Failed to load items:', error);
263
+ }
264
+ }
265
+
266
+ function displayItems(items, container) {
267
+ if (items.length === 0) {
268
+ container.innerHTML = '<div class="loading">No items found</div>';
269
+ return;
270
+ }
271
+
272
+ container.innerHTML = items.map(item => `
273
+ <div class="item-card" onclick="showItemDetail('${item.id}')">
274
+ <img src="${item.imageUrl || 'https://via.placeholder.com/280x200?text=No+Image'}"
275
+ alt="${item.name}"
276
+ class="item-image"
277
+ onerror="this.src='https://via.placeholder.com/280x200?text=No+Image'">
278
+ <div class="item-content">
279
+ <span class="item-category">${getCategoryName(item.categoryId)}</span>
280
+ <span class="item-status ${item.availability ? 'available' : 'unavailable'}">
281
+ ${item.availability ? 'Available' : 'Unavailable'}
282
+ </span>
283
+ <div class="item-name">${item.name}</div>
284
+ <div class="item-description">${item.description}</div>
285
+ <div class="item-price">$${item.pricePerDay}/day</div>
286
+ </div>
287
+ </div>
288
+ `).join('');
289
+ }
290
+
291
+ function getCategoryName(categoryId) {
292
+ const category = categories.find(c => c.id === categoryId);
293
+ return category ? category.name : 'Unknown';
294
+ }
295
+
296
+ function searchItems() {
297
+ const searchTerm = document.getElementById('searchInput').value.toLowerCase();
298
+ const filtered = allItems.filter(item =>
299
+ item.name.toLowerCase().includes(searchTerm) ||
300
+ item.description.toLowerCase().includes(searchTerm)
301
+ );
302
+ displayItems(filtered, document.getElementById('itemsGrid'));
303
+ }
304
+
305
+ async function loadMyItems() {
306
+ const grid = document.getElementById('myItemsGrid');
307
+ grid.innerHTML = '<div class="loading">Loading your items...</div>';
308
+
309
+ try {
310
+ const items = await apiCall(`/items/owner/${currentUser.id}`);
311
+
312
+ if (items.length === 0) {
313
+ grid.innerHTML = '<div class="loading">You haven\'t added any items yet</div>';
314
+ return;
315
+ }
316
+
317
+ grid.innerHTML = items.map(item => `
318
+ <div class="item-card">
319
+ <img src="${item.imageUrl || 'https://via.placeholder.com/280x200?text=No+Image'}"
320
+ alt="${item.name}"
321
+ class="item-image"
322
+ onerror="this.src='https://via.placeholder.com/280x200?text=No+Image'">
323
+ <div class="item-content">
324
+ <span class="item-category">${getCategoryName(item.categoryId)}</span>
325
+ <span class="item-status ${item.availability ? 'available' : 'unavailable'}">
326
+ ${item.availability ? 'Available' : 'Unavailable'}
327
+ </span>
328
+ <div class="item-name">${item.name}</div>
329
+ <div class="item-description">${item.description}</div>
330
+ <div class="item-price">$${item.pricePerDay}/day</div>
331
+ <button onclick="toggleItemAvailability('${item.id}', ${item.availability})" class="btn btn-secondary">
332
+ ${item.availability ? 'Mark Unavailable' : 'Mark Available'}
333
+ </button>
334
+ <button onclick="deleteItem('${item.id}')" class="btn btn-danger">Delete</button>
335
+ </div>
336
+ </div>
337
+ `).join('');
338
+ } catch (error) {
339
+ grid.innerHTML = '<div class="loading">Failed to load your items</div>';
340
+ console.error('Failed to load my items:', error);
341
+ }
342
+ }
343
+
344
+ async function handleAddItem(event) {
345
+ event.preventDefault();
346
+
347
+ const name = document.getElementById('itemName').value;
348
+ const description = document.getElementById('itemDescription').value;
349
+ const categoryId = document.getElementById('itemCategory').value;
350
+ const pricePerDay = parseFloat(document.getElementById('itemPrice').value);
351
+ const imageUrl = document.getElementById('itemImage').value;
352
+ const errorDiv = document.getElementById('addItemError');
353
+
354
+ try {
355
+ await apiCall('/items', {
356
+ method: 'POST',
357
+ body: JSON.stringify({
358
+ name,
359
+ description,
360
+ categoryId,
361
+ pricePerDay,
362
+ imageUrl: imageUrl || null,
363
+ ownerId: currentUser.id
364
+ })
365
+ });
366
+
367
+ document.getElementById('addItemForm').reset();
368
+ errorDiv.classList.remove('show');
369
+ showPage('my-items');
370
+ } catch (error) {
371
+ errorDiv.textContent = error.message;
372
+ errorDiv.classList.add('show');
373
+ }
374
+ }
375
+
376
+ async function toggleItemAvailability(itemId, currentAvailability) {
377
+ try {
378
+ await apiCall(`/items/${itemId}/availability`, {
379
+ method: 'PATCH',
380
+ body: JSON.stringify({ availability: !currentAvailability })
381
+ });
382
+ loadMyItems();
383
+ } catch (error) {
384
+ alert('Failed to update item availability');
385
+ console.error('Failed to toggle availability:', error);
386
+ }
387
+ }
388
+
389
+ async function deleteItem(itemId) {
390
+ if (!confirm('Are you sure you want to delete this item?')) {
391
+ return;
392
+ }
393
+
394
+ try {
395
+ await apiCall(`/items/${itemId}`, {
396
+ method: 'DELETE'
397
+ });
398
+ loadMyItems();
399
+ } catch (error) {
400
+ alert('Failed to delete item');
401
+ console.error('Failed to delete item:', error);
402
+ }
403
+ }
404
+
405
+ // Item Detail
406
+ async function showItemDetail(itemId) {
407
+ showPage('itemDetail');
408
+ const content = document.getElementById('itemDetailContent');
409
+ content.innerHTML = '<div class="loading">Loading item details...</div>';
410
+
411
+ try {
412
+ const item = await apiCall(`/items/${itemId}`);
413
+
414
+ content.innerHTML = `
415
+ <div class="item-detail">
416
+ <img src="${item.imageUrl || 'https://via.placeholder.com/800x400?text=No+Image'}"
417
+ alt="${item.name}"
418
+ class="item-detail-image"
419
+ onerror="this.src='https://via.placeholder.com/800x400?text=No+Image'">
420
+ <h1>${item.name}</h1>
421
+ <div class="item-detail-info">
422
+ <span class="item-category">${getCategoryName(item.categoryId)}</span>
423
+ <span class="item-status ${item.availability ? 'available' : 'unavailable'}">
424
+ ${item.availability ? 'Available' : 'Unavailable'}
425
+ </span>
426
+ </div>
427
+ <div class="item-detail-section">
428
+ <h3>Description</h3>
429
+ <p>${item.description}</p>
430
+ </div>
431
+ <div class="item-detail-section">
432
+ <h3>Price</h3>
433
+ <p class="item-price">$${item.pricePerDay}/day</p>
434
+ </div>
435
+ ${currentUser && currentUser.id !== item.ownerId && item.availability ? `
436
+ <div class="item-detail-section">
437
+ <h3>Book this item</h3>
438
+ <form id="bookingForm" onsubmit="handleBooking(event, '${item.id}')">
439
+ <div class="form-group">
440
+ <label>Start Date</label>
441
+ <input type="date" id="bookingStartDate" required min="${new Date().toISOString().split('T')[0]}">
442
+ </div>
443
+ <div class="form-group">
444
+ <label>End Date</label>
445
+ <input type="date" id="bookingEndDate" required min="${new Date().toISOString().split('T')[0]}">
446
+ </div>
447
+ <div class="error-message" id="bookingError"></div>
448
+ <button type="submit" class="btn btn-primary">Book Now</button>
449
+ <button type="button" onclick="showPage('browse')" class="btn btn-secondary">Back to Browse</button>
450
+ </form>
451
+ </div>
452
+ ` : `
453
+ <button onclick="showPage('browse')" class="btn btn-secondary">Back to Browse</button>
454
+ `}
455
+ </div>
456
+ `;
457
+ } catch (error) {
458
+ content.innerHTML = '<div class="loading">Failed to load item details</div>';
459
+ console.error('Failed to load item detail:', error);
460
+ }
461
+ }
462
+
463
+ async function handleBooking(event, itemId) {
464
+ event.preventDefault();
465
+
466
+ const startDate = document.getElementById('bookingStartDate').value;
467
+ const endDate = document.getElementById('bookingEndDate').value;
468
+ const errorDiv = document.getElementById('bookingError');
469
+
470
+ try {
471
+ await apiCall('/bookings', {
472
+ method: 'POST',
473
+ body: JSON.stringify({
474
+ itemId,
475
+ borrowerId: currentUser.id,
476
+ startDate,
477
+ endDate
478
+ })
479
+ });
480
+
481
+ alert('Booking request submitted successfully!');
482
+ showPage('my-bookings');
483
+ } catch (error) {
484
+ errorDiv.textContent = error.message;
485
+ errorDiv.classList.add('show');
486
+ }
487
+ }
488
+
489
+ // Bookings
490
+ async function loadMyBookings() {
491
+ const container = document.getElementById('bookingsList');
492
+ container.innerHTML = '<div class="loading">Loading bookings...</div>';
493
+
494
+ try {
495
+ const bookings = await apiCall(`/bookings/borrower/${currentUser.id}`);
496
+
497
+ if (bookings.length === 0) {
498
+ container.innerHTML = '<div class="loading">You have no bookings yet</div>';
499
+ return;
500
+ }
501
+
502
+ container.innerHTML = bookings.map(booking => `
503
+ <div class="booking-card">
504
+ <h3>${booking.itemName || 'Item'}</h3>
505
+ <div class="booking-info">
506
+ <strong>Start Date:</strong> ${new Date(booking.startDate).toLocaleDateString()}
507
+ </div>
508
+ <div class="booking-info">
509
+ <strong>End Date:</strong> ${new Date(booking.endDate).toLocaleDateString()}
510
+ </div>
511
+ <div class="booking-info">
512
+ <strong>Total Price:</strong> $${booking.totalPrice}
513
+ </div>
514
+ <span class="booking-status ${booking.status}">${booking.status}</span>
515
+ ${booking.status === 'PENDING' ? `
516
+ <button onclick="cancelBooking('${booking.id}')" class="btn btn-danger">Cancel Booking</button>
517
+ ` : ''}
518
+ </div>
519
+ `).join('');
520
+ } catch (error) {
521
+ container.innerHTML = '<div class="loading">Failed to load bookings</div>';
522
+ console.error('Failed to load bookings:', error);
523
+ }
524
+ }
525
+
526
+ async function cancelBooking(bookingId) {
527
+ if (!confirm('Are you sure you want to cancel this booking?')) {
528
+ return;
529
+ }
530
+
531
+ try {
532
+ await apiCall(`/bookings/${bookingId}/cancel`, {
533
+ method: 'PATCH'
534
+ });
535
+ loadMyBookings();
536
+ } catch (error) {
537
+ alert('Failed to cancel booking');
538
+ console.error('Failed to cancel booking:', error);
539
+ }
540
+ }
backend/src/main/resources/static/index.html ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LocalLend - Lend and Borrow Locally</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <!-- Navigation -->
11
+ <nav class="navbar">
12
+ <div class="container">
13
+ <div class="nav-brand">LocalLend</div>
14
+ <div class="nav-links" id="navLinks">
15
+ <a href="#" onclick="showPage('home')">Home</a>
16
+ <a href="#" onclick="showPage('browse')" id="browseLink">Browse</a>
17
+ <a href="#" onclick="showPage('my-items')" id="myItemsLink" style="display:none;">My Items</a>
18
+ <a href="#" onclick="showPage('my-bookings')" id="myBookingsLink" style="display:none;">My Bookings</a>
19
+ <a href="#" onclick="showPage('login')" id="loginLink">Login</a>
20
+ <a href="#" onclick="showPage('register')" id="registerLink">Register</a>
21
+ <a href="#" onclick="logout()" id="logoutLink" style="display:none;">Logout</a>
22
+ </div>
23
+ </div>
24
+ </nav>
25
+
26
+ <!-- Main Container -->
27
+ <div class="container main-content">
28
+
29
+ <!-- Home Page -->
30
+ <div id="homePage" class="page active">
31
+ <div class="hero">
32
+ <h1>Welcome to LocalLend</h1>
33
+ <p>Lend and borrow items within your local community</p>
34
+ <button onclick="showPage('browse')" class="btn btn-primary">Browse Items</button>
35
+ <button onclick="showPage('login')" class="btn btn-secondary" id="heroLoginBtn">Get Started</button>
36
+ </div>
37
+
38
+ <div class="features">
39
+ <div class="feature-card">
40
+ <h3>🏠 Local</h3>
41
+ <p>Connect with people in your community</p>
42
+ </div>
43
+ <div class="feature-card">
44
+ <h3>💰 Save Money</h3>
45
+ <p>Borrow instead of buying</p>
46
+ </div>
47
+ <div class="feature-card">
48
+ <h3>🌱 Sustainable</h3>
49
+ <p>Reduce waste by sharing</p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Login Page -->
55
+ <div id="loginPage" class="page">
56
+ <div class="auth-container">
57
+ <h2>Login</h2>
58
+ <form id="loginForm" onsubmit="handleLogin(event)">
59
+ <div class="form-group">
60
+ <label>Username</label>
61
+ <input type="text" id="loginUsername" required>
62
+ </div>
63
+ <div class="form-group">
64
+ <label>Password</label>
65
+ <input type="password" id="loginPassword" required>
66
+ </div>
67
+ <div class="error-message" id="loginError"></div>
68
+ <button type="submit" class="btn btn-primary">Login</button>
69
+ </form>
70
+ <p class="auth-switch">Don't have an account? <a href="#" onclick="showPage('register')">Register</a></p>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Register Page -->
75
+ <div id="registerPage" class="page">
76
+ <div class="auth-container">
77
+ <h2>Register</h2>
78
+ <form id="registerForm" onsubmit="handleRegister(event)">
79
+ <div class="form-group">
80
+ <label>Username</label>
81
+ <input type="text" id="registerUsername" required>
82
+ </div>
83
+ <div class="form-group">
84
+ <label>Email</label>
85
+ <input type="email" id="registerEmail" required>
86
+ </div>
87
+ <div class="form-group">
88
+ <label>Full Name</label>
89
+ <input type="text" id="registerName" required>
90
+ </div>
91
+ <div class="form-group">
92
+ <label>Phone</label>
93
+ <input type="tel" id="registerPhone" required>
94
+ </div>
95
+ <div class="form-group">
96
+ <label>Address</label>
97
+ <input type="text" id="registerAddress" required>
98
+ </div>
99
+ <div class="form-group">
100
+ <label>Password</label>
101
+ <input type="password" id="registerPassword" required minlength="6">
102
+ </div>
103
+ <div class="error-message" id="registerError"></div>
104
+ <button type="submit" class="btn btn-primary">Register</button>
105
+ </form>
106
+ <p class="auth-switch">Already have an account? <a href="#" onclick="showPage('login')">Login</a></p>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Browse Items Page -->
111
+ <div id="browsePage" class="page">
112
+ <h2>Browse Items</h2>
113
+ <div class="search-bar">
114
+ <input type="text" id="searchInput" placeholder="Search items..." onkeyup="searchItems()">
115
+ <select id="categoryFilter" onchange="loadItems()">
116
+ <option value="">All Categories</option>
117
+ </select>
118
+ </div>
119
+ <div class="items-grid" id="itemsGrid">
120
+ <div class="loading">Loading items...</div>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- My Items Page -->
125
+ <div id="myItemsPage" class="page">
126
+ <div class="page-header">
127
+ <h2>My Items</h2>
128
+ <button onclick="showPage('add-item')" class="btn btn-primary">Add Item</button>
129
+ </div>
130
+ <div class="items-grid" id="myItemsGrid">
131
+ <div class="loading">Loading your items...</div>
132
+ </div>
133
+ </div>
134
+
135
+ <!-- Add Item Page -->
136
+ <div id="addItemPage" class="page">
137
+ <div class="auth-container">
138
+ <h2>Add New Item</h2>
139
+ <form id="addItemForm" onsubmit="handleAddItem(event)">
140
+ <div class="form-group">
141
+ <label>Item Name</label>
142
+ <input type="text" id="itemName" required>
143
+ </div>
144
+ <div class="form-group">
145
+ <label>Description</label>
146
+ <textarea id="itemDescription" required rows="4"></textarea>
147
+ </div>
148
+ <div class="form-group">
149
+ <label>Category</label>
150
+ <select id="itemCategory" required></select>
151
+ </div>
152
+ <div class="form-group">
153
+ <label>Price Per Day ($)</label>
154
+ <input type="number" id="itemPrice" required min="0" step="0.01">
155
+ </div>
156
+ <div class="form-group">
157
+ <label>Image URL</label>
158
+ <input type="url" id="itemImage" placeholder="https://...">
159
+ </div>
160
+ <div class="error-message" id="addItemError"></div>
161
+ <button type="submit" class="btn btn-primary">Add Item</button>
162
+ <button type="button" onclick="showPage('my-items')" class="btn btn-secondary">Cancel</button>
163
+ </form>
164
+ </div>
165
+ </div>
166
+
167
+ <!-- My Bookings Page -->
168
+ <div id="myBookingsPage" class="page">
169
+ <h2>My Bookings</h2>
170
+ <div class="bookings-list" id="bookingsList">
171
+ <div class="loading">Loading bookings...</div>
172
+ </div>
173
+ </div>
174
+
175
+ <!-- Item Detail Page -->
176
+ <div id="itemDetailPage" class="page">
177
+ <div id="itemDetailContent"></div>
178
+ </div>
179
+
180
+ </div>
181
+
182
+ <script src="/app.js"></script>
183
+ </body>
184
+ </html>
backend/src/main/resources/static/styles.css ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
9
+ background: #f5f5f5;
10
+ color: #333;
11
+ }
12
+
13
+ .container {
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ padding: 0 20px;
17
+ }
18
+
19
+ /* Navigation */
20
+ .navbar {
21
+ background: #1976d2;
22
+ color: white;
23
+ padding: 1rem 0;
24
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
25
+ }
26
+
27
+ .navbar .container {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ }
32
+
33
+ .nav-brand {
34
+ font-size: 1.5rem;
35
+ font-weight: bold;
36
+ }
37
+
38
+ .nav-links {
39
+ display: flex;
40
+ gap: 1.5rem;
41
+ }
42
+
43
+ .nav-links a {
44
+ color: white;
45
+ text-decoration: none;
46
+ transition: opacity 0.2s;
47
+ }
48
+
49
+ .nav-links a:hover {
50
+ opacity: 0.8;
51
+ }
52
+
53
+ /* Main Content */
54
+ .main-content {
55
+ padding: 2rem 20px;
56
+ }
57
+
58
+ .page {
59
+ display: none;
60
+ }
61
+
62
+ .page.active {
63
+ display: block;
64
+ animation: fadeIn 0.3s;
65
+ }
66
+
67
+ @keyframes fadeIn {
68
+ from { opacity: 0; }
69
+ to { opacity: 1; }
70
+ }
71
+
72
+ /* Hero Section */
73
+ .hero {
74
+ text-align: center;
75
+ padding: 4rem 0;
76
+ background: white;
77
+ border-radius: 8px;
78
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
79
+ margin-bottom: 2rem;
80
+ }
81
+
82
+ .hero h1 {
83
+ font-size: 3rem;
84
+ margin-bottom: 1rem;
85
+ color: #1976d2;
86
+ }
87
+
88
+ .hero p {
89
+ font-size: 1.25rem;
90
+ color: #666;
91
+ margin-bottom: 2rem;
92
+ }
93
+
94
+ /* Features */
95
+ .features {
96
+ display: grid;
97
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
98
+ gap: 2rem;
99
+ margin-top: 3rem;
100
+ }
101
+
102
+ .feature-card {
103
+ background: white;
104
+ padding: 2rem;
105
+ border-radius: 8px;
106
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
107
+ text-align: center;
108
+ }
109
+
110
+ .feature-card h3 {
111
+ font-size: 2rem;
112
+ margin-bottom: 1rem;
113
+ }
114
+
115
+ /* Buttons */
116
+ .btn {
117
+ padding: 0.75rem 1.5rem;
118
+ border: none;
119
+ border-radius: 4px;
120
+ font-size: 1rem;
121
+ cursor: pointer;
122
+ transition: all 0.2s;
123
+ margin: 0.5rem;
124
+ }
125
+
126
+ .btn-primary {
127
+ background: #1976d2;
128
+ color: white;
129
+ }
130
+
131
+ .btn-primary:hover {
132
+ background: #1565c0;
133
+ }
134
+
135
+ .btn-secondary {
136
+ background: #f5f5f5;
137
+ color: #333;
138
+ border: 1px solid #ddd;
139
+ }
140
+
141
+ .btn-secondary:hover {
142
+ background: #e0e0e0;
143
+ }
144
+
145
+ .btn-danger {
146
+ background: #d32f2f;
147
+ color: white;
148
+ }
149
+
150
+ .btn-danger:hover {
151
+ background: #c62828;
152
+ }
153
+
154
+ /* Auth Forms */
155
+ .auth-container {
156
+ max-width: 500px;
157
+ margin: 2rem auto;
158
+ background: white;
159
+ padding: 2rem;
160
+ border-radius: 8px;
161
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
162
+ }
163
+
164
+ .auth-container h2 {
165
+ margin-bottom: 1.5rem;
166
+ color: #1976d2;
167
+ }
168
+
169
+ .form-group {
170
+ margin-bottom: 1.5rem;
171
+ }
172
+
173
+ .form-group label {
174
+ display: block;
175
+ margin-bottom: 0.5rem;
176
+ font-weight: 500;
177
+ }
178
+
179
+ .form-group input,
180
+ .form-group select,
181
+ .form-group textarea {
182
+ width: 100%;
183
+ padding: 0.75rem;
184
+ border: 1px solid #ddd;
185
+ border-radius: 4px;
186
+ font-size: 1rem;
187
+ }
188
+
189
+ .form-group input:focus,
190
+ .form-group select:focus,
191
+ .form-group textarea:focus {
192
+ outline: none;
193
+ border-color: #1976d2;
194
+ }
195
+
196
+ .error-message {
197
+ color: #d32f2f;
198
+ margin-bottom: 1rem;
199
+ padding: 0.75rem;
200
+ background: #ffebee;
201
+ border-radius: 4px;
202
+ display: none;
203
+ }
204
+
205
+ .error-message.show {
206
+ display: block;
207
+ }
208
+
209
+ .success-message {
210
+ color: #2e7d32;
211
+ margin-bottom: 1rem;
212
+ padding: 0.75rem;
213
+ background: #e8f5e9;
214
+ border-radius: 4px;
215
+ display: none;
216
+ }
217
+
218
+ .success-message.show {
219
+ display: block;
220
+ }
221
+
222
+ .auth-switch {
223
+ text-align: center;
224
+ margin-top: 1rem;
225
+ }
226
+
227
+ .auth-switch a {
228
+ color: #1976d2;
229
+ text-decoration: none;
230
+ }
231
+
232
+ /* Search Bar */
233
+ .search-bar {
234
+ display: flex;
235
+ gap: 1rem;
236
+ margin-bottom: 2rem;
237
+ }
238
+
239
+ .search-bar input {
240
+ flex: 1;
241
+ padding: 0.75rem;
242
+ border: 1px solid #ddd;
243
+ border-radius: 4px;
244
+ font-size: 1rem;
245
+ }
246
+
247
+ .search-bar select {
248
+ padding: 0.75rem;
249
+ border: 1px solid #ddd;
250
+ border-radius: 4px;
251
+ font-size: 1rem;
252
+ }
253
+
254
+ /* Items Grid */
255
+ .items-grid {
256
+ display: grid;
257
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
258
+ gap: 1.5rem;
259
+ }
260
+
261
+ .item-card {
262
+ background: white;
263
+ border-radius: 8px;
264
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
265
+ overflow: hidden;
266
+ transition: transform 0.2s;
267
+ cursor: pointer;
268
+ }
269
+
270
+ .item-card:hover {
271
+ transform: translateY(-4px);
272
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
273
+ }
274
+
275
+ .item-image {
276
+ width: 100%;
277
+ height: 200px;
278
+ object-fit: cover;
279
+ background: #f5f5f5;
280
+ }
281
+
282
+ .item-content {
283
+ padding: 1rem;
284
+ }
285
+
286
+ .item-name {
287
+ font-size: 1.25rem;
288
+ font-weight: bold;
289
+ margin-bottom: 0.5rem;
290
+ }
291
+
292
+ .item-description {
293
+ color: #666;
294
+ margin-bottom: 0.5rem;
295
+ display: -webkit-box;
296
+ -webkit-line-clamp: 2;
297
+ -webkit-box-orient: vertical;
298
+ overflow: hidden;
299
+ }
300
+
301
+ .item-price {
302
+ font-size: 1.25rem;
303
+ color: #1976d2;
304
+ font-weight: bold;
305
+ }
306
+
307
+ .item-category {
308
+ display: inline-block;
309
+ padding: 0.25rem 0.75rem;
310
+ background: #e3f2fd;
311
+ color: #1976d2;
312
+ border-radius: 12px;
313
+ font-size: 0.875rem;
314
+ margin-bottom: 0.5rem;
315
+ }
316
+
317
+ .item-status {
318
+ display: inline-block;
319
+ padding: 0.25rem 0.75rem;
320
+ border-radius: 12px;
321
+ font-size: 0.875rem;
322
+ margin-left: 0.5rem;
323
+ }
324
+
325
+ .item-status.available {
326
+ background: #e8f5e9;
327
+ color: #2e7d32;
328
+ }
329
+
330
+ .item-status.unavailable {
331
+ background: #ffebee;
332
+ color: #d32f2f;
333
+ }
334
+
335
+ /* Page Header */
336
+ .page-header {
337
+ display: flex;
338
+ justify-content: space-between;
339
+ align-items: center;
340
+ margin-bottom: 2rem;
341
+ }
342
+
343
+ /* Loading */
344
+ .loading {
345
+ text-align: center;
346
+ padding: 2rem;
347
+ color: #666;
348
+ }
349
+
350
+ /* Item Detail */
351
+ .item-detail {
352
+ background: white;
353
+ border-radius: 8px;
354
+ padding: 2rem;
355
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
356
+ }
357
+
358
+ .item-detail-image {
359
+ width: 100%;
360
+ max-height: 400px;
361
+ object-fit: cover;
362
+ border-radius: 8px;
363
+ margin-bottom: 1.5rem;
364
+ }
365
+
366
+ .item-detail h1 {
367
+ margin-bottom: 1rem;
368
+ color: #1976d2;
369
+ }
370
+
371
+ .item-detail-info {
372
+ margin-bottom: 1.5rem;
373
+ }
374
+
375
+ .item-detail-section {
376
+ margin-bottom: 1.5rem;
377
+ }
378
+
379
+ .item-detail-section h3 {
380
+ margin-bottom: 0.5rem;
381
+ }
382
+
383
+ /* Bookings */
384
+ .booking-card {
385
+ background: white;
386
+ padding: 1.5rem;
387
+ border-radius: 8px;
388
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
389
+ margin-bottom: 1rem;
390
+ }
391
+
392
+ .booking-card h3 {
393
+ color: #1976d2;
394
+ margin-bottom: 0.5rem;
395
+ }
396
+
397
+ .booking-info {
398
+ margin-bottom: 0.5rem;
399
+ color: #666;
400
+ }
401
+
402
+ .booking-status {
403
+ display: inline-block;
404
+ padding: 0.25rem 0.75rem;
405
+ border-radius: 12px;
406
+ font-size: 0.875rem;
407
+ margin-top: 0.5rem;
408
+ }
409
+
410
+ .booking-status.PENDING {
411
+ background: #fff3e0;
412
+ color: #f57c00;
413
+ }
414
+
415
+ .booking-status.CONFIRMED {
416
+ background: #e8f5e9;
417
+ color: #2e7d32;
418
+ }
419
+
420
+ .booking-status.CANCELLED {
421
+ background: #ffebee;
422
+ color: #d32f2f;
423
+ }
424
+
425
+ .booking-status.COMPLETED {
426
+ background: #e3f2fd;
427
+ color: #1976d2;
428
+ }
429
+
430
+ /* Responsive */
431
+ @media (max-width: 768px) {
432
+ .hero h1 {
433
+ font-size: 2rem;
434
+ }
435
+
436
+ .search-bar {
437
+ flex-direction: column;
438
+ }
439
+
440
+ .items-grid {
441
+ grid-template-columns: 1fr;
442
+ }
443
+
444
+ .nav-links {
445
+ flex-wrap: wrap;
446
+ gap: 0.5rem;
447
+ }
448
+ }