124#161
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
…nt structure and styling
- 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR introduces a shared ChangesMulti-page redesign and CMS integration
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 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: 8
🧹 Nitpick comments (1)
src/components/common/Accordion.tsx (1)
9-14: ⚡ Quick winRequire an accessible trigger label at the prop contract.
Line 9-Line 14 allow both
headerandtitleto 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
📒 Files selected for processing (65)
src/app/(app)/[lang]/ehk-osztondij/page.tsxsrc/app/(app)/[lang]/emlekeztetok/components/YearSection.tsxsrc/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]/kisokosok/components/HelpPageList.tsxsrc/app/(app)/[lang]/kisokosok/components/skeletons/LoadingHelpPageGrid.tsxsrc/app/(app)/[lang]/kisokosok/page.tsxsrc/app/(app)/[lang]/nyelvoktatas/components/LanguageEducationContent.tsxsrc/app/(app)/[lang]/nyelvoktatas/page.tsxsrc/app/(app)/[lang]/oktatasi-szabalyzatok/loading.tsxsrc/app/(app)/[lang]/oktatasi-szabalyzatok/page.tsxsrc/app/(app)/[lang]/szocialis-osztondijak/page.tsxsrc/app/(app)/[lang]/tanulmanyi-osztondij/components/StudyScholarshipContent.tsxsrc/app/(app)/[lang]/tanulmanyi-osztondij/page.tsxsrc/app/(app)/globals.csssrc/collections/EhkScholarships.tssrc/collections/News.tssrc/collections/Permissions.tssrc/components/common/Accordion.tsxsrc/components/common/FileCard.tsxsrc/components/regulations/RegulationsListClient.tsxsrc/components/regulations/skeletons/LoadingRegulationsGrid.tsxsrc/dictionaries/en/language_education.jsonsrc/dictionaries/en/news.jsonsrc/dictionaries/en/permissions.jsonsrc/dictionaries/en/regulations.jsonsrc/dictionaries/en/scholarships.jsonsrc/dictionaries/hu/language_education.jsonsrc/dictionaries/hu/news.jsonsrc/dictionaries/hu/permissions.jsonsrc/dictionaries/hu/regulations.jsonsrc/dictionaries/hu/scholarships.jsonsrc/globals/AcademicScholarshipFAQ.tssrc/globals/SocialScholarshipsFAQ.tssrc/lib/payload-cms.tssrc/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/20260619_173736_academic_scholarship_faq.jsonsrc/migrations/20260619_173736_academic_scholarship_faq.tssrc/migrations/20260620_123301.jsonsrc/migrations/20260620_123301.tssrc/migrations/20260620_151905_ehk_scholarships.tssrc/migrations/index.tssrc/payload-types.tssrc/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
| const hasRichText = (data: LexicalData): data is NonNullable<LexicalData> => { | ||
| const children = data?.root?.children; | ||
| return Array.isArray(children) && children.length > 0; | ||
| }; |
There was a problem hiding this comment.
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.
| <Link | ||
| href={`/hirek/${article.id}`} | ||
| className="inline-flex items-center justify-between gap-4 text-xs font-bold text-[#862633]" |
There was a problem hiding this comment.
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.
| <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.
| 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 ?? []; |
There was a problem hiding this comment.
🧩 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)"
doneRepository: 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]" -nRepository: 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.
| 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.
| {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> |
There was a problem hiding this comment.
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.
| {links.map((link, index) => { | ||
| const label = getLocalizedTitle(link, isEn); | ||
| const description = getSidebarDescription( | ||
| label, | ||
| mueperDescription, | ||
| eszbDescription, | ||
| ); |
There was a problem hiding this comment.
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.
| { | ||
| 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.
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.
| { | |
| 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.
| if (externalUrl) { | ||
| return { url: externalUrl, ext: extensionLabel || "link", filesize: null }; | ||
| } |
There was a problem hiding this comment.
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.
| name: "actionLink", | ||
| type: "text", | ||
| required: true, | ||
| label: "ESZB gomb URL", | ||
| admin: { | ||
| placeholder: "https://...", | ||
| }, | ||
| }, |
There was a problem hiding this comment.
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.
closes #123 #124
Summary by CodeRabbit
New Features
Bug Fixes & Improvements
Style