Skip to main content

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.

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.

Directory Layout

frontend/src/
├── pages/           # One thin component per route — imports from modules
├── modules/         # Self-contained feature units
│   └── <Name>/
│       ├── components/
│       ├── hooks/
│       └── css/
├── components/      # Shared primitives (Navbar, Footer, ModalForms, route guards)
├── hooks/           # Shared custom hooks used across modules
├── services/        # API utilities and third-party wrappers
└── context/
    ├── auth/        # AuthContext
    └── store/       # Global useReducer store

Feature Modules

Each directory inside modules/ 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 at context/auth/AuthContext.jsx. Manages JWT session lifecycle with automatic expiration checking on mount.
import { useAuth } from '../context/auth/AuthContext';

const { user, token, loading, login, logout, setSession, updateUserContext } = useAuth();
Value / MethodTypeDescription
userobject | nullDecoded JWT payload: id, email, is_admin, and all token claims
tokenstring | nullRaw JWT string, persisted to localStorage under wayfy_token
loadingbooleantrue while the token is being validated on mount — render null or a spinner
login(email, password)async fnPOSTs to /api/users/login, calls setSession on success, throws on failure
logout()fnClears wayfy_token and wayfy_token_expiration from localStorage, resets state
setSession(token, expiresIn)fnPersists token + expiry, decodes JWT, updates user state
updateUserContext(data, newToken?, expiresIn?)fnMerges 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 at context/store/. Consumed via the useGlobalReducer() hook which returns { state, dispatch }. The reducer lives in storeReducer.js; default values are defined in initialState.js.
import { useGlobalReducer } from '../hooks/useGlobalReducer';

const { state, dispatch } = useGlobalReducer();

// Example: apply wheelchair filter
dispatch({ type: 'SET_ACTIVE_FILTERS', payload: ['yes', 'limited'] });

All Dispatch Action Types

Action TypeState Key UpdatedPayload
UPDATE_LOCATIONviewStatePartial { longitude, latitude, zoom }
SET_SELECTED_LOCATIONselectedLocationLocation object or null
ADD_PLACEplacesPlace object to append
REMOVE_PLACEplacesPlace id to remove
SET_FAVORITESfavoritesFull favorites array
ADD_FAVORITEfavoritesFavorite object to append
REMOVE_FAVORITEfavoritesFavorite id string to remove
SET_ACTIVE_FILTERSactiveFiltersArray of 'yes', 'limited', 'no'
SET_ACTIVE_CATEGORIESactiveCategoriesArray of category slugs (see below)
SET_SELECTED_FEATUREselectedFeatureGeoJSON feature or null
SET_AI_QUERY_INFOaiQueryInfoAI response payload or null
SET_THEMEtheme'light' or 'dark' (also persisted to localStorage)
SET_LISTENINGisListeningboolean — microphone active for AI assistant
SET_PROCESSINGisProcessingboolean — AI query in flight
SET_HOTELS_SEARCHhotelsSearch{ hotels: [], location: null } — hotel search results and location
SET_HOTELS_FILTERShotelsFilters{ wheelchair: [...] } — active hotel filters
SET_HOTELS_ITEMS_PER_PAGEhotelsItemsPerPagenumber — pagination size for hotels listing
SET_RESTAURANTS_SEARCHrestaurantsSearch{ restaurants: [], location: null } — restaurant search results and location
SET_RESTAURANTS_FILTERSrestaurantsFilters{ wheelchair: [...] } — active restaurant filters
SET_RESTAURANTS_ITEMS_PER_PAGErestaurantsItemsPerPagenumber — pagination size for restaurants listing
SET_TRANSPORTS_SEARCHtransportsSearch{ transports: [], location: null } — transport search results and location
SET_TRANSPORTS_FILTERStransportsFilters{ wheelchair: [...] } — active transport filters
SET_TRANSPORTS_ITEMS_PER_PAGEtransportsItemsPerPagenumber — pagination size for transports listing
SET_ENTERTAINMENT_SEARCHentertainmentSearch{ items: [], location: null } — entertainment search results and location
SET_ENTERTAINMENT_FILTERSentertainmentFilters{ wheelchair: [...] } — active entertainment filters
SET_ENTERTAINMENT_ITEMS_PER_PAGEentertainmentItemsPerPagenumber — pagination size for entertainment listing

