Member-facing single-page application for Altafit Gym Club — login and account management, subscription and billing status, gym access history, class booking with a live schedule, activity timetable, member perks, training programmes (GBody / GBox), the “Club Corredor” content feed, news and club contact with maps.
The production member portal for Altafit Gym Club, built solo as a freelance project alongside full-time work at Melkart. The backend was a third-party gym-management API — I built the entire client and the integration layer over it.
~7.2k lines of JavaScript across 104 files. Fully responsive, mobile-first, four breakpoints. Spanish UI throughout.
redux-promise-middleware promise action expanding into FETCHING / FETCHED / ERROR, so loading, success and error states are uniform across the whole app.getReducer(ACTION) higher-order reducer generates the request-state slice for all 19 endpoints, combineReducers-mounted under state.api; adding an endpoint is one line, not a new reducer file.state.api (raw request lifecycle), state.pages (per-screen view state), and state.storage / state.template / state.noti (session, layout chrome, global notifications).*_ERROR action while authenticated, detects the backend's invalid-token response, and dispatches a global logout instead of letting each page handle it.localStorage at store construction, kept in sync on login/logout, and mirrored into a static Config so the HTTP layer always sends the current token./users route tree is wrapped in a connected component that redirects to /auth/login when unauthenticated, so no protected page repeats the check.profile, reserva, acceso, club, evento, contenido, categoria, noti, ajax, login, contacto normalize the third-party API's Spanish JSON payloads into stable client-side shapes.Express 4 on Node, used to develop against a stable local surface while the third-party gym API was in flux. Hardened with Helmet, CORS whitelisting for local dev origins, body-parser, Morgan request logging and request for upstream proxying, behind a Grunt + Nodemon watch pipeline with JSHint linting.
Yarn with a committed lockfile and the CRA build pipeline producing a hashed static bundle served behind a web server. Integrated against two third-party services: the userapi REST backend (/v2/…, token-in-path auth with platform and API-key headers) and a separate media/file server for images and documents.
The FETCHING/FETCHED/ERROR action-type convention plus a reducer factory removed ~19 near-identical reducers and made every screen's loading and error handling identical by construction.
Token invalidation is caught once in the Redux middleware pipeline and turned into a clean global logout, rather than re-implemented in every page's error branch.
Reading localStorage before createStore means the app boots already authenticated, with no login flash or post-mount redirect.
A static Styles class supplies colors, breakpoints and helpers (such as a truncate(width) mixin) to 43 styled-component files, keeping a single source of visual truth without a CSS framework rewrite.
Model classes and a wrapped Axios instance isolate the app from an external, Spanish-named, token-in-URL API whose shape the project did not control.
Category filtering and paginated fetches, backed by the same generic API-state machinery as every other screen.
The portal ran in production for Altafit's members; the domain now hosts a later, non-React implementation by another vendor, which is why this page carries no screenshots.
It was built on the stack that was current then: React 16 class components, Redux with thunks and promise middleware, CRA 1.x, Bootstrap 4. Today's equivalents would be function components with hooks, TanStack Query or RTK Query in place of the custom request-state reducers, TypeScript for the API models and Vite instead of react-scripts — the architectural separations (adapter layer, generic request state, middleware-level session handling) map cleanly onto those modern tools.