Skip to content

124#161

Open
DankaMarci wants to merge 12 commits into
mainfrom
124
Open

124#161
DankaMarci wants to merge 12 commits into
mainfrom
124

Conversation

@DankaMarci

@DankaMarci DankaMarci commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

closes #123 #124

Summary by CodeRabbit

  • New Features

    • Added expandable accordion sections for scholarship FAQs and help content.
    • Introduced search functionality for representatives directory.
    • Added support for displaying submission processes and external links in permissions pages.
  • Bug Fixes & Improvements

    • Enhanced news detail pages with improved contact information display and refined layout.
    • Redesigned representative cards and modals with better visual hierarchy.
    • Updated scholarship pages with cleaner accordion-based layouts.
    • Improved empty state handling across multiple pages.
  • Style

    • Updated background colors and spacing throughout the application for visual consistency.

- 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.
- Created migration for academic scholarship FAQ with accordion items.
- Updated migrations index to include the new academic scholarship FAQ migration.
- Defined AcademicScholarshipFaq and AcademicScholarshipFaqSelect interfaces in payload-types.
- Configured global settings to include AcademicScholarshipFAQ in payload configuration.
- Implemented migration for social scholarships FAQ with types and sidebar links.
- Added migration for EHK scholarships with detailed scholarship entries.
- Updated index to include new migrations.
- Extended payload types to support new scholarship structures.
- Modified payload configuration to include new globals for social scholarships FAQ.
@DankaMarci DankaMarci requested a review from peterlipt June 20, 2026 14:30
@DankaMarci DankaMarci self-assigned this Jun 20, 2026
@vercel

vercel Bot commented Jun 20, 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 Jun 20, 2026 2:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a shared Accordion component and extends FileCard with an "open" action type, then adds three new Payload CMS schemas (EhkScholarships collection, AcademicScholarshipFAQ and SocialScholarshipsFAQ globals) with five database migrations. It refactors scholarship, representatives, news detail, permissions, decisions, help, and language education pages to use the new components and CMS-driven content, and removes several now-unused utilities (BackNav, ShareButton, FileIcon, news-utils).

Changes

Multi-page redesign and CMS integration

Layer / File(s) Summary
Shared Accordion component
src/components/common/Accordion.tsx, src/app/(app)/globals.css
New client-side AccordionItem and Accordion wrapper with controlled/uncontrolled open state, ARIA wiring, CSS grid height animation, and single-open-at-a-time mode. Rich-text CSS rules for blockquote, heading, paragraph, and list styling inside .richtext accordions.
FileCard open action type
src/components/common/FileCard.tsx
FileCardProps gains externalUrl, extensionLabel, and actionType="open"; getFileInfo routes external-link and extension-label overrides; renders an <a target="_blank"> with ExternalLink icon.
CMS collections, globals, migrations, and generated types
src/collections/EhkScholarships.ts, src/collections/News.ts, src/collections/Permissions.ts, src/globals/AcademicScholarshipFAQ.ts, src/globals/SocialScholarshipsFAQ.ts, src/lib/payload-cms.ts, src/migrations/*, src/payload-types.ts, src/payload.config.ts
Adds EhkScholarships collection with bilingual Lexical rich-text fields; extends News with representative/contactEmail; extends Permissions with submissionProcess_hu/en and externalLink; adds AcademicScholarshipFAQ and SocialScholarshipsFAQ globals; registers five migrations (schema + seed data); updates generated types and payload config.
Representatives helpers, card, modal, and grid
src/app/(app)/[lang]/kepviselok/components/representatives.helpers.ts, src/app/(app)/[lang]/kepviselok/components/RepresentativeCard.tsx, src/app/(app)/[lang]/kepviselok/components/RepresentativeModal.tsx, src/app/(app)/[lang]/kepviselok/components/RepresentativesGrid.tsx, src/app/(app)/[lang]/kepviselok/components/RepresentativesGridClient.tsx, src/app/(app)/[lang]/kepviselok/components/skeletons/*, src/app/(app)/[lang]/kepviselok/page.tsx
New representatives.helpers.ts provides facultyStyles, getPrimaryPosition, getRepresentativePicture, getRepresentativeInitials, getFileInfo, and formatFileSize. Card switches from Card/Badge to semantic article; Modal restructures into header/mobile/desktop blocks with custom file-link cards; Grid accepts title/description props with back-navigation; skeleton UIs updated.
Scholarship pages: EHK, Academic, Social
src/app/(app)/[lang]/ehk-osztondij/page.tsx, src/app/(app)/[lang]/tanulmanyi-osztondij/..., src/app/(app)/[lang]/szocialis-osztondijak/page.tsx, src/dictionaries/*/scholarships.json
All three pages validate lang, fetch CMS content, map entries to AccordionEntry[], and render Accordion or EmptyState. Social page adds inline ScholarshipCard and ImportantLinksSidebar sub-components with RichText sections and external-link buttons. Dictionary academic/ehk detail content replaced with empty_title/empty_description placeholders.
News detail refactor
src/app/(app)/[lang]/hirek/[slug]/components/NewsDetail.tsx, .../NewsDetailMain.tsx, .../NewsDetailSidebar.tsx, .../RelatedNewsClient.tsx, src/dictionaries/*/news.json
Removes BackNav, ShareButton, FileIcon, NewsDetailClient, and news-utils (reading-time, formatBytes). Layout switches to flexbox with aside. NewsDetailMain adds useRouter-based back button, new UTC formatDate, and article layout with FileCard attachments. Sidebar becomes a contacts-only panel. RelatedNews uses static tag pills and custom section markup.
Other pages: Permissions, Decisions, Help, Language Education, Regulations, Reminders
src/app/(app)/[lang]/engedelyek/..., src/app/(app)/[lang]/hatarozatok-tara/..., src/app/(app)/[lang]/kisokosok/..., src/app/(app)/[lang]/nyelvoktatas/..., src/app/(app)/[lang]/oktatasi-szabalyzatok/..., src/app/(app)/[lang]/emlekeztetok/..., src/components/regulations/..., src/dictionaries/*
PermissionsListClient adds submissionProcess rich-text and externalLink branch; page removes PageHeader. DecisionsArchiveClient adopts EmptyState and language-aware title selection. HelpPageList adds EmptyState and new skeleton component. LanguageEducationContent becomes data-driven with paragraphs/link props. YearSection uses AccordionItem. RegulationsListClient and skeleton updated for new styling.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • kir-dev/ehk#150: Both PRs modify src/components/common/FileCard.tsx — the retrieved PR refactors FileCard and this PR extends it further with the "open" action type and externalUrl/extensionLabel props.

Suggested reviewers

  • peterlipt

Poem

🐇 Hop, hop, the cards have gone away,
Accordions fold the content today!
New globals grow in Payload's ground,
While migrations lay the tables sound.
The rabbit says: "Rich text is neat—
Now every scholarship looks complete!" ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title '124' is vague and does not describe the changes. It references an issue number but provides no meaningful information about the work performed. Replace the title with a descriptive summary of the main changes, such as 'Redesign social scholarship page with CMS integration and UI updates'.
Out of Scope Changes check ⚠️ Warning The PR contains substantial out-of-scope changes unrelated to issue #123: extensive news detail page refactoring (removing BackNav, ShareButton, FileIcon, redesigning NewsDetailMain/Sidebar), permissions field additions, representative component refactoring with new helpers, language education restructuring, EHK scholarships collection/migration, academic scholarship FAQ implementation, and multiple help page updates. Separate out-of-scope changes into dedicated PRs. Keep this PR focused on issue #123 (social scholarships redesign) and move news, permissions, representative, EHK scholarship, and other unrelated changes to their own PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.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.
Linked Issues check ✅ Passed The PR implements the core objectives from issue #123: creates SocialScholarshipsFAQ global, adds scholarship cards with localized rich-text content, implements Important Links sidebar, uses PageHeader, and ensures responsive layout. However, the PR also includes significant unplanned work (news detail refactoring, permissions updates, representative components, language education, help pages, etc.) beyond issue scope.

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

✨ 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 124

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: 8

🧹 Nitpick comments (1)
src/components/common/Accordion.tsx (1)

9-14: ⚡ Quick win

Require an accessible trigger label at the prop contract.

Line 9-Line 14 allow both header and title to be omitted, which can produce an unlabeled toggle button. Please require at least one of these props in the type.

Proposed type-safe contract
-export interface AccordionItemProps {
+type AccordionLabelProps =
+  | { header: React.ReactNode; title?: React.ReactNode }
+  | { title: React.ReactNode; header?: React.ReactNode };
+
+export type AccordionItemProps = AccordionLabelProps & {
   /** Header / title text shown in the always-visible row. */
-  header?: React.ReactNode;
+  header?: React.ReactNode;
   /** Alias for `header`, useful when consuming the component as a title/content pair. */
