From 2cc902870ca5e10561ffe9d8e105cb31dc054da3 Mon Sep 17 00:00:00 2001 From: Daniel Kpatamia Date: Fri, 3 Jul 2026 22:16:43 +0000 Subject: [PATCH 1/4] Starting to create socket engine for client to communicate withh server --- Makefile | 7 +- .../index.md => 01-setup-and-overview.md} | 0 docs/{auth/index.md => 02-authentication.md} | 0 docs/03-game-and-lobby-ws.md | 6 + docs/04-integration-roadmap.md | 251 +++++++++++++ docs/05-backend-completion-guide.md | 302 ++++++++++++++++ docs/06-frontend-integration-guide.md | 335 ++++++++++++++++++ server/go.mod | 1 + server/go.sum | 2 + server/internals/api/user-handler.go | 6 +- server/internals/route/route.go | 42 ++- server/internals/store/db_test.go | 2 - server/internals/store/users_store.go | 4 +- server/internals/utils/constants.go | 5 + web-client/coverage/auth-reducer.ts.html | 241 ------------- web-client/coverage/base.css | 224 ------------ web-client/coverage/block-navigation.js | 87 ----- web-client/coverage/clover.xml | 17 - web-client/coverage/coverage-final.json | 2 - web-client/coverage/favicon.png | Bin 445 -> 0 bytes web-client/coverage/index.html | 116 ------ web-client/coverage/prettify.css | 1 - web-client/coverage/prettify.js | 2 - web-client/coverage/sort-arrow-sprite.png | Bin 138 -> 0 bytes web-client/coverage/sorter.js | 210 ----------- web-client/package-lock.json | 332 ++++++++++++++++- web-client/package.json | 1 + web-client/src/App.tsx | 19 +- web-client/src/components/footer.tsx | 7 +- .../src/components/game/arena/bottom-nav.tsx | 2 +- web-client/src/hooks/use-socket.ts | 31 ++ .../{components/auth => pages}/auth-gate.tsx | 37 +- web-client/src/pages/home/index.tsx | 3 - web-client/src/pages/home/layout.tsx | 11 + web-client/src/pages/not-found.tsx | 3 - web-client/src/services/api.ts | 23 +- web-client/src/state/context/auth-context.tsx | 10 +- web-client/src/types/messages.ts | 35 ++ web-client/src/types/type.ts | 8 +- 39 files changed, 1401 insertions(+), 984 deletions(-) rename docs/{setup/index.md => 01-setup-and-overview.md} (100%) rename docs/{auth/index.md => 02-authentication.md} (100%) create mode 100644 docs/03-game-and-lobby-ws.md create mode 100644 docs/04-integration-roadmap.md create mode 100644 docs/05-backend-completion-guide.md create mode 100644 docs/06-frontend-integration-guide.md delete mode 100644 server/internals/store/db_test.go create mode 100644 server/internals/utils/constants.go delete mode 100644 web-client/coverage/auth-reducer.ts.html delete mode 100644 web-client/coverage/base.css delete mode 100644 web-client/coverage/block-navigation.js delete mode 100644 web-client/coverage/clover.xml delete mode 100644 web-client/coverage/coverage-final.json delete mode 100644 web-client/coverage/favicon.png delete mode 100644 web-client/coverage/index.html delete mode 100644 web-client/coverage/prettify.css delete mode 100644 web-client/coverage/prettify.js delete mode 100644 web-client/coverage/sort-arrow-sprite.png delete mode 100644 web-client/coverage/sorter.js create mode 100644 web-client/src/hooks/use-socket.ts rename web-client/src/{components/auth => pages}/auth-gate.tsx (95%) create mode 100644 web-client/src/pages/home/layout.tsx create mode 100644 web-client/src/types/messages.ts diff --git a/Makefile b/Makefile index 03fbf40..f892c13 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ help: @echo " make lint Lint server and client code" @echo " make test Run tests for server and client" @echo " make ui-test Run React/Vite component and unit tests" + @echo " make start-pg Spawn docker Development postgres container" + @echo " make pg-connect Connect to running postgres instance " install: @echo "📦 Installing Go dependencies..." @@ -34,7 +36,10 @@ dev-client: # Build for production build: build-server build-client @echo "✅ Build complete. Server binary at ./server && Client bundle at ./web-client/dist" - +pg-connect: + docker exec -it taresDB psql -U logic -d tares_db +start-pg: + docker compose up # needs refinement build-server: @echo "🔨 Building Go server..." diff --git a/docs/setup/index.md b/docs/01-setup-and-overview.md similarity index 100% rename from docs/setup/index.md rename to docs/01-setup-and-overview.md diff --git a/docs/auth/index.md b/docs/02-authentication.md similarity index 100% rename from docs/auth/index.md rename to docs/02-authentication.md diff --git a/docs/03-game-and-lobby-ws.md b/docs/03-game-and-lobby-ws.md new file mode 100644 index 0000000..a3721fc --- /dev/null +++ b/docs/03-game-and-lobby-ws.md @@ -0,0 +1,6 @@ +State Machine (Client <-> Server wss) +User authenticate(login/signup)---> UI(take user to game-lobby) +User request to create arena ---> Server picks up ---> Process the wss request ---> Return an error/success +If error is returned ---> User is updated +If result is returned ---> UI(updates with arena details: name, capacity, and current participants) +User can request to join an arena---> UI ---> server ---> response/error ---> UI updates appropriately diff --git a/docs/04-integration-roadmap.md b/docs/04-integration-roadmap.md new file mode 100644 index 0000000..d81db7e --- /dev/null +++ b/docs/04-integration-roadmap.md @@ -0,0 +1,251 @@ +# Integration Roadmap + +This document explains how to finish the project by connecting the Go backend and the React web client into one coherent runtime. + +The current state is good for UI exploration, but not yet complete for a real multiplayer game: + +- The backend exposes auth and websocket entry points. +- The frontend renders the game UI and auth UI. +- The two sides do not yet share a stable message contract. +- The game websocket flow exists on the server, but the client does not yet drive it. + +The goal of this roadmap is simple: + +1. Make auth reliable. +2. Make websocket connection reliable. +3. Make lobby and arena state flow through one shared contract. +4. Make the game loop actually progress. +5. Make the UI reflect the server state instead of local mock state. + +--- + +## System View + +```mermaid +flowchart LR + U[Player Browser] --> A[React UI] + A -->|HTTP auth| H[Go HTTP API] + A -->|WS connect| W[Go WebSocket Handler] + H --> D[(PostgreSQL)] + W --> M[Room Manager] + M --> R[Room] + R --> E[Game Engine] + E --> B[Broadcast State] + B --> A +``` + +The important design rule is that the browser should never invent authoritative game state. The browser can render animations and local UX, but the backend must remain the source of truth for: + +- authenticated user identity +- room membership +- room capacity +- round state +- timer state +- score updates +- win/lose events + +If the client and server disagree, the server wins. + +--- + +## What Exists Today + +### Backend already present + +- `server/main.go` starts the HTTP server. +- `server/internals/route/route.go` registers `/users/signup`, `/users/login`, and `/ws/rooms`. +- `server/internals/api/user-handler.go` handles signup and login. +- `server/internals/middleware/middleware.go` validates auth tokens. +- `server/internals/ws/manager.go` owns the lobby and room channels. +- `server/internals/ws/client.go` reads and writes websocket messages. +- `server/internals/ws/room.go` defines the room lifecycle structure. +- `server/internals/events/events.go` defines the message envelope. + +### Frontend already present + +- `web-client/src/services/api.ts` handles HTTP auth. +- `web-client/src/state/context/auth-context.tsx` stores auth state. +- `web-client/src/pages/auth-gate.tsx` handles login and signup forms. +- `web-client/src/pages/game/lobby/game-lobby.tsx` renders lobby UI. +- `web-client/src/pages/game/arena/game-arena.tsx` renders arena UI. +- `web-client/src/state/context/game-context.tsx` is intended to hold game state. +- `web-client/src/types/messages.ts` and `web-client/src/types/type.ts` define client-facing types. + +--- + +## The Main Integration Problem + +The project is missing a single stable integration contract. + +At the moment: + +- HTTP auth uses one set of request/response shapes. +- Websocket messages use another set of message shapes. +- The frontend routes use one naming scheme. +- The backend websocket code uses another naming scheme. +- The room/game loop is only partly wired. + +That means the UI can render, but it cannot reliably control the server. + +The fix is not to add more UI first. The fix is to establish the contract first, then wire the UI to it. + +--- + +## Recommended Implementation Order + +```mermaid +flowchart TD + S[Step 1: Normalize auth responses] --> T[Step 2: Fix websocket auth transport] + T --> U[Step 3: Define one websocket envelope] + U --> V[Step 4: Add client websocket service] + V --> W[Step 5: Wire GameContext to socket events] + W --> X[Step 6: Complete room/game broadcasts] + X --> Y[Step 7: Connect lobby and arena UI to state] + Y --> Z[Step 8: Add tests and end-to-end checks] +``` + +Do not start with the arena UI logic. The UI is already visually good enough to sit on top of the real state machine. The missing piece is the state machine itself. + +--- + +## High-Level Integration Rules + +### Rule 1: HTTP is for identity + +Use HTTP for: + +- signup +- login +- token creation or refresh +- user profile bootstrap + +### Rule 2: WebSocket is for live game state + +Use WebSocket for: + +- joining lobby +- creating room +- joining room +- leaving room +- submitting a word +- pausing or resuming game state +- broadcasting timer, score, round, and room updates + +### Rule 3: Client state is derived + +The React state should be a projection of the backend state. + +That means the client should: + +- connect +- send actions +- receive broadcasts +- update context/reducer state +- render based on that state + +The client should not try to maintain a parallel authoritative room model. + +--- + +## Where Integration Must Happen + +| Concern | Current File | What Must Happen | +|---|---|---| +| Backend startup | `server/main.go` | Start one room manager loop once, not per socket connection | +| Route setup | `server/internals/route/route.go` | Expose a browser-compatible websocket auth flow | +| Auth handling | `server/internals/middleware/middleware.go` | Safely attach user identity to request context | +| WS entrypoint | `server/internals/ws/manager.go` | Create one manager instance and share it | +| Room loop | `server/internals/ws/room.go` | Consume inbound events and broadcast state | +| Socket client | `web-client/src/services/` | Add a websocket client module | +| Game state | `web-client/src/state/context/game-context.tsx` | Mount the correct provider and listen to socket events | +| Game types | `web-client/src/types/messages.ts` | Match backend message envelope exactly | +| Route paths | `web-client/src/App.tsx` and nav components | Make all paths consistent | + +--- + +## Authentication Flow + +```mermaid +sequenceDiagram + participant Browser as React App + participant API as Go HTTP API + participant DB as PostgreSQL + participant WS as WebSocket Server + + Browser->>API: POST /users/signup or /users/login + API->>DB: read/write user and token rows + DB-->>API: user + token + API-->>Browser: auth response + Browser->>Browser: store token in auth state + Browser->>WS: connect using browser-safe token strategy + WS-->>Browser: authenticated socket ready +``` + +Why this matters: + +- the browser can keep the user logged in across refreshes +- the websocket can know who is sending actions +- the room manager can use the authenticated user identity for room membership + +--- + +## Game Flow + +```mermaid +sequenceDiagram + participant UI as React Lobby UI + participant WS as WebSocket Client + participant RM as Room Manager + participant R as Room + participant GE as Game Engine + + UI->>WS: CREATE_ROOM or JOIN_ROOM + WS->>RM: send lobby action + RM->>R: create or join room + R->>GE: initialize or update game session + GE-->>R: updated game state + R-->>WS: broadcast lobby/game state + WS-->>UI: update React context +``` + +This is the loop you want to complete. Once this loop exists, every other feature becomes much easier. + +--- + +## Minimal Completion Checklist + +1. Make auth responses and request bodies consistent across frontend and backend. +2. Decide how the websocket receives the token from the browser. +3. Define one canonical websocket envelope. +4. Create a websocket client service in the frontend. +5. Add a game provider that listens to socket events. +6. Make room manager and room loops broadcast state changes. +7. Remove route mismatches in navigation and game pages. +8. Add tests for login, websocket connect, room join, and room broadcast. + +--- + +## Why This Order Is Best + +This order reduces rework. + +If you wire the UI too early, you will spend time rewriting components after the backend contract changes. + +If you wire the backend too late, you will keep building mock UI that cannot talk to real game state. + +If you normalize the contract first, both sides can be implemented in parallel. + +--- + +## Practical End State + +When the project is complete, this should be true: + +- The user signs up or logs in through HTTP. +- The token is stored once and reused. +- The websocket opens with the authenticated user. +- The lobby shows live room data. +- The user can create or join rooms. +- The arena reflects live timer and score updates. +- The server owns the authoritative state. +- The UI is only a renderer plus input layer. diff --git a/docs/05-backend-completion-guide.md b/docs/05-backend-completion-guide.md new file mode 100644 index 0000000..e5a62ee --- /dev/null +++ b/docs/05-backend-completion-guide.md @@ -0,0 +1,302 @@ +# Backend Completion Guide + +This document explains how to finish the Go backend so it can properly power the web client. + +The backend already has the right building blocks, but several parts are not yet connected in a production-safe way. + +The key backend responsibilities are: + +- authenticate the player +- manage websocket connections +- maintain room membership +- run the room/game state loop +- broadcast state changes back to the browser + +--- + +## Backend Architecture + +```mermaid +flowchart LR + A[main.go] --> B[app.NewApplication] + B --> C[route.SetupRoute] + C --> D[Auth Middleware] + C --> E[WS Handler] + E --> F[Room Manager] + F --> G[Room] + G --> H[Game Engine] + H --> I[Timer / Score / Word State] + I --> J[Broadcast to Clients] +``` + +The backend should be thought of as a set of layers: + +1. HTTP layer for auth. +2. Middleware layer for identity. +3. Websocket layer for real-time actions. +4. Room layer for live session management. +5. Engine layer for game logic. + +--- + +## Current Backend Gaps + +### 1. Room manager loop starts in the wrong place + +In `server/internals/ws/manager.go`, the manager loop is started from inside `HandleWS`. + +That means every socket connection can start a new manager loop. + +Why this is a problem: + +- multiple loops compete over the same channels +- lobby state may be handled more than once +- memory usage grows as clients connect +- shutdown becomes hard to reason about + +What to do instead: + +- create one manager at application startup +- start `Run()` once in a goroutine +- keep the same manager instance for all websocket connections + +### 2. Room does not yet process game events + +In `server/internals/ws/room.go`, the room loop handles join and leave, but not the inbound game events. + +Why this matters: + +- users can connect, but no actual game turn can progress +- word submissions do not update room state +- timers and scores do not broadcast + +What to do instead: + +- add handling for `inboundEvents` +- translate client actions into engine actions +- push broadcasts into each client channel + +### 3. Browser auth transport is not yet websocket-safe + +The websocket route currently relies on the auth middleware reading `Authorization` from the HTTP request. + +That is fine for normal fetch calls, but browsers cannot reliably attach custom headers to native `WebSocket` handshakes. + +Why this matters: + +- the browser may connect without auth +- `RequireAuth` may reject legitimate clients +- the socket layer and the client will disagree on how auth works + +What to do instead: + +- use a token query parameter, or +- use a cookie-based session, or +- authenticate the socket immediately after connect with a first message + +The simplest integration path is to standardize on a query token for the websocket handshake if you want minimal code change. + +--- + +## Recommended Backend Wiring + +### A. Application startup + +Move manager creation into application setup so there is one shared manager instance. + +Suggested ownership: + +- `app.NewApplication()` creates the room manager +- `route.SetupRoute()` receives that manager through the application struct +- `HandleWS()` only upgrades the connection and registers the client + +### B. Websocket connection lifecycle + +```mermaid +sequenceDiagram + participant Browser as Browser + participant Route as WS Route + participant Auth as Middleware + participant RM as Room Manager + participant Client as WS Client + + Browser->>Route: GET /ws/rooms?token=... + Route->>Auth: authenticate request + Auth-->>Route: attach user to context + Route->>RM: hand off authenticated socket + RM->>Client: register client in lobby + Client->>Browser: send lobby state updates +``` + +### C. Room lifecycle + +```mermaid +flowchart TD + A[Client sends lobby action] --> B[Room manager receives action] + B --> C{Action type?} + C -->|CREATE_ROOM| D[Create room instance] + C -->|JOIN_ROOM| E[Check room exists and has capacity] + C -->|GET_ROOMS| F[Return room list] + D --> G[Start room loop] + E --> H[Move client from lobby to room] + G --> I[Room broadcasts state] + H --> I +``` + +--- + +## File-by-File Backend Integration + +### 1. `server/main.go` + +Purpose: + +- create the application +- create one manager +- start the HTTP server + +What to integrate here: + +- initialize the room manager once +- start the manager goroutine once +- pass it into the route setup through the application struct + +Why: + +- this is the top-level bootstrapper +- it should own long-lived services + +### 2. `server/internals/app/app.go` + +Purpose: + +- wire stores, handlers, middleware, and shared runtime services + +What to integrate here: + +- add the room manager to the `Application` struct +- construct it during app startup +- keep it shared across the entire server lifetime + +Why: + +- app setup is the cleanest place to build long-lived dependencies + +### 3. `server/internals/route/route.go` + +Purpose: + +- declare all HTTP and websocket routes + +What to integrate here: + +- route auth endpoints as they are +- route websocket endpoint to the shared room manager +- ensure auth middleware is compatible with the browser socket strategy + +Why: + +- route configuration should not create application state +- it should only connect handlers to the shared app services + +### 4. `server/internals/ws/manager.go` + +Purpose: + +- keep lobby and room state in one place + +What to integrate here: + +- one loop for lobby joins/leaves and lobby actions +- room creation and discovery logic +- room registry access guarded by locks + +Why: + +- this is the central coordinator for all connected clients + +### 5. `server/internals/ws/room.go` + +Purpose: + +- own the state for one room + +What to integrate here: + +- process inbound game actions +- coordinate with the engine +- broadcast updated room state +- manage join and leave events cleanly + +Why: + +- rooms are the unit of gameplay +- the game engine should not have to know about HTTP or browser concerns + +### 6. `server/internals/ws/client.go` + +Purpose: + +- adapt socket bytes into typed actions and typed broadcasts + +What to integrate here: + +- decode the browser message envelope +- route lobby messages to the room manager +- route game messages to the room +- serialize broadcasts back to the browser + +Why: + +- this is the protocol adapter between the browser and the backend + +--- + +## Backend Message Contract + +The server already defines a canonical envelope in `server/internals/events/events.go`: + +- `RawMessage` +- `InlobbyUserAction` +- `IngameUserAction` +- `LobbyStateBroadcast` +- `GameStateBroadcast` + +The contract should stay stable and be mirrored by the frontend. + +Recommended meaning: + +- `msg_type` tells the server whether the message belongs to lobby state or game state. +- `payload` holds the typed action body. + +### Why the envelope is useful + +- it keeps the socket protocol small +- it allows future extension without breaking every client +- it keeps lobby and game actions separate + +--- + +## Backend Completion Checklist + +1. Create one shared room manager instance at app startup. +2. Start the manager loop once. +3. Make the websocket auth path browser-compatible. +4. Align the middleware behavior with the websocket connection strategy. +5. Add room event handling for `CREATE_ROOM`, `JOIN_ROOM`, and `GET_ROOMS`. +6. Add game event handling for submit/pause/resume/stop actions. +7. Broadcast room and game updates back to connected clients. +8. Add tests for auth, room join, and websocket broadcast behavior. + +--- + +## Backend End-State + +When backend integration is complete, the server should behave like this: + +- one HTTP API handles auth +- one websocket service handles live game state +- one room manager owns all active rooms +- each room has a game loop +- clients only send intent and receive state +- the server remains authoritative diff --git a/docs/06-frontend-integration-guide.md b/docs/06-frontend-integration-guide.md new file mode 100644 index 0000000..2d9e618 --- /dev/null +++ b/docs/06-frontend-integration-guide.md @@ -0,0 +1,335 @@ +# Frontend Integration Guide + +This document explains how to connect the React UI to the backend in a clean and maintainable way. + +The frontend already has strong visual work. What it lacks is a real runtime bridge to the backend. + +The frontend must do three things well: + +1. Authenticate the user. +2. Connect to the websocket. +3. Render state from the server. + +--- + +## Frontend Architecture + +```mermaid +flowchart LR + UI[React Pages] --> Ctx[Auth/Game Context] + Ctx --> Api[HTTP API Client] + Ctx --> Ws[WebSocket Client] + Ws --> Msg[Typed Message Envelope] + Msg --> UI +``` + +The important principle is separation of concerns: + +- pages render screens +- contexts manage app state +- services talk to the backend +- types define the contract + +--- + +## What Exists Today + +The client already has: + +- `web-client/src/services/api.ts` for auth HTTP calls +- `web-client/src/state/context/auth-context.tsx` for auth state +- `web-client/src/pages/auth-gate.tsx` for login/signup UI +- `web-client/src/pages/game/lobby/game-lobby.tsx` for lobby UI +- `web-client/src/pages/game/arena/game-arena.tsx` for arena UI +- `web-client/src/state/game-reducer.ts` for game state transitions +- `web-client/src/types/messages.ts` for message shapes + +The missing pieces are connection wiring and contract alignment. + +--- + +## Main Frontend Gaps + +### 1. The game provider is mounted incorrectly + +In `web-client/src/state/context/game-context.tsx`, the provider currently renders the auth provider instead of the game provider. + +Why this matters: + +- the game state context is never actually provided +- game screens cannot receive state updates properly + +Fix: + +- return `GameContext.Provider` + +### 2. There is no websocket client service + +The frontend currently has an HTTP API service, but no socket service. + +Why this matters: + +- the lobby cannot send create/join events +- the UI cannot listen for room or game broadcasts + +Fix: + +- add a websocket client module under `web-client/src/services/` +- connect it from a game hook or game provider + +### 3. The frontend message types do not match the backend + +`web-client/src/types/messages.ts` currently uses a different structure from `server/internals/events/events.go`. + +Why this matters: + +- the socket payloads will not decode correctly +- TypeScript may compile while runtime decoding fails + +Fix: + +- mirror the backend envelope exactly + +### 4. Route paths are inconsistent + +The app router and navigation components point at different URL shapes. + +Why this matters: + +- the app can appear broken even when the state is correct +- active nav styling becomes unreliable + +Fix: + +- choose one route naming convention and apply it everywhere + +--- + +## Recommended Frontend Wiring + +### Auth flow + +```mermaid +sequenceDiagram + participant User + participant Form as Auth Form + participant API as HTTP API Client + participant Store as Auth Context + participant Storage as Local Storage + + User->>Form: enter email/password + Form->>API: login or signup request + API-->>Form: token + user + Form->>Storage: persist token + Form->>Store: update authenticated state +``` + +### Websocket flow + +```mermaid +sequenceDiagram + participant UI as Lobby/Arena UI + participant Hook as Game Hook / Provider + participant WS as WebSocket Client + participant Server as Go WebSocket Server + + UI->>Hook: user clicks join/create/play + Hook->>WS: send typed action + WS->>Server: transmit envelope + Server-->>WS: broadcast state + WS-->>Hook: dispatch reducer action + Hook-->>UI: re-render with new state +``` + +--- + +## File-by-File Frontend Integration + +### 1. `web-client/src/services/api.ts` + +Purpose: + +- handle login and signup HTTP requests + +What to integrate here: + +- keep token injection +- ensure the base URL matches the backend dev server +- normalize error response handling + +Why: + +- auth must be consistent and reusable everywhere + +### 2. `web-client/src/state/context/auth-context.tsx` + +Purpose: + +- keep auth state and login/signup actions in one place + +What to integrate here: + +- restore token on app boot +- persist token in local storage +- expose a single auth source of truth + +Why: + +- the app should not re-authenticate on every screen change + +### 3. `web-client/src/state/context/game-context.tsx` + +Purpose: + +- hold live game state + +What to integrate here: + +- mount the correct provider +- connect reducer and socket events +- dispatch lobby and arena state transitions + +Why: + +- the game UI needs a durable state bridge + +### 4. `web-client/src/state/game-reducer.ts` + +Purpose: + +- represent game flow transitions + +What to integrate here: + +- align actions with real server events +- add statuses that match actual room/game lifecycle + +Why: + +- state transitions become predictable and testable + +### 5. `web-client/src/types/messages.ts` + +Purpose: + +- define the browser-side websocket contract + +What to integrate here: + +- mirror the backend envelope +- keep lobby and ingame actions explicit + +Why: + +- typed sockets prevent broken payloads from spreading through the app + +### 6. `web-client/src/pages/game/lobby/game-lobby.tsx` + +Purpose: + +- allow the player to create, list, and join rooms + +What to integrate here: + +- dispatch socket actions from buttons +- render room data from game state +- replace placeholder cards with live data + +Why: + +- lobby should be a real entry point into play, not a static showcase + +### 7. `web-client/src/pages/game/arena/game-arena.tsx` + +Purpose: + +- show active game state + +What to integrate here: + +- render timer, score, word, and success feedback from server state +- send word submissions through the socket service + +Why: + +- the arena is where the live game must feel immediate and responsive + +### 8. `web-client/src/App.tsx` + +Purpose: + +- define top-level navigation and route gating + +What to integrate here: + +- ensure authenticated users can reach game routes +- ensure route names match nav links + +Why: + +- the app shell controls how the user enters the game + +--- + +## Recommended Frontend State Model + +The auth state and game state should be separate. + +Auth state should answer: + +- who is the user +- is the user logged in +- what token is stored + +Game state should answer: + +- which room is active +- who is in the room +- what is the current word +- how many seconds remain +- what the scores are +- whether the round is idle, waiting, playing, or ended + +Do not mix these two domains. + +--- + +## Browser-Safe WebSocket Strategy + +Because browsers cannot set arbitrary websocket headers the same way fetch can, the frontend needs a transport plan. + +Recommended options, in order: + +1. Query token in websocket URL. +2. HttpOnly cookie session. +3. First-message authentication after connect. + +For this codebase, the simplest implementation is usually query token, because it requires the least amount of restructuring. + +If you use query token, the frontend socket URL should be built from the same base URL used by the HTTP client. + +--- + +## Frontend Completion Checklist + +1. Fix the game context provider. +2. Add a websocket service. +3. Align client message types with the server envelope. +4. Dispatch reducer actions from websocket broadcasts. +5. Replace mock lobby cards with live room data. +6. Wire the arena UI to server-driven timer and score state. +7. Normalize route names across router and nav components. +8. Add client tests for auth, reducer transitions, and socket event handling. + +--- + +## Frontend End-State + +When complete, the frontend should behave like this: + +- a user can log in and remain authenticated +- the lobby fetches live room state +- the player can create or join a room +- the arena listens to live broadcasts +- the UI updates instantly without manual refresh +- the visible state always reflects the server diff --git a/server/go.mod b/server/go.mod index aec0217..8af851f 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,6 +5,7 @@ go 1.26.3 require ( github.com/go-chi/chi v1.5.5 // indirect github.com/go-chi/chi/v5 v5.3.0 // indirect + github.com/go-chi/cors v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect diff --git a/server/go.sum b/server/go.sum index 1c376b6..5fb6132 100644 --- a/server/go.sum +++ b/server/go.sum @@ -12,6 +12,8 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= +github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= diff --git a/server/internals/api/user-handler.go b/server/internals/api/user-handler.go index 6d00d97..226e86c 100644 --- a/server/internals/api/user-handler.go +++ b/server/internals/api/user-handler.go @@ -10,6 +10,7 @@ import ( "net/http" "strings" "time" + "github.com/logic-gate-sys/tares-cli/internals/store" "github.com/logic-gate-sys/tares-cli/internals/tokens" "github.com/logic-gate-sys/tares-cli/internals/utils" @@ -48,8 +49,9 @@ func (uh *UserHandler) HandleUserSignup(w http.ResponseWriter, r *http.Request) utils.WriteJSON(w, 400, utils.Envlope{"Bad request": "Bad request"}) return } + fmt.Printf("Password: %s", *usr.Password.PlainText) // add context for logging and memory management - ctx, cancel := context.WithTimeout(r.Context(), 1500*time.Millisecond) + ctx, cancel := context.WithTimeout(r.Context(), utils.REQUEST_TIMEOUT) defer cancel() traceID := fmt.Sprintf("req-%d", time.Now().UnixNano()) ctx = context.WithValue(ctx, traceKey, traceID) @@ -118,7 +120,7 @@ func (uh *UserHandler) HandleUserSignin(w http.ResponseWriter, r *http.Request) return } // add context for logging and memory management - ctx, cancel := context.WithTimeout(r.Context(), 1500*time.Millisecond) // 1.5 seconds + ctx, cancel := context.WithTimeout(r.Context(), utils.REQUEST_TIMEOUT) defer cancel() traceID := fmt.Sprintf("req-%d", time.Now().UnixNano()) diff --git a/server/internals/route/route.go b/server/internals/route/route.go index 521522e..15a5570 100644 --- a/server/internals/route/route.go +++ b/server/internals/route/route.go @@ -2,28 +2,38 @@ package route import ( "net/http" + "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" "github.com/logic-gate-sys/tares-cli/internals/app" "github.com/logic-gate-sys/tares-cli/internals/ws" ) +func SetupRoute(app *app.Application) *chi.Mux { + router := chi.NewRouter() + // 2. Configure and inject CORS middleware at the root level + router.Use(cors.Handler(cors.Options{ + // AllowedOrigins: []string{"https://foo.com"}, // Use this for production + AllowedOrigins: []string{"http://localhost:5173"}, // Your Vite dev server + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: true, // Set to true if you are passing HTTP-only cookies or auth headers + MaxAge: 300, // Maximum value for Preflight request caching (in seconds) + })) + router.Post("/users/signup", app.UserHandler.HandleUserSignup) + router.Post("/users/login", app.UserHandler.HandleUserSignin) -func SetupRoute(app *app.Application)*chi.Mux{ - router := chi.NewRouter() - router.Post("/users/signup", app.UserHandler.HandleUserSignup) - router.Post("/users/login", app.UserHandler.HandleUserSignin ) - - // protected routes - router.Group(func(r chi.Router){ - // authenticate all routes here - r.Use(app.Middleware.Authenticate) + // protected routes + router.Group(func(r chi.Router) { + // authenticate all routes here + r.Use(app.Middleware.Authenticate) - // websocket upgrade require authorisation - rm := ws.NewRoomManager() - r.Get("/ws/rooms", app.Middleware.RequireAuth(http.HandlerFunc(rm.HandleWS)) ) - }) + // websocket upgrade require authorisation + rm := ws.NewRoomManager() + r.Get("/ws", app.Middleware.RequireAuth(http.HandlerFunc(rm.HandleWS))) + }) - - // export router - return router + // export router + return router } diff --git a/server/internals/store/db_test.go b/server/internals/store/db_test.go deleted file mode 100644 index e33c21c..0000000 --- a/server/internals/store/db_test.go +++ /dev/null @@ -1,2 +0,0 @@ -//go:build integration - diff --git a/server/internals/store/users_store.go b/server/internals/store/users_store.go index 4468b2b..90dc20e 100644 --- a/server/internals/store/users_store.go +++ b/server/internals/store/users_store.go @@ -10,8 +10,8 @@ import ( ) type Password struct { - PlainText *string - Hash []byte + PlainText *string `json:"plain_text"` + Hash []byte `json:"_"` } func(ps *Password) Set(plaintext string)(error){ diff --git a/server/internals/utils/constants.go b/server/internals/utils/constants.go new file mode 100644 index 0000000..3890d93 --- /dev/null +++ b/server/internals/utils/constants.go @@ -0,0 +1,5 @@ +package utils + +import "time" + +const REQUEST_TIMEOUT = 5000*time.Millisecond \ No newline at end of file diff --git a/web-client/coverage/auth-reducer.ts.html b/web-client/coverage/auth-reducer.ts.html deleted file mode 100644 index 8d31869..0000000 --- a/web-client/coverage/auth-reducer.ts.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - Code coverage report for auth-reducer.ts - - - - - - - - - -
-
-