Filter & Category Values

activeFilters holds OSM wheelchair tag values:
'yes'  |  'limited'  |  'no'
activeCategories holds category slugs (all enabled by default in initialState.js):
gastronomia  |  alojamiento    |  transporte   |  salud
cultura_turismo  |  recreacion  |  deporte     |  gobierno
baños  |  dinero  |  tiendas

Routing

WayFy uses React Router v6 with createBrowserRouter. 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

PathComponentGuardLazy
/PageHomeNo
/mapPageMapsYes
/loginPageLoginYes
/registerPageRegisterYes
/forgot-passwordPageForgotPasswordYes
/hotelsPageHotelsYes
/restaurantsPageRestaurantsYes
/transportsPageTransportsYes
/entertainmentPageEntertainmentYes
/trips/publicPagePublicTripsYes
/trips/public/:tripIdPagePublicTripDetailYes
/user-dashboardPageUserDashboardProtectedRouteYes
/user-profilePageUserProfileProtectedRouteYes
/user-favoritesPageUserFavoritesProtectedRouteYes
/tripsPageUserTripsProtectedRouteYes
/trips/:tripIdPageUserTripDetailProtectedRouteYes
/admin/placesPageAdminPlacesAdminRouteYes
*Page404No
Checks !!user from useAuth(). Redirects to /login if the user is not authenticated. Renders an <Outlet /> when the check passes.
// components/ProtectedRoute.jsx
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return <Outlet />;

API Utility Pattern

All network calls use native fetch with two helpers exported from services/apiUtils.js.
import { API_BASE_URL, getAuthHeader, handleResponse } from '../services/apiUtils';

// Authenticated POST
const data = await handleResponse(
  await fetch(`${API_BASE_URL}/api/trips`, {
    method: 'POST',
    headers: getAuthHeader(token),
    body: JSON.stringify({ title: 'Madrid Accessible', is_public: false }),
  })
);

// Unauthenticated GET
const places = await handleResponse(
  await fetch(`${API_BASE_URL}/api/places`)
);
getAuthHeader(token) returns:
{
  Authorization: `Bearer ${token}`,
  'Content-Type': 'application/json',
}
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.
Never hardcode localhost:3001 in component or hook files. Always import API_BASE_URL from services/apiUtils.js so the build works correctly across development, staging, and production environments.

Shared Hooks

These hooks live in hooks/ and can be imported by any module or page.
HookPurpose
useGlobalReducerReturns { state, dispatch } from the global store context
useClickOutsideFires a callback when a click occurs outside a ref’d element — used by dropdowns and modals
useFiltersManages active wheelchair filter and category state in sync with the global store
useItemsPerPageTracks per-module pagination size and dispatches the relevant SET_*_ITEMS_PER_PAGE action
usePaginationComputes current page slice, total pages, and page-change handler from a flat array
usePlacePhotoFetches and caches an OG image or Google Places photo for a given place via /api/utils/og-image and /api/utils/google-photo
useProtectedImageLoads images that require a JWT-authenticated request (e.g. avatars served from /api/users/avatar/)
useTooltipManages hover tooltip visibility state for TooltipButton

Shared Components

The components/ directory holds primitives imported by multiple modules and pages.
ProtectedRoute — wraps authenticated-only routes; checks !!user.
AdminRoute — wraps admin-only routes; checks user?.is_admin.
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.
1

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.
2

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.
3

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.
4

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.
5

Notifications via react-toastify

Import toast from react-toastify for all user-facing feedback. Use toast.success() for confirmations and toast.error() for failures.
import { toast } from 'react-toastify';
toast.success('Trip saved successfully');
toast.error('Failed to load places');
Sass is available (sass is listed as a dev dependency). If a module needs more than a handful of custom rules, a .scss file in modules/<Name>/css/ is preferable to a long plain CSS file — it keeps nesting and variable usage readable.

Build docs developers (and LLMs) love