Skip to content

Services page#4104

Open
ncarazon wants to merge 7 commits intomainfrom
feat/services-page
Open

Services page#4104
ncarazon wants to merge 7 commits intomainfrom
feat/services-page

Conversation

@ncarazon
Copy link
Contributor

@ncarazon ncarazon commented Jan 19, 2026

This PR updates Services page.

Added new workshop block:

image

Case study section with cards:

image

Stacked images animation:

stacked_images.mov

Updated Private instances mock device pictures:

image

Redesigned "Get in touch" form:

image

Summary by CodeRabbit

  • New Features

    • Case studies section with partner logos, report previews and layered preview image effect
    • New contact section with intro panel, modernized contact form, section headings and expanded services page layouts
  • Localization

    • Added Czech, English, Spanish, Portuguese, Simplified Chinese and Traditional Chinese strings for new UI text
  • Refactor

    • Replaced legacy contact form/checkbox with new contact components and updated services pages integration

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Adds localized strings across multiple languages; introduces case-study types, sample data, and UI (CaseStudyCard, StackedPreviewImage); replaces legacy get-in-touch form with a new ContactSection (ContactIntro + ContactForm); adds SectionHeading; updates service templates and imports accordingly.

Changes

Cohort / File(s) Summary
Localization additions
front_end/messages/cs.json, front_end/messages/en.json, front_end/messages/es.json, front_end/messages/pt.json, front_end/messages/zh-TW.json, front_end/messages/zh.json
Added 13 new translation keys (case studies, initiative/workshop text, report labels, pagination, contact form placeholders) across languages.
Case studies types & data
front_end/src/app/(main)/services/components/case_studies/types.ts, front_end/src/app/(main)/services/components/case_studies/constants.tsx
New TS types (TCaseStudyCard, CaseStudyReportCard, CaseStudyPartnerLogo) and exported CASE_STUDIES sample array with two entries and asset imports.
Case study UI
front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx
New CaseStudyCard component rendering title, intro, bullets, optional initiative, partner logos, report preview (stacked/inline), and CTA.
Contact section (new)
front_end/src/app/(main)/services/components/contact_section/contact_form.tsx, .../contact_intro.tsx, .../contact_section.tsx
New ContactForm (react-hook-form + zod) with ServiceOption type, ContactIntro aside, and ContactSection composing both; submits via submitGetInTouchForm, fires analytics, supports preselectedService and serviceOptions.
Removed legacy contact code
front_end/src/app/(main)/services/components/get_in_touch_checkbox.tsx (deleted), front_end/src/app/(main)/services/components/get_in_touch_form.tsx (deleted)
Removed older checkbox and monolithic get-in-touch form; functionality migrated to contact_section/*.
Reusable UI components
front_end/src/app/(main)/services/components/section_heading.tsx, front_end/src/components/ui/stacked-preview-image.tsx
Added SectionHeading (title/subtitle/alignment) and StackedPreviewImage (3-layer parallax preview image).
Templates — contact integration & layout changes
front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx, .../pro_forecasters_page_template.tsx, .../tournaments_page_template.tsx, .../services_page_template.tsx
Replaced GetInTouchForm with ContactSection, changed prop from preselectedServices: ServiceType[]preselectedService: ServiceType, adjusted imports and local layout; services page now supports optional workshop and caseStudies props and renders case studies grid.
Page wiring
front_end/src/app/(main)/services/page.tsx
Imports CASE_STUDIES and passes caseStudies and workshop props into ServicesPageTemplate.
Import path updates
front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx
Updated ServiceOption import path to ../../../components/contact_section/contact_form.
Light/dark icon tweak
front_end/src/app/(main)/aib/components/aib/light-dark-icon.tsx
Added `variant?: "icon"

Sequence Diagram(s)

sequenceDiagram
  participant U as User (Browser)
  participant CF as ContactForm (Client)
  participant A as Analytics
  participant API as submitGetInTouchForm (Client API)
  participant S as Server (API)

  U->>CF: Fill fields & submit
  CF->>A: fire analytics event (form_submit_attempt) rgba(0,128,0,0.5)
  CF->>API: POST form payload
  API->>S: process request (validate, persist, notify) rgba(0,0,255,0.5)
  S-->>API: 200 OK / error
  API-->>CF: response (success/failure)
  CF->>A: fire analytics event (form_submit_result) rgba(128,0,128,0.5)
  CF-->>U: show success or error state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • elisescu
  • cemreinanc
  • hlbmtc

Poem

🐰 I hopped through keys and stitched new scenes,
Cards and previews layered in greens,
Forms reborn, translations sing,
Contact paths and logos spring,
A little rabbit cheers these changes!

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Services page' is vague and generic, providing minimal information about the substantial changes made across multiple files, components, and localization strings. Consider using a more descriptive title that highlights the main changes, such as 'Add case studies, workshop section, and redesigned contact form to services page' or 'Services page: case studies, workshop, and contact form redesign'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 21, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4104-feat-services-page-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:feat-services-page-1129c5f
🗄️ PostgreSQL NeonDB branch preview/pr-4104-feat-services-page
Redis Fly Redis mtc-redis-pr-4104-feat-services-page

Details

  • Commit: 1129c5fad1352fe77415c5ce9be5414d2be76c2c
  • Branch: feat/services-page
  • Fly App: metaculus-pr-4104-feat-services-page

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

@ncarazon ncarazon marked this pull request as ready for review January 26, 2026 15:13
@ncarazon ncarazon marked this pull request as draft January 26, 2026 15:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In
`@front_end/src/app/`(main)/services/components/case_studies/case_study_card.tsx:
- Around line 50-61: The two <hr> separators render even when
card.aboutInitiative is falsy, producing orphaned or duplicate rules; fix by
conditionally rendering the separator(s) together with the aboutInitiative
block: move or wrap the first <hr> inside the conditional (the block referencing
card.aboutInitiative) or render the trailing <hr> only when card.aboutInitiative
exists so that an <hr> appears only when the aboutInitiative section is present;
update the JSX in case_study_card.tsx around the card.aboutInitiative
conditional accordingly.

In `@front_end/src/app/`(main)/services/components/case_studies/constants.tsx:
- Around line 22-56: The aboutInitiative field in the case study object with id
"nih-nsf-pro-forecasting-2" currently contains placeholder copy; replace that
string with the final initiative blurb or remove the aboutInitiative property
from the object until final copy is ready to avoid shipping placeholder text
(locate the object entry where id === "nih-nsf-pro-forecasting-2" in
constants.tsx and update the aboutInitiative key accordingly).

In
`@front_end/src/app/`(main)/services/components/contact_section/contact_form.tsx:
- Around line 82-141: The default service value set in ContactForm's useForm
(defaultValues.service) can be outside the passed serviceOptions list; update
the initialization so defaultValues.service is derived from serviceOptions (or
validate preselectedService is included) — e.g., if preselectedService is
provided use it only if serviceOptions.some(o => o.value ===
preselectedService), otherwise fall back to the first serviceOptions[0].value
(or ServiceType.GENERAL_INQUIRY only if present in serviceOptions); update the
logic around defaultValues in useForm and ensure selectOptions generation still
maps serviceOptions so the rendered select always contains the form's initial
value.
- Around line 55-58: Update the contactFormSchema to trim input and enforce a
proper email format: for the email field in contactFormSchema, chain
.trim().min(1, { message: "Email is required" }).email({ message: "Invalid email
address" }) so whitespace is normalized and invalid formats are rejected;
likewise apply .trim().min(1, { message: "Name is required" }) to the name field
and use .trim().optional() for organization to keep consistent normalization
across the schema.

In
`@front_end/src/app/`(main)/services/components/contact_section/contact_intro.tsx:
- Around line 45-50: The anchor in the ContactIntro component rendering (the <a>
created with key={p.alt}, href={p.href}, target="_blank") currently uses
rel="noreferrer" only; update that rel attribute to include "noopener" (e.g.,
"noreferrer noopener") to prevent reverse-tabnabbing when opening external links
in a new tab. Locate the anchor element in contact_intro.tsx where key={p.alt}
and target="_blank" are used and modify the rel string accordingly.

In `@front_end/src/components/ui/stacked-preview-image.tsx`:
- Around line 6-112: The Image elements in StackedPreviewImage accept src typed
as ImageProps["src"] (allowing string URLs) but only set height, which breaks
Next.js unless images are static imports; either restrict the Props to static
imports by changing src: ImageProps["src"] to StaticImageData and layerSrcs
tuple to StaticImageData, or keep URL support and add explicit width handling to
each Image (back/mid/front Image instances) — e.g., set width={200} to match
height or switch those Image usages to fill with appropriate parent sizing;
update the Props/type and the three Image components (backSrc, midSrc, frontSrc)
accordingly.
🧹 Nitpick comments (8)
front_end/src/app/(main)/services/components/contact_section/contact_section.tsx (1)

6-11: Prop type design creates potential confusion with dual input paths.

The Props type allows the same properties (pageLabel, preselectedService, serviceOptions) to be passed both at the top level and via formProps. While the merging logic on lines 39-43 handles precedence correctly (preferring formProps), this dual-path API can be confusing for consumers.

Consider either:

  1. Documenting the precedence clearly with JSDoc
  2. Simplifying to only accept these props via formProps
front_end/src/app/(main)/services/components/case_studies/constants.tsx (1)

25-61: Consider locale-friendly date handling for report metadata.
publishedAtLabel is hard-coded in English, so non‑EN locales will still show English dates. Consider storing an ISO date and formatting it in the UI with the active locale (e.g., via Intl.DateTimeFormat or next‑intl utilities).

front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx (3)

42-46: Consider using a more stable key for list items.

Using ${item}-${idx} as a key can be problematic when item is a React.ReactNode (which could be an object). This will result in [object Object]-0 keys. If item is always a string, this is fine, but the type definition allows ReactNode.

Suggested improvement

If the bullets are guaranteed to be unique strings, use just the item. Otherwise, consider using only the index or restructuring the data to include explicit keys:

 <ul className="m-0 list-disc pl-5">
   {card.body.bullets.map((item, idx) => (
-    <li key={`${item}-${idx}`}>{item}</li>
+    <li key={idx}>{item}</li>
   ))}
 </ul>

71-78: Missing width prop on Image component may cause layout shift.

The Image component has height={14} but no width prop. While w-auto is applied via className, Next.js Image component benefits from explicit dimensions to prevent layout shift. Consider adding a width prop or using the fill layout.

Suggested fix
 <Image
   key={logo.src + idx}
   src={logo.src}
   alt={logo.alt}
   height={14}
+  width={0}
   className="h-[14px] w-auto"
   unoptimized
 />

Using width={0} with w-auto class allows the image to size naturally while satisfying the prop requirement.


102-108: Missing width prop on mobile report preview Image.

Similar to the partner logos, this Image has height={50} but no width prop.

Suggested fix
 <Image
   src={card.report.previewImageSrc}
   alt={card.report.previewImageAlt ?? t("reportPreviewAlt")}
   height={50}
+  width={0}
   className="h-[50px] w-auto rounded object-cover sm:hidden"
   unoptimized
 />
front_end/src/app/(main)/services/components/case_studies/types.ts (1)

3-7: href property defined but not used in CaseStudyCard.

The CaseStudyPartnerLogo type includes an optional href property, but the CaseStudyCard component renders partner logos as plain images without wrapping them in links. Either remove the unused property or implement the linking functionality.

If the href is intended for future use, consider adding a TODO comment. Otherwise, if partner logos should be clickable:

// In case_study_card.tsx, wrap the Image in a link when href is present:
{logo.href ? (
  <a href={logo.href} target="_blank" rel="noopener noreferrer">
    <Image ... />
  </a>
) : (
  <Image ... />
)}
front_end/src/app/(main)/services/components/templates/services_page_template.tsx (2)

166-209: Empty secondPart renders an empty paragraph element.

When proForecasters.secondPart is an empty string (as shown in the config snippet), an empty <p> element is still rendered with margins (mt-5), potentially causing unintended spacing.

Suggested fix
             <p className="m-0 mt-5 text-center text-sm font-normal text-blue-500 sm:text-start sm:text-lg sm:font-medium">
               {proForecasters.firstPart}
             </p>
-            <p className="m-0 mt-5 text-center text-sm font-normal text-blue-500 sm:text-start sm:text-lg sm:font-medium">
-              {proForecasters.secondPart}
-            </p>
+            {proForecasters.secondPart && (
+              <p className="m-0 mt-5 text-center text-sm font-normal text-blue-500 sm:text-start sm:text-lg sm:font-medium">
+                {proForecasters.secondPart}
+              </p>
+            )}

224-232: Duplicate WorkshopImage rendering for responsive display.

The same WorkshopImage is rendered twice—once for mobile/tablet (lines 226-231, hidden on lg:) and once for desktop (lines 244-249, shown only on lg:). While this achieves the layout goal, consider using CSS-only responsive approaches or extracting to a shared element to avoid duplication.

This is acceptable for achieving different layout positions, but if the image markup grows more complex, consider extracting to a variable:

const workshopImageElement = (
  <Image
    src={WorkshopImage}
    alt="Workshop illustration"
    unoptimized
    className="h-auto w-full max-w-[420px]"
  />
);

// Then use {workshopImageElement} in both places

Also applies to: 243-250

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb9e1e and c787d1a.

⛔ Files ignored due to path filters (5)
  • front_end/src/app/(main)/services/assets/aei.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/coefficient-giving.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/forbes.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/nih-nsf-report-preview.png is excluded by !**/*.png
  • front_end/src/app/(main)/services/assets/workshop.svg is excluded by !**/*.svg
📒 Files selected for processing (24)
  • front_end/messages/cs.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/pt.json
  • front_end/messages/zh-TW.json
  • front_end/messages/zh.json
  • front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx
  • front_end/src/app/(main)/services/assets/desktop.webp
  • front_end/src/app/(main)/services/assets/phone.webp
  • front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx
  • front_end/src/app/(main)/services/components/case_studies/constants.tsx
  • front_end/src/app/(main)/services/components/case_studies/types.ts
  • front_end/src/app/(main)/services/components/contact_section/contact_form.tsx
  • front_end/src/app/(main)/services/components/contact_section/contact_intro.tsx
  • front_end/src/app/(main)/services/components/contact_section/contact_section.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_checkbox.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_form.tsx
  • front_end/src/app/(main)/services/components/section_heading.tsx
  • front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/pro_forecasters_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/services_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx
  • front_end/src/app/(main)/services/page.tsx
  • front_end/src/components/ui/stacked-preview-image.tsx
💤 Files with no reviewable changes (2)
  • front_end/src/app/(main)/services/components/get_in_touch_form.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_checkbox.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
front_end/src/app/(main)/services/components/case_studies/constants.tsx (1)
front_end/src/app/(main)/services/components/case_studies/types.ts (1)
  • TCaseStudyCard (17-35)
front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx (2)
front_end/src/app/(main)/services/components/case_studies/types.ts (1)
  • TCaseStudyCard (17-35)
front_end/src/components/ui/stacked-preview-image.tsx (1)
  • StackedPreviewImage (24-116)
front_end/src/app/(main)/services/components/templates/services_page_template.tsx (1)
front_end/src/app/(main)/services/(pages)/climate/config.ts (4)
  • solutions (13-17)
  • tournaments (19-23)
  • privateInstances (25-29)
  • proForecasters (31-36)
front_end/src/app/(main)/services/components/contact_section/contact_form.tsx (6)
front_end/src/constants/services.ts (1)
  • ServiceType (1-14)
front_end/src/services/api/misc/misc.shared.ts (1)
  • ContactForm (4-8)
front_end/src/utils/analytics.ts (1)
  • sendAnalyticsEvent (11-17)
front_end/src/utils/core/errors.ts (1)
  • logError (117-151)
front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx (1)
  • serviceOptions (56-85)
front_end/src/components/ui/form_field.tsx (3)
  • Input (111-128)
  • FormErrorMessage (82-109)
  • Textarea (167-181)
front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx (2)
front_end/src/app/(main)/services/(pages)/financial-services/config.ts (1)
  • tournaments (19-23)
front_end/src/constants/services.ts (1)
  • ServiceType (1-14)
🔇 Additional comments (15)
front_end/src/app/(main)/services/components/contact_section/contact_section.tsx (1)

22-47: LGTM!

The component structure is clean with proper composition of ContactIntro and ContactForm. The layout uses appropriate responsive classes and the prop delegation logic correctly handles the merge precedence.

front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx (1)

43-109: LGTM!

The migration from GetInTouchForm to ContactSection is well-executed. The fragment wrapper appropriately allows ContactSection to be a sibling of main while maintaining semantic structure. The preselectedService correctly uses ServiceType.RUNNING_TOURNAMENT and the step rendering with conditional arrows is properly implemented.

front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx (1)

97-103: LGTM!

The ContactSection integration follows the same pattern as the tournaments template, with appropriate pageLabel and preselectedService values for private instances. The component structure is clean and consistent.

front_end/messages/es.json (1)

1892-1904: LGTM!

The new Spanish localization keys are properly formatted with correct ICU message syntax for plurals and interpolation. The translations align with the UI components introduced in this PR (case studies, workshops, contact form).

front_end/messages/zh-TW.json (1)

1889-1901: LGTM!

The Traditional Chinese localization keys are properly added with correct ICU message syntax. The translations are appropriate for Taiwan Chinese conventions and align with the parallel additions in other locale files.

front_end/messages/cs.json (1)

1892-1904: Czech localization additions look consistent.
Thanks for mirroring the new services/case study/workshop strings across locales.

front_end/messages/en.json (1)

1885-1897: English localization additions look good.
The new strings align with the services page updates.

front_end/src/app/(main)/services/page.tsx (1)

80-89: Wiring for workshop and case studies looks good.
Props align cleanly with the new template sections.

front_end/src/app/(main)/services/components/templates/pro_forecasters_page_template.tsx (1)

46-114: Contact section integration and layout updates look solid.
The new ContactSection flow and the revised content layout read cleanly.

front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx (1)

134-139: LGTM!

The Button component correctly handles the CTA with proper fallback for the label using translations. The styling is comprehensive with appropriate dark mode support.

front_end/src/app/(main)/services/components/case_studies/types.ts (1)

9-35: LGTM!

The type definitions are well-structured with appropriate use of optional fields and React.ReactNode for flexible content. The use of StaticImageData from Next.js for previewImageSrc is correct for supporting both static imports and URL strings.

front_end/src/app/(main)/services/components/templates/services_page_template.tsx (4)

10-21: LGTM!

The imports are well-organized, bringing in the new components and assets needed for the enhanced template. The SVG import with ?url suffix is the correct pattern for Next.js.


36-47: LGTM!

The new optional props for caseStudies and workshop are well-typed and follow the existing patterns in the Props type. Making them optional ensures backward compatibility with existing usages.


255-268: LGTM!

The case studies section is well-implemented with proper conditional rendering, section heading, and grid layout. Using card.id as the key is the correct approach for stable list rendering.


113-276: Overall template structure is well-organized.

The refactored template properly uses a React fragment to wrap the main content and contact section. The conditional rendering for optional sections (workshop, caseStudies) is correctly implemented. The responsive design patterns are consistent throughout.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 22 to 56
aboutInitiative:
"A short paragraph about the initiative explaining what it was all about and why this report was created in the first place.",
report: {
previewImageSrc: NihNsfPreview,
previewImageAlt: "NIH/NSF report preview",
fileName: "research-agencies-report-2",
pageCount: 8,
publishedAtLabel: "Sep 16, 2025",
},
cta: {
label: "Read full report",
href: "/notebooks/39977/report-pro-forecasters-expect-steady-nih-and-nsf-funding/",
},
},
{
id: "nih-nsf-pro-forecasting-2",
title: "Pro Forecasters Expect Steady NIH and NSF Funding",
body: {
intro: "Among the key findings:",
bullets: [
"Both NIH and NSF are expected to avoid deep cuts, with appropriations broadly stable.",
"NSF faces greater near-term risk in FY2026 due to a $2B gap between House and Senate proposals, but outcomes are expected to align more closely with the Senate’s status quo position.",
],
},
partners: {
label: "In partnership with",
logos: [
{ src: AEILogo, alt: "AEI" },
{ src: CoefficientGivingLogo, alt: "Coefficient Giving" },
{ src: ForbesLogo, alt: "Forbes" },
],
},
aboutInitiative:
"A short paragraph about the initiative explaining what it was all about and why this report was created in the first place.",
report: {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace placeholder initiative copy before release.
aboutInitiative still uses placeholder text, which will surface to users. Either supply final copy or omit the field until it’s ready.

🛠️ Suggested interim fix (omit placeholder copy)
-    aboutInitiative:
-      "A short paragraph about the initiative explaining what it was all about and why this report was created in the first place.",
-    aboutInitiative:
-      "A short paragraph about the initiative explaining what it was all about and why this report was created in the first place.",
🤖 Prompt for AI Agents
In `@front_end/src/app/`(main)/services/components/case_studies/constants.tsx
around lines 22 - 56, The aboutInitiative field in the case study object with id
"nih-nsf-pro-forecasting-2" currently contains placeholder copy; replace that
string with the final initiative blurb or remove the aboutInitiative property
from the object until final copy is ready to avoid shipping placeholder text
(locate the object entry where id === "nih-nsf-pro-forecasting-2" in
constants.tsx and update the aboutInitiative key accordingly).

@cemreinanc
Copy link
Contributor

CleanShot 2026-01-27 at 16 33 20@2x

is it possible to use light logos here in case study card? these are seeking some contrast

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@front_end/messages/cs.json`:
- Line 1898: The Czech plural key "pagesWithCount" needs a 'few' plural category
added so 2–4 use the correct form; update the ICU plural string for
"pagesWithCount" to include few {# stránky} alongside the existing one {#
stránka} and other {# stránek} (i.e., one {# stránka} few {# stránky} other {#
stránek}) so Czech grammar is correct for counts 2–4.

In
`@front_end/src/app/`(main)/services/components/case_studies/case_study_card.tsx:
- Around line 71-78: The partner logo Image in case_study_card.tsx is missing an
explicit width which causes layout shift; update the Image element (the instance
in the CaseStudyCard component) to provide a width prop (e.g., width={80})
matching the rendered height or alternatively switch to using fill with a sized
container around the Image so both dimensions are specified and prevent layout
shifts.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef0de20 and 38ad515.

⛔ Files ignored due to path filters (5)
  • front_end/src/app/(main)/services/assets/aei.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/coefficient-giving.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/forbes.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/nih-nsf-report-preview.png is excluded by !**/*.png
  • front_end/src/app/(main)/services/assets/workshop.svg is excluded by !**/*.svg
📒 Files selected for processing (24)
  • front_end/messages/cs.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/pt.json
  • front_end/messages/zh-TW.json
  • front_end/messages/zh.json
  • front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx
  • front_end/src/app/(main)/services/assets/desktop.webp
  • front_end/src/app/(main)/services/assets/phone.webp
  • front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx
  • front_end/src/app/(main)/services/components/case_studies/constants.tsx
  • front_end/src/app/(main)/services/components/case_studies/types.ts
  • front_end/src/app/(main)/services/components/contact_section/contact_form.tsx
  • front_end/src/app/(main)/services/components/contact_section/contact_intro.tsx
  • front_end/src/app/(main)/services/components/contact_section/contact_section.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_checkbox.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_form.tsx
  • front_end/src/app/(main)/services/components/section_heading.tsx
  • front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/pro_forecasters_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/services_page_template.tsx
  • front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx
  • front_end/src/app/(main)/services/page.tsx
  • front_end/src/components/ui/stacked-preview-image.tsx
💤 Files with no reviewable changes (2)
  • front_end/src/app/(main)/services/components/get_in_touch_form.tsx
  • front_end/src/app/(main)/services/components/get_in_touch_checkbox.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
  • front_end/src/app/(main)/services/components/section_heading.tsx
  • front_end/src/app/(main)/services/components/contact_section/contact_section.tsx
  • front_end/messages/pt.json
  • front_end/src/app/(main)/services/components/case_studies/types.ts
  • front_end/messages/en.json
  • front_end/messages/zh-TW.json
  • front_end/messages/es.json
  • front_end/messages/zh.json
  • front_end/src/components/ui/stacked-preview-image.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx (2)
front_end/src/app/(main)/services/components/case_studies/types.ts (1)
  • TCaseStudyCard (17-35)
front_end/src/components/ui/stacked-preview-image.tsx (1)
  • StackedPreviewImage (24-119)
front_end/src/app/(main)/services/components/case_studies/constants.tsx (2)
front_end/src/app/(main)/services/components/case_studies/types.ts (1)
  • TCaseStudyCard (17-35)
front_end/src/app/(main)/(home)/components/featured-in-logos.tsx (1)
  • ForbesLogo (139-151)
front_end/src/app/(main)/services/page.tsx (1)
front_end/src/app/(main)/services/components/case_studies/constants.tsx (1)
  • CASE_STUDIES (7-68)
front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx (2)
front_end/src/app/(main)/services/(pages)/financial-services/private-instances/config.tsx (6)
  • title (9-14)
  • sinceDebut (16-17)
  • deployedDescription (19-20)
  • platformBlock (22-33)
  • stepsDescription (35-35)
  • steps (37-54)
front_end/src/constants/services.ts (1)
  • ServiceType (1-14)
front_end/src/app/(main)/services/components/contact_section/contact_form.tsx (6)
front_end/src/constants/services.ts (1)
  • ServiceType (1-14)
front_end/src/services/api/misc/misc.shared.ts (1)
  • ContactForm (4-8)
front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx (1)
  • serviceOptions (56-85)
front_end/src/utils/analytics.ts (1)
  • sendAnalyticsEvent (11-17)
front_end/src/utils/core/errors.ts (1)
  • logError (117-151)
front_end/src/components/ui/form_field.tsx (3)
  • Input (111-128)
  • FormErrorMessage (82-109)
  • Textarea (167-181)
front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx (1)
front_end/src/constants/services.ts (1)
  • ServiceType (1-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: Backend Checks
  • GitHub Check: Frontend Checks
  • GitHub Check: integration-tests
🔇 Additional comments (8)
front_end/messages/cs.json (1)

1892-1904: LGTM!

The new localization keys for case studies, workshop block, and contact form are well-translated and properly structured. The translations are grammatically correct and consistent with the existing style in the file.

front_end/src/app/(main)/services/(pages)/financial-services/pro-forecasters/config.tsx (1)

5-5: Import update looks good.

Aligning ServiceOption with the new contact_section module is consistent with the refactor.

front_end/src/app/(main)/services/page.tsx (1)

80-89: LGTM — new workshop and case studies wiring is clear.

front_end/src/app/(main)/services/components/templates/tournaments_page_template.tsx (1)

42-109: LGTM — layout and ContactSection integration look solid.

front_end/src/app/(main)/services/components/templates/pro_forecasters_page_template.tsx (1)

107-113: Nice ContactSection wiring.
Passing preselectedService and serviceOptions keeps the new contact flow aligned with the page context.

front_end/src/app/(main)/services/components/contact_section/contact_form.tsx (1)

95-103: Good guard for preselected service.
The resolvedService logic avoids invalid defaults when serviceOptions are narrowed.

front_end/src/app/(main)/services/components/templates/private_instances_page_template.tsx (1)

97-102: ContactSection integration looks clean.
The anchor id and preselected service align with the page’s context.

front_end/src/app/(main)/services/components/templates/services_page_template.tsx (1)

255-266: Case studies block is well-structured.
Conditional rendering + keyed cards keeps this section tidy and extensible.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@front_end/src/app/`(main)/services/components/case_studies/case_study_card.tsx:
- Around line 127-133: The Image element rendering the report preview
(src={card.report.previewImageSrc}, alt={card.report.previewImageAlt}) is
missing an explicit width which causes build warnings and layout shift; add a
numeric width prop (e.g., width={50} to match the existing height={50} or
another design-approved pixel value) to that <Image> in case_study_card.tsx so
Next.js has both dimensions for the dynamic src.

In
`@front_end/src/app/`(main)/services/components/templates/services_page_template.tsx:
- Around line 226-231: The Image usage with src={WorkshopImage} (a string URL)
must include explicit dimensions or use fill; update the Image components that
render WorkshopImage (both the mobile instance using Image with className
"h-auto w-full max-w-[420px]" and the desktop instance around the later block)
to either add width and height props matching the actual asset dimensions or
replace them with layout fill by wrapping the Image in a positioned container
with explicit size and using fill; ensure you update the Image props (width,
height or fill) rather than relying solely on unoptimized or CSS.
🧹 Nitpick comments (3)
front_end/src/app/(main)/services/components/templates/services_page_template.tsx (2)

226-231: Duplicate WorkshopImage for responsive layout is acceptable but could be simplified.

The same WorkshopImage is rendered twice with opposing visibility classes (lg:hidden and hidden lg:flex). This is a common pattern for responsive layouts with different positioning needs. However, if the image is identical at all breakpoints, CSS-only repositioning might be cleaner.

Also applies to: 244-249


90-92: Consider logging errors for observability.

The catch blocks silently swallow errors by only returning fallbacks. While graceful degradation is good for UX, consider adding server-side logging to help diagnose issues in production.

Example
     } catch (error) {
+      console.error("Failed to fetch site stats:", error);
       finalStatsList = [];
     }

Also applies to: 108-110

front_end/src/app/(main)/aib/components/aib/light-dark-icon.tsx (1)

23-45: Reserve intrinsic dimensions for logo images to reduce CLS.

The <img> tags don’t set width/height, so layout may shift on load. You can use StaticImageData dimensions when available to reserve space without changing the visible size.

♻️ Possible refinement
   if (variant === "logo") {
     const toUrl = (src: IconLike) => (typeof src === "string" ? src : src.src);
+    const toDims = (src: IconLike) =>
+      typeof src === "string" ? undefined : { width: src.width, height: src.height };
+    const lightDims = toDims(lightSrc);
+    const darkDims = toDims(darkSrc);

     return (
       <span
         className={cn("inline-flex items-center", className)}
         style={{ height: px }}
       >
         <img
           src={toUrl(lightSrc)}
           alt={alt}
+          width={lightDims?.width}
+          height={lightDims?.height}
           className="h-full w-auto object-contain dark:hidden"
           loading="lazy"
         />
         <img
           src={toUrl(darkSrc)}
           alt={alt}
+          width={darkDims?.width}
+          height={darkDims?.height}
           className="hidden h-full w-auto object-contain dark:block"
           loading="lazy"
         />
       </span>
     );
   }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38ad515 and f0b016c.

⛔ Files ignored due to path filters (2)
  • front_end/src/app/(main)/services/assets/coefficient-giving-light.svg is excluded by !**/*.svg
  • front_end/src/app/(main)/services/assets/forbes-light.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • front_end/src/app/(main)/aib/components/aib/light-dark-icon.tsx
  • front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx
  • front_end/src/app/(main)/services/components/case_studies/constants.tsx
  • front_end/src/app/(main)/services/components/case_studies/types.ts
  • front_end/src/app/(main)/services/components/templates/services_page_template.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • front_end/src/app/(main)/services/components/case_studies/types.ts
  • front_end/src/app/(main)/services/components/case_studies/constants.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
