Skip to content

Minigame Overhaul, Leaderboard System, and Onboarding/UI Improvements - #527

Merged
KevinMB0220 merged 26 commits into
AquaStark:mainfrom
KevinMB0220:main
Nov 27, 2025
Merged

Minigame Overhaul, Leaderboard System, and Onboarding/UI Improvements#527
KevinMB0220 merged 26 commits into
AquaStark:mainfrom
KevinMB0220:main

Conversation

@KevinMB0220

@KevinMB0220 KevinMB0220 commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

## 🔥 Pull Request: Minigame Overhaul, Leaderboard System, and Onboarding/UI Improvements

📌 Related Issue

Closes #ISSUE_NUMBER


📝 Description

This PR introduces a complete update to the minigame flow, adds the new Leaderboard system, and improves several UI areas including the hero section and onboarding screens.
It also adds new hooks, updates client configuration, and integrates all backend changes required to support the new score submission workflow.


✅ Changes Made

Backend

  • Refactored miniggameController with updated logic and cleaner endpoint structure.
  • Updated minigameService to support the new score submission and validation flow.
  • Modified index.js to register updated minigame routes and server configuration.

Frontend

  • Added a full Leaderboard feature (page + new components folder).
  • Added new hooks: useMinigameApi and useGameScoreSubmission.
  • Updated api.ts and policies.ts with new endpoints and constants.
  • Improved hero section design and logic.
  • Updated onboarding flow (onboarding.tsx and start.tsx).
  • Updated the Floppy Fish minigame canvas with improved game logic.
  • Integrated all new features and routes into App.tsx.
  • Updated component exports in components/index.ts.
  • Updated Vite config.

📷 Evidence

  • Frontend: (Attach a screenshot of the updated UI or leaderboard page.)
  • Backend: (Attach an image of test runs or console output verifying score submission.)

🚀 Additional Notes

  • This PR is part of the minigame/engagement feature set.
  • All changes are backward-compatible with the existing API structure.
  • Ready for testing and staging deployment.

Summary by CodeRabbit

  • New Features

    • Leaderboard page/component (global and per-game) with top‑3 highlights and configurable limits.
    • New minigame types added and automatic score submission tied to session lifecycle.
    • Minigame API endpoints and game-types discovery exposed to the client.
  • Improvements

    • Stronger wallet validation, clearer toasts, improved onboarding and returning-player flows.
    • Leaderboards show per-player best/total scores and rank formatting.
  • Chores

    • Expanded CORS origin and reorganized contract/policy configuration.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 27, 2025

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'global', 'files', 'languages', 'security', 'performance', 'quality', 'rules', 'project', 'templates'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Adds a minigame subsystem: backend session/XP computation, per-game and global leaderboards, and game-type metadata; frontend adds API hooks, score-submission flow, leaderboard UI/pages/routes, onboarding/landing syncs, and policy/config updates.

Changes

Cohort / File(s) Summary
Backend: Minigame Controller
backend/src/controllers/minigameController.js
New/extended game types; controllers derive walletAddress from auth/params/queries, accept limit for leaderboards, and forward wallet info to service calls.
Backend: Minigame Service
backend/src/services/minigameService.js
Sessions now store player_id (resolved from wallet), endGameSession(sessionId, finalScore, gameType) computes XP, added aggregated per-player game & global leaderboards, getGameSession, getGameTypes, getGameLeaderboard, and responses now surface player_wallet.
Backend: CORS
backend/src/index.js
Production CORS origins expanded to include https://localhost:5173.
Frontend: App routing & Component exports
client/src/App.tsx, client/src/components/index.ts, client/src/components/leaderboard/index.ts
New /leaderboard route; leaderboard module re-exported in component barrels.
Frontend: Leaderboard UI & Page
client/src/components/leaderboard/leaderboard.tsx, client/src/pages/leaderboard.tsx
New Leaderboard component and LeaderboardPage supporting per-game/global views, top-3 styling, truncated wallet display, and configurable limits/titles.
Frontend: API config
client/src/config/api.ts
Added API_CONFIG.ENDPOINTS.MINIGAMES endpoints (sessions, session end, player stats, leaderboards, types, bonus XP).
Frontend: Hooks & API surface
client/src/hooks/use-minigame-api.ts, client/src/hooks/use-game-score-submission.ts, client/src/hooks/index.ts
New useMinigameApi (session lifecycle, leaderboards, stats, game types) and useGameScoreSubmission hooks; both exported from hooks barrel.
Frontend: Game integrations
client/src/components/mini-games/floppy-fish/floppy-fish-game-canvas.tsx, client/src/components/mini-games/fish-dodge-game.tsx, client/src/pages/bubble-jumper.tsx
Wired score-submission via useGameScoreSubmission into floppy-fish, fish-dodge, and bubble-jumper flows; prevented duplicate submissions.
Frontend: Landing & Onboarding
client/src/components/landing/hero-section.tsx, client/src/pages/onboarding/onboarding.tsx, client/src/pages/onboarding/start.tsx
Stronger wallet validation, pre-checks for existing aquariums, sync-to-backend flows (validatePlayer/syncPlayerToBackend), processing/loading states, improved toast/error handling, and adjusted redirect timing.
Frontend: Policies & Config
client/src/config/policies.ts
Replaced single WORLD_ADDRESS policy with multiple per-contract address entries and updated DEV_POLICIES mapping and method lists.
Frontend: Misc & Tooling
client/vite.config.ts, client/src/config/policies.ts, client/src/config/api.ts, client/package.json
Enabled mkcert plugin in Vite; bumped prettier dev dependency; minor type/formatting tweaks in UI components and types.

Sequence Diagram(s)

sequenceDiagram
  participant Player as Frontend (Player UI)
  participant Hook as useMinigameApi / useGameScoreSubmission
  participant Server as Backend API (MinigameController)
  participant DB as Database

  Note over Player,Hook: Game ends -> submit score
  Player->>Hook: handleGameOver(finalScore, gameType)
  Hook->>Hook: map frontend gameType -> backend gameType
  alt no active session
    Hook->>Server: POST /v1/minigames/sessions { wallet, gameType }
    Server->>DB: find_or_create player_id by wallet_address
    DB-->>Server: player_id
    Server-->>Hook: 201 { sessionId, player_wallet }
  end
  Hook->>Server: POST /v1/minigames/sessions/:sessionId/end { finalScore, gameType }
  Server->>Server: calculateXP(gameType, finalScore)
  Server->>DB: update session with finalScore, xp, player_id
  DB-->>Server: updated session
  Server->>DB: aggregate leaderboards / best scores (on demand)
  DB-->>Server: leaderboard rows
  Server-->>Hook: success + leaderboard/ack
  Hook-->>Player: show result / updated leaderboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus:
    • backend/src/services/minigameService.js — player_id resolution, XP calculation logic, aggregation queries and return shapes.
    • client/src/hooks/use-minigame-api.ts & use-game-score-submission.ts — mapping game types, session lifecycle, race conditions and wallet-state error handling.
    • Onboarding/landing components — async flows, delayed redirects, and sync-to-backend error paths.
    • client/src/config/policies.ts — correctness of contract address keys and method lists.

