diff --git a/frontend/src/__tests__/pages/LandingPage.test.tsx b/frontend/src/__tests__/pages/LandingPage.test.tsx index aed05e68..0e119d2b 100644 --- a/frontend/src/__tests__/pages/LandingPage.test.tsx +++ b/frontend/src/__tests__/pages/LandingPage.test.tsx @@ -105,9 +105,7 @@ describe("LandingPage", () => { // Wait for bookings for Room B await screen.findByText("Meeting B"); - expect( - screen.getByRole("button", { name: /navigate to booking page/i }) - ).toBeInTheDocument(); + expect(screen.getByTestId("desktop-book-btn")).toBeInTheDocument(); }); test("displays error message and retry button on API failure", async () => { diff --git a/frontend/src/components/BookingList.tsx b/frontend/src/components/BookingList.tsx index 9877b904..489721d7 100644 --- a/frontend/src/components/BookingList.tsx +++ b/frontend/src/components/BookingList.tsx @@ -99,31 +99,34 @@ const BookingList: FC = ({ }, [roomId, date, enableSSE]); return ( - - + + Today's Bookings - - {liveBookings.length > 0 ? ( - liveBookings.map((booking) => ( - - - - )) - ) : ( - No bookings for today. - )} - + + {liveBookings.length > 0 ? ( + liveBookings.map((booking) => ( + + + + )) + ) : ( + No bookings for today. + )} + ); }; diff --git a/frontend/src/components/RoomList.tsx b/frontend/src/components/RoomList.tsx index a923a143..27c30799 100644 --- a/frontend/src/components/RoomList.tsx +++ b/frontend/src/components/RoomList.tsx @@ -65,46 +65,49 @@ const RoomList: FC = ({ onSelectRoom, }) => { return ( - - + + Available Rooms - - {rooms.length > 0 ? ( - rooms.map((room) => - onSelectRoom ? ( - onSelectRoom(room.id)} - aria-label={`Select room ${room.name}`} - data-testid={`room-item-${room.id}`} - > - - - ) : ( - - - - ) - ) - ) : ( - No rooms available. - )} - + + {rooms.length > 0 ? ( + rooms.map((room) => + onSelectRoom ? ( + onSelectRoom(room.id)} + aria-label={`Select room ${room.name}`} + data-testid={`room-item-${room.id}`} + > + + + ) : ( + + + + ) + ) + ) : ( + No rooms available. + )} + ); }; diff --git a/frontend/src/pages/LandingPage.css b/frontend/src/pages/LandingPage.css new file mode 100644 index 00000000..c279600f --- /dev/null +++ b/frontend/src/pages/LandingPage.css @@ -0,0 +1,262 @@ +/* Responsive layout for LandingPage */ + +.landing-root { + min-height: 100vh; + width: 100vw; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + position: relative; +} + +.landing-header { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #fff; + padding-top: 2.5rem; + padding-bottom: 1.5rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +.landing-main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 0; +} + +.landing-main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + min-height: 0; + width: 100vw; +} + +.landing-columns-row { + display: flex; + flex-direction: row; + justify-content: center; + align-items: stretch; + gap: 10vw; + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 2rem 0 0 0; + min-height: 60vh; +} + +.landing-section { + flex: 1 1 0; + min-width: 320px; + max-width: 420px; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: stretch; +} + +/* Make both columns the same width to avoid line wrapping and for consistency */ +.landing-columns-row > .landing-section { + min-width: 420px; + max-width: 540px; +} +.landing-card { + background: #fff; + border-radius: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); + border: 1px solid #e0e0e0; + padding: 2rem 1.5rem 1.5rem 1.5rem; + margin-bottom: 0; + transition: box-shadow 0.2s; +} + +.landing-card:focus-within, +.landing-card:hover { + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.13); +} + +.landing-card { + background: #23272f; + border-radius: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.13); + border: 1px solid #22242a; + padding: 2rem 1.5rem 1.5rem 1.5rem; + margin-bottom: 0; + transition: box-shadow 0.2s; + color: #f5f6fa; + flex: 1 1 0; + height: 60vh; + display: flex; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; +} +.landing-card:focus-within, +.landing-card:hover { + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.18); +} +.landing-card-dark { + background: #23272f; + border: 1px solid #22242a; + color: #f5f6fa; +} + +.landing-book-btn-desktop-wide { + width: 100vw; + text-align: center; + margin: 2.5rem 0 0 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.2rem; +} + +.landing-hr-wide { + width: 420px; + border: none; + border-top: 2px solid #444; + margin: 0.5rem auto 1.5rem auto; + background: none; +} + +.landing-book-btn-desktop { + position: absolute; + left: 0; + bottom: 0; + width: 100vw; + text-align: center; + padding-bottom: 2rem; + background: linear-gradient(to top, #fff 80%, rgba(255, 255, 255, 0)); + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.landing-book-btn-mobile { + display: none; +} + +.landing-hr { + width: 220px; + border: none; + border-top: 2px solid #e0e0e0; + margin: 0.5rem auto 1.5rem auto; + background: none; +} + +@media (max-width: 900px), (orientation: portrait) { + .landing-root { + flex-direction: column; + min-height: 100vh; + width: 100vw; + overflow: visible; + position: static; + } + .landing-header { + position: static; + padding-top: 1.5rem; + padding-bottom: 1rem; + box-shadow: none; + } + .landing-main { + padding: 0; + } + .landing-components-row { + flex-direction: column; + gap: 1.5rem; + width: 100vw; + max-width: 100vw; + padding: 0; + } + .landing-section { + min-width: 0; + max-width: 100vw; + width: 100vw; + padding: 0; + } + .landing-card { + border-radius: 0; + box-shadow: none; + border: none; + background: #fff; + padding: 1.2rem 0.5rem 1.5rem 0.5rem; + } + .landing-book-btn-desktop { + display: none; + } + .landing-book-btn-mobile { + display: block; + width: 100%; + text-align: center; + margin-top: 2rem; + margin-bottom: 2rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + } + + .landing-hr { + width: 80vw; + margin: 0.5rem auto 1.5rem auto; + } + .landing-main { + padding: 0; + } + .landing-columns-row { + flex-direction: column; + gap: 1.5rem; + width: 100vw; + max-width: 100vw; + padding: 0; + min-height: unset; + } + .landing-section { + min-width: 0; + max-width: 100vw; + width: 100vw; + padding: 0; + height: auto; + overflow: visible; + } + .landing-card, + .landing-card-dark { + border-radius: 0; + box-shadow: none; + border: none; + background: #fff; + color: #23272f; + padding: 1.2rem 0.5rem 1.5rem 0.5rem; + } + .landing-book-btn-desktop-wide { + display: none; + } + .landing-book-btn-mobile { + display: block; + width: 100%; + text-align: center; + margin-top: 2rem; + margin-bottom: 2rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + } + .landing-hr, + .landing-hr-wide { + width: 80vw; + margin: 0.5rem auto 1.5rem auto; + border-top: 2px solid #e0e0e0; + } +} diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index b1fa0a3d..732f506f 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -9,14 +9,8 @@ * * @component */ -import { - Container, - Grid, - Typography, - Button, - Skeleton, - Box, -} from "@mui/material"; +import { Typography, Button, Skeleton, Box } from "@mui/material"; +import "./LandingPage.css"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import axios from "axios"; @@ -78,110 +72,133 @@ const LandingPage: FC = () => { // Display skeleton loaders during data fetching if (roomsLoading || bookingsLoading) { return ( - - - - +
+
+ + + {[...Array(3)].map((_, i) => ( - {[...Array(3)].map((_, i) => ( - - ))} - - - - + ))} + +
+
+ + + {[...Array(3)].map((_, i) => ( - {[...Array(3)].map((_, i) => ( - - ))} - - - - + ))} + +
+
); } // Display error message with retry option if (roomsError || bookingsError) { return ( - - - {roomsError?.message || - bookingsError?.message || - "Failed to fetch data."} - - - +
+
+ + {roomsError?.message || + bookingsError?.message || + "Failed to fetch data."} + + +
+
); } return ( - - - Conference Room Booking System - - - - - - - - - - +
+
+ + Conference Room Booking System + +
+
+
+
+ +
+
+ +
+
+ {/* Desktop/landscape Book a Room button and HR */} +
+
+ +
+
+ {/* Mobile/portrait Book a Room button and HR */} +
+
- - +
+
); };