Skip to content

Sanity#119

Merged
ViktorSvertoka merged 3 commits into
developfrom
sanity
Jan 9, 2026
Merged

Sanity#119
ViktorSvertoka merged 3 commits into
developfrom
sanity

Conversation

@KomrakovaAnna
Copy link
Copy Markdown
Collaborator

@KomrakovaAnna KomrakovaAnna commented Jan 9, 2026

Adding styles to the blog page

Summary by CodeRabbit

  • New Features

    • Blog posts now available in English, Polish, and Ukrainian.
    • Enhanced author profiles with image, job title, company, location, and bio display.
    • Author details accessible via modal dialog.
  • Design & UI

    • Blog cards redesigned with improved spacing and visual hierarchy.
    • Featured images enlarged with enhanced styling effects.
    • Tags displayed as interactive pills at card bottom.
  • Localization

    • Added translations for new blog interface elements across all supported languages.

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

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 9, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 4d94451
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/69605ce7f4d08d0008f28953
😎 Deploy Preview https://deploy-preview-119--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

The pull request introduces comprehensive localization support across the blog system. Frontend components (PostDetails, blog pages, cards) now accept a locale parameter and fetch locale-specific content with fallbacks to English. Sanity studio schemas for author and post are restructured to store content as multilingual objects (en, pl, uk) instead of single strings. Translation files are updated with new keys and removed arrow symbols from resource links.

Changes

Cohort / File(s) Summary
Frontend Blog Page Integration
frontend/app/[locale]/blog/[slug]/page.tsx, frontend/app/[locale]/blog/[slug]/PostDetails.tsx
Added locale parameter to generateMetadata and Page signatures; locale passed through to PostDetails component; GROQ query updated to use coalesce(title[$locale], title.en, title) for locale-aware title retrieval.
Frontend Blog Listing
frontend/app/[locale]/blog/page.tsx
GROQ projection refactored to fetch locale-specific title, body, and author fields (name, company, jobTitle, city, bio) with English/default fallbacks; locale parameter passed to fetch call.
Frontend Blog UI Components
frontend/components/blog/BlogCard.tsx
Significant layout restructure: card styling changed from white chrome to transparent group-based container; image wrapped in Link with updated filters; title/link typography and hover states updated; author section moved to bottom with AuthorModal; tags now rendered as clickable pills; excerpt extraction updated to handle PortableTextSpan with fallbacks for undefined bodies.
Frontend Blog Author & Filter Components
frontend/components/blog/AuthorModal.tsx, frontend/components/blog/BlogFilters.tsx
AuthorModal: trigger button simplified to show metaText (authorName + date) with updated styling; modal content retained; added top border/padding to social section. BlogFilters: new normalizeTag() utility function added to normalize tag strings and improve autocomplete logic; documentation added.
Frontend Grid Layout
frontend/components/blog/BlogGrid.tsx
Grid gap spacing increased from gap-10 to gap-12.
Sanity Author Schema
studio/schemaTypes/author.ts
Fields converted from single strings to multilingual objects with en, pl, uk subfields: name, company, jobTitle, city; bio converted from single block array to multilingual object with per-language block arrays; slug source and preview updated to use name.en.
Sanity Post Schema
studio/schemaTypes/post.ts
Title and body converted to multilingual objects (en, pl, uk); slug source and preview updated to use title.en; body now stores per-language block arrays.
Translation Files
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
visitResource arrow removed across all locales; new keys added: readArticle, aboutAuthor with localized values.

Sequence Diagram

sequenceDiagram
    actor User
    participant Page as page.tsx<br/>[locale]/blog/[slug]
    participant PostDetails as PostDetails.tsx
    participant Sanity as Sanity GROQ
    participant Trans as Translations

    User->>Page: Request blog post
    Note over Page: Extract params: { slug, locale }
    Page->>Sanity: Fetch post<br/>query with coalesce(<br/>title[$locale],<br/>title.en, title)
    Sanity-->>Page: Return post with<br/>localized title, body,<br/>author fields
    Page->>PostDetails: Pass slug + locale
    PostDetails->>Trans: getTranslations('blog')
    Trans-->>PostDetails: t function
    PostDetails->>PostDetails: Resolve body[$locale]<br/>Render author section<br/>with localized fields
    PostDetails-->>User: Render localized<br/>post + translations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • Sanity #79: Modifies BlogCard and BlogFilters components with portable text handling and tag normalization logic changes that directly parallel this PR's refactoring in those areas.

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 Whiskers twitch with translation glee,
Locales bloom in threes—en, pl, uk!
Posts now speak in many tongues so free,
With fallbacks dancing, luck is on our side,
A multilingual blog, hopping worldwide!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Sanity" is too vague and generic. While the PR does involve Sanity schema changes, the actual changes span much broader functionality including blog localization, UI improvements, and component refactoring across multiple files. Revise the title to reflect the primary objective. Consider: "Add blog localization and redesign author/card components" or "Implement multilingual blog support with schema and UI updates".
✅ 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

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.

Copy link
Copy Markdown
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)

145-165: Avoid Math.random() as React key and consider using next/image for body images.

Two concerns with body rendering:

  1. Math.random() as a fallback key (line 150) breaks React's reconciliation and can cause unnecessary re-renders. Use a stable identifier like the block index.

  2. The <img> tag for body images bypasses Next.js image optimization. Consider using next/image with appropriate sizing.

🔧 Suggested fix for key and image optimization
-        {post.body?.map((block: any) => {
+        {post.body?.map((block: any, index: number) => {
           if (block?._type === 'block') {
             const text = (block.children || [])
               .map((c: any) => c.text || '')
               .join('');
-            return <p key={block._key || Math.random()}>{text}</p>;
+            return <p key={block._key || `block-${index}`}>{text}</p>;
           }

           if (block?._type === 'image' && block?.url) {
             return (
-              <img
-                key={block._key || block.url}
+              <Image
+                key={block._key || `image-${index}`}
                 src={block.url}
                 alt={post.title || 'Post image'}
+                width={800}
+                height={450}
                 className="rounded-xl border border-gray-200 my-6"
               />
             );
           }
🤖 Fix all issues with AI agents
In @frontend/app/[locale]/blog/page.tsx:
- Around line 28-64: The GROQ coalesce fallbacks are returning whole objects as
the third argument; update all coalesce calls in the query so scalar/locale
fallbacks only use the locale and English keys (remove the third fallback that
references the object). Specifically, change coalesce(title[$locale], title.en,
title) → coalesce(title[$locale], title.en), coalesce(body[$locale], body.en,
body)[] → coalesce(body[$locale], body.en)[], and likewise for all author
subfields (coalesce(name[$locale], name.en, name) → coalesce(name[$locale],
name.en), coalesce(company[$locale], company.en, company) →
coalesce(company[$locale], company.en), coalesce(jobTitle[$locale], jobTitle.en,
jobTitle) → coalesce(jobTitle[$locale], jobTitle.en), coalesce(city[$locale],
city.en, city) → coalesce(city[$locale], city.en), coalesce(bio[$locale],
bio.en, bio) → coalesce(bio[$locale], bio.en)); ensure you only remove the third
argument and keep the array-unpacking ([]) on body as shown.
🧹 Nitpick comments (5)
frontend/components/blog/BlogFilters.tsx (1)

55-68: LGTM! Clear documentation added.

The JSDoc comment accurately describes the normalization behavior, and the function implementation matches the documentation. Exporting this utility enables reuse across other components.

📝 Optional: Enhance JSDoc with type annotations

For improved IDE intellisense and auto-completion, consider adding @param and @returns tags:

 /**
  * Normalize a tag/search input:
  * - removes leading "#"
  * - trims spaces
  * - lowercases
  * - collapses multiple spaces
+ * @param input - The tag string to normalize
+ * @returns The normalized tag string
  */
 export function normalizeTag(input: string) {
studio/schemaTypes/post.ts (1)

8-17: Localization structure looks good.

The multilingual object pattern for title with en/pl/uk subfields is well-structured. Consider adding validation to ensure at least the English field is required, as the slug generation depends on it.

💡 Optional: Add validation for required English title
     defineField({
       name: 'title',
       title: 'Title',
       type: 'object',
       fields: [
-        defineField({name: 'en', title: 'English', type: 'string'}),
+        defineField({name: 'en', title: 'English', type: 'string', validation: (Rule) => Rule.required()}),
         defineField({name: 'pl', title: 'Polish', type: 'string'}),
         defineField({name: 'uk', title: 'Ukrainian', type: 'string'}),
       ],
     }),
frontend/components/blog/BlogCard.tsx (1)

111-122: Add explicit button type for accessibility.

Buttons inside forms default to type="submit". While this isn't in a form, explicitly setting type="button" is a best practice to prevent unexpected behavior if the component is later used within a form context.

💡 Suggested improvement
                   <button
                     key={`${norm}-${i}`}
+                    type="button"
                     onClick={() => onTagToggle(norm)}
studio/schemaTypes/author.ts (1)

8-17: Consistent multilingual pattern across author fields.

The localization structure for name, company, jobTitle, and city follows the same pattern as the post schema. This consistency is good for maintainability.

Consider extracting a reusable helper for the multilingual string pattern to reduce repetition:

// Example helper (could be in a shared utils file)
const localizedString = (name: string, title: string) =>
  defineField({
    name,
    title,
    type: 'object',
    fields: [
      defineField({name: 'en', title: 'English', type: 'string'}),
      defineField({name: 'pl', title: 'Polish', type: 'string'}),
      defineField({name: 'uk', title: 'Ukrainian', type: 'string'}),
    ],
  });

Also applies to: 82-111

frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)

13-32: Type definitions could be more precise.

The types correctly mark fields as optional, which aligns with the GROQ coalesce fallback pattern. Consider using more specific types for bio and body (e.g., PortableTextBlock[]) instead of any for better type safety.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aeca682 and 4d94451.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • studio/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • frontend/app/[locale]/blog/[slug]/PostDetails.tsx
  • frontend/app/[locale]/blog/[slug]/page.tsx
  • frontend/app/[locale]/blog/page.tsx
  • frontend/components/blog/AuthorModal.tsx
  • frontend/components/blog/BlogCard.tsx
  • frontend/components/blog/BlogFilters.tsx
  • frontend/components/blog/BlogGrid.tsx
  • frontend/messages/en.json
  • frontend/messages/pl.json
  • frontend/messages/uk.json
  • studio/schemaTypes/author.ts
  • studio/schemaTypes/post.ts
🔇 Additional comments (18)
frontend/components/blog/BlogGrid.tsx (1)

23-23: LGTM! Improved spacing for blog grid.

The increased gap from gap-10 to gap-12 (40px → 48px) provides better visual separation between blog cards and accommodates the richer content blocks introduced by the localization changes. The adjustment aligns well with the PR's styling objectives.

frontend/components/blog/BlogFilters.tsx (1)

86-91: LGTM! Component documentation enhances maintainability.

The JSDoc clearly describes the component's key features, particularly the AND filtering semantics and Tab-to-accept autocomplete UX, which are valuable details for future maintainers.

frontend/messages/pl.json (1)

54-56: LGTM! Translation updates are consistent.

The removal of the arrow symbol from visitResource and the addition of the new keys readArticle and aboutAuthor align with the broader localization effort across all locale files.

frontend/messages/uk.json (1)

54-56: LGTM! Translation updates match other locales.

The Ukrainian translations are consistent with the changes in the English and Polish locale files, properly supporting the blog localization feature.

frontend/messages/en.json (1)

54-56: LGTM! Base translations established correctly.

The English translations serve as the fallback locale in the coalesce pattern used throughout the blog localization. The new keys readArticle and aboutAuthor are properly defined.

frontend/components/blog/AuthorModal.tsx (4)

113-114: Good defensive programming with fallback handling.

The fallback to 'Unknown author' prevents display issues when author name is missing. The conditional metaText formatting appropriately handles both cases (with and without date).


144-146: LGTM! Trigger button simplified with better hover states.

The updated styling improves user experience with clear hover feedback, and simplifying the trigger to display metaText makes the component more maintainable.


181-181: Minor alignment improvement.

The -mt-px adjustment ensures the close button glyph is properly centered.


228-228: Visual separation improved for social links section.

Adding a top border and padding properly delineates the social links from the author bio content.

frontend/app/[locale]/blog/page.tsx (1)

6-26: Async params handling is correct for Next.js 15+.

The use of Promise<{ locale: string }> for params and awaiting it before use aligns with Next.js 15's async params pattern. The locale is correctly propagated to both translation and data fetching functions.

studio/schemaTypes/post.ts (1)

76-100: Body localization structure is consistent.

The multilingual body field correctly maintains the array of blocks/images per language, consistent with the title field pattern.

frontend/components/blog/BlogCard.tsx (2)

19-26: Excerpt extraction is well-implemented.

The null-safe handling with post.body ?? [] and type guards for filtering blocks is robust. The nested children traversal correctly handles the PortableTextSpan type.


44-63: Image link implementation looks good.

The image is correctly wrapped in a Link using post.slug.current. The grayscale/brightness styling provides a nice visual effect. Ensure that the parent component passes the locale-resolved post.title string for proper alt text.

studio/schemaTypes/author.ts (1)

35-79: Bio localization structure is intentionally simplified.

The bio field correctly uses arrays of blocks per language with limited styling (normal only, no lists). This is appropriate for author bios which typically don't need complex formatting.

frontend/app/[locale]/blog/[slug]/page.tsx (2)

22-25: GROQ locale fallback chain is reasonable.

The coalesce(title[$locale], title.en, title) pattern provides good fallbacks. Note that the final title fallback would return the entire object if both locale-specific and English fields are missing—though this edge case is unlikely if content validation is enforced in Sanity.


5-13: The current implementation is correct. The parent [locale]/layout.tsx has export const dynamic = 'force-dynamic', which forces all child pages in the [locale] subtree to render dynamically. This means generateStaticParams at the child level is never executed, and the suggested fix to add locale variants is unnecessary. The page correctly receives both slug and locale from the route parameters and uses them in generateMetadata and the page component.

Likely an incorrect or invalid review comment.

frontend/app/[locale]/blog/[slug]/PostDetails.tsx (2)

72-86: Locale-aware data fetching is well-implemented.

The component correctly accepts and uses the locale prop for both translations and GROQ queries. The fallback chain in the query ensures graceful degradation when translations are missing.


181-212: Author section implementation is solid.

The conditional rendering logic, translated heading, and metadata composition are well-implemented. The author image correctly uses next/image with proper sizing and styling.

Comment on lines +28 to +64
const posts = await client.fetch(
groq`
*[_type == "post" && defined(slug.current)]
| order(publishedAt desc) {
_id,
"title": coalesce(title[$locale], title.en, title),
slug,
publishedAt,
tags,
resourceLink,

"categories": categories[]->title,
"categories": categories[]->title,

body[] {
...,
children[]{
text
}
},
"mainImage": mainImage.asset->url,
"body": coalesce(body[$locale], body.en, body)[]{
...,
children[]{
text
}
},
"mainImage": mainImage.asset->url,
"author": author->{
name,
company,
jobTitle,
city,
bio,
"name": coalesce(name[$locale], name.en, name),
"company": coalesce(company[$locale], company.en, company),
"jobTitle": coalesce(jobTitle[$locale], jobTitle.en, jobTitle),
"city": coalesce(city[$locale], city.en, city),
"bio": coalesce(bio[$locale], bio.en, bio),
"image": image.asset->url,
socialMedia[]{
_key,
platform,
url
platform,
url
}
}
}
}
`);
`,
{ locale }
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for Sanity schema files and multilingual field definitions
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) -path "*/schema*" -o -path "*/schemaTypes*" | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 533


🏁 Script executed:

# Look for schema type definitions, especially for post and author
fd -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs rg -l "defineType|defineField|schema" | grep -i schema | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 495


🏁 Script executed:

# Search for post and author type definitions
rg "defineType.*post|defineType.*author" -A 20 | head -100

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

# Check directory structure for typical Sanity project layout
ls -la | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 1089


🏁 Script executed:

wc -l studio/schemaTypes/post.ts studio/schemaTypes/author.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 147


🏁 Script executed:

cat -n studio/schemaTypes/post.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 3420


🏁 Script executed:

cat -n studio/schemaTypes/author.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 4061


Schema changes are in place, but fix the coalesce fallback patterns.

The Sanity schema files have been correctly updated with multilingual object-based fields (en, pl, uk) for title, body, and all author fields, which aligns with the GROQ implementation.

However, the coalesce fallback patterns have a critical bug: the third fallback option should be removed. Currently:

  • coalesce(title[$locale], title.en, title) → the fallback to title returns the entire object instead of a string
  • coalesce(body[$locale], body.en, body)[] → applying [] to an object breaks the iteration

Fix by removing the problematic third fallback:

  • Scalar fields: coalesce(title[$locale], title.en)
  • Array fields: coalesce(body[$locale], body.en)[]

This ensures the query gracefully handles missing locales by falling back to English without attempting to return object references.

🤖 Prompt for AI Agents
In @frontend/app/[locale]/blog/page.tsx around lines 28 - 64, The GROQ coalesce
fallbacks are returning whole objects as the third argument; update all coalesce
calls in the query so scalar/locale fallbacks only use the locale and English
keys (remove the third fallback that references the object). Specifically,
change coalesce(title[$locale], title.en, title) → coalesce(title[$locale],
title.en), coalesce(body[$locale], body.en, body)[] → coalesce(body[$locale],
body.en)[], and likewise for all author subfields (coalesce(name[$locale],
name.en, name) → coalesce(name[$locale], name.en), coalesce(company[$locale],
company.en, company) → coalesce(company[$locale], company.en),
coalesce(jobTitle[$locale], jobTitle.en, jobTitle) → coalesce(jobTitle[$locale],
jobTitle.en), coalesce(city[$locale], city.en, city) → coalesce(city[$locale],
city.en), coalesce(bio[$locale], bio.en, bio) → coalesce(bio[$locale], bio.en));
ensure you only remove the third argument and keep the array-unpacking ([]) on
body as shown.

@ViktorSvertoka ViktorSvertoka merged commit 0078242 into develop Jan 9, 2026
9 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sanity branch January 9, 2026 01:48
This was referenced Jan 15, 2026
This was referenced Jan 25, 2026
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.

2 participants