118 implement redesigned educational regulations oktatási szabályzatok page#155
Hidden character warning
Conversation
- Updated RepresentativeCard component to enhance accessibility and visual design. - Refactored RepresentativeModal to streamline layout and improve user experience. - Modified RepresentativesGrid to accept title and description props for better context. - Enhanced RepresentativesGridClient with a back button and improved loading states. - Created a new helpers file for common representative functions and styles. - Improved loading skeletons for representative cards and grid. - Updated page structure in RepresentativesPage for better layout and responsiveness.
… table - Created a new migration to add `representative_id` and `contact_email` columns to the `news` table. - Added foreign key constraint for `representative_id` referencing the `representatives` table. - Created an index on `representative_id` for improved query performance. - Updated the payload types to include optional `representative` and `contactEmail` fields in the News interface.
…rnal link - Created a new migration to add columns for submission process in Hungarian and English, and an external link to the permissions table. - Updated the migrations index to include the new migration. - Extended the Permission interface in payload-types.ts to include types for the new submission process fields and external link.
…th improved styling and structure
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR extends the News collection with ChangesSchema, Migrations, and FileCard Extension
Representatives Helpers, Card, Modal, Grid, and Skeletons
News Detail Page Redesign and Contacts Sidebar
Permissions, Regulations, and Decisions UI Updates
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/(app)/[lang]/engedelyek/page.tsx (1)
14-19:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate
langbefore dictionary/provider usage.Line 14–19 currently trust-casts
lang; an unsupported locale can break dictionary loading and provider language state at runtime. Mirror thevalidLangguard pattern used in the regulations page.Suggested fix
export default async function PermissionsPage({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; - const dictionary = await getDictionary(lang as "hu" | "en", "permissions"); + const validLang = + i18n.locales.includes(lang as "hu" | "en") + ? (lang as "hu" | "en") + : i18n.defaultLocale; + const dictionary = await getDictionary(validLang, "permissions"); return ( - <LanguageProvider defaultLang={(lang as string).toUpperCase() as "HU" | "EN"} dictionary={dictionary}> + <LanguageProvider defaultLang={validLang.toUpperCase() as "HU" | "EN"} dictionary={dictionary}> <div className="min-h-screen bg-gray-50">🤖 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]/engedelyek/page.tsx around lines 14 - 19, The lang parameter is being trust-cast without validation before being used in getDictionary and LanguageProvider, which could break dictionary loading and language state if an unsupported locale is passed. Add a validLang guard check (mirroring the pattern used in the regulations page) to validate that lang is either "hu" or "en" before passing it to getDictionary and LanguageProvider. This should validate lang as soon as it's destructured from params and handle invalid values appropriately.
🧹 Nitpick comments (1)
src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailMain.tsx (1)
29-38: 💤 Low valueConsider extracting
formatDateto a shared utility.This date formatting logic is duplicated in
RelatedNewsClient.tsx(lines 12-21). Extracting it to a shared utility (e.g., insrc/lib/utils.tsalongsidetranslateTags) would improve maintainability and ensure consistent date formatting across the news detail components.🤖 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]/hirek/[slug]/components/NewsDetailMain.tsx around lines 29 - 38, The formatDate function in NewsDetailMain.tsx is duplicated in RelatedNewsClient.tsx, which reduces maintainability. Extract the formatDate function from NewsDetailMain.tsx and move it to src/lib/utils.ts alongside other shared utilities like translateTags. Then import this shared formatDate function in both NewsDetailMain.tsx and RelatedNewsClient.tsx, removing the duplicate function definitions from both files. This ensures consistent date formatting logic across components and makes future updates easier.
🤖 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/app/`(app)/[lang]/hatarozatok-tara/components/DecisionsArchiveClient.tsx:
- Around line 37-41: The getDecisions() function in src/lib/payload-cms.ts is
missing the depth: 1 parameter in its payload.find() query, which causes the
file relation to remain unpopulated as a numeric ID instead of a full object.
This breaks the FileCard component in DecisionsArchiveClient.tsx which expects a
properly populated file object. Add depth: 1 to the payload.find() call in the
getDecisions() function, following the same pattern used in similar functions
like getRepresentatives(), getPermissions(), and getAcademicRegulations() which
all include this parameter to properly populate their relations.
In `@src/app/`(app)/[lang]/kepviselok/components/representatives.helpers.ts:
- Around line 82-99: The formatFileSize function does not validate for negative
byte values, which causes Math.log(bytes) to return NaN, resulting in malformed
output. Add a check in the formatFileSize function to handle negative bytes
values (bytes < 0) before the logarithm calculation, returning null as a safe
fallback for invalid negative inputs, similar to how the function handles
undefined, null, and NaN values.
In `@src/collections/Permissions.ts`:
- Around line 100-108: The externalLink field in the Permissions collection
accepts free-form text without validation, which creates a potential XSS
vulnerability when the URL is rendered as an href attribute in anchors. Add a
validate function to the externalLink field configuration that checks the URL
scheme at write-time, allowing only safe schemes like http and https while
rejecting javascript:, data:, and other dangerous protocols. This validation
should be applied before the data is persisted to prevent malicious payloads
from being stored.
In
`@src/migrations/20260619_141957_add_permissions_submission_and_external_link.ts`:
- Around line 5-7: The migration file contains column names that do not follow
the ORM's camelCase-to-snake_case mapping convention. The columns
submissionprocess_hu and submissionprocess_en are missing the underscore
separator between "submission" and "process", while the corresponding fields in
Permissions.ts are defined as submissionProcess_hu and submissionProcess_en. The
ORM adapter will convert these camelCase field names to submission_process_hu
and submission_process_en (with underscore), so the migration columns must match
this pattern. Rename both submissionprocess_hu and submissionprocess_en to
submission_process_hu and submission_process_en respectively in both the up and
down migration statements.
---
Outside diff comments:
In `@src/app/`(app)/[lang]/engedelyek/page.tsx:
- Around line 14-19: The lang parameter is being trust-cast without validation
before being used in getDictionary and LanguageProvider, which could break
dictionary loading and language state if an unsupported locale is passed. Add a
validLang guard check (mirroring the pattern used in the regulations page) to
validate that lang is either "hu" or "en" before passing it to getDictionary and
LanguageProvider. This should validate lang as soon as it's destructured from
params and handle invalid values appropriately.
---
Nitpick comments:
In `@src/app/`(app)/[lang]/hirek/[slug]/components/NewsDetailMain.tsx:
- Around line 29-38: The formatDate function in NewsDetailMain.tsx is duplicated
in RelatedNewsClient.tsx, which reduces maintainability. Extract the formatDate
function from NewsDetailMain.tsx and move it to src/lib/utils.ts alongside other
shared utilities like translateTags. Then import this shared formatDate function
in both NewsDetailMain.tsx and RelatedNewsClient.tsx, removing the duplicate
function definitions from both files. This ensures consistent date formatting
logic across components and makes future updates easier.
🪄 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
Run ID: cce014b9-cbb8-4f9e-bbb1-1e077aa2b47a
📒 Files selected for processing (39)
src/app/(app)/[lang]/engedelyek/components/PermissionsListClient.tsxsrc/app/(app)/[lang]/engedelyek/page.tsxsrc/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchiveClient.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/BackNav.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/FileIcon.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/NewsDetail.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/NewsDetailClient.tssrc/app/(app)/[lang]/hirek/[slug]/components/NewsDetailMain.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/NewsDetailSidebar.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/RelatedNewsClient.tsxsrc/app/(app)/[lang]/hirek/[slug]/components/ShareButton.tsxsrc/app/(app)/[lang]/hirek/[slug]/lib/news-utils.tssrc/app/(app)/[lang]/kepviselok/components/RepresentativeCard.tsxsrc/app/(app)/[lang]/kepviselok/components/RepresentativeModal.tsxsrc/app/(app)/[lang]/kepviselok/components/RepresentativesGrid.tsxsrc/app/(app)/[lang]/kepviselok/components/RepresentativesGridClient.tsxsrc/app/(app)/[lang]/kepviselok/components/representatives.helpers.tssrc/app/(app)/[lang]/kepviselok/components/skeletons/LoadingRepresentativeCard.tsxsrc/app/(app)/[lang]/kepviselok/components/skeletons/LoadingRepresentativesGrid.tsxsrc/app/(app)/[lang]/kepviselok/page.tsxsrc/app/(app)/[lang]/oktatasi-szabalyzatok/loading.tsxsrc/app/(app)/[lang]/oktatasi-szabalyzatok/page.tsxsrc/collections/News.tssrc/collections/Permissions.tssrc/components/common/FileCard.tsxsrc/components/regulations/RegulationsListClient.tsxsrc/components/regulations/skeletons/LoadingRegulationsGrid.tsxsrc/dictionaries/en/news.jsonsrc/dictionaries/en/permissions.jsonsrc/dictionaries/en/regulations.jsonsrc/dictionaries/hu/news.jsonsrc/dictionaries/hu/permissions.jsonsrc/dictionaries/hu/regulations.jsonsrc/migrations/20260619_105937_add_news_representative_and_contact_email.jsonsrc/migrations/20260619_105937_add_news_representative_and_contact_email.tssrc/migrations/20260619_141957_add_permissions_submission_and_external_link.jsonsrc/migrations/20260619_141957_add_permissions_submission_and_external_link.tssrc/migrations/index.tssrc/payload-types.ts
💤 Files with no reviewable changes (5)
- src/app/(app)/[lang]/hirek/[slug]/lib/news-utils.ts
- src/app/(app)/[lang]/hirek/[slug]/components/BackNav.tsx
- src/app/(app)/[lang]/hirek/[slug]/components/ShareButton.tsx
- src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailClient.ts
- src/app/(app)/[lang]/hirek/[slug]/components/FileIcon.tsx
| <FileCard | ||
| key={decision.id} | ||
| file={decision.file} | ||
| title={title} | ||
| secondaryText={secondary} | ||
| actionType="view" | ||
| actionType="view" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find Decisions list/server loader files =="
fd -i 'decisions|hatarozat|archive' src app
echo
echo "== Locate Decision queries and check relation depth/select =="
rg -n -C3 'find\\(|collection:\\s*["'\'']decisions["'\'']|get.*Decision|loader\\s*=|file\\s*:' src app
echo
echo "== Inspect where DecisionsArchiveClient is fed data =="
rg -n -C3 'DecisionsArchiveClient\\s*\\(|<DecisionsArchiveClient' src appRepository: kir-dev/ehk
Length of output: 664
🏁 Script executed:
cat -n src/collections/Decisions.tsRepository: kir-dev/ehk
Length of output: 1281
🏁 Script executed:
cat -n src/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchive.tsxRepository: kir-dev/ehk
Length of output: 219
🏁 Script executed:
cat -n src/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchiveClient.tsxRepository: kir-dev/ehk
Length of output: 225
🏁 Script executed:
cat -n 'src/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchive.tsx'Repository: kir-dev/ehk
Length of output: 411
🏁 Script executed:
cat -n 'src/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchiveClient.tsx'Repository: kir-dev/ehk
Length of output: 1913
🏁 Script executed:
find src -name '*payload-cms*' -o -name '*payload*' | grep -E '\.(ts|tsx)$' | head -20Repository: kir-dev/ehk
Length of output: 121
🏁 Script executed:
rg -l 'getDecisions' srcRepository: kir-dev/ehk
Length of output: 148
🏁 Script executed:
cat -n 'src/components/common/FileCard.tsx'Repository: kir-dev/ehk
Length of output: 6669
🏁 Script executed:
cat -n src/lib/payload-cms.tsRepository: kir-dev/ehk
Length of output: 6790
🏁 Script executed:
head -100 src/payload-types.tsRepository: kir-dev/ehk
Length of output: 2709
Add depth: 1 to getDecisions() query to populate the file relation.
The getDecisions() function in src/lib/payload-cms.ts (line 75-82) is missing the depth: 1 parameter. Without it, the file relation remains unpopulated as a numeric ID, causing FileCard to render a disabled link. Compare with similar functions like getRepresentatives() (line 58), getPermissions() (line 90), and getAcademicRegulations() (line 100), which all include depth: 1 to populate relations. Update the query to:
const decisions = await payload.find({
collection: "decisions",
depth: 1,
limit: 1000 });🤖 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]/hatarozatok-tara/components/DecisionsArchiveClient.tsx
around lines 37 - 41, The getDecisions() function in src/lib/payload-cms.ts is
missing the depth: 1 parameter in its payload.find() query, which causes the
file relation to remain unpopulated as a numeric ID instead of a full object.
This breaks the FileCard component in DecisionsArchiveClient.tsx which expects a
properly populated file object. Add depth: 1 to the payload.find() call in the
getDecisions() function, following the same pattern used in similar functions
like getRepresentatives(), getPermissions(), and getAcademicRegulations() which
all include this parameter to properly populate their relations.
| export function formatFileSize(bytes?: number | null) { | ||
| if (bytes === undefined || bytes === null || Number.isNaN(bytes)) { | ||
| return null; | ||
| } | ||
|
|
||
| if (bytes === 0) { | ||
| return "0 B"; | ||
| } | ||
|
|
||
| const units = ["B", "KB", "MB", "GB"]; | ||
| const unitIndex = Math.min( | ||
| Math.floor(Math.log(bytes) / Math.log(1024)), | ||
| units.length - 1, | ||
| ); | ||
| const size = bytes / 1024 ** unitIndex; | ||
|
|
||
| return `${parseFloat(size.toFixed(1))} ${units[unitIndex]}`; | ||
| } |
There was a problem hiding this comment.
Handle invalid negative byte values before logarithm.
Math.log(bytes) on negative values yields NaN, which renders malformed output (NaN undefined) instead of a safe fallback.
💡 Suggested fix
export function formatFileSize(bytes?: number | null) {
- if (bytes === undefined || bytes === null || Number.isNaN(bytes)) {
+ if (
+ bytes === undefined ||
+ bytes === null ||
+ !Number.isFinite(bytes) ||
+ bytes < 0
+ ) {
return null;
}
if (bytes === 0) {
return "0 B";
}📝 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.
| export function formatFileSize(bytes?: number | null) { | |
| if (bytes === undefined || bytes === null || Number.isNaN(bytes)) { | |
| return null; | |
| } | |
| if (bytes === 0) { | |
| return "0 B"; | |
| } | |
| const units = ["B", "KB", "MB", "GB"]; | |
| const unitIndex = Math.min( | |
| Math.floor(Math.log(bytes) / Math.log(1024)), | |
| units.length - 1, | |
| ); | |
| const size = bytes / 1024 ** unitIndex; | |
| return `${parseFloat(size.toFixed(1))} ${units[unitIndex]}`; | |
| } | |
| export function formatFileSize(bytes?: number | null) { | |
| if ( | |
| bytes === undefined || | |
| bytes === null || | |
| !Number.isFinite(bytes) || | |
| bytes < 0 | |
| ) { | |
| return null; | |
| } | |
| if (bytes === 0) { | |
| return "0 B"; | |
| } | |
| const units = ["B", "KB", "MB", "GB"]; | |
| const unitIndex = Math.min( | |
| Math.floor(Math.log(bytes) / Math.log(1024)), | |
| units.length - 1, | |
| ); | |
| const size = bytes / 1024 ** unitIndex; | |
| return `${parseFloat(size.toFixed(1))} ${units[unitIndex]}`; | |
| } |
🤖 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]/kepviselok/components/representatives.helpers.ts around
lines 82 - 99, The formatFileSize function does not validate for negative byte
values, which causes Math.log(bytes) to return NaN, resulting in malformed
output. Add a check in the formatFileSize function to handle negative bytes
values (bytes < 0) before the logarithm calculation, returning null as a safe
fallback for invalid negative inputs, similar to how the function handles
undefined, null, and NaN values.
| { | ||
| name: "externalLink", | ||
| type: "text", | ||
| required: false, | ||
| label: "Külső link (űrlap URL)", | ||
| admin: { | ||
| description: "Külső webes űrlaphoz tartozó URL (pl. \"Rendezvénybejelentő űrlap\"). Ha meg van adva, a kártya ezt nyitja meg fájl helyett.", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Validate externalLink URL scheme at write-time.
externalLink is stored as free-form text but later rendered into an anchor href via FileCard open mode. Without validation, javascript:/data: payloads can become stored XSS vectors.
🔒 Suggested fix
{
name: "externalLink",
type: "text",
required: false,
label: "Külső link (űrlap URL)",
+ validate: (value: string | null | undefined) => {
+ if (!value) return true
+ try {
+ const parsed = new URL(value.trim())
+ return ["http:", "https:"].includes(parsed.protocol)
+ ? true
+ : "Csak http/https URL engedélyezett."
+ } catch {
+ return "Érvényes URL-t adj meg (http/https)."
+ }
+ },
admin: {
description: "Külső webes űrlaphoz tartozó URL (pl. \"Rendezvénybejelentő űrlap\"). Ha meg van adva, a kártya ezt nyitja meg fájl helyett.",
},
},🤖 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/Permissions.ts` around lines 100 - 108, The externalLink
field in the Permissions collection accepts free-form text without validation,
which creates a potential XSS vulnerability when the URL is rendered as an href
attribute in anchors. Add a validate function to the externalLink field
configuration that checks the URL scheme at write-time, allowing only safe
schemes like http and https while rejecting javascript:, data:, and other
dangerous protocols. This validation should be applied before the data is
persisted to prevent malicious payloads from being stored.
| ALTER TABLE "permissions" ADD COLUMN "submissionprocess_hu" jsonb; | ||
| ALTER TABLE "permissions" ADD COLUMN "submissionprocess_en" jsonb; | ||
| ALTER TABLE "permissions" ADD COLUMN "external_link" varchar;`) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Field names defined in collections =="
rg -n --type ts 'name:\s*"submissionProcess_(hu|en)"|name:\s*"externalLink"|name:\s*"contactEmail"' src/collections
echo
echo "== Column names used in migrations =="
rg -n --type ts '"submissionprocess_(hu|en)"|"submission_process_(hu|en)"|"external_link"|"contact_email"' src/migrations
echo
echo "Expected: consistent camelCase->snake_case mapping across migrations."Repository: kir-dev/ehk
Length of output: 1637
Column names in migration must match ORM field mapping convention.
The migration uses submissionprocess_hu/en while the field definitions in Permissions.ts are submissionProcess_hu/en. This breaks the camelCase-to-snake_case mapping pattern—the adapter will map submissionProcess_* to submission_process_* (with underscore), not submissionprocess_*. The same migration correctly maps externalLink to external_link and the prior migration maps contactEmail to contact_email. Rename the columns in both up and down migrations to submission_process_hu/en.
Suggested fix
- ALTER TABLE "permissions" ADD COLUMN "submissionprocess_hu" jsonb;
- ALTER TABLE "permissions" ADD COLUMN "submissionprocess_en" jsonb;
+ ALTER TABLE "permissions" ADD COLUMN "submission_process_hu" jsonb;
+ ALTER TABLE "permissions" ADD COLUMN "submission_process_en" jsonb;
ALTER TABLE "permissions" ADD COLUMN "external_link" varchar;`)
...
- ALTER TABLE "permissions" DROP COLUMN "submissionprocess_hu";
- ALTER TABLE "permissions" DROP COLUMN "submissionprocess_en";
+ ALTER TABLE "permissions" DROP COLUMN "submission_process_hu";
+ ALTER TABLE "permissions" DROP COLUMN "submission_process_en";
ALTER TABLE "permissions" DROP COLUMN "external_link";`)📝 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.
| ALTER TABLE "permissions" ADD COLUMN "submissionprocess_hu" jsonb; | |
| ALTER TABLE "permissions" ADD COLUMN "submissionprocess_en" jsonb; | |
| ALTER TABLE "permissions" ADD COLUMN "external_link" varchar;`) | |
| ALTER TABLE "permissions" ADD COLUMN "submission_process_hu" jsonb; | |
| ALTER TABLE "permissions" ADD COLUMN "submission_process_en" jsonb; | |
| ALTER TABLE "permissions" ADD COLUMN "external_link" varchar;`) |
🤖 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/20260619_141957_add_permissions_submission_and_external_link.ts`
around lines 5 - 7, The migration file contains column names that do not follow
the ORM's camelCase-to-snake_case mapping convention. The columns
submissionprocess_hu and submissionprocess_en are missing the underscore
separator between "submission" and "process", while the corresponding fields in
Permissions.ts are defined as submissionProcess_hu and submissionProcess_en. The
ORM adapter will convert these camelCase field names to submission_process_hu
and submission_process_en (with underscore), so the migration columns must match
this pattern. Rename both submissionprocess_hu and submissionprocess_en to
submission_process_hu and submission_process_en respectively in both the up and
down migration statements.
Summary by CodeRabbit
New Features
Style