The WayFy frontend is a React 18 single-page application built with Vite. Its defining architectural principle is a strict separation between pages (thin route wrappers that compose modules) and modules (self-contained feature units that own their own components, hooks, and CSS). This boundary keeps individual features independently iterable without cross-cutting concerns bleeding between routes.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jhonyes04/new-wayfy/llms.txt
Use this file to discover all available pages before exploring further.
Directory Layout
Feature Modules
Each directory insidemodules/ encapsulates one product feature end-to-end. Pages import from these modules but modules never import from pages.
AIAssistant
Natural-language map navigation powered by Groq/Llama.
AccessibilityMap
Mapbox GL map with OSM wheelchair layers and community pins.
Admin
Admin-only place moderation UI (mirrors
/admin/places).Entertainment
Discovery and filtering for entertainment venues.
Favorites
Saved places management for authenticated users.
FilterPanel
Wheelchair filter (
yes / limited / no) and category toggles.Hotels
Hotel search and accessibility-filtered listing.
Restaurants
Restaurant search and accessibility-filtered listing.
SearchAutocomplete
Geoapify-backed address and POI autocomplete.
Transports
Accessible transport stops and routes.
Trips
Full trip planner: days, places, cover images, and forking.
UserDashboard
Profile, favorites, and trip overview for signed-in users.
State Management
WayFy uses two independent React contexts that cover orthogonal concerns. Neither wraps the other — both are provided at the root of the component tree.AuthContext
Located atcontext/auth/AuthContext.jsx. Manages JWT session lifecycle with automatic expiration checking on mount.
| Value / Method | Type | Description |
|---|---|---|
user | object | null | Decoded JWT payload: id, email, is_admin, and all token claims |
token | string | null | Raw JWT string, persisted to localStorage under wayfy_token |
loading | boolean | true while the token is being validated on mount — render null or a spinner |
login(email, password) | async fn | POSTs to /api/users/login, calls setSession on success, throws on failure |
logout() | fn | Clears wayfy_token and wayfy_token_expiration from localStorage, resets state |
setSession(token, expiresIn) | fn | Persists token + expiry, decodes JWT, updates user state |
updateUserContext(data, newToken?, expiresIn?) | fn | Merges profile changes into user or replaces the session if a new token is provided |
The
AuthContext.Provider suppresses rendering children (via {!loading && children}) while loading is true. The Provider node itself remains mounted, but no child components are rendered until the token validation completes. Components that read from useAuth() will never see a stale user value during the initial token validation tick.Global Store
Located atcontext/store/. Consumed via the useGlobalReducer() hook which returns { state, dispatch }. The reducer lives in storeReducer.js; default values are defined in initialState.js.
All Dispatch Action Types
| Action Type | State Key Updated | Payload |
|---|---|---|
UPDATE_LOCATION | viewState | Partial { longitude, latitude, zoom } |
SET_SELECTED_LOCATION | selectedLocation | Location object or null |
ADD_PLACE | places | Place object to append |
REMOVE_PLACE | places | Place id to remove |
SET_FAVORITES | favorites | Full favorites array |
ADD_FAVORITE | favorites | Favorite object to append |
REMOVE_FAVORITE | favorites | Favorite id string to remove |
SET_ACTIVE_FILTERS | activeFilters | Array of 'yes', 'limited', 'no' |
SET_ACTIVE_CATEGORIES | activeCategories | Array of category slugs (see below) |
SET_SELECTED_FEATURE | selectedFeature | GeoJSON feature or null |
SET_AI_QUERY_INFO | aiQueryInfo | AI response payload or null |
SET_THEME | theme | 'light' or 'dark' (also persisted to localStorage) |
SET_LISTENING | isListening | boolean — microphone active for AI assistant |
SET_PROCESSING | isProcessing | boolean — AI query in flight |
SET_HOTELS_SEARCH | hotelsSearch | { hotels: [], location: null } — hotel search results and location |
SET_HOTELS_FILTERS | hotelsFilters | { wheelchair: [...] } — active hotel filters |
SET_HOTELS_ITEMS_PER_PAGE | hotelsItemsPerPage | number — pagination size for hotels listing |
SET_RESTAURANTS_SEARCH | restaurantsSearch | { restaurants: [], location: null } — restaurant search results and location |
SET_RESTAURANTS_FILTERS | restaurantsFilters | { wheelchair: [...] } — active restaurant filters |
SET_RESTAURANTS_ITEMS_PER_PAGE | restaurantsItemsPerPage | number — pagination size for restaurants listing |
SET_TRANSPORTS_SEARCH | transportsSearch | { transports: [], location: null } — transport search results and location |
SET_TRANSPORTS_FILTERS | transportsFilters | { wheelchair: [...] } — active transport filters |
SET_TRANSPORTS_ITEMS_PER_PAGE | transportsItemsPerPage | number — pagination size for transports listing |
SET_ENTERTAINMENT_SEARCH | entertainmentSearch | { items: [], location: null } — entertainment search results and location |
SET_ENTERTAINMENT_FILTERS | entertainmentFilters | { wheelchair: [...] } — active entertainment filters |
SET_ENTERTAINMENT_ITEMS_PER_PAGE | entertainmentItemsPerPage | number — pagination size for entertainment listing |
Filter & Category Values
activeFilters holds OSM wheelchair tag values:
activeCategories holds category slugs (all enabled by default in initialState.js):
Routing
WayFy uses React Router v6 withcreateBrowserRouter. Every page except PageHome and Page404 is lazy-loaded via React.lazy() wrapped in a <Suspense> boundary. The fallback renders a Bootstrap Spinner centred in the viewport.
Full Route Table
| Path | Component | Guard | Lazy |
|---|---|---|---|
/ | PageHome | — | No |
/map | PageMaps | — | Yes |
/login | PageLogin | — | Yes |
/register | PageRegister | — | Yes |
/forgot-password | PageForgotPassword | — | Yes |
/hotels | PageHotels | — | Yes |
/restaurants | PageRestaurants | — | Yes |
/transports | PageTransports | — | Yes |
/entertainment | PageEntertainment | — | Yes |
/trips/public | PagePublicTrips | — | Yes |
/trips/public/:tripId | PagePublicTripDetail | — | Yes |
/user-dashboard | PageUserDashboard | ProtectedRoute | Yes |
/user-profile | PageUserProfile | ProtectedRoute | Yes |
/user-favorites | PageUserFavorites | ProtectedRoute | Yes |
/trips | PageUserTrips | ProtectedRoute | Yes |
/trips/:tripId | PageUserTripDetail | ProtectedRoute | Yes |
/admin/places | PageAdminPlaces | AdminRoute | Yes |
* | Page404 | — | No |
- ProtectedRoute
- AdminRoute
- Lazy Loading
Checks
!!user from useAuth(). Redirects to /login if the user is not authenticated. Renders an <Outlet /> when the check passes.API Utility Pattern
All network calls use nativefetch with two helpers exported from services/apiUtils.js.
getAuthHeader(token) returns:
handleResponse(response) — parses the response body as JSON when the Content-Type is application/json. Throws an Error with body.msg if response.ok is false, passing through the server’s error message. Falls back to "Error {status} : {statusText}" for non-JSON responses.
API_BASE_URL is sourced from import.meta.env.VITE_BACKEND_URL at build time.
Shared Hooks
These hooks live inhooks/ and can be imported by any module or page.
| Hook | Purpose |
|---|---|
useGlobalReducer | Returns { state, dispatch } from the global store context |
useClickOutside | Fires a callback when a click occurs outside a ref’d element — used by dropdowns and modals |
useFilters | Manages active wheelchair filter and category state in sync with the global store |
useItemsPerPage | Tracks per-module pagination size and dispatches the relevant SET_*_ITEMS_PER_PAGE action |
usePagination | Computes current page slice, total pages, and page-change handler from a flat array |
usePlacePhoto | Fetches and caches an OG image or Google Places photo for a given place via /api/utils/og-image and /api/utils/google-photo |
useProtectedImage | Loads images that require a JWT-authenticated request (e.g. avatars served from /api/users/avatar/) |
useTooltip | Manages hover tooltip visibility state for TooltipButton |
Shared Components
Thecomponents/ directory holds primitives imported by multiple modules and pages.
Route Guards
Route Guards
ProtectedRoute — wraps authenticated-only routes; checks !!user.AdminRoute — wraps admin-only routes; checks user?.is_admin.Navigation
Navigation
UI Primitives
UI Primitives
FilterPanel — renders wheelchair filter chips and category toggles; dispatches to the global store.ModalForms — reusable Bootstrap Modal wrapper for login, register, and edit forms.Loading — full-viewport loading spinner.PaginationControls — renders Bootstrap Pagination from usePagination output.PasswordStrengthIndicator — visual password strength bar used on register and reset forms.TooltipButton — icon button with an accessible hover tooltip powered by useTooltip.Styling Conventions
WayFy uses React Bootstrap as its primary component library. Custom CSS is the last resort, not the default.Use Bootstrap utilities and React Bootstrap components first
Reach for
Container, Row, Col, Button, Card, Modal, Spinner, and Bootstrap utility classes (d-flex, gap-2, text-muted, etc.) before writing any custom CSS.Always write responsive layouts
Every layout element must use
xs/sm/md/lg/xl breakpoints on Col and Container. Never hardcode pixel widths on layout elements.Scope custom CSS to the module
Custom styles that Bootstrap cannot express go in
modules/<Name>/css/. Global rules go in src/index.css. Never add inline style props for layout.Icons via FontAwesome
Use the global FontAwesome icon font:
<i className="fa-solid fa-wheelchair" />. The SVG core (@fortawesome/fontawesome-svg-core) is configured globally so no per-component library import is needed.