-  title?: React.ReactNode;
+  title?: React.ReactNode;
   /** Expanded content. */
   children: React.ReactNode;
@@
   headerClassName?: string;
   contentClassName?: string;
   iconClassName?: string;
-}
+};
🤖 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/components/common/Accordion.tsx` around lines 9 - 14, The
AccordionItemProps interface allows both header and title properties to be
optional, which can result in an inaccessible toggle button with no label.
Update the AccordionItemProps type definition to require at least one of these
properties by creating a discriminated union type. Define two type variants: one
where header is required and title is optional, and another where title is
required and header is optional, then export the union of these two types as the
AccordionItemProps contract.
🤖 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]/engedelyek/components/PermissionsListClient.tsx:
- Around line 18-21: The hasRichText function in PermissionsListClient.tsx only
checks if the root has children nodes, but Lexical can have empty structural
nodes like empty paragraphs. Modify the hasRichText function to not only verify
that children exist but also validate that those children contain actual
meaningful content by recursively checking if any nodes have text content or
other substantive properties beyond just being structural placeholders. This
will prevent empty Lexical documents from being treated as having rich text
content.

In `@src/app/`(app)/[lang]/hirek/[slug]/components/RelatedNewsClient.tsx:
- Around line 61-63: The Link component in RelatedNewsClient.tsx constructs a
hardcoded href to `/hirek/${article.id}` which omits the language segment
required by the dynamic [lang] route. To fix this, retrieve the current language
parameter from the route context using useParams() hook and construct the href
to include the language segment as `/${lang}/hirek/${article.id}` to maintain
proper routing within the language-scoped route namespace.

In `@src/app/`(app)/[lang]/szocialis-osztondijak/page.tsx:
- Around line 129-141: The item.actionLink value from the CMS is being used
directly in the Link component's href attribute without validation, which could
expose users to malformed or non-HTTP(S) URLs. Create a validation function that
checks whether the URL starts with a valid protocol (http:// or https://), and
apply this validation to item.actionLink before rendering the Link component.
Only render the Link with the actionLink if validation passes, or conditionally
handle invalid URLs appropriately. Apply the same validation fix to both
occurrences mentioned: the Link component around line 135 and the similar Link
component around lines 200-207.
- Around line 185-191: The getSidebarDescription function call is tightly
coupled to the translated label text because it relies on substring matching
(looking for "műeper" and "eszb" in the label). Instead of passing the label
parameter to getSidebarDescription, pass the link object itself or an identifier
from the link. Then update the getSidebarDescription function to determine which
description to return based on the link data or identifier rather than checking
for substrings in the translated label. This decouples the description mapping
from label translations and prevents descriptions from silently disappearing if
labels are renamed.
- Around line 51-63: The SocialScholarshipPage component fetches live CMS data
through the getSocialScholarshipsFAQ() function but lacks an explicit cache
policy, which can cause stale content to be served after CMS updates. Add an
export statement before the SocialScholarshipPage function definition to set
dynamic rendering behavior to "force-dynamic", ensuring fresh data is always
fetched from the CMS rather than serving cached content. This matches the
pattern already implemented in the EHK scholarship page.

In `@src/collections/Permissions.ts`:
- Around line 100-108: The externalLink field in the Permissions.ts file
currently accepts arbitrary text without validation, but according to the
description it is used as an externally opened URL which poses a security risk.
Add validation to the externalLink field definition to validate the URL format
and protocol at the schema level, ensuring only safe protocols (such as http and
https) are accepted and rejecting malformed or unsafe URLs before they are
stored or rendered.

In `@src/components/common/FileCard.tsx`:
- Around line 46-48: The externalUrl validation at line 46 does not normalize
the input before checking its truthiness, allowing whitespace-only or invalid
URLs to be rendered as clickable links. Trim the externalUrl value before
checking if it exists, and use the trimmed value in the return statement that
constructs the link object. This ensures that whitespace-only strings are
treated as invalid and gatekeep such values to the disabled state within the
FileCard component itself rather than relying on call sites to validate.

In `@src/globals/SocialScholarshipsFAQ.ts`:
- Around line 80-87: The actionLink field and the sidebar url field in
SocialScholarshipsFAQ.ts are used as navigable external destinations but
currently accept unrestricted text input. Add schema-level validation to both
fields to ensure they only accept valid HTTP(S) URLs by implementing a URL
validation pattern or format constraint that blocks unsafe protocols like
javascript: and other malformed URLs. This validation should be applied at the
field definition level in the schema.

---

Nitpick comments:
In `@src/components/common/Accordion.tsx`:
- Around line 9-14: The AccordionItemProps interface allows both header and
title properties to be optional, which can result in an inaccessible toggle
button with no label. Update the AccordionItemProps type definition to require
at least one of these properties by creating a discriminated union type. Define
two type variants: one where header is required and title is optional, and
another where title is required and header is optional, then export the union of
these two types as the AccordionItemProps contract.
🪄 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: 02ccd703-2668-4f4f-b22f-e59abedb14bd

📥 Commits

Reviewing files that changed from the base of the PR and between 6844674 and 7bcceec.

📒 Files selected for processing (65)
  • src/app/(app)/[lang]/ehk-osztondij/page.tsx
  • src/app/(app)/[lang]/emlekeztetok/components/YearSection.tsx
  • src/app/(app)/[lang]/engedelyek/components/PermissionsListClient.tsx
  • src/app/(app)/[lang]/engedelyek/page.tsx
  • src/app/(app)/[lang]/hatarozatok-tara/components/DecisionsArchiveClient.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/BackNav.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/FileIcon.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/NewsDetail.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailClient.ts
  • src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailMain.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailSidebar.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/RelatedNewsClient.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/ShareButton.tsx
  • src/app/(app)/[lang]/hirek/[slug]/lib/news-utils.ts
  • src/app/(app)/[lang]/kepviselok/components/RepresentativeCard.tsx
  • src/app/(app)/[lang]/kepviselok/components/RepresentativeModal.tsx
  • src/app/(app)/[lang]/kepviselok/components/RepresentativesGrid.tsx
  • src/app/(app)/[lang]/kepviselok/components/RepresentativesGridClient.tsx
  • src/app/(app)/[lang]/kepviselok/components/representatives.helpers.ts
  • src/app/(app)/[lang]/kepviselok/components/skeletons/LoadingRepresentativeCard.tsx
  • src/app/(app)/[lang]/kepviselok/components/skeletons/LoadingRepresentativesGrid.tsx
  • src/app/(app)/[lang]/kepviselok/page.tsx
  • src/app/(app)/[lang]/kisokosok/components/HelpPageList.tsx
  • src/app/(app)/[lang]/kisokosok/components/skeletons/LoadingHelpPageGrid.tsx
  • src/app/(app)/[lang]/kisokosok/page.tsx
  • src/app/(app)/[lang]/nyelvoktatas/components/LanguageEducationContent.tsx
  • src/app/(app)/[lang]/nyelvoktatas/page.tsx
  • src/app/(app)/[lang]/oktatasi-szabalyzatok/loading.tsx
  • src/app/(app)/[lang]/oktatasi-szabalyzatok/page.tsx
  • src/app/(app)/[lang]/szocialis-osztondijak/page.tsx
  • src/app/(app)/[lang]/tanulmanyi-osztondij/components/StudyScholarshipContent.tsx
  • src/app/(app)/[lang]/tanulmanyi-osztondij/page.tsx
  • src/app/(app)/globals.css
  • src/collections/EhkScholarships.ts
  • src/collections/News.ts
  • src/collections/Permissions.ts
  • src/components/common/Accordion.tsx
  • src/components/common/FileCard.tsx
  • src/components/regulations/RegulationsListClient.tsx
  • src/components/regulations/skeletons/LoadingRegulationsGrid.tsx
  • src/dictionaries/en/language_education.json
  • src/dictionaries/en/news.json
  • src/dictionaries/en/permissions.json
  • src/dictionaries/en/regulations.json
  • src/dictionaries/en/scholarships.json
  • src/dictionaries/hu/language_education.json
  • src/dictionaries/hu/news.json
  • src/dictionaries/hu/permissions.json
  • src/dictionaries/hu/regulations.json
  • src/dictionaries/hu/scholarships.json
  • src/globals/AcademicScholarshipFAQ.ts
  • src/globals/SocialScholarshipsFAQ.ts
  • src/lib/payload-cms.ts
  • src/migrations/20260619_105937_add_news_representative_and_contact_email.json
  • src/migrations/20260619_105937_add_news_representative_and_contact_email.ts
  • src/migrations/20260619_141957_add_permissions_submission_and_external_link.json
  • src/migrations/20260619_141957_add_permissions_submission_and_external_link.ts
  • src/migrations/20260619_173736_academic_scholarship_faq.json
  • src/migrations/20260619_173736_academic_scholarship_faq.ts
  • src/migrations/20260620_123301.json
  • src/migrations/20260620_123301.ts
  • src/migrations/20260620_151905_ehk_scholarships.ts
  • src/migrations/index.ts
  • src/payload-types.ts
  • src/payload.config.ts
💤 Files with no reviewable changes (5)
  • src/app/(app)/[lang]/hirek/[slug]/components/BackNav.tsx
  • src/app/(app)/[lang]/hirek/[slug]/lib/news-utils.ts
  • src/app/(app)/[lang]/hirek/[slug]/components/NewsDetailClient.ts
  • src/app/(app)/[lang]/hirek/[slug]/components/FileIcon.tsx
  • src/app/(app)/[lang]/hirek/[slug]/components/ShareButton.tsx

Comment on lines +18 to +21
const hasRichText = (data: LexicalData): data is NonNullable<LexicalData> => {
const children = data?.root?.children;
return Array.isArray(children) && children.length > 0;
};

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 | ⚡ Quick win

hasRichText is too permissive for Lexical empty content.

Line 20 only checks node count, but empty Lexical roots can still have structural children (e.g., empty paragraph), so this can render a blank “submission process” block.

Proposed fix
 const hasRichText = (data: LexicalData): data is NonNullable<LexicalData> => {
   const children = data?.root?.children;
-  return Array.isArray(children) && children.length > 0;
+  if (!Array.isArray(children) || children.length === 0) return false;
+
+  const hasText = (nodes: unknown[]): boolean =>
+    nodes.some((node) => {
+      if (!node || typeof node !== "object") return false;
+      const n = node as { text?: unknown; children?: unknown[] };
+      if (typeof n.text === "string" && n.text.trim().length > 0) return true;
+      return Array.isArray(n.children) ? hasText(n.children) : false;
+    });
+
+  return hasText(children);
 };
🤖 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/components/PermissionsListClient.tsx around
lines 18 - 21, The hasRichText function in PermissionsListClient.tsx only checks
if the root has children nodes, but Lexical can have empty structural nodes like
empty paragraphs. Modify the hasRichText function to not only verify that
children exist but also validate that those children contain actual meaningful
content by recursively checking if any nodes have text content or other
substantive properties beyond just being structural placeholders. This will
prevent empty Lexical documents from being treated as having rich text content.

Comment on lines +61 to +63
<Link
href={`/hirek/${article.id}`}
className="inline-flex items-center justify-between gap-4 text-xs font-bold text-[#862633]"

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 | ⚡ Quick win

Keep related-news links inside the current [lang] route namespace.

Line 62 builds an absolute /hirek/... URL, which drops the language segment expected by the surrounding route tree (.../[lang]/hirek/[slug]) and can send users to the wrong route/404.

Proposed fix
-              <Link
-                href={`/hirek/${article.id}`}
+              <Link
+                href={`../${article.id}`}
                 className="inline-flex items-center justify-between gap-4 text-xs font-bold text-[`#862633`]"
               >