front_end/src/app/(main)/aib/components/aib/light-dark-icon.tsx (1)
front_end/src/app/(main)/aib/components/aib/leaderboard/bot_meta.ts (1)
  • IconLike (20-20)
🪛 Biome (2.1.2)
front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx

[error] 78-85: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: integration-tests
  • GitHub Check: Frontend Checks
  • GitHub Check: Backend Checks
🔇 Additional comments (5)
front_end/src/app/(main)/services/components/case_studies/case_study_card.tsx (2)

73-104: Static analysis false positive - keys are correctly placed.

The Biome linter flagged LightDarkIcon as missing a key, but keys are correctly applied to the outermost elements in the map callback (<a> on line 90, <span> on line 100). The content variable is just extracted for code reuse.


18-167: Component structure looks good.

The component is well-organized with proper TypeScript typing, conditional rendering for optional content, responsive breakpoints, dark mode support, and accessibility considerations (aria-labels on logo links, alt text on images).

front_end/src/app/(main)/services/components/templates/services_page_template.tsx (2)

255-268: Case studies section implementation looks good.

The conditional rendering, SectionHeading usage, and CaseStudyCard mapping with proper key={card.id} are all correct. The grid layout provides a clean structure for the cards.


113-276: Template structure and layout are well-organized.

The async server component properly handles data fetching, the responsive layout is consistent, and the new sections (workshop, case studies, contact) integrate cleanly with the existing structure. The fragment wrapper and semantic <main> element are appropriate.

