Skip to content

feat: add Vue SPA frontend for browsing seasons, divisions, and players#387

Open
dambrisco wants to merge 5 commits intotrunkfrom
claude/evaluate-frontend-frameworks-h7xIo
Open

feat: add Vue SPA frontend for browsing seasons, divisions, and players#387
dambrisco wants to merge 5 commits intotrunkfrom
claude/evaluate-frontend-frameworks-h7xIo

Conversation

@dambrisco
Copy link
Contributor

Summary

This PR adds a new Vue 3 single-page application (SPA) frontend to the RD2L project, providing a user-friendly interface for browsing seasons, divisions, and player information. The SPA is served at /app and integrates with the existing Express backend API.

Key Changes

  • New Vue SPA Structure: Created a complete Vue 3 application with TypeScript support, Vue Router for client-side navigation, and Bulma CSS framework for styling

    • client/src/main.ts - Application entry point
    • client/src/App.vue - Root component with navigation bar and router outlet
    • client/index.html - HTML template with Bulma and FontAwesome CDN links
    • client/tsconfig.json - TypeScript configuration for Vue
    • client/vite.config.ts - Vite build configuration with relative asset paths for /app mounting
  • API Client Layer: Created client/src/api/index.ts with TypeScript interfaces and thin wrapper functions for backend endpoints

    • Interfaces for Season, Division, DivisionDetail, Player, and Captain
    • API methods for seasons, divisions, players, and captains endpoints
  • Views/Pages:

    • HomeView.vue - Landing page with navigation cards to seasons, divisions, and rules
    • SeasonsView.vue - Table listing all seasons
    • DivisionsView.vue - Grid of division cards with Discord links
    • DivisionView.vue - Detail page showing division admins and links
    • PlayersView.vue - Tabbed interface showing all players or captains with MMR data
  • Components:

    • NavBar.vue - Navigation bar with responsive mobile menu and Steam login button
  • Routing: client/src/router/index.ts with routes for home, seasons, divisions, and players pages

  • Server Integration:

    • Updated src/server.ts to serve static SPA assets from /app and handle client-side routing with a catch-all that returns index.html
    • Added build scripts to package.json for building both server and client
    • Added Vue, Vue Router, Vite, and related dependencies

Notable Implementation Details

  • The SPA uses relative asset paths (base: './' in Vite config) to work correctly when served from /app/* sub-paths
  • Error handling and loading states are implemented across all views
  • The catch-all route in Express ensures Vue Router can handle all client-side navigation
  • Steam authentication link is available in the navigation bar
  • Player names link to individual profile pages at /profile/{steam_id}

https://claude.ai/code/session_01H9bg5MmtXCkaQe2SUyAKLu

Add a Vue 3 single-page application accessible at /app alongside the
existing server-rendered Pug interface, which remains fully intact.

Framework choice — Vue 3
- Template syntax (HTML-like directives) is the gentlest transition from
  Pug for a team already working in server-rendered HTML
- Vue 3 + Composition API has first-class TypeScript support, consistent
  with the rest of the codebase
- Vite is Vue's official build tool: fast HMR in dev, optimised output
  in production, minimal configuration needed
- vue-router 4 matches the nested season/division URL structure already
  used by the SSR layer
- Smaller runtime footprint than Angular; more opinionated structure than
  plain Preact/Solid which helps onboarding

What was added
- client/ — Vue 3 source tree (index.html, vite.config.ts, tsconfig.json)
  - src/main.ts — app entry point
  - src/App.vue — root component with NavBar + <RouterView>
  - src/router/index.ts — routes: /, /seasons, /divisions, /divisions/:id,
    /seasons/:s/divisions/:d/players
  - src/api/index.ts — thin fetch wrappers for the existing /api/v1/* endpoints
  - src/components/NavBar.vue — responsive Bulma navbar, links to Steam auth
  - src/views/ — HomeView, SeasonsView, DivisionsView, DivisionView, PlayersView
- package.json: build:server / build:client / dev:client scripts;
  build now runs both tsc and vite; vue, vue-router, vite, @vitejs/plugin-vue,
  @vue/tsconfig added as devDependencies
- src/server.ts: serves dist/public/ at /app (static) and catches all
  /app/* paths with index.html for client-side routing
@railway-app railway-app bot temporarily deployed to rd2l / server-pr-387 February 16, 2026 22:35 Destroyed
@railway-app
Copy link

railway-app bot commented Feb 16, 2026

🚅 Deployed to the server-pr-387 environment in rd2l

Service Status Web Updated (UTC)
server ✅ Success (View Logs) Web Feb 17, 2026 at 9:00 pm

@dambrisco dambrisco changed the title Add Vue SPA frontend for browsing seasons, divisions, and players feat: add Vue SPA frontend for browsing seasons, divisions, and players Feb 16, 2026
The build stage only copied src/ and tsconfig.json, so the Vite config
and Vue source in client/ were absent when `npm run build` ran, causing
esbuild to fail with "Could not resolve /app/client/vite.config.ts".

Adding `COPY client ./client` before the build step makes the full
source available. The built SPA lands in dist/public/ which is already
covered by the existing `COPY --from=builder /app/dist ./dist` in the
run stage.
@railway-app railway-app bot temporarily deployed to rd2l / server-pr-387 February 16, 2026 22:38 Destroyed
CodeQL flagged /app/*splat as performing a file system access (sendFile)
without rate limiting, making it a potential DoS vector.

Install express-rate-limit and apply a dedicated limiter (120 req/min
per IP) to the catch-all handler. Static assets under /app are served by
express.static and are not affected — the OS page-cache and Express's
built-in ETag/304 handling already make those cheap.

Closes CodeQL alert: Missing rate limiting (src/server.ts:338)
@railway-app railway-app bot temporarily deployed to rd2l / server-pr-387 February 16, 2026 22:43 Destroyed
Two additional route groups perform expensive operations per request
with no rate limiting:

- /auth/steam and /auth/steam/return trigger outbound calls to the
  Steam OpenID / Web API; limited to 20 requests per 15 min per IP.
- /rules and /inhouserules call fs.readFile on every request;
  limited to 60 requests per minute per IP.

Uses the same express-rate-limit instance already introduced for the
SPA catch-all; only new limiter instances are added for appropriate
thresholds per route group.
@railway-app railway-app bot temporarily deployed to rd2l / server-pr-387 February 17, 2026 20:51 Destroyed
- fix(api/divisions): rename response key divisionAdmins → admins to
  match the DivisionDetail client type and DivisionView template

- feat(api/me): add GET /api/v1/me — returns { loggedIn: false } for
  anonymous visitors, or { loggedIn, steamId, displayName, avatar,
  isAdmin, activityCheckRequired } for authenticated users

- feat(api/csrf): add GET /api/v1/csrf — returns { token } so the SPA
  can obtain a CSRF token before issuing POST requests

- feat(server): accept CSRF token via x-csrf-token request header in
  addition to req.body._csrf; this lets the SPA send JSON POSTs without
  needing to embed the token in the request body

- feat(client/api): add MeResponse union type, api.me(), and api.post()
  helper — post() fetches a fresh CSRF token then performs a JSON POST
  with the x-csrf-token header automatically set

- feat(client/NavBar): call api.me() on mount; show avatar + display
  name + Sign out when logged in, Sign in with Steam when anonymous;
  auth buttons are hidden until the /api/v1/me response arrives
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments