From f9f83ae46971dcc042bf9733e71262003d23282a Mon Sep 17 00:00:00 2001 From: Jimuelle07 Date: Fri, 12 Jun 2026 18:03:07 +0800 Subject: [PATCH] finished the ui for mobiles --- .../AdminDashboardPage/AdminDashboardPage.css | 76 +++++++ .../src/BrowseItemPage/BrowseItemsPage.jsx | 123 ++++++++--- frontend/src/BrowseItemPage/browse.css | 131 ++++++++++++ .../src/ItemDetailPage/ItemDetailPage.jsx | 99 +++++---- frontend/src/ItemDetailPage/detail.css | 62 ++++++ frontend/src/ProfilePage/profile.css | 105 ++++++++++ frontend/src/ReportItemPage/report.css | 51 +++++ frontend/src/components/PublicNav.jsx | 145 +++++++++---- frontend/src/shared.css | 27 ++- frontend/src/style.css | 197 ++++++++++++++++-- 10 files changed, 894 insertions(+), 122 deletions(-) diff --git a/frontend/src/AdminDashboardPage/AdminDashboardPage.css b/frontend/src/AdminDashboardPage/AdminDashboardPage.css index 3b74eb1..c8bf912 100644 --- a/frontend/src/AdminDashboardPage/AdminDashboardPage.css +++ b/frontend/src/AdminDashboardPage/AdminDashboardPage.css @@ -290,3 +290,79 @@ 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 760px) { + .admin-dashboard-wrapper { + flex-direction: column; + position: relative; + height: auto; + min-height: 100vh; + } + + .admin-sidebar { + width: 100%; + flex-direction: row; + border-right: none; + border-bottom: 1px solid #e2e8f0; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + flex-shrink: 0; + } + + .admin-sidebar::-webkit-scrollbar { + display: none; + } + + .admin-brand { + display: none; + } + + .admin-nav-links { + flex-direction: row; + padding: 8px 12px; + gap: 4px; + overflow-x: auto; + flex: 1; + } + + .nav-link { + white-space: nowrap; + border-left: none; + border-bottom: 3px solid transparent; + padding: 0.6rem 1rem; + font-size: 0.9rem; + border-radius: 8px 8px 0 0; + } + + .nav-link.active { + border-left-color: transparent; + border-bottom-color: var(--accent); + border-radius: 8px 8px 0 0; + } + + .admin-main-content { + flex: 1; + overflow-y: auto; + } + + .admin-topbar { + padding: 0 1rem; + height: 52px; + } + + .admin-page-title { + font-size: 1rem; + } + + .admin-content-body { + padding: 1rem; + } + + .admin-metric-cards { + grid-template-columns: 1fr; + gap: 1rem; + } +} diff --git a/frontend/src/BrowseItemPage/BrowseItemsPage.jsx b/frontend/src/BrowseItemPage/BrowseItemsPage.jsx index 81c11c0..62ff0c6 100644 --- a/frontend/src/BrowseItemPage/BrowseItemsPage.jsx +++ b/frontend/src/BrowseItemPage/BrowseItemsPage.jsx @@ -7,7 +7,7 @@ import Pagination from './Pagination' import SkeletonGrid from './SkeletonGrid' import EmptyState from './EmptyState' import Brand from '../components/Brand' -import { Search, User, LogOut } from 'lucide-react' +import { Search, User, LogOut, Menu, X, SlidersHorizontal } from 'lucide-react' import NavChatDropdown from '../components/NavChatDropdown' const API_BASE_URL = (import.meta.env.VITE_API_URL || 'http://localhost:5000') + '/api' const PAGE_SIZE = 9 @@ -40,6 +40,8 @@ export default function BrowseItemsPage() { const [locationDraft, setLocationDraft] = useState(searchParams.get('locationId') || '') const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState('') + const [mobileNavOpen, setMobileNavOpen] = useState(false) + const [filtersOpen, setFiltersOpen] = useState(false) const isLoggedIn = !!getStoredToken() const filters = useMemo( @@ -134,13 +136,14 @@ export default function BrowseItemsPage() { ].filter(Boolean).length return ( + <>
{/* Dynamic Header */}
- + setMobileNavOpen(false)}> - +
+ {mobileNavOpen && ( +
setMobileNavOpen(false)}> + +
+ )} +
{/* Main Browse Hero */}
-
-