front_end/src/app/(main)/aib/components/aib/light-dark-icon.tsx (1)

57-65: The sizes prop should reflect viewport-relative units, not fixed pixel dimensions.

When using fill with Next.js Image, the sizes attribute should describe how wide the image renders in viewport units (e.g., "20px", "50vw", or a media query). While the px variable is correctly normalized as a string, passing fixed pixel values like "20px" to sizes doesn't align with Next.js best practices. For a fixed-size icon like this, using fixed width and height props instead of fill would be more appropriate and avoid confusion about responsive sizing.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +127 to +133
<Image
src={card.report.previewImageSrc}
alt={card.report.previewImageAlt ?? t("reportPreviewAlt")}
height={50}
className="h-[50px] w-auto rounded object-cover sm:hidden"
unoptimized
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit width for report preview image.

The <Image> component requires both width and height when src is a dynamic string, even with unoptimized. Without an explicit width, you may see build warnings or layout shift.

Suggested fix
             <Image
               src={card.report.previewImageSrc}
               alt={card.report.previewImageAlt ?? t("reportPreviewAlt")}
+              width={50}
               height={50}
               className="h-[50px] w-auto rounded object-cover sm:hidden"
               unoptimized
             />
🤖 Prompt for AI Agents
In
`@front_end/src/app/`(main)/services/components/case_studies/case_study_card.tsx
around lines 127 - 133, The Image element rendering the report preview
(src={card.report.previewImageSrc}, alt={card.report.previewImageAlt}) is
missing an explicit width which causes build warnings and layout shift; add a
numeric width prop (e.g., width={50} to match the existing height={50} or
another design-approved pixel value) to that <Image> in case_study_card.tsx so
Next.js has both dimensions for the dynamic src.