📝 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
<Link
href={`/hirek/${article.id}`}
className="inline-flex items-center justify-between gap-4 text-xs font-bold text-[#862633]"
<Link
href={`../${article.id}`}
className="inline-flex items-center justify-between gap-4 text-xs font-bold text-[`#862633`]"
🤖 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/RelatedNewsClient.tsx around
lines 61 - 63, The Link component in RelatedNewsClient.tsx constructs a
hardcoded href to `/hirek/${article.id}` which omits the language segment
required by the dynamic [lang] route. To fix this, retrieve the current language
parameter from the route context using useParams() hook and construct the href
to include the language segment as `/${lang}/hirek/${article.id}` to maintain
proper routing within the language-scoped route namespace.

Comment on lines 51 to +63
export default async function SocialScholarshipPage({
params }: { params: Promise<{ lang: Locale }> }) {
params,
}: {
params: Promise<{ lang: Locale }>;
}) {
const { lang } = await params;
const dictionary = await getDictionary(lang, 'scholarships');
const validLang = i18n.locales.includes(lang) ? lang : i18n.defaultLocale;
const isEn = validLang === "en";
const dictionary = await getDictionary(validLang, "scholarships");
const social = dictionary.scholarships.social;
const { scholarshipTypes, sidebarLinks } = await getSocialScholarshipsFAQ();
const items = scholarshipTypes ?? [];
const links = sidebarLinks ?? [];

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

targets=(
  "src/app/(app)/[lang]/ehk-osztondij/page.tsx"
  "src/app/(app)/[lang]/szocialis-osztondijak/page.tsx"
  "src/app/(app)/[lang]/tanulmanyi-osztondij/page.tsx"
)

for f in "${targets[@]}"; do
  echo "== $f =="
  sed -n '1,50p' "$f" | rg -n 'export const (dynamic|revalidate)\b' || echo "(no explicit cache policy)"
done

Repository: kir-dev/ehk

Length of output: 316


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check getSocialScholarshipsFAQ implementation
echo "=== Searching for getSocialScholarshipsFAQ function ==="
rg -n "getSocialScholarshipsFAQ" --type tsx --type ts

echo ""
echo "=== Full content of ehk-osztondij/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/ehk-osztondij/page.tsx"

echo ""
echo "=== Full content of szocialis-osztondijak/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/szocialis-osztondijak/page.tsx"

echo ""
echo "=== Full content of tanulmanyi-osztondij/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/tanulmanyi-osztondij/page.tsx"

Repository: kir-dev/ehk

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for getSocialScholarshipsFAQ without file type filter
echo "=== Searching for getSocialScholarshipsFAQ function ==="
rg "getSocialScholarshipsFAQ" -n

echo ""
echo "=== Full content of ehk-osztondij/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/ehk-osztondij/page.tsx"

echo ""
echo "=== Full content of szocialis-osztondijak/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/szocialis-osztondijak/page.tsx"

echo ""
echo "=== Full content of tanulmanyi-osztondij/page.tsx (first 30 lines) ==="
head -30 "src/app/(app)/[lang]/tanulmanyi-osztondij/page.tsx"

Repository: kir-dev/ehk

Length of output: 4231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the getSocialScholarshipsFAQ function implementation
echo "=== getSocialScholarshipsFAQ implementation ==="
sed -n '242,280p' "src/lib/payload-cms.ts"

echo ""
echo "=== Check for other CMS fetch functions in same file ==="
rg "export async function get" "src/lib/payload-cms.ts" -n

echo ""
echo "=== Check if any other scholarship page uses revalidate ==="
rg "export const (dynamic|revalidate)" "src/app/(app)/[lang]" -n

Repository: kir-dev/ehk

Length of output: 1954


Add explicit cache policy for live CMS data on this route.

This page fetches live CMS data via getSocialScholarshipsFAQ() (line 61) but lacks an explicit cache policy, unlike the EHK scholarship page which uses export const dynamic = "force-dynamic". Without this, stale content may be served after CMS updates.

Suggested fix
+export const dynamic = "force-dynamic";
+
 export default async function SocialScholarshipPage({
📝 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
export default async function SocialScholarshipPage({
params }: { params: Promise<{ lang: Locale }> }) {
params,
}: {
params: Promise<{ lang: Locale }>;
}) {
const { lang } = await params;
const dictionary = await getDictionary(lang, 'scholarships');
const validLang = i18n.locales.includes(lang) ? lang : i18n.defaultLocale;
const isEn = validLang === "en";
const dictionary = await getDictionary(validLang, "scholarships");
const social = dictionary.scholarships.social;
const { scholarshipTypes, sidebarLinks } = await getSocialScholarshipsFAQ();
const items = scholarshipTypes ?? [];
const links = sidebarLinks ?? [];
export const dynamic = "force-dynamic";
export default async function SocialScholarshipPage({
params,
}: {
params: Promise<{ lang: Locale }>;
}) {
const { lang } = await params;
const validLang = i18n.locales.includes(lang) ? lang : i18n.defaultLocale;
const isEn = validLang === "en";
const dictionary = await getDictionary(validLang, "scholarships");
const social = dictionary.scholarships.social;
const { scholarshipTypes, sidebarLinks } = await getSocialScholarshipsFAQ();
const items = scholarshipTypes ?? [];
const links = sidebarLinks ?? [];
🤖 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]/szocialis-osztondijak/page.tsx around lines 51 - 63,
The SocialScholarshipPage component fetches live CMS data through the
getSocialScholarshipsFAQ() function but lacks an explicit cache policy, which
can cause stale content to be served after CMS updates. Add an export statement
before the SocialScholarshipPage function definition to set dynamic rendering
behavior to "force-dynamic", ensuring fresh data is always fetched from the CMS
rather than serving cached content. This matches the pattern already implemented
in the EHK scholarship page.

Comment on lines +129 to +141
{item.actionLink && (
<Button
asChild
className="min-h-9 h-auto w-full justify-between whitespace-normal rounded-full bg-[#862633] px-4 py-2 text-left text-[12px] font-bold uppercase leading-tight tracking-normal text-white hover:bg-[#9e2d3e] md:w-auto md:justify-center"
>
<Link
href={item.actionLink}
target="_blank"
rel="noopener noreferrer"
>
<span>{actionLabel}</span>
<ExternalLink className="size-4" />
</Link>

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 | ⚡ Quick win

Validate CMS-provided URLs before rendering external links.

Lines 135 and 204 use raw CMS URL strings as href. Without protocol validation, malformed or non-HTTP(S) links can be published and exposed to users.

🔒 Suggested fix
+const getSafeExternalHref = (value?: string | null) => {
+  if (!value) return null;
+  try {
+    const parsed = new URL(value);
+    return parsed.protocol === "http:" || parsed.protocol === "https:"
+      ? value
+      : null;
+  } catch {
+    return null;
+  }
+};
+
 function ScholarshipCard({
@@
 }) {
+  const actionHref = getSafeExternalHref(item.actionLink);
@@
-        {item.actionLink && (
+        {actionHref && (
@@
-            <Link
-              href={item.actionLink}
+            <Link
+              href={actionHref}
               target="_blank"
               rel="noopener noreferrer"
             >
@@
 function ImportantLinksSidebar({
@@
         {links.map((link, index) => {
+          const linkHref = getSafeExternalHref(link.url);
           const label = getLocalizedTitle(link, isEn);
@@
-              <Button
+              {linkHref && (
+              <Button
                 asChild
                 className="h-auto min-h-9 whitespace-normal rounded-full bg-[`#862633`] px-4 py-2 text-xs font-bold uppercase text-white hover:bg-[`#9e2d3e`]"
               >
-                <Link href={link.url} target="_blank" rel="noopener noreferrer">
+                <Link href={linkHref} target="_blank" rel="noopener noreferrer">
                   <span>{label}</span>
                   <ExternalLink className="size-4" />
                 </Link>
               </Button>
+              )}

Also applies to: 200-207

🤖 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]/szocialis-osztondijak/page.tsx around lines 129 - 141,
The item.actionLink value from the CMS is being used directly in the Link
component's href attribute without validation, which could expose users to
malformed or non-HTTP(S) URLs. Create a validation function that checks whether
the URL starts with a valid protocol (http:// or https://), and apply this
validation to item.actionLink before rendering the Link component. Only render
the Link with the actionLink if validation passes, or conditionally handle
invalid URLs appropriately. Apply the same validation fix to both occurrences
mentioned: the Link component around line 135 and the similar Link component
around lines 200-207.

Comment on lines +185 to +191
{links.map((link, index) => {
const label = getLocalizedTitle(link, isEn);
const description = getSidebarDescription(
label,
mueperDescription,
eszbDescription,
);

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 | ⚡ Quick win

Avoid coupling sidebar descriptions to translated label text.

getSidebarDescription keys off label substrings ("műeper", "eszb"). If editors rename labels (especially in English), descriptions silently disappear.

🛠️ Suggested fix
-          const description = getSidebarDescription(
-            label,
+          const description = getSidebarDescription(
+            link.url,
             mueperDescription,
             eszbDescription,
           );
@@
 function getSidebarDescription(
-  label: string,
+  url: string,
   mueperDescription: string,
   eszbDescription: string,
 ) {
-  const normalized = label.toLocaleLowerCase("hu-HU");
+  const normalized = url.toLocaleLowerCase("hu-HU");

Also applies to: 217-233

🤖 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]/szocialis-osztondijak/page.tsx around lines 185 - 191,
The getSidebarDescription function call is tightly coupled to the translated
label text because it relies on substring matching (looking for "műeper" and
"eszb" in the label). Instead of passing the label parameter to
getSidebarDescription, pass the link object itself or an identifier from the
link. Then update the getSidebarDescription function to determine which
description to return based on the link data or identifier rather than checking
for substrings in the translated label. This decouples the description mapping
from label translations and prevents descriptions from silently disappearing if
labels are renamed.

Comment on lines +100 to +108
{
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.",
},
},

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 | ⚡ Quick win

Add protocol validation for externalLink before storing/rendering it.

Line 101 currently accepts arbitrary text, but Line 106 indicates it is used as an externally opened URL. This should reject unsafe protocols and malformed values at schema level.

Suggested fix
         {
             name: "externalLink",
             type: "text",
             required: false,
             label: "Külső link (űrlap URL)",
+            validate: (value: string | null | undefined) => {
+                if (!value?.trim()) return true;
+                try {
+                    const parsed = new URL(value);
+                    return parsed.protocol === "http:" || parsed.protocol === "https:"
+                        ? true
+                        : "Csak http/https URL engedélyezett.";
+                } catch {
+                    return "Érvénytelen 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.",
             },
         },
📝 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
{
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.",
},
},
{
name: "externalLink",
type: "text",
required: false,
label: "Külső link (űrlap URL)",
validate: (value: string | null | undefined) => {
if (!value?.trim()) return true;
try {
const parsed = new URL(value);
return parsed.protocol === "http:" || parsed.protocol === "https:"
? true
: "Csak http/https URL engedélyezett.";
} catch {
return "Érvénytelen 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.",
},
},
🤖 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.ts file currently accepts arbitrary text without
validation, but according to the description it is used as an externally opened
URL which poses a security risk. Add validation to the externalLink field
definition to validate the URL format and protocol at the schema level, ensuring
only safe protocols (such as http and https) are accepted and rejecting
malformed or unsafe URLs before they are stored or rendered.

Comment on lines +46 to +48
if (externalUrl) {
return { url: externalUrl, ext: extensionLabel || "link", filesize: null };
}

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 | ⚡ Quick win

Normalize externalUrl before treating it as actionable.

Line 46 enables link mode for any truthy string, so whitespace/invalid URLs can still render as clickable at Line 151. Normalize with trim() and gate invalid values to the disabled state in this component (not only at call sites).

Proposed fix
-    if (externalUrl) {
-      return { url: externalUrl, ext: extensionLabel || "link", filesize: null };
-    }
+    const normalizedExternalUrl = externalUrl?.trim();
+    if (normalizedExternalUrl) {
+      return {
+        url: normalizedExternalUrl,
+        ext: extensionLabel || "link",
+        filesize: null,
+      };
+    }

Also applies to: 149-153

🤖 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/components/common/FileCard.tsx` around lines 46 - 48, The externalUrl
validation at line 46 does not normalize the input before checking its
truthiness, allowing whitespace-only or invalid URLs to be rendered as clickable
links. Trim the externalUrl value before checking if it exists, and use the
trimmed value in the return statement that constructs the link object. This
ensures that whitespace-only strings are treated as invalid and gatekeep such
values to the disabled state within the FileCard component itself rather than
relying on call sites to validate.

Comment on lines +80 to +87
name: "actionLink",
type: "text",
required: true,
label: "ESZB gomb URL",
admin: {
placeholder: "https://...",
},
},

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 | ⚡ Quick win

Validate actionLink and sidebar url values as safe HTTP(S) URLs.

Line 80 and Line 115 are used as navigable external destinations but currently accept unrestricted text. Add schema-level validation to block unsafe protocols and malformed URLs.

Suggested fix
+const validateHttpUrl = (value: string | null | undefined) => {
+  if (!value?.trim()) return true;
+  try {
+    const parsed = new URL(value);
+    return parsed.protocol === "http:" || parsed.protocol === "https:"
+      ? true
+      : "Csak http/https URL engedélyezett.";
+  } catch {
+    return "Érvénytelen URL.";
+  }
+};

 export const SocialScholarshipsFAQ: GlobalConfig = {
   slug: "social-scholarships-faq",
@@
         {
           name: "actionLink",
           type: "text",
           required: true,
           label: "ESZB gomb URL",
+          validate: validateHttpUrl,
           admin: {
             placeholder: "https://...",
           },
         },
@@
         {
           name: "url",
           type: "text",
           required: true,
           label: "URL",
+          validate: validateHttpUrl,
           admin: {
             placeholder: "https://...",
           },
         },

Also applies to: 115-122

🤖 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/globals/SocialScholarshipsFAQ.ts` around lines 80 - 87, The actionLink
field and the sidebar url field in SocialScholarshipsFAQ.ts are used as
navigable external destinations but currently accept unrestricted text input.
Add schema-level validation to both fields to ensure they only accept valid
HTTP(S) URLs by implementing a URL validation pattern or format constraint that
blocks unsafe protocols like javascript: and other malformed URLs. This
validation should be applied at the field definition level in the schema.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant