Skip to content

feat: Refactor admin view toggle into a dedicated component and enhan…#516

Merged
Peu77 merged 4 commits intodevfrom
group-phase-bracket-design-improvements
Feb 6, 2026
Merged

feat: Refactor admin view toggle into a dedicated component and enhan…#516
Peu77 merged 4 commits intodevfrom
group-phase-bracket-design-improvements

Conversation

@PaulicStudios
Copy link
Member

@PaulicStudios PaulicStudios commented Feb 4, 2026

…ce graph visualization with improved ReactFlow configuration and layout for bracket and group pages.

Summary by CodeRabbit

  • New Features

    • Added admin view toggle on bracket and group pages
    • Team names in match results are now clickable to view team details
  • Refactor

    • Enhanced tournament graphs with improved zoom, pan, fit-view and interaction behavior
    • Updated responsive layout and styling for bracket and group pages
    • Team creation form now supports submit-on-enter and improved form handling
  • Documentation

    • Added seeding instructions to the API README
  • Chores

    • Added a project script to run the user/team seeding tool

…ce graph visualization with improved ReactFlow configuration and layout for bracket and group pages.
@PaulicStudios PaulicStudios requested a review from Peu77 February 4, 2026 21:51
@PaulicStudios PaulicStudios self-assigned this Feb 4, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Adds interactive admin toggles, overhauls ReactFlow graph generation and interaction settings for bracket/group views, restructures page layouts with responsive headers and containers, makes match team names navigable, wraps team creation in a form submit flow, and adds a DB seeding script plus npm script for seeding users/teams.

Changes

Cohort / File(s) Summary
Admin Toggle Controls
frontend/app/events/[id]/bracket/actions.tsx, frontend/app/events/[id]/groups/actions.tsx
Replace noop components with Switch controls that read/write the adminReveal search param and call router.replace to update the URL.
Bracket GraphView
frontend/app/events/[id]/bracket/graphView.tsx
Rework node/edge generation to deterministic per-round nodes and edges, remove embedded admin UI, and expand ReactFlow config (fitView padding, min/max zoom, interaction toggles, proOptions).
Groups GraphView
frontend/app/events/[id]/groups/graphView.tsx
Add RoundNode node type; adjust layout constants and node positioning; expand ReactFlow options (zoom/pan, fitView padding, proOptions); remove admin toggle UI.
Page Layouts
frontend/app/events/[id]/bracket/page.tsx, frontend/app/events/[id]/groups/page.tsx
Replace minimal layouts with stacked, responsive headers and descriptions; conditionally render Actions when eventAdmin; wrap GraphView in styled containers with height constraints and overflow handling.
Match Node UI
frontend/components/match/MatchNode.tsx
Add router/hooks and eventId usage; make finished-match team names clickable (navigates to /events/{eventId}/teams/{teamId}); add optional showTargetHandle / showSourceHandle flags and render Flow Handles when set.
Team Creation UX
frontend/components/team/TeamCreationSection.tsx
Wrap input and button in a form with onSubmit (prevents default and invokes create logic); change button to type="submit".
API Seeding & Docs
api/src/scripts/seed-users-teams.ts, api/package.json, api/README.md
Add a TypeORM-based seeding script to create users, permissions, and teams for an event; add seed:users npm script; document seeding usage in README.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Peu77

Poem

🐇 I flipped the switch with eager cheer,

nodes connect and edges steer,
teams that click to find their place,
forms that send with gentle pace,
seeds sown for a lively race 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: refactoring admin view toggle into a dedicated component and enhancing graph visualization for bracket/group phases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch group-phase-bracket-design-improvements

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/components/match/MatchNode.tsx`:
- Around line 135-145: The span in MatchNode.tsx shows hover/cursor styles
unconditionally though navigation only happens when team.id exists, causing
misleading UX; update the span to apply "cursor-pointer" and "hover:underline"
classes only when team.id is truthy and only attach the onClick navigation
handler (router.push(`/events/${eventId}/teams/${team.id}`)) when team.id
exists, preserving e.stopPropagation() where used; reference the span rendering
of formatTeamName(team.name), the onClick that checks team.id, and router.push
to locate and change the JSX to conditionally set classes and the onClick
handler.
🧹 Nitpick comments (8)
frontend/app/events/[id]/groups/actions.tsx (2)

2-2: Remove unused useParams import.

useParams is imported but never used in this component.

Proposed fix
-import { useParams, useRouter, useSearchParams } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";

6-29: Consider extracting a shared AdminViewToggle component.

This component is nearly identical to frontend/app/events/[id]/bracket/actions.tsx. Both implement the same admin view toggle functionality with identical UI and logic.

Consider extracting this into a shared component (e.g., @/components/AdminViewToggle) to reduce duplication and ensure consistent behavior across bracket and groups pages.

frontend/app/events/[id]/bracket/graphView.tsx (2)

9-9: Remove unused Switch import.

The Switch component is imported but no longer used in this file after the admin toggle was moved to actions.tsx.

Proposed fix
-import { Switch } from "@/components/ui/switch";

205-210: Consider removing explicit variant={undefined}.

Setting variant={undefined} is unusual. If you want the default behavior, simply omit the prop. If you intend to disable the variant pattern, verify this is the correct approach for the ReactFlow Background component.

Proposed fix
         <Background
           color="currentColor"
           className="opacity-10"
           gap={20}
-          variant={undefined}
         />
frontend/app/events/[id]/groups/page.tsx (1)

47-52: LGTM, but note prop naming inconsistency.

The layout and conditional rendering are well implemented. However, there's a naming inconsistency between bracket and groups GraphView components:

  • bracket/graphView.tsx uses isEventAdmin
  • groups/graphView.tsx uses eventAdmin

Consider aligning prop names for consistency across similar components.

frontend/app/events/[id]/groups/graphView.tsx (3)

9-9: Remove unused Switch import.

The Switch component is imported but no longer used after the admin toggle was moved to actions.tsx.

Proposed fix
-import { Switch } from "@/components/ui/switch";

112-113: Add setNodes to the dependency array.

While setNodes from useNodesState is stable and won't cause re-renders, it should be included in the dependency array to satisfy the exhaustive-deps lint rule and maintain consistency.

Proposed fix
     setNodes(newNodes);
-  }, [matches, eventAdmin, eventId, router]);
+  }, [matches, eventAdmin, eventId, router, setNodes]);

138-143: Consider removing explicit variant={undefined}.

Same as in bracket/graphView.tsx - setting variant={undefined} is unusual. If default behavior is intended, simply omit the prop.

Proposed fix
         <Background
           color="currentColor"
           className="opacity-10"
           gap={20}
-          variant={undefined}
         />

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@api/src/scripts/seed-users-teams.ts`:
- Around line 56-66: The seed script creates UserEntity records with fixed
emails (user{i}@example.com) which will collide on repeated runs; update the
email generation in the users loop to include the run-unique suffix (use the
existing now or the same githubId/username suffix) so each email is unique
(e.g., `user{i}+{now}@example.com`), or alternatively add a cleanup step that
removes previously seeded users before inserting; modify the loop that
constructs UserEntity (variables users, user.githubId, user.username) to include
the unique suffix in user.email or add a pre-insert cleanup for those seed
records.
- Around line 17-50: The bootstrap function currently calls process.exit on
errors and can leave the DataSource open; refactor bootstrap to initialize and
run seeding inside a try/finally so dataSource.destroy() is always called: move
the seeding logic (including dataSource.initialize(), repository usage like
userRepository/teamRepository/eventRepository/permissionRepository, and the
event lookup) into a try block, perform any validation/errors by throwing
exceptions (not calling process.exit) and in the finally block call await
dataSource.destroy(); let the top-level caller catch errors and exit process if
needed so cleanup always runs.