All files auth-reducer.ts

-
- -
- 62.5% - Statements - 5/8 -
- - -
- 50% - Branches - 3/6 -
- - -
- 100% - Functions - 1/1 -
- - -
- 62.5% - Lines - 5/8 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -3x -  -1x -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  - 
export interface AuthState {
-    user: { id: string; email:string; username: string } | null;
-    token: string | null;
-    status: "iddle"|"is-loading" | "error" | "is-authenticated";
-    error: unknown; 
-}
- 
-export type AuthAction =
-    | { type: "start" }
-    | { type: "success"; payload: { token: string; user: AuthState["user"]}}  
-    | { type: "error"; payload: string }
-    | { type: "logout" }
-    | { type: "restore-token"; payload: string }; // On app load
- 
-export const initialAuthState: AuthState = {
-    user: null,
-    token: null,
-    status: 'iddle',
-    error: null
-};
- 
-export function authReducer(state: AuthState, action: AuthAction): AuthState {
-    switch (action.type) {
-        case "start":
-            return { ...state, status:'is-loading'};
- 
-        case "success":
-            return {
-                ...state,
-                token: action.payload.token,
-                user: action.payload.user,
-                status:'is-authenticated'
-            };
- 
-        case "error":
-            return {
-                ...state,
-                status: 'error',
-                error: action.payload,
-            };
- 
-        case "logout":
-            return { ...initialAuthState };
- 
-        case "restore-token":
-            // Token was in localStorage, restore it
-            return { ...state, token: action.payload, status:'is-authenticated' };
- 
-        default:
-            return state;
-    }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/web-client/coverage/base.css b/web-client/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/web-client/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/web-client/coverage/block-navigation.js b/web-client/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/web-client/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/web-client/coverage/clover.xml b/web-client/coverage/clover.xml deleted file mode 100644 index ac801ab..0000000 --- a/web-client/coverage/clover.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/web-client/coverage/coverage-final.json b/web-client/coverage/coverage-final.json deleted file mode 100644 index 5902662..0000000 --- a/web-client/coverage/coverage-final.json +++ /dev/null @@ -1,2 +0,0 @@ -{"/home/logic/tares_game/web-client/src/state/auth-reducer.ts": {"path":"/home/logic/tares_game/web-client/src/state/auth-reducer.ts","statementMap":{"0":{"start":{"line":15,"column":43},"end":{"line":20,"column":null}},"1":{"start":{"line":23,"column":4},"end":{"line":51,"column":null}},"2":{"start":{"line":25,"column":12},"end":{"line":25,"column":null}},"3":{"start":{"line":28,"column":12},"end":{"line":33,"column":null}},"4":{"start":{"line":36,"column":12},"end":{"line":40,"column":null}},"5":{"start":{"line":43,"column":12},"end":{"line":43,"column":null}},"6":{"start":{"line":47,"column":12},"end":{"line":47,"column":null}},"7":{"start":{"line":50,"column":12},"end":{"line":50,"column":null}}},"fnMap":{"0":{"name":"authReducer","decl":{"start":{"line":22,"column":16},"end":{"line":22,"column":28}},"loc":{"start":{"line":22,"column":77},"end":{"line":52,"column":null}},"line":22}},"branchMap":{"0":{"loc":{"start":{"line":23,"column":4},"end":{"line":51,"column":null}},"type":"switch","locations":[{"start":{"line":24,"column":8},"end":{"line":25,"column":null}},{"start":{"line":27,"column":8},"end":{"line":33,"column":null}},{"start":{"line":35,"column":8},"end":{"line":40,"column":null}},{"start":{"line":42,"column":8},"end":{"line":43,"column":null}},{"start":{"line":45,"column":8},"end":{"line":47,"column":null}},{"start":{"line":49,"column":8},"end":{"line":50,"column":null}}],"line":23}},"s":{"0":1,"1":3,"2":1,"3":1,"4":0,"5":1,"6":0,"7":0},"f":{"0":3},"b":{"0":[1,1,0,1,0,0]},"meta":{"lastBranch":1,"lastFunction":1,"lastStatement":8,"seen":{"s:15:43:20:Infinity":0,"f:22:16:22:28":0,"b:24:8:25:Infinity:27:8:33:Infinity:35:8:40:Infinity:42:8:43:Infinity:45:8:47:Infinity:49:8:50:Infinity":0,"s:23:4:51:Infinity":1,"s:25:12:25:Infinity":2,"s:28:12:33:Infinity":3,"s:36:12:40:Infinity":4,"s:43:12:43:Infinity":5,"s:47:12:47:Infinity":6,"s:50:12:50:Infinity":7},"fnNames":{}}} -} diff --git a/web-client/coverage/favicon.png b/web-client/coverage/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 62.5% - Statements - 5/8 -
- - -
- 50% - Branches - 3/6 -
- - -
- 100% - Functions - 1/1 -
- - -
- 62.5% - Lines - 5/8 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
auth-reducer.ts -
-
62.5%5/850%3/6100%1/162.5%5/8
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/web-client/coverage/prettify.css b/web-client/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/web-client/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/web-client/coverage/prettify.js b/web-client/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/web-client/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/web-client/coverage/sort-arrow-sprite.png b/web-client/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/web-client/coverage/sorter.js b/web-client/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/web-client/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/web-client/package-lock.json b/web-client/package-lock.json index e2da7cc..9cc134d 100644 --- a/web-client/package-lock.json +++ b/web-client/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.3.1", + "axios": "^1.18.1", "gsap": "^3.15.0", "lucide-react": "^1.22.0", "react": "^19.2.7", @@ -769,6 +770,27 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", @@ -1591,6 +1613,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1637,6 +1671,24 @@ "dev": true, "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1708,6 +1760,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001799", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", @@ -1739,6 +1804,18 @@ "node": ">=18" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1785,7 +1862,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1806,6 +1882,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1815,6 +1900,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.381", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", @@ -1835,6 +1934,24 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz", @@ -1842,6 +1959,33 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2160,6 +2304,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2174,6 +2354,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2184,6 +2373,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2210,6 +2436,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2232,6 +2470,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -2256,6 +2533,19 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2766,6 +3056,36 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -2786,7 +3106,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -2971,6 +3290,15 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/web-client/package.json b/web-client/package.json index 17bf686..b6fd8d2 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@tailwindcss/vite": "^4.3.1", + "axios": "^1.18.1", "gsap": "^3.15.0", "lucide-react": "^1.22.0", "react": "^19.2.7", diff --git a/web-client/src/App.tsx b/web-client/src/App.tsx index 8a55959..c1a8621 100644 --- a/web-client/src/App.tsx +++ b/web-client/src/App.tsx @@ -1,22 +1,29 @@ import { AuthProvider } from "./state/context/auth-context"; -import { AuthGate } from "#components/auth/auth-gate"; +import { AuthGate } from "#pages/auth-gate"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { Home } from "#pages/home/index"; import { NotFound } from "#pages/not-found"; import { Lobby } from "#pages/game/lobby/game-lobby"; import { Game } from "#pages/game/layout"; -import { Arena } from "#pages/game/arena/game-arena"; +import HomeLayout from "#pages/home/layout"; function App() { return ( - } /> - }> - } /> - }/> + {/*-------- Home layouts --------------*/} + }> + } /> + {/*-------- Auth Gate -----------------*/} + }> + }> + } /> + } /> + + + }/> diff --git a/web-client/src/components/footer.tsx b/web-client/src/components/footer.tsx index 7f6b1c7..500d8ca 100644 --- a/web-client/src/components/footer.tsx +++ b/web-client/src/components/footer.tsx @@ -1,3 +1,4 @@ +import { NavLink } from "react-router-dom" export const Footer = () => { return ( <> @@ -5,9 +6,9 @@ export const Footer = () => {