Skip to content

138 implement redesigned competitive teams - #178

Merged
peterlipt merged 3 commits into
mainfrom
138-implement-redesigned-competitive-teams
Jul 26, 2026
Merged

138 implement redesigned competitive teams#178
peterlipt merged 3 commits into
mainfrom
138-implement-redesigned-competitive-teams

Conversation

@peterlipt

@peterlipt peterlipt commented Jul 26, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added a dedicated Competitive Teams page with localized headings and descriptions.
    • Displayed team presentations, statistics, activities, departments, events, social links, galleries, and join options.
    • Added responsive loading placeholders while team data is retrieved.
    • Introduced content management support for creating and managing competitive teams.
  • Bug Fixes
    • Updated the BME Formula Racing Team website and join links to the correct URL.
    • Added localized fallbacks and validation for team information.

@peterlipt peterlipt self-assigned this Jul 26, 2026
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ehk Ready Ready Preview, Comment Jul 26, 2026 6:57pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Payload CMS collection for competitive teams, migrates legacy team data, exposes typed retrieval, and replaces the page with localized cards showing team details, events, contacts, galleries, and join links.

Changes

Competitive Teams

Layer / File(s) Summary
CMS schema and generated contracts
src/collections/CompetitiveTeams.ts, src/payload-types.ts, src/payload.config.ts
Defines the localized competitive-team collection, validation rules, nested fields, generated types, and Payload registration.
Schema migration and seeded records
src/migrations/20260726_203000_competitive_teams_collection.*, src/migrations/20260726_204000_fix_frt_url.ts, src/migrations/index.ts
Creates competitive-team tables and relationships, imports legacy organizations into Payload, registers migrations, and updates Formula Racing Team URLs.
Localized retrieval and card rendering
src/lib/payload-cms.ts, src/app/(app)/[lang]/kozelet/versenycsapatok/*
Fetches ordered teams, derives localized stats and content, renders team cards, handles umbrella organizations, and adds a loading skeleton.
Page copy and source URL updates
src/dictionaries/en/competition_teams.json, src/dictionaries/hu/competition_teams.json
Updates page titles, subtitles, and Formula Racing Team website links in both locales.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CompetitiveTeamsPage
  participant getCompetitiveTeams
  participant PayloadCMS
  participant CompetitiveTeamCard
  participant OrganizationCard
  CompetitiveTeamsPage->>getCompetitiveTeams: request ordered teams
  getCompetitiveTeams->>PayloadCMS: query competitive-teams
  PayloadCMS-->>getCompetitiveTeams: return team documents
  getCompetitiveTeams-->>CompetitiveTeamsPage: return CompetitiveTeam[]
  CompetitiveTeamsPage->>CompetitiveTeamCard: pass team and locale
  CompetitiveTeamCard->>OrganizationCard: pass localized card data
Loading

Possibly related PRs

  • kir-dev/ehk#174: Introduced or refactored the shared OrganizationCard used by the new competitive-team card.
🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a redesign of the competitive teams feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 138-implement-redesigned-competitive-teams

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.

@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: 4

🧹 Nitpick comments (4)
src/app/(app)/[lang]/kozelet/versenycsapatok/competitiveTeamCard.utils.ts (1)

87-91: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Use a schema-backed umbrella flag.

Line 87 derives card semantics from the editable display name. Renaming MVK silently changes section labels and swaps activities/departments. Persist a stable kind or boolean on the collection and use that instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(app)/[lang]/kozelet/versenycsapatok/competitiveTeamCard.utils.ts
around lines 87 - 91, Update isUmbrellaOrganization to use the schema-backed
stable kind or boolean field on CompetitiveTeam instead of comparing or
searching the editable team.name. Ensure the persisted umbrella classification
continues to drive section labels and activity/department behavior even when the
display name changes.
src/collections/CompetitiveTeams.ts (2)

155-171: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Admin labels render lowercase (website URL, facebook URL).

name.replace("Url", " URL") yields un-capitalized labels that look out of place next to the other Hungarian labels. A small map is clearer and keeps the validate wiring.

✏️ Suggested label map
-      fields: [
-        "websiteUrl",
-        "facebookUrl",
-        "instagramUrl",
-        "linkedinUrl",
-        "youtubeUrl",
-      ].map((name) => ({
-        name,
-        label: name.replace("Url", " URL"),
+      fields: (
+        [
+          ["websiteUrl", "Weboldal URL"],
+          ["facebookUrl", "Facebook URL"],
+          ["instagramUrl", "Instagram URL"],
+          ["linkedinUrl", "LinkedIn URL"],
+          ["youtubeUrl", "YouTube URL"],
+        ] as const
+      ).map(([name, label]) => ({
+        name,
+        label,
         type: "text" as const,
         validate: validateOptionalUrl,
       })),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/CompetitiveTeams.ts` around lines 155 - 171, Update the
contacts field mapping in CompetitiveTeams to use an explicit label map with
capitalized labels for website, Facebook, Instagram, LinkedIn, and YouTube while
preserving each field name, text type, and validateOptionalUrl wiring.

44-201: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge the shared fields into a single factory.

CompetitiveTeams and SpecializedColleges define the same field names/structure, with only localized labels/placeholders differing. A parameterized factory would avoid duplicating field changes across both collections.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/collections/CompetitiveTeams.ts` around lines 44 - 201, Extract the
shared field definitions from CompetitiveTeams and SpecializedColleges into one
parameterized field factory, using its argument to supply the differing
localized labels or placeholders. Update both collection configs to consume the
factory while preserving their existing field names, structure, validation, and
collection-specific fields.
src/migrations/20260726_203000_competitive_teams_collection.json (1)

4602-4607: 🩺 Stability & Availability | 🔵 Trivial

competitive_teams_gallery.image_id is NOT NULL but its FK is ON DELETE set null.

Deleting a referenced media row will raise a not-null violation rather than clearing the reference. This mirrors the existing clubs_gallery / specialized_colleges_gallery definitions, so it's a pre-existing Payload-generated pattern rather than something new here — worth tracking separately if media deletion is ever exercised in the admin.

Also applies to: 4656-4669

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrations/20260726_203000_competitive_teams_collection.json` around
lines 4602 - 4607, Track the schema inconsistency between
competitive_teams_gallery.image_id being notNull and its foreign key using ON
DELETE SET NULL as a separate follow-up; no change is required in this
migration, and note that the same existing pattern appears in clubs_gallery and
specialized_colleges_gallery.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/migrations/20260726_203000_competitive_teams_collection.ts`:
- Around line 7-8: The competitive teams seed must be an immutable migration
snapshot rather than reading mutable dictionaries. In
src/migrations/20260726_203000_competitive_teams_collection.ts lines 7-8,
replace the enDictionary and huDictionary imports with inline seed data
containing the correct Formula Racing Team URL. In
src/migrations/20260726_204000_fix_frt_url.ts lines 20-28, remove the patch
migration if safe for existing environments; otherwise retain it but make its
down() method a no-op instead of restoring the invented URL.
- Around line 169-179: Normalize scheme-less HTTP-style URLs returned by
getContacts() before payload.create() seeds competitive-teams. Ensure
contacts.websiteUrl and the non-MVK joinUrl value are prefixed with an
appropriate scheme when needed, while preserving already valid URLs and existing
MVK behavior.
- Around line 163-167: Update the organization pairing loop to find the English
organization by matching its id rather than using the positional order index,
falling back to the Hungarian organization when no match exists. Also derive the
MVK flag from the organization id instead of assuming order zero, while
preserving the existing bme_motorsport id-based check.

In `@src/migrations/20260726_204000_fix_frt_url.ts`:
- Around line 7-8: The migration’s down() must not restore an invented
previousUrl or assume the seeded team name matches exactly. Make down() a no-op
if rollback fidelity cannot be guaranteed, and remove the hardcoded previousUrl
restoration path; otherwise, capture and restore the original social_links value
and use the exact seed identity.

---

Nitpick comments:
In `@src/app/`(app)/[lang]/kozelet/versenycsapatok/competitiveTeamCard.utils.ts:
- Around line 87-91: Update isUmbrellaOrganization to use the schema-backed
stable kind or boolean field on CompetitiveTeam instead of comparing or
searching the editable team.name. Ensure the persisted umbrella classification
continues to drive section labels and activity/department behavior even when the
display name changes.

In `@src/collections/CompetitiveTeams.ts`:
- Around line 155-171: Update the contacts field mapping in CompetitiveTeams to
use an explicit label map with capitalized labels for website, Facebook,
Instagram, LinkedIn, and YouTube while preserving each field name, text type,
and validateOptionalUrl wiring.
- Around line 44-201: Extract the shared field definitions from CompetitiveTeams
and SpecializedColleges into one parameterized field factory, using its argument
to supply the differing localized labels or placeholders. Update both collection
configs to consume the factory while preserving their existing field names,
structure, validation, and collection-specific fields.

In `@src/migrations/20260726_203000_competitive_teams_collection.json`:
- Around line 4602-4607: Track the schema inconsistency between
competitive_teams_gallery.image_id being notNull and its foreign key using ON
DELETE SET NULL as a separate follow-up; no change is required in this
migration, and note that the same existing pattern appears in clubs_gallery and
specialized_colleges_gallery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 411a63a9-bd7a-4fe0-a4f5-9707bd1218fa

📥 Commits

Reviewing files that changed from the base of the PR and between ffae5ec and ac5ece2.

📒 Files selected for processing (14)
  • src/app/(app)/[lang]/kozelet/versenycsapatok/CompetitiveTeamCard.tsx
  • src/app/(app)/[lang]/kozelet/versenycsapatok/competitiveTeamCard.utils.ts
  • src/app/(app)/[lang]/kozelet/versenycsapatok/loading.tsx
  • src/app/(app)/[lang]/kozelet/versenycsapatok/page.tsx
  • src/collections/CompetitiveTeams.ts
  • src/dictionaries/en/competition_teams.json
  • src/dictionaries/hu/competition_teams.json
  • src/lib/payload-cms.ts
  • src/migrations/20260726_203000_competitive_teams_collection.json
  • src/migrations/20260726_203000_competitive_teams_collection.ts
  • src/migrations/20260726_204000_fix_frt_url.ts
  • src/migrations/index.ts
  • src/payload-types.ts
  • src/payload.config.ts

Comment on lines +7 to +8
import enDictionary from "../dictionaries/en/competition_teams.json";
import huDictionary from "../dictionaries/hu/competition_teams.json";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Both migrations stem from one root cause: the seed's source of truth is mutable dictionary JSON containing a stale FRT URL. Because the seed reads src/dictionaries/{hu,en}/competition_teams.json at build time, the wrong Formula Racing Team URL had to be patched by a second migration whose down() can only guess the prior value.

  • src/migrations/20260726_203000_competitive_teams_collection.ts#L7-L8: inline the seed data as a literal constant in the migration (with the correct FRT URL) instead of importing the dictionaries, so the migration is an immutable snapshot.
  • src/migrations/20260726_204000_fix_frt_url.ts#L20-L28: once the seed carries the correct URL, drop this patch migration, or keep it for already-migrated environments and make down() a no-op rather than writing the invented https://mvk.bme.hu value.
📍 Affects 2 files
  • src/migrations/20260726_203000_competitive_teams_collection.ts#L7-L8 (this comment)
  • src/migrations/20260726_204000_fix_frt_url.ts#L20-L28
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrations/20260726_203000_competitive_teams_collection.ts` around lines
7 - 8, The competitive teams seed must be an immutable migration snapshot rather
than reading mutable dictionaries. In
src/migrations/20260726_203000_competitive_teams_collection.ts lines 7-8,
replace the enDictionary and huDictionary imports with inline seed data
containing the correct Formula Racing Team URL. In
src/migrations/20260726_204000_fix_frt_url.ts lines 20-28, remove the patch
migration if safe for existing environments; otherwise retain it but make its
down() method a no-op instead of restoring the invented URL.

Comment on lines +163 to +167
for (const [order, organization] of huOrganizations.entries()) {
const english = enOrganizations[order] ?? organization;
const contacts = getContacts(organization);
const isMvk = order === 0;
const isMotorsport = organization.id === "bme_motorsport";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

HU/EN pairing relies on positional index and MVK on order === 0.

enOrganizations[order] assumes both dictionaries list the same organizations in the same order, and isMvk assumes source.mvk is always spread first. A reordered or shorter EN list silently seeds Hungarian text as the English presentation. Matching on organization.id (as already done for bme_motorsport) would be robust.

🔒 Suggested id-based pairing
-    const english = enOrganizations[order] ?? organization;
+    const english =
+      (organization.id &&
+        enOrganizations.find((item) => item.id === organization.id)) ||
+      enOrganizations[order] ||
+      organization;
📝 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
for (const [order, organization] of huOrganizations.entries()) {
const english = enOrganizations[order] ?? organization;
const contacts = getContacts(organization);
const isMvk = order === 0;
const isMotorsport = organization.id === "bme_motorsport";
for (const [order, organization] of huOrganizations.entries()) {
const english =
(organization.id &&
enOrganizations.find((item) => item.id === organization.id)) ||
enOrganizations[order] ||
organization;
const contacts = getContacts(organization);
const isMvk = order === 0;
const isMotorsport = organization.id === "bme_motorsport";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrations/20260726_203000_competitive_teams_collection.ts` around lines
163 - 167, Update the organization pairing loop to find the English organization
by matching its id rather than using the positional order index, falling back to
the Hungarian organization when no match exists. Also derive the MVK flag from
the organization id instead of assuming order zero, while preserving the
existing bme_motorsport id-based check.

Comment on lines +169 to +179
await payload.create({
collection: "competitive-teams",
data: {
name: isMvk
? "Műegyetemi Versenycsapat Közösség"
: organization.title,
presentation_hu: richTextDocument(organization.description),
presentation_en: richTextDocument(english.description),
contacts,
order,
...(!isMvk && { joinUrl: contacts.websiteUrl }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
for f in src/dictionaries/hu/competition_teams.json src/dictionaries/en/competition_teams.json; do
  echo "== $f"
  jq -r '.competition_teams | [.mvk] + .teams | .[] | .social_links[]? | .url' "$f"
done

Repository: kir-dev/ehk

Length of output: 2651


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file outline/size =="
wc -l src/migrations/20260726_203000_competitive_teams_collection.ts || true
ast-grep outline src/migrations/20260726_203000_competitive_teams_collection.ts --view expanded || true

echo "== migration relevant sections =="
sed -n '1,240p' src/migrations/20260726_203000_competitive_teams_collection.ts

echo "== locate collection and validateOptionalUrl =="
rg -n "competitive-teams|validateOptionalUrl|social_links|contacts|joinUrl" src -S

Repository: kir-dev/ehk

Length of output: 41465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CompetitiveTeams field validation =="
sed -n '1,60p' src/collections/CompetitiveTeams.ts

echo "== competitiveTeams collection payload config usage =="
sed -n '230,260p' src/lib/payload-cms.ts

echo "== existing URL sanitizer/migrator if any =="
rg -n "validateOptionalUrl|sanitize|new URL\\(|protocol|joinUrl|contacts_website_url" src/collections src/migrations -S

Repository: kir-dev/ehk

Length of output: 5956


🌐 Web query:

Payload CMS 3 create collection field hooks access hooks documentation

💡 Result:

In Payload CMS 3, hooks and access control are distinct features used for different purposes in your document lifecycle [1]. Hooks allow you to execute custom logic during specific lifecycle events [2][3], while access control defines which users have permission to perform operations on documents or fields [4][5]. Field-Level Hooks Field hooks run specifically on individual fields [3][6]. To implement them, use the hooks property within your field configuration [3][6]. Common field hooks include [3][7]: - beforeValidate: Used for data formatting or cleanup before validation [7]. - beforeChange: Useful for business logic modifications [7]. - afterChange: Appropriate for side effects after a field has been updated [3]. - afterRead: Frequently used for computed virtual fields [3][6]. Field-Level Access Control Field-level access control restricts specific fields within a document based on user roles or data criteria [5]. You define this using the access property inside a field's configuration [5]. Available operations include [5]: - create: Determines if a user can set the field value during creation [5]. - read: Controls if a user can view the field value [5]. - update: Controls if a user can modify the field value [5]. Collection-Level Hooks and Access Hooks and access can also be applied at the collection level. Collection hooks are defined in your CollectionConfig and trigger for the document lifecycle [2]. Collection access control defines global permissions for the entire collection (e.g., who can find, create, or update documents) [8]. Key Differences - Hooks: Execute code before or after operations (e.g., transforming data, triggering emails) [2][3]. - Access Control: Determine if an operation is permitted to proceed [4]. For implementation details, refer to the official Payload documentation for Collection Hooks [2], Field Hooks [3], and Access Control [4].

Citations:


Pre-sanitize the URL values before seeding competitive-teams.

getContacts() copies values directly into contacts.websiteUrl, which later becomes joinUrl for non-MVK teams. For example, linktr.ee/bmefrt fails new URL() because it has no scheme, causing the validation error during payload.create() and leaving the DDL-applied collection partially populated. Normalize or prefix scheme-less HTTP-style URLs before writing fields validated by validateOptionalUrl.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrations/20260726_203000_competitive_teams_collection.ts` around lines
169 - 179, Normalize scheme-less HTTP-style URLs returned by getContacts()
before payload.create() seeds competitive-teams. Ensure contacts.websiteUrl and
the non-MVK joinUrl value are prefixed with an appropriate scheme when needed,
while preserving already valid URLs and existing MVK behavior.

Comment on lines +7 to +8
const correctUrl = "https://frtbme.hu/";
const previousUrl = "https://mvk.bme.hu";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

down() writes a guessed previous value rather than restoring the original.

previousUrl is hardcoded to https://mvk.bme.hu, which is the MVK umbrella site, not necessarily what the FRT row held before up() ran (the seed derives it from social_links, and it may have been NULL). Rolling back therefore corrupts the row instead of restoring it. Also note the WHERE "name" = 'BME Formula Racing Team' predicate silently matches zero rows if the seeded title differs by a character.

If a rollback fidelity guarantee isn't needed here, a no-op down() would be more honest than writing an invented URL.

Also applies to: 20-28

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrations/20260726_204000_fix_frt_url.ts` around lines 7 - 8, The
migration’s down() must not restore an invented previousUrl or assume the seeded
team name matches exactly. Make down() a no-op if rollback fidelity cannot be
guaranteed, and remove the hardcoded previousUrl restoration path; otherwise,
capture and restore the original social_links value and use the exact seed
identity.

@peterlipt
peterlipt merged commit b67fc28 into main Jul 26, 2026
3 checks passed
@peterlipt
peterlipt deleted the 138-implement-redesigned-competitive-teams branch July 26, 2026 19:01
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.

Implement Redesigned Competitive Teams (Versenycsapatok) Page and Create CMS Collection

1 participant