Campus item recovery

-

Search for an item

-

- Browse reported lost and found belongings across PUP locations. -

+
+

Campus item recovery

+

Search for an item

+

+ Browse reported lost and found belongings across PUP locations. +

+
+
+ + {/* Mobile filter toggle bar */} +
+ + + {isLoading ? 'Loading…' : `${meta.total} item${meta.total === 1 ? '' : 's'}`} +
- {/* Layout Content */}
-
+ ) } diff --git a/frontend/src/BrowseItemPage/browse.css b/frontend/src/BrowseItemPage/browse.css index 35c3b7a..92300a7 100644 --- a/frontend/src/BrowseItemPage/browse.css +++ b/frontend/src/BrowseItemPage/browse.css @@ -488,3 +488,134 @@ display: flex; gap: 4px; } + +/* ─── MOBILE FILTER BAR ──────────────────────────── */ + +.mobile-filter-bar { + display: none; + align-items: center; + justify-content: space-between; + padding: 10px 16px; + border-bottom: 1px solid var(--border); + background: #fff; +} + +.mobile-filter-toggle { + display: flex; + align-items: center; + gap: 7px; + height: 36px; + padding: 0 14px; + border: 1.5px solid var(--border); + border-radius: 9px; + background: #fff; + color: var(--ink); + font-size: 13px; + font-weight: 700; + cursor: pointer; + font-family: inherit; + transition: background 160ms ease, border-color 160ms ease, color 160ms ease; +} + +.mobile-filter-toggle.has-filters { + border-color: var(--blue); + color: var(--blue); + background: #eff6ff; +} + +.mobile-filter-toggle:hover { + background: #eff6ff; + border-color: var(--blue); + color: var(--blue); +} + +.mobile-results-count { + font-size: 13px; + font-weight: 600; + color: var(--muted); +} + +.mobile-filter-close { + display: none; + width: 100%; + padding: 12px; + border: none; + border-radius: 10px; + background: var(--blue); + color: #fff; + font-size: 15px; + font-weight: 700; + cursor: pointer; + margin-top: 4px; + font-family: inherit; + transition: background 160ms ease; +} + +.mobile-filter-close:hover { + background: var(--blue-dark); +} + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 760px) { + .feed-header { + padding: 20px 16px 0; + } + + .feed-header h1 { + font-size: 22px; + } + + .mobile-filter-bar { + display: flex; + } + + .browse-layout { + grid-template-columns: 1fr; + padding: 14px 16px 40px; + gap: 14px; + } + + /* Filters panel hidden by default on mobile, shown when toggled */ + .filters-panel { + display: none; + position: static; + top: auto; + } + + .filters-panel.mobile-open { + display: flex; + } + + .mobile-filter-close { + display: block; + } + + /* Item grid: 2 columns on mobile */ + .item-grid { + grid-template-columns: repeat(2, 1fr); + gap: 10px; + } + + .item-image { + height: 130px; + } + + .item-card-body { + padding: 10px 12px; + } + + .item-card-body h2 { + font-size: 13px; + } + + .results-toolbar { + display: none; + } +} + +@media (max-width: 400px) { + .item-grid { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/ItemDetailPage/ItemDetailPage.jsx b/frontend/src/ItemDetailPage/ItemDetailPage.jsx index 9971c48..b02df97 100644 --- a/frontend/src/ItemDetailPage/ItemDetailPage.jsx +++ b/frontend/src/ItemDetailPage/ItemDetailPage.jsx @@ -7,7 +7,7 @@ import ChatPanel from './ChatPanel' import UpdateStatusModal from './UpdateStatusModal' import DeleteConfirmModal from './DeleteConfirmModal' import Brand from '../components/Brand' -import { Search, LogOut } from 'lucide-react' +import { Search, LogOut, Menu, X } from 'lucide-react' import NavChatDropdown from '../components/NavChatDropdown' import { useSocket } from '../context/SocketContext' @@ -36,6 +36,7 @@ export default function ItemDetailPage() { const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [isSavingStatus, setIsSavingStatus] = useState(false) const [isDeleting, setIsDeleting] = useState(false) + const [mobileNavOpen, setMobileNavOpen] = useState(false) // Use the global socket context const socket = useSocket() @@ -155,40 +156,46 @@ export default function ItemDetailPage() { - -
- setDraftSearch(e.target.value)} - placeholder="Search for an item" - /> + setDraftSearch(e.target.value)} placeholder="Search for an item" />
- -
- - Report Lost Item - - -
+ + {mobileNavOpen && ( +
setMobileNavOpen(false)}> + +
+ )}
{/* Lucide AlertOctagon Icon */} @@ -209,48 +216,55 @@ export default function ItemDetailPage() { } return ( + <>
{/* Dynamic Header */}
- + setMobileNavOpen(false)}> - - - {/* Global Search Bar */}
- setDraftSearch(e.target.value)} - placeholder="Search for an item" - /> + setDraftSearch(e.target.value)} placeholder="Search for an item" />
- -
- - Report Lost Item - +
+ Report Lost Item -
+
+ {mobileNavOpen && ( +
setMobileNavOpen(false)}> + +
+ )} + {/* Main Two-Panel Layout */}
@@ -306,5 +320,6 @@ export default function ItemDetailPage() { )}
+ ) } diff --git a/frontend/src/ItemDetailPage/detail.css b/frontend/src/ItemDetailPage/detail.css index d5d8274..252e91c 100644 --- a/frontend/src/ItemDetailPage/detail.css +++ b/frontend/src/ItemDetailPage/detail.css @@ -485,3 +485,65 @@ opacity: 0.65; cursor: not-allowed; } + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 900px) { + .item-detail-grid { + grid-template-columns: 1fr; + gap: 16px; + } + + .item-detail-layout-container { + padding: 18px 20px 40px; + } + + .chat-panel-container { + min-height: 420px; + max-height: 520px; + } +} + +@media (max-width: 760px) { + .item-detail-layout-container { + padding: 14px 14px 32px; + } + + .item-detail-title { + font-size: 17px; + } + + .item-detail-image-box { + min-height: 180px; + } + + .item-detail-img { + max-height: 280px; + } + + .reporter-actions-box { + flex-direction: column; + } + + .reporter-actions-box .btn-primary, + .reporter-actions-box .btn-danger-outline { + width: 100%; + justify-content: center; + } + + .modal-footer { + flex-direction: column-reverse; + } + + .modal-footer .btn-primary, + .modal-footer .btn-secondary, + .modal-footer .btn-danger { + width: 100%; + justify-content: center; + } + + .chat-panel-container { + min-height: 380px; + max-height: 480px; + } +} diff --git a/frontend/src/ProfilePage/profile.css b/frontend/src/ProfilePage/profile.css index 66dcc1b..ef93029 100644 --- a/frontend/src/ProfilePage/profile.css +++ b/frontend/src/ProfilePage/profile.css @@ -516,3 +516,108 @@ .form-actions { padding-top: 4px; } + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 760px) { + /* Sidebar becomes a horizontal tab strip */ + .dashboard-shell-container { + flex-direction: column; + } + + .dashboard-sidebar { + width: 100%; + height: auto; + min-height: unset; + position: sticky; + top: 0; + z-index: 90; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 0 12px; + flex-direction: row; + align-items: center; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + gap: 0; + background: #fff; + } + + .dashboard-sidebar::-webkit-scrollbar { + display: none; + } + + .sidebar-brand { + display: none; + } + + .sidebar-nav { + flex-direction: row; + flex: 1; + gap: 4px; + padding: 8px 0; + overflow-x: auto; + } + + .sidebar-nav-btn { + white-space: nowrap; + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + } + + .sidebar-nav-btn:hover:not(.active) { + transform: none; + } + + .sidebar-footer { + margin-top: 0; + padding-top: 0; + border-top: none; + padding-left: 10px; + border-left: 1px solid var(--border); + flex-shrink: 0; + } + + .dashboard-main-content { + padding: 16px; + flex: 1; + overflow-y: auto; + } + + /* Dashboard overview grid */ + .dashboard-widgets-grid { + grid-template-columns: 1fr; + } + + /* Reported items table */ + .reported-items-table th:nth-child(1), + .reported-items-table td:nth-child(1) { + display: none; + } + + .dashboard-content-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .dashboard-content-header .btn-primary { + width: 100%; + justify-content: center; + } + + /* Profile settings */ + .profile-settings-container { + padding: 20px 16px; + } + + .grid-2-col { + grid-template-columns: 1fr; + } + + .profile-settings-form { + max-width: 100%; + } +} diff --git a/frontend/src/ReportItemPage/report.css b/frontend/src/ReportItemPage/report.css index 51d5911..386e904 100644 --- a/frontend/src/ReportItemPage/report.css +++ b/frontend/src/ReportItemPage/report.css @@ -421,3 +421,54 @@ .btn-submit-report { min-width: 140px; } + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 760px) { + .report-form-container { + padding: 16px 14px 40px; + } + + .report-step-panel { + padding: 20px 16px; + } + + .stepper-progress-bar { + margin-bottom: 24px; + } + + .step-circle { + width: 38px; + height: 38px; + } + + .step-label { + font-size: 10px; + } + + .predefined-locations-grid { + grid-template-columns: 1fr; + } + + .step-actions { + flex-direction: column-reverse; + } + + .step-actions .btn-primary, + .step-actions .btn-secondary { + width: 100%; + justify-content: center; + } + + .status-selector-row { + flex-direction: column; + } + + .status-btn { + height: 52px; + } + + .summary-row dt { + min-width: 80px; + } +} diff --git a/frontend/src/components/PublicNav.jsx b/frontend/src/components/PublicNav.jsx index 44036d3..3da81fc 100644 --- a/frontend/src/components/PublicNav.jsx +++ b/frontend/src/components/PublicNav.jsx @@ -1,5 +1,6 @@ +import { useState } from "react"; import { Link, useNavigate, useLocation } from "react-router-dom"; -import { LogOut, User } from "lucide-react"; +import { LogOut, User, Menu, X } from "lucide-react"; import Brand from "./Brand.jsx"; import ScrollProgress from "./ScrollProgress.jsx"; import NavChatDropdown from "./NavChatDropdown.jsx"; @@ -7,56 +8,126 @@ import NavChatDropdown from "./NavChatDropdown.jsx"; export default function PublicNav({ showScrollProgress = false }) { const navigate = useNavigate(); const location = useLocation(); + const [mobileOpen, setMobileOpen] = useState(false); const showChatBubble = location.pathname.startsWith("/items") || location.pathname.startsWith("/profile"); const isLoggedIn = !!localStorage.getItem("kyurToken"); + const handleLogout = () => { localStorage.removeItem("kyurToken"); navigate("/login"); + setMobileOpen(false); }; + const closeMobile = () => setMobileOpen(false); + return ( -
- - - - - - - {isLoggedIn ? ( -
- - Report Lost Item - - - {showChatBubble ? ( - + <> +
+ + + + + + +
+ {isLoggedIn ? ( + <> + Report Lost Item + {showChatBubble ? ( + + ) : ( + + + + )} + + ) : ( - - - + <> + Report Lost Item + Log In + )} - -
- ) : ( -
- - Report Lost Item - - - Log In - + + +
+ + {mobileOpen && ( +
+
)} {showScrollProgress && } -
+ ); } diff --git a/frontend/src/shared.css b/frontend/src/shared.css index 03504a9..65543a3 100644 --- a/frontend/src/shared.css +++ b/frontend/src/shared.css @@ -27,7 +27,7 @@ background: #fff; position: sticky; top: 0; - z-index: 100; + z-index: 300; flex-shrink: 0; } @@ -484,3 +484,28 @@ color: #374151; border: 1px solid #e5e7eb; } + +/* ─── RESPONSIVE ─────────────────────────────────── */ + +@media (max-width: 760px) { + .topbar { + padding: 0 16px; + min-height: 54px; + gap: 8px; + flex-wrap: wrap; + } + + .topbar-nav { + display: none; + } + + .topbar-search-mock { + max-width: 100%; + flex: 1; + } + + .error-panel { + margin: 40px 16px; + padding: 28px 20px; + } +} diff --git a/frontend/src/style.css b/frontend/src/style.css index aebc5ac..27d0077 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -63,7 +63,7 @@ button:disabled { background: #fff; position: sticky; top: 0; - z-index: 100; + z-index: 300; } .brandLink { @@ -918,6 +918,131 @@ button:disabled { opacity: 0.75; } +/* ─── NAV ACTIONS & HAMBURGER ──────────────────── */ + +.navActions { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.navIconBtn { + width: 40px; + height: 40px; + padding: 0; + min-width: unset; + border-radius: 9px; + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; +} + +.navTextBtn { + padding: 8px 16px; + border-radius: 8px; + text-decoration: none; + color: var(--ink); + font-weight: 700; + display: inline-flex; + align-items: center; +} + +.nav-hamburger { + display: none; + width: 40px; + height: 40px; + border: 1.5px solid #e5e7eb; + border-radius: 9px; + background: #fff; + color: #374151; + cursor: pointer; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; + transition: background 160ms ease, color 160ms ease, border-color 160ms ease; +} + +.nav-hamburger:hover { + background: #eff6ff; + color: var(--blue); + border-color: var(--blue); +} + +/* ─── MOBILE NAV MENU ──────────────────────────── */ + +.mobile-nav-overlay { + position: fixed; + top: 58px; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.35); + z-index: 200; +} + +.mobile-nav-menu { + position: absolute; + top: 0; + left: 0; + right: 0; + background: #fff; + border-bottom: 1px solid var(--border); + padding: 20px; + display: flex; + flex-direction: column; + gap: 14px; + box-shadow: 0 12px 32px rgba(7, 84, 184, 0.12); + animation: slideDown 160ms ease; +} + +@keyframes slideDown { + from { transform: translateY(-12px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.mobile-nav-links { + display: flex; + flex-direction: column; + gap: 2px; +} + +.mobile-nav-links a { + padding: 10px 14px; + border-radius: 10px; + color: #374151; + text-decoration: none; + font-size: 15px; + font-weight: 600; + transition: background 160ms ease, color 160ms ease; + display: block; +} + +.mobile-nav-links a:hover { + background: #eff6ff; + color: var(--blue); +} + +.mobile-nav-divider { + border: none; + border-top: 1px solid var(--border); + margin: 0; +} + +.mobile-nav-actions { + display: flex; + flex-direction: column; + gap: 8px; +} + +.mobile-full-btn { + width: 100%; + justify-content: center; + text-align: center; +} + /* ─── RESPONSIVE ───────────────────────────────── */ @media (max-width: 1400px) { @@ -948,11 +1073,6 @@ button:disabled { grid-column: 1 / -1; } - .reportBtn { - grid-column: 1 / -1; - justify-self: start; - } - .homeHero, .lowerHome, .aboutTop, @@ -966,30 +1086,46 @@ button:disabled { } @media (max-width: 760px) { - .kyurNav { - grid-template-columns: 1fr; - padding: 16px 18px; + /* Show hamburger, hide desktop nav items */ + .nav-hamburger { + display: flex; } - .navLinks { - justify-content: flex-start; - flex-wrap: wrap; + .kyurNav .navLinks, + .kyurNav .navSearch, + .kyurNav .navActions { + display: none; } + .kyurNav { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 18px; + min-height: 58px; + gap: 0; + } + + /* Landing hero */ .homeHero { padding: 28px 18px 18px; } + .heroCopy { + margin-left: 0; + } + .heroCopy h1 { - font-size: 32px; + font-size: clamp(28px, 8vw, 36px); + white-space: normal; } .heroCopy h2 { - font-size: 24px; + font-size: 20px; } .heroCopy p { - font-size: 16px; + font-size: 15px; } .bigBlueBtn, @@ -999,7 +1135,7 @@ button:disabled { } .heroProduct { - min-height: 340px; + min-height: 260px; } .benefitGrid, @@ -1045,7 +1181,34 @@ button:disabled { } .authCard { - padding: 28px 24px; + padding: 28px 22px; + } + + .authCard h1 { + font-size: 26px; + } +} + +@media (max-width: 480px) { + .heroButtons { + flex-direction: column; + } + + .bigBlueBtn, + .bigWhiteBtn { + width: 100%; + } + + .targetUsersGrid { + grid-template-columns: repeat(3, 1fr); + } + + .lowerHome { + padding: 8px 14px 28px; + } + + .whyCard { + padding: 18px 16px 22px; } }