Possibly related PRs

Suggested reviewers

  • Josue19-08
  • BrayanMQ

Poem

🐰 I hopped to count each tiny score,

Wallets clapped as leaderboards soared.
Fish and bubbles danced in line,
I tallied hops and called them mine.
Hooray — small wins in code, divine!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the primary changes across backend and frontend: minigame system overhaul, new leaderboard feature, and onboarding/UI improvements.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 697f365 and 581f986.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • backend/src/middleware/auth.js (1 hunks)
  • backend/src/services/minigameService.js (8 hunks)
  • client/package.json (1 hunks)
  • client/src/components/mini-games/fish-dodge-game.tsx (2 hunks)
  • client/src/components/ui/badge.tsx (1 hunks)
  • client/src/components/ui/button.tsx (1 hunks)
  • client/src/hooks/use-minigame-api.ts (1 hunks)
  • client/src/pages/bubble-jumper.tsx (3 hunks)
  • client/src/pages/onboarding/start.tsx (5 hunks)
  • client/src/types/market.ts (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
client/src/components/landing/hero-section.tsx (1)

1-10: Fix Prettier formatting issues.

The CI pipeline is failing due to Prettier formatting violations. Run the formatter to resolve:

npx prettier --write client/src/components/landing/hero-section.tsx
client/src/pages/onboarding/start.tsx (1)

1-1: Fix Prettier formatting issues.

The CI pipeline reports Prettier formatting errors. Run npx prettier --write client/src/pages/onboarding/start.tsx to fix code style issues before merging.

backend/src/services/minigameService.js (1)

1-1: Fix Prettier formatting issues.

The pipeline detected code style issues. Run the formatter before merging.

#!/bin/bash
# Fix formatting issues
npx prettier --write backend/src/services/minigameService.js
🧹 Nitpick comments (9)
client/src/components/landing/hero-section.tsx (3)

78-107: Use consistent language for comments.

Lines 79 and 102 contain Spanish comments while the rest of the codebase uses English. Consider translating for consistency.

-        // Para jugadores existentes, obtener su último acuario desde backend
+        // For existing players, fetch their last aquarium from backend
         console.log('🏠 Fetching player aquariums from backend...');
-          // Sin acuarios, tratar como jugador nuevo o redirigir a onboarding para crear uno
+          // No aquariums found, redirect to onboarding to create one
           console.log('⚠️ No aquariums found, redirecting to /onboarding');

69-75: Inconsistent notification API usage.

The component mixes toast.error() (from sonner) with success() and info() (from useNotifications). This creates inconsistent UX patterns and makes the code harder to maintain.

Consider standardizing on one notification system throughout the component:

-import { toast } from 'sonner';
 // ... in the component
-              toast.error("Error loading aquarium data. Please contact support.");
+              // Use the notification hook consistently, or add error() to useNotifications

If useNotifications doesn't expose an error() method, either extend it or use toast consistently throughout.

Also applies to: 91-92, 104-104, 112-112


108-115: Error recovery path could be improved.

When aquarium fetch fails, showing a toast and staying on the page is safer than redirecting, but the user has no clear recovery action. Consider adding a retry mechanism or clearer guidance.

The current approach is acceptable for now, but a future enhancement could be:

        } catch (aqError) {
           console.error("Failed to fetch aquariums", aqError);
-          toast.error("Could not load your aquariums. Please try again.");
+          toast.error("Could not load your aquariums. Please try again.", {
+            action: {
+              label: "Retry",
+              onClick: () => handleStartGame(),
+            },
+          });
        }
client/vite.config.ts (1)

35-35: Consider English comments for codebase consistency.

The new comments are in Spanish while the rest of the codebase uses English. For consistency and broader team accessibility, consider using English.

Apply this diff:

-    // https se habilita automáticamente con vite-plugin-mkcert
+    // HTTPS is automatically enabled with vite-plugin-mkcert
     port: 5173,
     host: true,
     fs: {
       allow: ['..'],
     },
     proxy: {
       '/api': {
         target: 'http://localhost:3001',
         changeOrigin: true,
-        secure: false, // Permitir conexión a backend HTTP desde HTTPS
+        secure: false, // Allow connection to HTTP backend from HTTPS
         rewrite: path => path.replace(/^\/api/, '/api'),
       },
     },

Also applies to: 45-45

client/src/config/policies.ts (2)

198-205: Empty methods array for Shop Catalog.

The SHOP_CATALOG_ADDRESS contract block has an empty methods array with a TODO-style comment. If no player-facing methods exist, consider removing this entry entirely to avoid confusion, or add the actual methods if they're known.

-    // ===== Shop Catalog System =====
-    [SHOP_CATALOG_ADDRESS]: {
-      name: 'Aqua Stark Shop',
-      description: 'Catálogo de la tienda',
-      methods: [
-        // Add methods if players interact with it directly (e.g. buying items not via Game)
-      ],
-    },

3-21: Contract addresses in the PR accurately match manifest_sepolia.json; however, they are hardcoded rather than loaded dynamically from the manifest.

The review comment's observation is verified: all 9 contract addresses in the PR (lines 3-21) exactly match those in client/manifest_sepolia.json. The suggestion to import or validate these from the manifest is valid best practice, though the PR currently hardcodes them. Since the client only maintains a Sepolia manifest (no dev variant), the current approach works for single-network deployments. For future multi-environment support, importing addresses from the manifest at runtime would be beneficial.

client/src/pages/onboarding/onboarding.tsx (1)

339-339: Consider making the indexing delay configurable.

The hardcoded 5-second delay for fish indexing works but is fragile. Consider extracting this to a constant or configuration value for easier tuning.

+const FISH_INDEXING_DELAY_MS = 5000;
+
 // ... in handleCreateFish
-     await delay(5000); // 5 seconds for both fish to be indexed
+     await delay(FISH_INDEXING_DELAY_MS); // Wait for fish to be indexed
client/src/pages/leaderboard.tsx (1)

59-59: Consider making the limit configurable.

The hardcoded limit of 50 works but could be made dynamic based on screen size or user preference for better UX.

backend/src/services/minigameService.js (1)

256-311: Consider performance optimization for leaderboard aggregation.

The leaderboard methods load all sessions for a game type (or all sessions globally) into memory, then aggregate in JavaScript. For large datasets (thousands of sessions), this approach may cause:

  • High memory usage
  • Slow response times
  • Database load from full table scans

Consider these optimizations:

  1. Database-side aggregation: Use SQL window functions or aggregations to compute best scores and rankings in the database.
  2. Caching: Cache leaderboard results with a short TTL (e.g., 1-5 minutes) to reduce load.
  3. Indexing: Ensure game_type, player_id, and score columns are indexed for faster queries.

Example SQL for game leaderboard (pseudo-code):

SELECT 
  player_id,
  MAX(score) as best_score,
  RANK() OVER (ORDER BY MAX(score) DESC) as rank
FROM minigame_sessions
WHERE game_type = $1 AND score > 0
GROUP BY player_id
ORDER BY best_score DESC
LIMIT $2

Would you like me to help draft a database-optimized version?

Also applies to: 341-406

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3e3bd and b58c128.

📒 Files selected for processing (18)
  • backend/src/controllers/minigameController.js (5 hunks)
  • backend/src/index.js (1 hunks)
  • backend/src/services/minigameService.js (8 hunks)
  • client/src/App.tsx (2 hunks)
  • client/src/components/index.ts (1 hunks)
  • client/src/components/landing/hero-section.tsx (4 hunks)
  • client/src/components/leaderboard/index.ts (1 hunks)
  • client/src/components/leaderboard/leaderboard.tsx (1 hunks)
  • client/src/components/mini-games/floppy-fish/floppy-fish-game-canvas.tsx (3 hunks)
  • client/src/config/api.ts (1 hunks)
  • client/src/config/policies.ts (8 hunks)
  • client/src/hooks/index.ts (1 hunks)
  • client/src/hooks/use-game-score-submission.ts (1 hunks)
  • client/src/hooks/use-minigame-api.ts (1 hunks)
  • client/src/pages/leaderboard.tsx (1 hunks)
  • client/src/pages/onboarding/onboarding.tsx (6 hunks)
  • client/src/pages/onboarding/start.tsx (5 hunks)
  • client/vite.config.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
client/src/hooks/use-minigame-api.ts (2)
backend/scripts/test-store-api.js (1)
  • url (32-32)
client/src/config/api.ts (3)
  • post (118-134)
  • put (136-152)
  • get (105-116)
client/src/App.tsx (1)
client/src/pages/leaderboard.tsx (1)
  • LeaderboardPage (7-72)
client/src/components/leaderboard/leaderboard.tsx (3)
client/src/components/leaderboard/index.ts (1)
  • Leaderboard (1-1)
client/src/hooks/use-minigame-api.ts (1)
  • LeaderboardEntry (29-35)
client/src/lib/utils.ts (1)
  • cn (4-6)
backend/src/services/minigameService.js (2)
backend/src/config/supabase.js (6)
  • supabase (7-10)
  • supabase (7-10)
  • TABLES (19-30)
  • TABLES (19-30)
  • supabaseAdmin (13-16)
  • supabaseAdmin (13-16)
backend/scripts/create-tables.js (5)
  • supabaseAdmin (32-49)
  • supabaseAdmin (63-77)
  • supabaseAdmin (91-106)
  • supabaseAdmin (120-133)
  • supabaseAdmin (147-160)
🪛 GitHub Actions: Backend CI/CD
backend/src/services/minigameService.js

[warning] 1-1: Code style issues found by Prettier. Run 'prettier --write' to fix.

🪛 GitHub Actions: Frontend CI
client/src/components/leaderboard/index.ts

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/hooks/use-game-score-submission.ts

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/hooks/use-minigame-api.ts

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/pages/onboarding/onboarding.tsx

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/pages/leaderboard.tsx

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/components/landing/hero-section.tsx

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/pages/onboarding/start.tsx

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

client/src/components/leaderboard/leaderboard.tsx

[error] 1-1: Prettier formatting issues detected. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Actions: General CI
client/src/components/leaderboard/index.ts

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/hooks/use-game-score-submission.ts

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/hooks/use-minigame-api.ts

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/pages/onboarding/onboarding.tsx

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/pages/leaderboard.tsx

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/components/landing/hero-section.tsx

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/pages/onboarding/start.tsx

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

client/src/components/leaderboard/leaderboard.tsx

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

backend/src/services/minigameService.js

[error] 1-1: Prettier formatting issues found. Run 'npx prettier --write' to fix formatting in this file.

🔇 Additional comments (24)
client/src/components/landing/hero-section.tsx (3)

30-39: Improved address validation.

Checking account?.address instead of just account is a safer approach since an account object could exist without a valid address property. The early return pattern here is clean.


56-59: Good fallback logic for player existence.

Treating a player as existing if they're on-chain even when backend check fails (validation.exists || validation.isOnChain) is a sensible resilience pattern that prevents re-registration of existing on-chain players.


89-93: Defensive check for missing aquarium ID is good.

Validating the presence of aquariumId before proceeding prevents downstream errors. The early return with user feedback is appropriate.

client/vite.config.ts (1)

7-7: The review comment is incorrect—mkcert import remains commented out and inactive.

The code review assumes the import statement on line 7 is active and that mkcert has been added to the plugins array (line 13), but the current repository state shows:

  • Line 7: // import mkcert from 'vite-plugin-mkcert'; (commented out)
  • Line 13: plugins array contains only [react(), wasm(), topLevelAwait()] (mkcert not included)
  • package.json: vite-plugin-mkcert is not installed (null in both dependencies and devDependencies)

The review comment describes a desired final state that was never applied to the codebase. The verification request about production builds is therefore moot since mkcert is not actively configured.

Likely an incorrect or invalid review comment.

client/src/config/policies.ts (4)

98-125: Duplicate fish-related methods across FishSystem and Game System.

Both contracts define add_fish_to_aquarium, breed_fishes, move_fish_to_aquarium, and new_fish. The comment on line 123 also expresses uncertainty about purchase_fish placement.

This duplication may be intentional if both contracts expose these entrypoints, but the uncertainty comment suggests the contract boundaries aren't fully clear. Consider documenting which contract is the canonical source for each action, or consolidate if one contract delegates to another.

Also applies to: 127-168


298-329: LGTM!

The Cartridge configuration is well-structured with sensible defaults for session duration, network configuration, and UX options.


331-346: LGTM!

Updated DEV_POLICIES to use the actual AQUA_STARK_ADDRESS instead of a zero-address, which is a more accurate representation for development testing. The minimal method set keeps the dev policy focused.


50-96: The review comment is based on incorrect assumptions about the file structure.

The current policies.ts file does not contain separate contract addresses (AQUA_STARK_ADDRESS, TRANSACTION_ADDRESS, FISH_SYSTEM_ADDRESS, GAME_ADDRESS) as claimed. All methods are unified under a single WORLD_ADDRESS contract. The methods that appear to overlap (e.g., add_fish_to_aquarium, breed_fishes) belong to different game systems but are configured in one policy set with clarifying display names (e.g., "Add Fish to Aquarium (Game)"). This is not a duplicate permissions issue since there is only one contract address and one policy configuration.

Likely an incorrect or invalid review comment.

client/src/pages/onboarding/start.tsx (2)

68-80: LGTM!

The redirect logic correctly waits for account loading to complete before starting the redirect timer, and properly cleans up the timeout.


267-287: LGTM!

The conditional rendering provides clear feedback during account loading, and the button correctly disables when registering or when no account address is available.

client/src/pages/onboarding/onboarding.tsx (3)

150-153: LGTM!

The enhanced wallet validation checking account?.address instead of just account is more robust, and the error message is user-friendly.


182-195: LGTM!

The sync operation is correctly wrapped in try-catch to prevent blocking the main onboarding flow if backend sync fails. This is appropriate since the on-chain creation is the critical path.


109-132: The review comment references code that does not exist in the current codebase.

The code snippet showing a useEffect with checkExistingAquariums and the dependency array [account, navigate, setActiveAquariumId] at lines 109-132 cannot be found in client/src/pages/onboarding/onboarding.tsx.

In the current file:

  • Lines 109-132 contain state declarations and the handleFishSelect function, not a useEffect
  • getPlayerAquariums is only called at lines 194 and 203 within handleCreateAquarium, not in a separate useEffect checking for existing aquariums
  • There is no checkExistingAquariums function in the file

Since the code being reviewed does not exist in the codebase, the dependency issue described in the review comment cannot be verified or addressed.

Likely an incorrect or invalid review comment.

client/src/config/api.ts (1)

60-70: Minigame endpoint block looks consistent with existing API config

Paths follow the established /v1/... pattern and placeholder style; this should plug cleanly into the new minigame flows (sessions, leaderboards, stats, game types, bonus XP) as long as minigameRoutes expose the same paths. Just keep this list in sync with any future backend renames (especially PLAYER_STATS and BONUS_XP).

backend/src/index.js (1)

60-67: Dev CORS origins update looks good

Allowing both http://localhost:5173 and https://localhost:5173 for non‑production CORS is reasonable and keeps local dev flexible without affecting production origins.

client/src/App.tsx (1)

34-35: Leaderboard route wiring is straightforward and consistent

Importing LeaderboardPage and exposing it at /leaderboard fits cleanly alongside the existing information/help routes, and the page itself already links back to /mini-games, so the navigation story looks coherent.

Also applies to: 85-85

client/src/components/index.ts (1)

51-53: Leaderboard components are correctly added to the central barrel

The new export * from './leaderboard'; entry follows the existing grouping pattern (comment + export) and will make leaderboard UI available via the main components barrel without disrupting anything else.

client/src/components/mini-games/floppy-fish/floppy-fish-game-canvas.tsx (1)

3-4: Score submission hook is cleanly integrated into Floppy Fish

Using useGameScoreSubmission('floppy-fish') and passing handleGameOver into useGameLogic is a tidy way to keep game logic and score submission concerns separated. The hook’s internal single‑flight guard should also protect against multiple game‑over events.

Just ensure useGameLogic’s signature has been updated everywhere it’s used to accept the callback parameter so TypeScript and runtime stay in sync.

Also applies to: 22-24, 40-40

client/src/hooks/index.ts (1)

86-87: LGTM!

The new hook exports follow the existing barrel pattern and integrate cleanly with the minigame feature set.

backend/src/controllers/minigameController.js (2)

55-58: LGTM!

Adding support for the new game types ('floppy_fish', 'bubble_jumper', 'fish_dodge') aligns with the minigame expansion described in the PR objectives.


172-178: Review comment is incorrect — the code snippet referenced does not exist in the repository.

The actual code at lines 172-178 in backend/src/controllers/minigameController.js is:

const { playerId } = req.params;

const stats = await MinigameService.getPlayerStats(playerId);

res.json({ success: true, data: stats });

This does not contain the wallet address fallback chain (req.user?.walletAddress || req.params?.playerId || req.query?.wallet) mentioned in the review comment. Additionally, authentication middleware (AuthMiddleware.verifyToken) is globally applied to all routes in minigameRoutes.js at line 9, before any route definitions, ensuring all endpoints—including /player/stats—require authentication. No security issue exists as described.

Likely an incorrect or invalid review comment.

backend/src/services/minigameService.js (2)

143-145: LGTM!

Adding game type aliases ('floppy_fish', 'bubble_jumper', 'fish_dodge') for XP calculation aligns with the new game types and provides good frontend flexibility.


602-661: LGTM!

The new getGameTypes() method provides a clean, static metadata source for available games. This is well-structured and supports the frontend game selection UI.

client/src/components/leaderboard/leaderboard.tsx (1)

39-39: I'll help you verify the review comment about the potential infinite loop from unstable function references in the useEffect dependency array. Let me start by examining the relevant code.
<function_calls>


#!/bin/bash

First, find the useMinigameApi hook file

fd -t f 'use-minigame-api' --type ts --type tsx


</function_calls>
<function_calls>


#!/bin/bash

Search for useMinigameApi definition

rg -nP 'useMinigameApi' -t ts -t tsx -A 2


</function_calls>
<function_calls>


#!/bin/bash

Look at the leaderboard.tsx file around line 39

cat -n client/src/components/leaderboard/leaderboard.tsx | head -60


</function_calls>
<function_calls>


#!/bin/bash

Search for all hook files

fd -t f -e ts -e tsx 'hook' client/src/


</function_calls>

static async getGameLeaderboard(req, res) {
try {
const { gameType } = req.params;
const limit = parseInt(req.query.limit) || 10;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix NaN bug from unvalidated parseInt.

parseInt(req.query.limit) returns NaN when limit is missing, non-numeric, or invalid. While the fallback || 10 handles NaN, it would be clearer and safer to validate explicitly.

Apply this diff to add validation:

-      const limit = parseInt(req.query.limit) || 10;
+      const limit = parseInt(req.query.limit, 10) || 10;
+      if (limit < 1 || limit > 100) {
+        return res.status(400).json({ error: 'Limit must be between 1 and 100' });
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const limit = parseInt(req.query.limit) || 10;
const limit = parseInt(req.query.limit, 10) || 10;
if (limit < 1 || limit > 100) {
return res.status(400).json({ error: 'Limit must be between 1 and 100' });
}
🤖 Prompt for AI Agents
In backend/src/controllers/minigameController.js around line 218, the current
parseInt(req.query.limit) can yield NaN for missing or invalid input; update
this line to explicitly validate the query param: parse with
parseInt(req.query.limit, 10) (or Number), check that the result is a finite
positive integer (e.g., Number.isInteger(value) && value > 0), and only use that
value; otherwise set limit = 10 as the safe default.

static async getGlobalLeaderboard(req, res) {
try {
const leaderboard = await MinigameService.getGlobalLeaderboard();
const limit = parseInt(req.query.limit) || 20;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix NaN bug from unvalidated parseInt.

Same issue as Line 218: parseInt(req.query.limit) can return NaN. Add explicit validation and consider enforcing a maximum limit to prevent performance issues.

Apply this diff to add validation:

-      const limit = parseInt(req.query.limit) || 20;
+      const limit = parseInt(req.query.limit, 10) || 20;
+      if (limit < 1 || limit > 100) {
+        return res.status(400).json({ error: 'Limit must be between 1 and 100' });
+      }
🤖 Prompt for AI Agents
In backend/src/controllers/minigameController.js around line 258,
parseInt(req.query.limit) can produce NaN and be used directly; validate the
parsed value, fall back to a safe default (e.g. 20) when invalid, coerce to an
integer, enforce a positive minimum and a configurable maximum cap (e.g. 100) to
prevent excessive limits, and use the validated value for limit. Ensure you
check isNaN or Number.isInteger after parsing and clamp the result between
allowed bounds before use.

Comment on lines +40 to +48
const { data: player, error: playerError } = await supabase
.from(TABLES.PLAYERS)
.select('player_id')
.eq('wallet_address', playerWallet)
.single();

if (playerError || !player) {
throw new Error(`Player not found for wallet: ${playerWallet}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract player lookup into a reusable helper method.

The player_id lookup pattern from wallet_address is duplicated in createGameSession (lines 40-48), getPlayerStats (lines 180-189), and awardBonusXP (lines 504-513). This violates DRY and makes updates harder.

Apply this diff to extract a helper:

+  /**
+   * Get player_id from wallet address
+   * @private
+   */
+  static async _getPlayerIdFromWallet(walletAddress) {
+    const { data: player, error } = await supabase
+      .from(TABLES.PLAYERS)
+      .select('player_id')
+      .eq('wallet_address', walletAddress)
+      .single();
+
+    if (error || !player) {
+      throw new Error(`Player not found for wallet: ${walletAddress}`);
+    }
+
+    return player.player_id;
+  }
+
   static async createGameSession(playerWallet, gameType) {
     try {
-      // Get player_id from wallet_address
-      const { data: player, error: playerError } = await supabase
-        .from(TABLES.PLAYERS)
-        .select('player_id')
-        .eq('wallet_address', playerWallet)
-        .single();
-
-      if (playerError || !player) {
-        throw new Error(`Player not found for wallet: ${playerWallet}`);
-      }
+      const playerId = await this._getPlayerIdFromWallet(playerWallet);

Then apply similar changes to getPlayerStats and awardBonusXP.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/src/services/minigameService.js around lines 40 to 48, extract the
repeated player lookup into a reusable helper (e.g., getPlayerIdByWallet) that
accepts supabase client and walletAddress, performs the
.from(TABLES.PLAYERS).select('player_id').eq('wallet_address', wallet).single()
call, throws a clear Error if not found or on query error, and returns the
player_id; then replace the inline query in createGameSession (lines 40-48),
getPlayerStats (lines ~180-189), and awardBonusXP (lines ~504-513) to call this
helper and use its returned player_id, preserving existing error behavior and
logging.

// Sum best scores from all games for each player
const playerTotalScores = {};
Object.entries(playerGameScores).forEach(([key, score]) => {
const playerId = key.split('_')[0]; // Extract player_id from key

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fragile player_id extraction from composite key.

The expression key.split('_')[0] assumes player_id values never contain underscores. If a player_id contains an underscore (e.g., UUID format or user-generated), this will extract only the first segment and cause data corruption.

Use a more robust key format or parsing strategy:

       const playerGameScores = {};
       sessions.forEach(session => {
         const playerId = session.player_id;
-        const key = `${playerId}_${session.game_type}`;
+        const key = JSON.stringify({ playerId, gameType: session.game_type });
         if (!playerGameScores[key] || session.score > playerGameScores[key]) {
           playerGameScores[key] = session.score;
         }
       });

       // Sum best scores from all games for each player
       const playerTotalScores = {};
-      Object.entries(playerGameScores).forEach(([key, score]) => {
-        const playerId = key.split('_')[0]; // Extract player_id from key
+      Object.entries(playerGameScores).forEach(([keyStr, score]) => {
+        const { playerId } = JSON.parse(keyStr);
         if (!playerTotalScores[playerId]) {
           playerTotalScores[playerId] = 0;
         }

Or use a Map with tuple keys for cleaner code.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/src/services/minigameService.js around line 365, the code uses
key.split('_')[0] which breaks when player_id contains underscores; replace this
with a robust parse: find the separator position with key.lastIndexOf('_') and
extract playerId as key.substring(0, sep) (and if sep === -1 fall back to the
whole key or throw), so you correctly handle player_ids containing underscores;
additionally, update any code that builds these composite keys (or migrate to a
structured format like JSON-encoded keys, a unique delimiter, or a Map with
tuple keys) so creation and parsing remain consistent.

Comment thread client/src/components/leaderboard/index.ts Outdated
Comment thread client/src/pages/onboarding/onboarding.tsx
Comment on lines +117 to +124
if (response && response.success && response.data && response.data.length > 0) {
console.log('✅ Found existing aquarium, redirecting to game...');
const primaryAquarium = response.data[0];
const existingId = primaryAquarium.on_chain_id;

setActiveAquariumId(existingId, account.address);
toast.success('Existing aquarium found! resuming game...');
navigate(`/loading?aquarium=${existingId}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add defensive check for on_chain_id property.

The code assumes primaryAquarium.on_chain_id exists, but if the API response doesn't include this field, existingId will be undefined, causing issues when passed to setActiveAquariumId and the navigation URL.

        if (response && response.success && response.data && response.data.length > 0) {
          console.log('✅ Found existing aquarium, redirecting to game...');
          const primaryAquarium = response.data[0];
          const existingId = primaryAquarium.on_chain_id;
          
+         if (!existingId) {
+           console.warn('⚠️ Aquarium found but missing on_chain_id');
+           return;
+         }
+
          setActiveAquariumId(existingId, account.address);
          toast.success('Existing aquarium found! resuming game...');
          navigate(`/loading?aquarium=${existingId}`);
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (response && response.success && response.data && response.data.length > 0) {
console.log('✅ Found existing aquarium, redirecting to game...');
const primaryAquarium = response.data[0];
const existingId = primaryAquarium.on_chain_id;
setActiveAquariumId(existingId, account.address);
toast.success('Existing aquarium found! resuming game...');
navigate(`/loading?aquarium=${existingId}`);
if (response && response.success && response.data && response.data.length > 0) {
console.log('✅ Found existing aquarium, redirecting to game...');
const primaryAquarium = response.data[0];
const existingId = primaryAquarium.on_chain_id;
if (!existingId) {
console.warn('⚠️ Aquarium found but missing on_chain_id');
return;
}
setActiveAquariumId(existingId, account.address);
toast.success('Existing aquarium found! resuming game...');
navigate(`/loading?aquarium=${existingId}`);
🤖 Prompt for AI Agents
In client/src/pages/onboarding/onboarding.tsx around lines 117 to 124, the code
assumes primaryAquarium.on_chain_id exists; add a defensive check that the
property is present and a non-empty string (or number as expected) before
assigning existingId, calling setActiveAquariumId, and navigating; if
on_chain_id is missing, log or toast an error/warning, do not call
setActiveAquariumId or navigate, and gracefully return or use a documented
fallback (e.g., primaryAquarium.id) only if appropriate.

Comment on lines +39 to +66
// Wait for account to load after login
useEffect(() => {
// Only redirect if no wallet connected (Starknet) OR no Cartridge
// Give time for account to load after Cartridge login
const checkAccount = () => {
if (account?.address) {
console.log('✅ Account loaded:', account.address);
setIsLoadingAccount(false);
} else {
console.log('⏳ Waiting for account to load...');
// Keep checking for a bit longer
setTimeout(() => {
if (!account?.address) {
console.log('⚠️ Account still not loaded after timeout');
setIsLoadingAccount(false);
// Don't redirect immediately, let user see the page
}
}, 3000);
}
};

// Initial check
checkAccount();

// Also check when account changes
if (account?.address) {
setIsLoadingAccount(false);
}
}, [account]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition and cleanup issue in account loading effect.

The useEffect has multiple issues:

  1. The setTimeout at line 49 creates a closure over stale account value and runs after the effect may have re-executed or unmounted.
  2. No cleanup for the inner timeout, causing potential state updates on unmounted components.
  3. The redundant check at lines 63-65 always runs after checkAccount(), potentially causing double state updates.

Apply this diff to fix the race condition and cleanup:

  // Wait for account to load after login
  useEffect(() => {
-   // Give time for account to load after Cartridge login
-   const checkAccount = () => {
-     if (account?.address) {
-       console.log('✅ Account loaded:', account.address);
-       setIsLoadingAccount(false);
-     } else {
-       console.log('⏳ Waiting for account to load...');
-       // Keep checking for a bit longer
-       setTimeout(() => {
-         if (!account?.address) {
-           console.log('⚠️ Account still not loaded after timeout');
-           setIsLoadingAccount(false);
-           // Don't redirect immediately, let user see the page
-         }
-       }, 3000);
-     }
-   };
-
-   // Initial check
-   checkAccount();
-
-   // Also check when account changes
    if (account?.address) {
+     console.log('✅ Account loaded:', account.address);
      setIsLoadingAccount(false);
+     return;
    }
+
+    console.log('⏳ Waiting for account to load...');
+    const timeoutId = setTimeout(() => {
+      console.log('⚠️ Account still not loaded after timeout');
+      setIsLoadingAccount(false);
+    }, 3000);
+
+    return () => clearTimeout(timeoutId);
  }, [account]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Wait for account to load after login
useEffect(() => {
// Only redirect if no wallet connected (Starknet) OR no Cartridge
// Give time for account to load after Cartridge login
const checkAccount = () => {
if (account?.address) {
console.log('✅ Account loaded:', account.address);
setIsLoadingAccount(false);
} else {
console.log('⏳ Waiting for account to load...');
// Keep checking for a bit longer
setTimeout(() => {
if (!account?.address) {
console.log('⚠️ Account still not loaded after timeout');
setIsLoadingAccount(false);
// Don't redirect immediately, let user see the page
}
}, 3000);
}
};
// Initial check
checkAccount();
// Also check when account changes
if (account?.address) {
setIsLoadingAccount(false);
}
}, [account]);
// Wait for account to load after login
useEffect(() => {
if (account?.address) {
console.log('✅ Account loaded:', account.address);
setIsLoadingAccount(false);
return;
}
console.log('⏳ Waiting for account to load...');
const timeoutId = setTimeout(() => {
console.log('⚠️ Account still not loaded after timeout');
setIsLoadingAccount(false);
}, 3000);
return () => clearTimeout(timeoutId);
}, [account]);
🤖 Prompt for AI Agents
client/src/pages/onboarding/start.tsx around lines 39 to 66: the effect that
waits for the account to load creates a setTimeout that closes over a
potentially stale account value, doesn't clear the timer on re-renders/unmounts,
and contains a redundant immediate check after calling checkAccount; fix by
removing the redundant post-check (lines 63-65), capture the timeout id in a ref
(e.g. timeoutRef) so you can clearTimeout on cleanup and before scheduling a new
one, and in the timeout callback reference the latest account via the account
variable from the effect scope (or via a ref) so it checks current state before
calling setIsLoadingAccount; finally return a cleanup function that clears any
pending timeout to prevent state updates after unmount.

Comment on lines +116 to +126
if (validation.exists) {
console.log('✅ Player already exists, redirecting...');
if (validation.isOnChain && !validation.isInBackend) {
setRegistrationStep('Syncing existing account...');
await syncPlayerToBackend(validation.playerData!, account.address);
}
toast.success('Welcome back!');
// Determine redirection based on existing data? For now, onboarding handles aquarium check
navigate('/onboarding');
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unsafe non-null assertion on validation.playerData.

At line 120, validation.playerData! assumes playerData is defined when validation.isOnChain is true. If validatePlayer returns { exists: true, isOnChain: true, isInBackend: false, playerData: undefined } due to an edge case, this will pass undefined to syncPlayerToBackend, potentially causing runtime errors.

Apply this diff to add a defensive check:

      if (validation.exists) {
        console.log('✅ Player already exists, redirecting...');
-       if (validation.isOnChain && !validation.isInBackend) {
+       if (validation.isOnChain && !validation.isInBackend && validation.playerData) {
          setRegistrationStep('Syncing existing account...');
-         await syncPlayerToBackend(validation.playerData!, account.address);
+         await syncPlayerToBackend(validation.playerData, account.address);
        }
        toast.success('Welcome back!');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (validation.exists) {
console.log('✅ Player already exists, redirecting...');
if (validation.isOnChain && !validation.isInBackend) {
setRegistrationStep('Syncing existing account...');
await syncPlayerToBackend(validation.playerData!, account.address);
}
toast.success('Welcome back!');
// Determine redirection based on existing data? For now, onboarding handles aquarium check
navigate('/onboarding');
return;
}
if (validation.exists) {
console.log('✅ Player already exists, redirecting...');
if (validation.isOnChain && !validation.isInBackend && validation.playerData) {
setRegistrationStep('Syncing existing account...');
await syncPlayerToBackend(validation.playerData, account.address);
}
toast.success('Welcome back!');
// Determine redirection based on existing data? For now, onboarding handles aquarium check
navigate('/onboarding');
return;
}
🤖 Prompt for AI Agents
In client/src/pages/onboarding/start.tsx around lines 116 to 126, the code uses
a non-null assertion validation.playerData! when calling syncPlayerToBackend
which may be undefined in edge cases; add a defensive check that ensures
validation.playerData is defined before calling syncPlayerToBackend — if it's
undefined, log an error, show a user-facing toast (or setRegistrationStep to an
error state), and return/abort the sync flow instead of calling the function
with undefined; do not use the '!' operator and only call syncPlayerToBackend
when validation.playerData is truthy (or attempt to re-fetch the player data
first), ensuring a clear early-return path for the error case.

Comment thread client/src/pages/onboarding/start.tsx Outdated
Comment on lines +164 to +174
if (errorMessage.includes('USERNAME ALREADY TAKEN') || errorMessage.includes('ALREADY REGISTERED')) {
toast.success('Account found! Redirecting...');
// Try to sync backend just in case and redirect
try {
const reValidation = await validatePlayer(account.address);
if (reValidation.isOnChain && !reValidation.isInBackend) {
await syncPlayerToBackend(reValidation.playerData!, account.address);
}
} catch (e) { console.warn("Sync failed on fallback", e); }

navigate('/onboarding');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same non-null assertion risk in error recovery path.

Line 170 uses reValidation.playerData! with the same risk as the earlier occurrence. Additionally, consider using toast.info instead of toast.success since this is an error recovery path, not a true success.

      if (errorMessage.includes('USERNAME ALREADY TAKEN') || errorMessage.includes('ALREADY REGISTERED')) {
-       toast.success('Account found! Redirecting...');
+       toast.info('Account found! Redirecting...');
        // Try to sync backend just in case and redirect
        try {
            const reValidation = await validatePlayer(account.address);
-           if (reValidation.isOnChain && !reValidation.isInBackend) {
-                await syncPlayerToBackend(reValidation.playerData!, account.address);
+           if (reValidation.isOnChain && !reValidation.isInBackend && reValidation.playerData) {
+                await syncPlayerToBackend(reValidation.playerData, account.address);
            }
        } catch (e) { console.warn("Sync failed on fallback", e); }
🤖 Prompt for AI Agents
In client/src/pages/onboarding/start.tsx around lines 164 to 174, the
error-recovery path uses a non-null assertion on reValidation.playerData! and
shows a success toast; change this to safely check that reValidation.playerData
is defined before calling syncPlayerToBackend (e.g., if (reValidation.isOnChain
&& !reValidation.isInBackend && reValidation.playerData) { await
syncPlayerToBackend(reValidation.playerData, account.address); }) to avoid
possible runtime null/undefined access, and replace toast.success('Account
found! Redirecting...') with toast.info('Account found — redirecting...') to
reflect an informational recovery state. Ensure the existing catch remains to
log sync failures.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
client/src/hooks/use-minigame-api.ts (2)

74-100: Standardize URL construction across all API calls.

This is already flagged in a past review. The code mixes raw endpoints (e.g., API_CONFIG.ENDPOINTS.MINIGAMES.SESSIONS) with buildApiUrl() calls. Pick one approach for consistency.

Also applies to: 116-118, 165-167, 186-201


13-17: Remove unused REVERSE_GAME_TYPE_MAP constant.

The static analysis correctly flags this constant as unused. Either remove it now or use it when mapping backend game types back to frontend slugs (e.g., in leaderboard display).

-const REVERSE_GAME_TYPE_MAP: Record<string, string> = {
-  floppy_fish: 'floppy-fish',
-  bubble_jumper: 'bubble-jumper',
-  fish_dodge: 'fish-dodge',
-};
client/src/hooks/use-game-score-submission.ts (1)

3-3: Remove unused toast import to fix pipeline failure.

The static analysis and pipeline correctly flag this as unused. Either remove the import or uncomment the toast calls if user feedback is desired.

 import { useCallback, useRef } from 'react';
 import { useMinigameApi } from './use-minigame-api';
-import { toast } from 'sonner';
client/src/components/leaderboard/leaderboard.tsx (1)

124-135: Fix score display logic to match context (already flagged).

The score value at lines 126-128 doesn't match the label logic at lines 130-134. For game-specific leaderboards, best_score should be displayed, but the current code prefers total_score.

             <div className='text-right'>
               <p className='text-yellow-400 font-bold text-lg'>
-                {entry.total_score?.toLocaleString() ||
-                  entry.best_score?.toLocaleString() ||
-                  0}
+                {gameType
+                  ? (entry.best_score?.toLocaleString() ?? 0)
+                  : (entry.total_score?.toLocaleString() ?? 0)}
               </p>
client/src/pages/onboarding/onboarding.tsx (1)

123-130: Add defensive check for on_chain_id property (already flagged).

Line 125 assumes primaryAquarium.on_chain_id exists. If the API response doesn't include this field, existingId will be undefined, causing issues with setActiveAquariumId and navigation.

          const primaryAquarium = response.data[0];
          const existingId = primaryAquarium.on_chain_id;

+         if (!existingId) {
+           console.warn('⚠️ Aquarium found but missing on_chain_id');
+           return;
+         }
+
          setActiveAquariumId(existingId, account.address);
client/src/pages/onboarding/start.tsx (3)

39-66: Race condition and cleanup issues in account loading effect (already flagged).

The setTimeout at line 49 closes over potentially stale account value and lacks cleanup. The redundant check at lines 63-65 may cause double state updates.

  useEffect(() => {
-   const checkAccount = () => {
-     if (account?.address) {
-       console.log('✅ Account loaded:', account.address);
-       setIsLoadingAccount(false);
-     } else {
-       console.log('⏳ Waiting for account to load...');
-       setTimeout(() => {
-         if (!account?.address) {
-           console.log('⚠️ Account still not loaded after timeout');
-           setIsLoadingAccount(false);
-         }
-       }, 3000);
-     }
-   };
-   checkAccount();
-   if (account?.address) {
-     setIsLoadingAccount(false);
-   }
+   if (account?.address) {
+     console.log('✅ Account loaded:', account.address);
+     setIsLoadingAccount(false);
+     return;
+   }
+
+   console.log('⏳ Waiting for account to load...');
+   const timeoutId = setTimeout(() => {
+     console.log('⚠️ Account still not loaded after timeout');
+     setIsLoadingAccount(false);
+   }, 3000);
+
+   return () => clearTimeout(timeoutId);
  }, [account]);

120-122: Unsafe non-null assertion on validation.playerData (already flagged).

Line 122 uses validation.playerData! assuming it's always defined when isOnChain is true. Add a defensive check to prevent runtime errors.

-       if (validation.isOnChain && !validation.isInBackend) {
+       if (validation.isOnChain && !validation.isInBackend && validation.playerData) {
          setRegistrationStep('Syncing existing account...');
-         await syncPlayerToBackend(validation.playerData!, account.address);
+         await syncPlayerToBackend(validation.playerData, account.address);
        }

173-179: Same non-null assertion risk in error recovery path (already flagged).

Line 176 uses reValidation.playerData! with the same risk. Also consider using toast.info instead of toast.success since this is an error recovery path.

-       toast.success('Account found! Redirecting...');
+       toast.info('Account found! Redirecting...');
        try {
          const reValidation = await validatePlayer(account.address);
-         if (reValidation.isOnChain && !reValidation.isInBackend) {
+         if (reValidation.isOnChain && !reValidation.isInBackend && reValidation.playerData) {
            await syncPlayerToBackend(
-             reValidation.playerData!,
+             reValidation.playerData,
              account.address
            );
          }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b58c128 and 777a693.

📒 Files selected for processing (8)
  • client/src/components/landing/hero-section.tsx (4 hunks)
  • client/src/components/leaderboard/index.ts (1 hunks)
  • client/src/components/leaderboard/leaderboard.tsx (1 hunks)
  • client/src/hooks/use-game-score-submission.ts (1 hunks)
  • client/src/hooks/use-minigame-api.ts (1 hunks)
  • client/src/pages/leaderboard.tsx (1 hunks)
  • client/src/pages/onboarding/onboarding.tsx (6 hunks)
  • client/src/pages/onboarding/start.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/src/pages/leaderboard.tsx
  • client/src/components/leaderboard/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
client/src/hooks/use-game-score-submission.ts (1)
client/src/hooks/use-minigame-api.ts (1)
  • useMinigameApi (57-258)
client/src/hooks/use-minigame-api.ts (1)
client/src/config/api.ts (3)
  • post (118-134)
  • put (136-152)
  • get (105-116)
client/src/pages/onboarding/start.tsx (1)
client/src/hooks/usePlayerValidation.ts (1)
  • usePlayerValidation (55-207)
🪛 GitHub Actions: Frontend CI
client/src/hooks/use-game-score-submission.ts

[error] 3-3: 'toast' is defined but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars

🪛 GitHub Check: frontend
client/src/hooks/use-game-score-submission.ts

[failure] 3-3:
'toast' is defined but never used. Allowed unused vars must match /^_/u

client/src/hooks/use-minigame-api.ts

[failure] 13-13:
'REVERSE_GAME_TYPE_MAP' is assigned a value but never used. Allowed unused vars must match /^_/u

🔇 Additional comments (8)
client/src/hooks/use-game-score-submission.ts (1)

17-44: Score submission guard logic is well-implemented.

The single-flight guard via isSubmittingRef prevents duplicate submissions, and the non-positive score check is appropriate. The error is logged without disrupting UX.

client/src/components/leaderboard/leaderboard.tsx (2)

91-139: Component structure and UI logic are well-organized.

The ranking display with icons, wallet formatting, and responsive styling are cleanly implemented. Empty state and error handling provide good UX.


24-39: Based on my verification, I found that the file referenced in the review comment (client/src/components/leaderboard/leaderboard.tsx) does not exist in this repository.

I searched extensively through:

  • The components directory structure
  • All .tsx files in the codebase
  • Specific searches for "leaderboard" files

The file path and code snippet in the review comment do not match any files in the AquaStark/Aqua-Stark repository. This suggests the review comment may have been copied from a different repository or PR, or the file has been removed/renamed.

File does not exist in repository - review comment references non-existent code.

The review addresses a useEffect dependency concern, but since the file being reviewed is not present in this codebase, the comment cannot be applied or verified.

Likely an incorrect or invalid review comment.

client/src/components/landing/hero-section.tsx (2)

78-120: Aquarium fetch flow has good defensive checks and error handling.

The null check for aquariumId (lines 89-98) and graceful error handling without forcing navigation (lines 113-119) are well-implemented improvements.

Minor: Consider translating Spanish comments to English for consistency (lines 79, 107).


56-59: Good defensive handling for on-chain-only players.

The playerExists check that considers both validation.exists and validation.isOnChain ensures players who exist on-chain but failed backend validation are still treated as existing users.

client/src/pages/onboarding/onboarding.tsx (1)

154-158: Good wallet validation tightening.

The stricter check for account?.address with an explicit error message improves UX when the wallet isn't properly connected.

client/src/pages/onboarding/start.tsx (1)

277-297: Loading state UI is a good addition.

The loading indicator while waiting for the account to load improves UX by providing clear feedback instead of showing a broken state.

client/src/hooks/use-minigame-api.ts (1)

83-97: The response object structure concern is unfounded. The code is correct.

After examining use-api.ts, I verified that:

  • useApi().post<T>() returns Promise<ApiResponse<T>> (line 461), where ApiResponse<T> is defined as { data: T, status, statusText, headers, success, error? }
  • In the minigame code, post<{ success: boolean; data: GameSession }>() means response.data is of type { success: boolean; data: GameSession }
  • Therefore, response.data.success and response.data.data are properly typed and valid accesses

The double-wrapping is intentional by design: ApiResponse wraps the API response payload. No runtime error will occur.

Comment on lines +136 to +137
checkExistingAquariums();
}, [account, navigate, setActiveAquariumId]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing getPlayerAquariums in useEffect dependency array.

The effect uses getPlayerAquariums but it's not listed in the dependency array. If the function reference changes, the effect won't re-run as expected. Add it or use useCallback to stabilize the function.

-  }, [account, navigate, setActiveAquariumId]);
+  }, [account, navigate, setActiveAquariumId, getPlayerAquariums]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checkExistingAquariums();
}, [account, navigate, setActiveAquariumId]);
checkExistingAquariums();
}, [account, navigate, setActiveAquariumId, getPlayerAquariums]);
🤖 Prompt for AI Agents
In client/src/pages/onboarding/onboarding.tsx around lines 136 to 137, the
useEffect calls getPlayerAquariums but doesn't include it in the dependency
array; add getPlayerAquariums to the dependency array or wrap getPlayerAquariums
in useCallback (exporting/stabilizing its reference) and then include that
stable reference in the dependency array so the effect re-runs correctly when
the function or its dependencies change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
client/src/hooks/use-minigame-api.ts (2)

109-112: Standardize URL construction for minigame endpoints

You still mix raw endpoint constants and buildApiUrl:

  • Raw constants: SESSIONS, GLOBAL_LEADERBOARD, PLAYER_STATS, GAME_TYPES
  • buildApiUrl: END_SESSION, GAME_LEADERBOARD

For maintainability, it’s better to pick one pattern (the repo’s other hooks generally pass raw endpoint constants into useApi, which then handles base URL logic) and use it consistently across:

  • endGameSession
  • getGameLeaderboard
  • getGlobalLeaderboard
  • getPlayerStats
  • getGameTypes
  • createGameSession

E.g., follow the use-player-api style and build any path/query params directly on the endpoint string before passing it to get/post/put.

Also applies to: 153-167, 180-195, 201-232


13-45: Consider adding a small mapping layer to decouple backend field naming

GameSession and LeaderboardEntry currently expose backend‑style snake_case fields (session_id, player_wallet, best_score, total_score), while PlayerStats is camelCase. That mixes naming styles in your public types and bakes backend response shapes directly into the hook’s consumer API.

Non‑blocking, but consider:

  • Keeping exported interfaces fully camelCase (e.g., sessionId, playerWallet, bestScore, totalScore).
  • Adding internal mappers that convert the raw backend JSON into these DTOs before returning from the hook.

This keeps the rest of the UI insulated from backend naming changes and reads more idiomatically in TS/React code.

🧹 Nitpick comments (1)
client/src/hooks/use-minigame-api.ts (1)

6-12: Game type mapping and submitScore composition look solid (optionally tighten types)

The GAME_TYPE_MAP + mapGameType pattern and the submitScore flow (create session → immediately end it) are straightforward and correct.

If you want to reduce stringly‑typed usage later, you could introduce a type GameSlug = 'floppy-fish' | 'bubble-jumper' | 'fish-dodge'; (plus any future slugs) and use that instead of plain string for gameType where applicable, but this is purely optional.

Also applies to: 58-63, 137-148

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 777a693 and 697f365.

📒 Files selected for processing (2)
  • client/src/hooks/use-game-score-submission.ts (1 hunks)
  • client/src/hooks/use-minigame-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/src/hooks/use-game-score-submission.ts
🧰 Additional context used
🧬 Code graph analysis (1)
client/src/hooks/use-minigame-api.ts (2)
backend/scripts/test-store-api.js (1)
  • url (32-32)
client/src/config/api.ts (3)
  • post (118-134)
  • put (136-152)
  • get (105-116)
🪛 GitHub Actions: Frontend CI
client/src/hooks/use-minigame-api.ts

[error] 82-82: tsc --noEmit failed with TS2559 in src/hooks/use-minigame-api.ts:82: Type 'string' has no properties in common with type 'Partial'.

🔇 Additional comments (1)
client/src/hooks/use-minigame-api.ts (1)

234-251: Hook surface is cohesive and matches minigame/leaderboard use cases

The returned state (currentSession, loading, error) plus actions and mapGameType give consumers a clean, focused API for the minigame flows and leaderboards. No changes needed here.

Comment thread client/src/hooks/use-minigame-api.ts
@KevinMB0220
KevinMB0220 merged commit e9ade41 into AquaStark:main Nov 27, 2025
7 of 8 checks passed
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.

1 participant