In `@frontend/app/events/`[id]/bracket/graphView.tsx:
- Around line 47-52: The placeholder-round calculation can produce 0 or
-Infinity when teamCount <= 1 because it uses Math.log2(teamCount); replace the
Math.log2-based computation with the existing getTotalRounds(teamCount) utility
so totalRounds is always a valid positive integer before the for-loop in the
branch that checks `if (!matches || matches.length === 0)` (refer to variables
`matches`, `teamCount`, and `totalRounds` in graphView.tsx and the helper
`getTotalRounds`).
- Around line 103-184: The layout uses a 0-based coordinate system but the data
and some branches treat match.round as 1-based, causing off-by-one positioning
and last-round/handle logic errors; normalize to a 0-based roundIndex used
everywhere for layout and comparisons: compute a roundIndex from the round key
or match.round (e.g., roundIndex = Number(roundKeyOrMatchRound) - 1) and then
use roundIndex for x positioning (ROUND_SPACING * roundIndex), spacing
calculations (2 ** roundIndex * VERTICAL_SPACING), nodeIdsByRound keys,
last-round checks (compare roundIndex to lastRoundIndex), handle visibility
(showTargetHandle/showSourceHandle), and when finding the placementMatch; update
all references that currently use raw round or match.round (including variables
roundKeys, placementMatch selection, and onClick logic that inspects lastRound)
to use this normalized roundIndex.

In `@frontend/components/match/MatchNode.tsx`:
- Around line 161-172: Replace the non-focusable clickable <span> in
MatchNode.tsx with a keyboard-accessible <button type="button"> that preserves
the visual styling (transparent background, no border, reset padding, text-left
alignment) so keyboard/assistive users can navigate; keep the existing onClick
handler behavior (call e.stopPropagation(), check team.id and call
router.push(`/events/${eventId}/teams/${team.id}`)) and continue to render the
team label via formatTeamName(team.name). Ensure the button uses the same
classes used on the span (e.g., hover:underline transition-all duration-200
cursor-pointer and truncate flex-1) to maintain appearance while adding
focusability.
- Around line 65-78: The MatchNode component unsafely casts useParams() to
string; update the call to useParams with a typed param (e.g., useParams<{ id?:
string }>()) and normalize the result before using it: read params.id into a
local const (e.g., const rawId = params.id) and then derive eventId via a safe
normalization (e.g., const eventId = rawId ?? '' or handle missing id
explicitly), so all uses of eventId in MatchNode are type-safe and won't crash
if the route variant changes.

Comment on lines +17 to +50
async function bootstrap() {
const eventId = process.argv[2];
if (!eventId) {
console.error("Please provide an eventId as the first argument");
process.exit(1);
}

console.log("Connecting to database...");
const configService = new ConfigService();
const databaseConfig = new DatabaseConfig(configService);
const baseConfig = databaseConfig.getConfig(true);

// Use a glob pattern that works with ts-node in development
const dataSource = new DataSource({
...baseConfig,
entities: [join(__dirname, "..", "**", "*.entity.ts")],
} as DataSourceOptions);

await dataSource.initialize();
console.log("Database connected!");

const userRepository = dataSource.getRepository(UserEntity);
const teamRepository = dataSource.getRepository(TeamEntity);
const eventRepository = dataSource.getRepository(EventEntity);
const permissionRepository = dataSource.getRepository(
UserEventPermissionEntity,
);

const event = await eventRepository.findOne({ where: { id: eventId } });
if (!event) {
console.error(`Event with ID ${eventId} not found`);
await dataSource.destroy();
process.exit(1);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "seed-users-teams.ts" -type f

Repository: 42core-team/website

Length of output: 100


🏁 Script executed:

fd "seed-users-teams.ts" --type f

Repository: 42core-team/website

Length of output: 98


🏁 Script executed:

cat -n api/src/scripts/seed-users-teams.ts

Repository: 42core-team/website

Length of output: 4081


Always close the DataSource on failures.

If any error is thrown after Line 35 (during dataSource.initialize() or the seeding operations), the catch handler at Line 103-105 exits without closing the connection. Wrap the seeding logic in try/finally and throw errors instead of calling process.exit inside bootstrap so cleanup always runs.

🧹 Suggested structure
 async function bootstrap() {
   const eventId = process.argv[2];
   if (!eventId) {
-    console.error("Please provide an eventId as the first argument");
-    process.exit(1);
+    throw new Error("Please provide an eventId as the first argument");
   }

   console.log("Connecting to database...");
   const configService = new ConfigService();
   const databaseConfig = new DatabaseConfig(configService);
   const baseConfig = databaseConfig.getConfig(true);

   const dataSource = new DataSource({
     ...baseConfig,
     entities: [join(__dirname, "..", "**", "*.entity.ts")],
   } as DataSourceOptions);

-  await dataSource.initialize();
-  console.log("Database connected!");
+  try {
+    await dataSource.initialize();
+    console.log("Database connected!");

     const userRepository = dataSource.getRepository(UserEntity);
     const teamRepository = dataSource.getRepository(TeamEntity);
     const eventRepository = dataSource.getRepository(EventEntity);
     const permissionRepository = dataSource.getRepository(
       UserEventPermissionEntity,
     );

     const event = await eventRepository.findOne({ where: { id: eventId } });
     if (!event) {
-      console.error(`Event with ID ${eventId} not found`);
-      await dataSource.destroy();
-      process.exit(1);
+      throw new Error(`Event with ID ${eventId} not found`);
     }

     // ... seed logic ...

-    console.log("Seeding completed successfully!");
-    await dataSource.destroy();
+    console.log("Seeding completed successfully!");
+  } finally {
+    if (dataSource.isInitialized) {
+      await dataSource.destroy();
+    }
+  }
 }
🤖 Prompt for AI Agents
In `@api/src/scripts/seed-users-teams.ts` around lines 17 - 50, The bootstrap
function currently calls process.exit on errors and can leave the DataSource
open; refactor bootstrap to initialize and run seeding inside a try/finally so
dataSource.destroy() is always called: move the seeding logic (including
dataSource.initialize(), repository usage like
userRepository/teamRepository/eventRepository/permissionRepository, and the
event lookup) into a try block, perform any validation/errors by throwing
exceptions (not calling process.exit) and in the finally block call await
dataSource.destroy(); let the top-level caller catch errors and exit process if
needed so cleanup always runs.

Comment on lines +56 to +66
const users: UserEntity[] = [];
const now = Date.now();
for (let i = 1; i <= 90; i++) {
const user = new UserEntity();
user.githubId = `seed-user-${i}-${now}`;
user.githubAccessToken = "dummy-token";
user.email = `user${i}@example.com`;
user.username = `seeduser${i}_${now.toString().slice(-5)}`;
user.name = `Seed User ${i}`;
user.profilePicture = `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`;
users.push(user);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent email collisions on repeated seeding runs.

Line 62 uses a fixed email pattern (user{i}@example.com). If UserEntity.email is unique (typical), a second run will fail and leave a partial seed. Add a run-unique suffix (similar to githubId/username) or clean existing seeds before insert.

🔧 Proposed fix
-    user.email = `user${i}@example.com`;
+    user.email = `user${i}_${now}@example.com`;
📝 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 users: UserEntity[] = [];
const now = Date.now();
for (let i = 1; i <= 90; i++) {
const user = new UserEntity();
user.githubId = `seed-user-${i}-${now}`;
user.githubAccessToken = "dummy-token";
user.email = `user${i}@example.com`;
user.username = `seeduser${i}_${now.toString().slice(-5)}`;
user.name = `Seed User ${i}`;
user.profilePicture = `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`;
users.push(user);
const users: UserEntity[] = [];
const now = Date.now();
for (let i = 1; i <= 90; i++) {
const user = new UserEntity();
user.githubId = `seed-user-${i}-${now}`;
user.githubAccessToken = "dummy-token";
user.email = `user${i}_${now}@example.com`;
user.username = `seeduser${i}_${now.toString().slice(-5)}`;
user.name = `Seed User ${i}`;
user.profilePicture = `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`;
users.push(user);
🤖 Prompt for AI Agents
In `@api/src/scripts/seed-users-teams.ts` around lines 56 - 66, The seed script
creates UserEntity records with fixed emails (user{i}@example.com) which will
collide on repeated runs; update the email generation in the users loop to
include the run-unique suffix (use the existing now or the same
githubId/username suffix) so each email is unique (e.g.,
`user{i}+{now}@example.com`), or alternatively add a cleanup step that removes
previously seeded users before inserting; modify the loop that constructs
UserEntity (variables users, user.githubId, user.username) to include the unique
suffix in user.email or add a pre-insert cleanup for those seed records.

@PaulicStudios PaulicStudios marked this pull request as draft February 5, 2026 07:25
@Peu77 Peu77 marked this pull request as ready for review February 6, 2026 14:45
@Peu77 Peu77 merged commit 86f12f9 into dev Feb 6, 2026
5 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.

2 participants