Comment on lines +226 to +231
<Image
src={WorkshopImage}
alt="Workshop illustration"
unoptimized
className="h-auto w-full max-w-[420px]"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit dimensions for WorkshopImage.

WorkshopImage is imported with ?url suffix (returns a string URL, not a static import). The <Image> component requires explicit width and height when src is a string, even with unoptimized. Consider adding dimensions or using fill with a sized container.

Suggested fix
                    <Image
                      src={WorkshopImage}
                      alt="Workshop illustration"
                      unoptimized
+                     width={420}
+                     height={280}
                      className="h-auto w-full max-w-[420px]"
                    />

Apply the same fix to the desktop instance on lines 244-249.

📝 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
<Image
src={WorkshopImage}
alt="Workshop illustration"
unoptimized
className="h-auto w-full max-w-[420px]"
/>
<Image
src={WorkshopImage}
alt="Workshop illustration"
unoptimized
width={420}
height={280}
className="h-auto w-full max-w-[420px]"
/>
🤖 Prompt for AI Agents
In
`@front_end/src/app/`(main)/services/components/templates/services_page_template.tsx
around lines 226 - 231, The Image usage with src={WorkshopImage} (a string URL)
must include explicit dimensions or use fill; update the Image components that
render WorkshopImage (both the mobile instance using Image with className
"h-auto w-full max-w-[420px]" and the desktop instance around the later block)
to either add width and height props matching the actual asset dimensions or
replace them with layout fill by wrapping the Image in a positioned container
with explicit size and using fill; ensure you update the Image props (width,
height or fill) rather than relying solely on unoptimized or CSS.

Copy link
Contributor

@cemreinanc cemreinanc left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

this file recently moved to futureeval folder. if we use it in multiple different pages, we may move it to components folder while resolving conflicts.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants