Skip to content

Sanity#145

Merged
ViktorSvertoka merged 6 commits into
developfrom
sanity
Jan 15, 2026
Merged

Sanity#145
ViktorSvertoka merged 6 commits into
developfrom
sanity

Conversation

@KomrakovaAnna
Copy link
Copy Markdown
Collaborator

@KomrakovaAnna KomrakovaAnna commented Jan 15, 2026

redesign of blog page

Summary by CodeRabbit

  • New Features

    • Added recommended posts section below articles with locale-aware related suggestions.
    • Added author and category selection for filtering blog lists.
  • Changed Behavior

    • Article layout reorganized: back-link, centered title, compact author/date line; author bio removed from main article area.
    • Blog list shows a featured post and subtitle beneath the main heading.
  • Localization

    • Expanded locale fallbacks and new translation keys for blog/author text.
  • Chores

    • CMS: enabled ordering support for categories.

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

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 15, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit edf62b2
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/696962d20b0c6f00082395f3
😎 Deploy Preview https://deploy-preview-145--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 15, 2026

📝 Walkthrough

Walkthrough

Refactors blog filtering to author/category, adds locale-aware GROQ projections, deterministic recommended-posts for PostDetails, updates BlogCard/BlogGrid APIs, expands localization keys, and enables orderable categories in Sanity schema.

Changes

Cohort / File(s) Summary
Post details & listing pages
frontend/app/[locale]/blog/[slug]/PostDetails.tsx, frontend/app/[locale]/blog/page.tsx
Added locale-fallback GROQ projections, extended Post type with _id and slug, deterministic recommended-posts (seededShuffle + hashString), reworked header/navigation, and added featuredPost/category flow in page.tsx.
Blog components (filter, grid, card)
frontend/components/blog/BlogFilters.tsx, frontend/components/blog/BlogGrid.tsx, frontend/components/blog/BlogCard.tsx
Replaced tag-based filtering with author/category selection; BlogFilters now accepts categories and featuredPost and exports Author type; BlogGrid/BlogCard props changed to onAuthorSelect(author). Updated date formatting and author interaction UI.
Localization
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Added blog.subtitle and multiple author-related keys (author, removeAuthor, articlesBy, articlesPublished, recommendedPosts, goBack); removed some Polish quiz/pagination keys.
Sanity CMS config & schemas
studio/package.json, studio/sanity.config.ts, studio/schemaTypes/category.ts, studio/schemaTypes/post.ts
Added @sanity/orderable-document-list; integrated orderableCategory desk item; added orderRankField and orderRankOrdering to category schema; adjusted post title localization structure and preview.select author mapping.

Sequence Diagram

sequenceDiagram
    participant User
    participant BlogFilters
    participant BlogGrid
    participant BlogCard
    participant PostDetails
    participant Sanity

    User->>BlogFilters: load blog page
    BlogFilters->>Sanity: fetch posts, categories, featuredPost
    Sanity-->>BlogFilters: return data
    BlogFilters->>BlogGrid: pass filtered posts & onAuthorSelect
    BlogGrid->>BlogCard: render posts with onAuthorSelect
    User->>BlogCard: click author
    BlogCard->>BlogFilters: onAuthorSelect(author)
    BlogFilters->>BlogGrid: re-filter posts by author
    User->>BlogCard: click post
    BlogCard->>PostDetails: navigate to post
    PostDetails->>Sanity: fetch post + recommended posts (locale-aware)
    Sanity-->>PostDetails: return post + recommendations
    PostDetails->>User: render article + recommended posts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Sanity #119 — touches same PostDetails.tsx and page.tsx GROQ/localization changes.
  • Feature/multilingual about #95 — modifies BlogCard and BlogFilters localization/props overlapping with this refactor.
  • Sanity #79 — refactors blog component types and props affecting BlogCard/BlogFilters interfaces.

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐇 I hopped through code with jittery paws,
Sorted categories and authors with applause,
Locale whispers threaded every post,
Three friends recommended — never lost,
Sanity ordered the garden, fine and fair.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 PR title 'Sanity' is vague and does not clearly convey the main changes, which involve a blog page redesign, schema updates for translations, and category/author filtering features. Use a more descriptive title that captures the main scope, such as 'Redesign blog page with author/category filtering and Sanity schema updates for translations' or similar.
✅ 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: 6

Caution

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

⚠️ Outside diff range comments (1)
studio/schemaTypes/post.ts (1)

11-22: Duplicate type and fields declarations in the title field definition.

The title field has duplicated type: 'object' and fields array declarations within the same defineField call. This appears to be a merge conflict or copy-paste error. In JavaScript objects, duplicate keys cause the later values to overwrite the earlier ones, making this code confusing and likely unintentional.

🐛 Proposed fix to remove duplicate declarations
     defineField({
       name: 'title',
       title: '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'}),
       ],
-      type: 'object',
-      fields: [
-        defineField({name: 'en', title: 'English', type: 'string'}),
-        defineField({name: 'pl', title: 'Polish', type: 'string'}),
-        defineField({name: 'uk', title: 'Ukrainian', type: 'string'}),
-      ],
     }),
🤖 Fix all issues with AI agents
In `@frontend/app/`[locale]/blog/[slug]/PostDetails.tsx:
- Around line 48-83: The GROQ query in PostDetails.tsx contains unresolved merge
markers (<<<<<<<, =======, >>>>>>>) that break parsing; remove all conflict
markers and consolidate each conflicted block into a single coherent variant for
the fields "title", "author" (including "name", "company", "jobTitle", "city",
"bio", "image", "socialMedia"), "body", and the recommended-posts fetch block —
pick and apply the intended locale fallback strategy consistently (e.g., either
the extended fallback list used in the HEAD variant or the simplified [ $locale,
en, default ] variant from develop) and ensure the final query contains no
leftover markers or duplicate alternatives.
- Around line 166-172: The date formatting uses new
Date(post.publishedAt).toLocaleDateString() which defaults to the server locale;
update PostDetails.tsx to pass the page locale into toLocaleDateString (e.g.,
new Date(post.publishedAt).toLocaleDateString(locale)) wherever dates are
formatted (the instance in the author/published block and the other occurrence
around the 257–260 area) so the blog dates render using the page locale; locate
the date calls in the PostDetails component and supply the locale variable used
by the page.

In `@frontend/app/`[locale]/blog/page.tsx:
- Around line 33-70: Remove the unresolved git conflict markers (<<<<<<<,
=======, >>>>>>>) and pick a single, consistent GROQ shape for the query fields
(do not leave both HEAD and develop variants); replace the mixed branches so
"title", "body", and all "author" subfields use one chosen fallback strategy
(e.g., the richer coalesce fallbacks: coalesce(title[$locale],
title[lower($locale)], title.uk, title.en, title.pl, title) and similarly for
body and author fields), ensure "categories": categories[]->title remains a
single valid field, and confirm there are no duplicate/conflicting definitions
(also apply the same fix where the same conflict appears around the
categories/featuredPost block).

In `@frontend/components/blog/BlogCard.tsx`:
- Around line 6-18: The file has unresolved git conflict markers in BlogCard.tsx
causing build failures; resolve by choosing the author-based branch: remove all
conflict markers (<<<<<<<, =======, >>>>>>>) and keep the imports from the
author branch (useTranslations, AuthorModal, type { Post, PortableTextBlock,
PortableTextSpan }), ensure the component uses the onAuthorSelect prop and the
AuthorModal usage instead of the locale-based branch, and reconcile styling and
author/tag UI blocks to match the author-driven implementation (replace the
conflicting sections at the import block and the UI blocks around the author/tag
rendering and modal handling so only the author-based code remains).

In `@frontend/components/blog/BlogFilters.tsx`:
- Around line 340-342: Remove the duplicate empty-state paragraph from
BlogFilters.tsx: the JSX block that checks {!filteredPosts.length && (<p
className="text-center text-gray-500 mt-10">{t('noPosts')}</p>)} should be
deleted so the empty-state is only rendered by BlogGrid; locate the conditional
that references filteredPosts in the BlogFilters component and remove that <p>
element, leaving BlogGrid to handle the "no posts" message.
- Around line 297-309: The "All" label is hardcoded; replace it with a localized
string using the component's translation hook (e.g., useTranslation / t) so the
button displays t('all') instead of "All"; update the BlogFilters component
where selectedAuthor, allCategories, selectedCategory, and setSelectedCategory
are used to call t('all') and ensure the 'all' key is added to the translation
files for each locale; if the component doesn't yet import or call
useTranslation, add that import and hook initialization before using t.
🧹 Nitpick comments (3)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)

176-176: Remove the no-op tag guard.
(post.tags?.length || 0) > 0 && null does nothing and adds noise.

🧹 Cleanup
-      {(post.tags?.length || 0) > 0 && null}
frontend/components/blog/BlogFilters.tsx (2)

85-87: plainTextExcerpt should actually truncate.
Right now it returns the full body, which can overwhelm the featured block. Consider capping length with an ellipsis.

♻️ Suggested refactor
-function plainTextExcerpt(value?: PortableText): string {
-  return plainTextFromPortableText(value);
-}
+function plainTextExcerpt(value?: PortableText, maxLength = 220): string {
+  const text = plainTextFromPortableText(value);
+  if (text.length <= maxLength) return text;
+  return `${text.slice(0, maxLength).trimEnd()}…`;
+}

333-333: Remove the no-op selectedCategory && null.
This expression is dead and can be deleted.

🧹 Cleanup
-        {selectedCategory && null}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a63a90a and ab490fc.

⛔ 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/page.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/package.json
  • studio/sanity.config.ts
  • studio/schemaTypes/category.ts
  • studio/schemaTypes/post.ts
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/app/[locale]/blog/page.tsx (1)
frontend/components/blog/BlogFilters.tsx (1)
  • BlogFilters (93-345)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)
frontend/components/blog/BlogFilters.tsx (1)
  • Post (38-49)
frontend/components/blog/BlogCard.tsx (1)
frontend/components/blog/BlogFilters.tsx (2)
  • Post (38-49)
  • Author (29-36)
frontend/components/blog/BlogFilters.tsx (1)
frontend/components/blog/BlogGrid.tsx (1)
  • BlogGrid (7-31)
🪛 Biome (2.1.2)
frontend/app/[locale]/blog/page.tsx

[error] 90-92: Expected a statement but instead found '=======

develop'.

Expected a statement here.

(parse)

frontend/app/[locale]/blog/[slug]/PostDetails.tsx

[error] 127-129: Expected a statement but instead found '=======

develop'.

Expected a statement here.

(parse)


[error] 271-271: Expected a JSX Expression, a Element, or a text but instead found '<<<<<<'.

Expected a JSX Expression, a Element, or a text here.

(parse)


[error] 272-272: expected ... but instead found post

Remove post

(parse)


[error] 274-274: expected ... but instead found (

Remove (

(parse)


[error] 271-309: Expected corresponding JSX closing tag for 'HEAD'.

Opening tag

closing tag

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 309-309: Unexpected token. Did you mean {'>'} or &gt;?

(parse)

frontend/components/blog/BlogCard.tsx

[error] 5-6: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)


[error] 13-14: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 17-18: Expected a statement but instead found '>>>>>>> develop'.

Expected a statement here.

(parse)


[error] 15-15: Shouldn't redeclare 'useTranslations'. Consider to delete it or rename it.

'useTranslations' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'Post'. Consider to delete it or rename it.

'Post' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'PortableTextBlock'. Consider to delete it or rename it.

'PortableTextBlock' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'PortableTextSpan'. Consider to delete it or rename it.

'PortableTextSpan' is defined here:

(lint/suspicious/noRedeclare)


[error] 89-90: Expected a JSX attribute but instead found '<<<<<<'.

Expected a JSX attribute here.

(parse)


[error] 90-90: expected > but instead found <

Remove <

(parse)


[error] 91-92: Expected a JSX attribute but instead found '======='.

Expected a JSX attribute here.

(parse)


[error] 90-94: Expected corresponding JSX closing tag for 'HEAD'.

Opening tag

closing tag

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 94-94: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 131-131: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 131-131: Expected an expression but instead found '<<'.

Expected an expression here.

(parse)


[error] 132-132: expected , but instead found className

Remove className

(parse)


[error] 132-132: expected , but instead found =

Remove =

(parse)


[error] 132-133: Expected a JSX attribute but instead found '======='.

Expected a JSX attribute here.

(parse)


[error] 134-134: expected > but instead found <

Remove <

(parse)


[error] 134-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 135-135: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 138-138: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 140-141: Expected a JSX Expression, a Element, or a text but instead found '<<<<<<'.

Expected a JSX Expression, a Element, or a text here.

(parse)


[error] 142-142: expected ... but instead found post

Remove post

(parse)


[error] 166-166: expected ... but instead found post

Remove post

(parse)


[error] 167-167: Expected a JSX attribute but instead found '======='.

Expected a JSX attribute here.

(parse)


[error] 131-133: Expected corresponding JSX closing tag for 'HEAD'.

Opening tag

closing tag

(parse)


[error] 141-218: Expected corresponding JSX closing tag for 'HEAD'.

Opening tag

closing tag

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 218-218: Unexpected token. Did you mean {'>'} or &gt;?

(parse)

🔇 Additional comments (20)
frontend/messages/en.json (2)

161-165: LGTM!

The exitModal section appears to have formatting adjustments only with no semantic changes.


169-186: LGTM!

The new i18n keys for blog subtitle and author-related functionality are well-structured:

  • Proper interpolation syntax with {name} and {count} placeholders
  • Keys align with the new author-based filtering and recommended posts features
  • Naming is consistent with existing conventions
studio/schemaTypes/post.ts (1)

112-112: LGTM!

Updating the preview selection to author.name.en correctly aligns with the localized author name structure, ensuring the English name is displayed in Sanity Studio previews.

studio/schemaTypes/category.ts (3)

2-2: LGTM!

Correct import of ordering utilities from @sanity/orderable-document-list.


9-9: LGTM!

The orderRankField is correctly configured with type: 'category' matching the document type, enabling drag-and-drop ordering in the Sanity Studio.


21-21: LGTM!

Registering orderRankOrdering enables the custom sort order to be applied when viewing categories.

studio/sanity.config.ts (2)

4-4: LGTM!

Correct import of orderableDocumentListDeskItem for the custom desk structure.


14-31: LGTM!

The custom structure configuration is well-implemented:

  • Correctly passes S (StructureBuilder) and context to the structure function
  • Standard document types (post, author) use S.documentTypeListItem()
  • Category uses orderableDocumentListDeskItem with proper parameters for drag-and-drop ordering
  • visionTool() is retained for GROQ query testing
studio/package.json (1)

18-18: The @sanity/orderable-document-list package is properly specified at version 1.4.2, which is the latest stable release. The package is actively maintained with no known security vulnerabilities.

frontend/messages/uk.json (2)

161-164: Exit modal strings look fine.

No issues spotted in the updated copy.


169-186: Blog subtitle and author-related keys look consistent.

New strings align with the updated blog UI.

frontend/messages/pl.json (2)

42-42: Looks good.

No concerns with this update.


46-63: Blog subtitle and author keys look good.

Strings align with the new blog UI surface.

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

5-13: Prop update is consistent with author-based filtering.

Nice, clean pass-through to BlogCard.

Also applies to: 26-26

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

96-104: Subtitle + BlogFilters wiring looks good.

Once conflicts are resolved, the layout additions and prop wiring are solid.

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

20-26: Author-based API update looks good.

The new onAuthorSelect prop matches the updated BlogGrid contract.


37-46: Locale-aware date formatting is a nice touch.

Clean, defensive formatting for optional dates.

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

24-35: Post type extensions look good.
Adding optional _id and slug keeps the type compatible with partial projections and the recommended-posts list.


91-103: Recommended-posts projection looks good.
The query includes the fields needed by the UI without overfetching author data.

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

173-187: Filtering logic looks solid.
Author and category filters are normalized consistently and applied in a clear, readable flow.

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

Comment thread frontend/app/[locale]/blog/[slug]/PostDetails.tsx Outdated
Comment on lines +166 to +172
{(authorName || post.publishedAt) && (
<div className="mt-4 flex justify-center gap-2 text-sm text-gray-500 dark:text-gray-400">
{authorName && <span>{authorName}</span>}
{authorName && post.publishedAt && <span>·</span>}
{post.publishedAt && (
<span>{new Date(post.publishedAt).toLocaleDateString()}</span>
)}
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 | 🟡 Minor

Use the page locale when formatting dates.
toLocaleDateString() without a locale uses the server default and can mismatch the blog locale. Pass locale in both places.

💡 Suggested fix
-          {post.publishedAt && (
-            <span>{new Date(post.publishedAt).toLocaleDateString()}</span>
-          )}
+          {post.publishedAt && (
+            <span>
+              {new Date(post.publishedAt).toLocaleDateString(locale)}
+            </span>
+          )}
-                      {item.publishedAt && (
-                        <span>
-                          {new Date(item.publishedAt).toLocaleDateString()}
-                        </span>
-                      )}
+                      {item.publishedAt && (
+                        <span>
+                          {new Date(item.publishedAt).toLocaleDateString(locale)}
+                        </span>
+                      )}

Also applies to: 257-260

🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/blog/[slug]/PostDetails.tsx around lines 166 - 172,
The date formatting uses new Date(post.publishedAt).toLocaleDateString() which
defaults to the server locale; update PostDetails.tsx to pass the page locale
into toLocaleDateString (e.g., new
Date(post.publishedAt).toLocaleDateString(locale)) wherever dates are formatted
(the instance in the author/published block and the other occurrence around the
257–260 area) so the blog dates render using the page locale; locate the date
calls in the PostDetails component and supply the locale variable used by the
page.

Comment thread frontend/app/[locale]/blog/page.tsx Outdated
Comment thread frontend/components/blog/BlogCard.tsx Outdated
Comment on lines +6 to +18
<<<<<<< HEAD
import { useLocale, useTranslations } from 'next-intl';
import type {
Author,
Post,
PortableTextBlock,
PortableTextSpan,
} from './BlogFilters';
=======
import { useTranslations } from 'next-intl';
import AuthorModal from './AuthorModal';
import type { Post, PortableTextBlock, PortableTextSpan } from './BlogFilters';
>>>>>>> develop
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

Resolve merge conflicts — file currently won’t compile.

Conflict markers span imports, styling, and author/tag UI. Pick a single branch and cleanly remove the markers. Given the rest of the PR now uses onAuthorSelect, the author-based branch is the consistent choice.

✅ Example resolution (author-based path)
-<<<<<<< HEAD
-import { useLocale, useTranslations } from 'next-intl';
-import type {
-  Author,
-  Post,
-  PortableTextBlock,
-  PortableTextSpan,
-} from './BlogFilters';
-=======
-import { useTranslations } from 'next-intl';
-import AuthorModal from './AuthorModal';
-import type { Post, PortableTextBlock, PortableTextSpan } from './BlogFilters';
->>>>>>> develop
+import { useLocale, useTranslations } from 'next-intl';
+import type {
+  Author,
+  Post,
+  PortableTextBlock,
+  PortableTextSpan,
+} from './BlogFilters';

-<<<<<<< HEAD
-        transition
-=======
-        transition-transform
-        hover:-translate-y-[2px]
->>>>>>> develop
+        transition

-<<<<<<< HEAD
-            dark:border dark:border-[rgba(56,189,248,0.25)]
-            dark:shadow-[0_0_0_1px_rgba(56,189,248,0.25),0_12px_28px_rgba(56,189,248,0.18)]
-            transition-transform duration-300
-=======
-            transition-transform duration-300
-            group-hover:translate-y-[-2px]
->>>>>>> develop
+            dark:border dark:border-[rgba(56,189,248,0.25)]
+            dark:shadow-[0_0_0_1px_rgba(56,189,248,0.25),0_12px_28px_rgba(56,189,248,0.18)]
+            transition-transform duration-300

-<<<<<<< HEAD
-            className="object-cover brightness-95 contrast-110 scale-[1.03] transition-transform duration-300 group-hover:scale-[1.06]"
-=======
-            className="object-cover grayscale brightness-95 contrast-110 scale-[1.03]"
->>>>>>> develop
+            className="object-cover brightness-95 contrast-110 scale-[1.03] transition-transform duration-300 group-hover:scale-[1.06]"

-<<<<<<< HEAD
-    text-[22px] md:text-[26px]
-    font-semibold
-    tracking-tight
-    leading-[1.2]
-    text-gray-950 dark:text-gray-100
-=======
-    text-[20px] md:text-[22px]
-    font-semibold
-    tracking-tight
-    leading-[1.25]
-    text-gray-950
->>>>>>> develop
+    text-[22px] md:text-[26px]
+    font-semibold
+    tracking-tight
+    leading-[1.2]
+    text-gray-950 dark:text-gray-100

-<<<<<<< HEAD
-          <p className="mt-4 text-[16px] md:text-[17px] leading-[1.65] text-gray-700 dark:text-gray-300 max-w-[60ch] line-clamp-3">
-=======
-          <p className="mt-4 text-[15px] md:text-[16px] leading-[1.7] text-gray-700 max-w-[60ch] line-clamp-3">
->>>>>>> develop
+          <p className="mt-4 text-[16px] md:text-[17px] leading-[1.65] text-gray-700 dark:text-gray-300 max-w-[60ch] line-clamp-3">

-<<<<<<< HEAD
-          {post.author?.name && (
-            <div className="mb-3 flex items-center gap-2 text-[13px] md:text-[14px] text-gray-500 dark:text-gray-400">
-              <button
-                type="button"
-                onClick={() => post.author && onAuthorSelect(post.author)}
-                className="flex items-center gap-2 hover:text-[`#ff00ff`] hover:underline underline-offset-4 transition"
-              >
-                {post.author?.image && (
-                  <span className="relative h-6 w-6 overflow-hidden rounded-full">
-                    <Image
-                      src={post.author.image}
-                      alt={post.author.name || 'Author'}
-                      fill
-                      className="object-cover"
-                    />
-                  </span>
-                )}
-                {post.author.name}
-              </button>
-              {formattedDate && <span>·</span>}
-              {formattedDate && <span>{formattedDate}</span>}
-            </div>
-          )}
-
-          {post.resourceLink && null}
-=======
-          {post.author && (
-            <div className="mb-3">
-              <AuthorModal
-                author={post.author}
-                publishedAt={post.publishedAt}
-              />
-            </div>
-          )}
-
-          {post.tags?.length ? (
-            <div className="flex flex-wrap gap-3">
-              {post.tags.map((tag, i) => {
-                const norm = tag.toLowerCase();
-                const active = selectedTags.includes(norm);
-
-                return (
-                  <button
-                    key={`${norm}-${i}`}
-                    onClick={() => onTagToggle(norm)}
-                    className={[
-                      'text-[13px] text-gray-400 transition underline-offset-4',
-                      active
-                        ? 'text-gray-950 underline'
-                        : 'hover:text-[`#ff00ff`] hover:underline',
-                    ].join(' ')}
-                  >
-                    #{norm}
-                  </button>
-                );
-              })}
-            </div>
-          ) : null}
-
-          {post.resourceLink && (
-            <a
-              href={post.resourceLink}
-              target="_blank"
-              rel="noopener noreferrer"
-              className="
-                mt-5 inline-flex
-                text-[13px] font-medium
-                text-gray-700
-                hover:text-[`#ff00ff`]
-                hover:underline
-                underline-offset-4
-              "
-            >
-              → {t('readArticle')}
-            </a>
-          )}
->>>>>>> develop
+          {post.author?.name && (
+            <div className="mb-3 flex items-center gap-2 text-[13px] md:text-[14px] text-gray-500 dark:text-gray-400">
+              <button
+                type="button"
+                onClick={() => post.author && onAuthorSelect(post.author)}
+                className="flex items-center gap-2 hover:text-[`#ff00ff`] hover:underline underline-offset-4 transition"
+              >
+                {post.author?.image && (
+                  <span className="relative h-6 w-6 overflow-hidden rounded-full">
+                    <Image
+                      src={post.author.image}
+                      alt={post.author.name || 'Author'}
+                      fill
+                      className="object-cover"
+                    />
+                  </span>
+                )}
+                {post.author.name}
+              </button>
+              {formattedDate && <span>·</span>}
+              {formattedDate && <span>{formattedDate}</span>}
+            </div>
+          )}

Also applies to: 59-65, 76-95, 105-118, 132-135, 141-218

🧰 Tools
🪛 Biome (2.1.2)

[error] 13-14: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 17-18: Expected a statement but instead found '>>>>>>> develop'.

Expected a statement here.

(parse)


[error] 15-15: Shouldn't redeclare 'useTranslations'. Consider to delete it or rename it.

'useTranslations' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'Post'. Consider to delete it or rename it.

'Post' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'PortableTextBlock'. Consider to delete it or rename it.

'PortableTextBlock' is defined here:

(lint/suspicious/noRedeclare)


[error] 17-17: Shouldn't redeclare 'PortableTextSpan'. Consider to delete it or rename it.

'PortableTextSpan' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogCard.tsx` around lines 6 - 18, The file has
unresolved git conflict markers in BlogCard.tsx causing build failures; resolve
by choosing the author-based branch: remove all conflict markers (<<<<<<<,
=======, >>>>>>>) and keep the imports from the author branch (useTranslations,
AuthorModal, type { Post, PortableTextBlock, PortableTextSpan }), ensure the
component uses the onAuthorSelect prop and the AuthorModal usage instead of the
locale-based branch, and reconcile styling and author/tag UI blocks to match the
author-driven implementation (replace the conflicting sections at the import
block and the UI blocks around the author/tag rendering and modal handling so
only the author-based code remains).

Comment on lines +297 to +309
{!selectedAuthor && allCategories.length > 0 && (
<div className="mt-5 flex flex-wrap gap-3">
<button
type="button"
onClick={() => setSelectedCategory(null)}
className={
!selectedCategory
? 'rounded-full border border-[#ff00ff] bg-[#ff00ff]/10 px-4 py-2 text-sm font-medium text-[#ff00ff] transition'
: 'rounded-full border border-gray-300 bg-white px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800'
}
>
All
</button>
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's read the BlogFilters.tsx file to see imports and current usage
head -50 frontend/components/blog/BlogFilters.tsx

Repository: DevLoversTeam/devlovers.net

Length of output: 1111


🏁 Script executed:

# Search for translation function usage in the file
rg -n "t\(" frontend/components/blog/BlogFilters.tsx | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 865


🏁 Script executed:

# Check if there's a useTranslation hook imported
rg -n "useTranslation\|import.*t\b" frontend/components/blog/BlogFilters.tsx

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

# Look for translation files to understand the structure
fd -e json -e ts -e js | grep -i "translation\|locales\|i18n\|lang" | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 144


🏁 Script executed:

# Search for other hardcoded strings in similar blog components to see if they're translated
rg -n "All\|allCategories" frontend/components/blog/ -A 2 -B 2

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

# Find translation/message files
fd -e json -e ts -e js | grep -E "(messages|locales|translations|i18n)" | grep -v node_modules | head -30

Repository: DevLoversTeam/devlovers.net

Length of output: 222


🏁 Script executed:

# Search for translation files in common locations
find frontend -type f \( -name "*.json" -o -name "*.ts" -o -name "*.js" \) | grep -E "(i18n|messages|locales|lang)" | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 222


🏁 Script executed:

# Look for where translation keys are defined
rg "all|All" --type json frontend/ | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 3427


🏁 Script executed:

# Check if there are translation files in public or messages directory
find . -type d -name "messages" -o -name "locales" -o -name "translations" 2>/dev/null | head -10

Repository: DevLoversTeam/devlovers.net

Length of output: 90


🏁 Script executed:

# Let's look at the i18n structure we found
ls -la frontend/i18n/

Repository: DevLoversTeam/devlovers.net

Length of output: 361


🏁 Script executed:

# Read the English translation file
cat frontend/messages/en.json

Repository: DevLoversTeam/devlovers.net

Length of output: 10873


🏁 Script executed:

# Search for 'all' key specifically in translation files
rg '"all"' frontend/messages/

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

# Search for category-related keys in translation files
rg -i "categor|author|article|blog" frontend/messages/ | head -30

Repository: DevLoversTeam/devlovers.net

Length of output: 1979


Localize the "All" category label.
This hardcoded string should be translated for consistency with the rest of the blog UI, which uses the translation system throughout. The 'all' key needs to be added to the translation files.

💡 Suggested change
-              All
+              {t('all')}
📝 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
{!selectedAuthor && allCategories.length > 0 && (
<div className="mt-5 flex flex-wrap gap-3">
<button
type="button"
onClick={() => setSelectedCategory(null)}
className={
!selectedCategory
? 'rounded-full border border-[#ff00ff] bg-[#ff00ff]/10 px-4 py-2 text-sm font-medium text-[#ff00ff] transition'
: 'rounded-full border border-gray-300 bg-white px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800'
}
>
All
</button>
{!selectedAuthor && allCategories.length > 0 && (
<div className="mt-5 flex flex-wrap gap-3">
<button
type="button"
onClick={() => setSelectedCategory(null)}
className={
!selectedCategory
? 'rounded-full border border-[`#ff00ff`] bg-[`#ff00ff`]/10 px-4 py-2 text-sm font-medium text-[`#ff00ff`] transition'
: 'rounded-full border border-gray-300 bg-white px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800'
}
>
{t('all')}
</button>
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogFilters.tsx` around lines 297 - 309, The "All"
label is hardcoded; replace it with a localized string using the component's
translation hook (e.g., useTranslation / t) so the button displays t('all')
instead of "All"; update the BlogFilters component where selectedAuthor,
allCategories, selectedCategory, and setSelectedCategory are used to call
t('all') and ensure the 'all' key is added to the translation files for each
locale; if the component doesn't yet import or call useTranslation, add that
import and hook initialization before using t.

Comment on lines 340 to 342
{!filteredPosts.length && (
<p className="text-center text-gray-500 mt-10">
{t('noPostsForTags')}
</p>
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
)}
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 | 🟡 Minor

Avoid duplicate empty-state messaging.
BlogGrid already renders a “no posts” message when empty, so this extra <p> duplicates the UI.

🧹 Suggested cleanup
-      {!filteredPosts.length && (
-        <p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
-      )}
📝 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
{!filteredPosts.length && (
<p className="text-center text-gray-500 mt-10">
{t('noPostsForTags')}
</p>
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
)}
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogFilters.tsx` around lines 340 - 342, Remove the
duplicate empty-state paragraph from BlogFilters.tsx: the JSX block that checks
{!filteredPosts.length && (<p className="text-center text-gray-500
mt-10">{t('noPosts')}</p>)} should be deleted so the empty-state is only
rendered by BlogGrid; locate the conditional that references filteredPosts in
the BlogFilters component and remove that <p> element, leaving BlogGrid to
handle the "no posts" message.

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

🤖 Fix all issues with AI agents
In `@frontend/app/`[locale]/blog/page.tsx:
- Around line 65-72: The categories query returns the raw title object instead
of a locale-resolved string; update the groq passed to client.fetch (the
categories query that assigns to categories) to project a locale-aware title
using coalesce with the current locale (e.g., title: coalesce(title[locale],
title.en, /* fallback */)) so the returned categories have a string title for
display; ensure you reference the same locale variable used elsewhere in
page.tsx.
♻️ Duplicate comments (2)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (2)

171-173: Pass locale to toLocaleDateString() for consistent formatting.

The date is formatted using the server's default locale rather than the page locale. This was flagged in a previous review.

🛠️ Suggested fix
          {post.publishedAt && (
-           <span>{new Date(post.publishedAt).toLocaleDateString()}</span>
+           <span>{new Date(post.publishedAt).toLocaleDateString(locale)}</span>
          )}

258-261: Pass locale to toLocaleDateString() in recommended posts.

Same issue as the main post date - should use the page locale for consistent formatting.

🛠️ Suggested fix
                      {item.publishedAt && (
                        <span>
-                         {new Date(item.publishedAt).toLocaleDateString()}
+                         {new Date(item.publishedAt).toLocaleDateString(locale)}
                        </span>
                      )}
🧹 Nitpick comments (4)
frontend/components/blog/BlogCard.tsx (2)

128-129: Simplify duplicate conditionals.

Two separate conditional checks for formattedDate can be consolidated.

♻️ Suggested simplification
-              {formattedDate && <span>·</span>}
-              {formattedDate && <span>{formattedDate}</span>}
+              {formattedDate && (
+                <>
+                  <span>·</span>
+                  <span>{formattedDate}</span>
+                </>
+              )}

133-133: Remove dead code.

{post.resourceLink && null} always evaluates to null or false, producing no output. If resource links are intentionally disabled, remove this line entirely.

♻️ Suggested fix
-          {post.resourceLink && null}
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (2)

177-177: Remove dead code.

This expression always evaluates to null regardless of the condition.

♻️ Suggested fix
-      {(post.tags?.length || 0) > 0 && null}

272-274: Remove dead code blocks.

These expressions always evaluate to null. If these features are intentionally disabled, remove the code entirely rather than leaving no-op statements.

♻️ Suggested fix
-      {post.resourceLink && null}
-
-      {(authorBio || authorName || authorMeta) && null}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab490fc and edf62b2.

⛔ 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 (3)
  • frontend/app/[locale]/blog/[slug]/PostDetails.tsx
  • frontend/app/[locale]/blog/page.tsx
  • frontend/components/blog/BlogCard.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/[locale]/blog/page.tsx (1)
frontend/components/blog/BlogFilters.tsx (1)
  • BlogFilters (93-345)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (2)
frontend/components/blog/BlogFilters.tsx (1)
  • Post (38-49)
frontend/client.ts (1)
  • client (3-8)
🔇 Additional comments (6)
frontend/app/[locale]/blog/page.tsx (1)

77-85: LGTM!

The UI updates and prop passing to BlogFilters are correctly aligned with the component's expected interface. The subtitle and layout changes look good.

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

3-12: LGTM!

The import changes are well-organized, and the type imports from BlogFilters ensure type consistency across components.


31-40: LGTM!

Good use of useMemo for date formatting with proper validation and locale awareness via Intl.DateTimeFormat.

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

46-63: LGTM!

The seededShuffle and hashString utilities provide deterministic shuffling based on the slug, ensuring consistent recommended posts across page loads. The implementation correctly uses a linear congruential generator.


94-106: LGTM!

The recommendedQuery correctly applies the same locale fallback pattern as the main query, ensuring consistent localization across the post detail and recommendations.


214-268: LGTM!

The recommended posts section is well-implemented with:

  • Deterministic selection via seeded shuffle
  • Responsive grid layout
  • Proper Next.js Image optimization
  • Accessible link structure with hover states

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

Comment on lines +65 to +72
const categories = await client.fetch(
groq`
*[_type == "category"] | order(orderRank asc) {
_id,
title
}
`
);
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 | 🟠 Major

Categories query lacks locale-aware title projection.

The categories query fetches raw title without locale coalesce, unlike the post fields. If title is stored as an object with locale keys (e.g., {en: "...", uk: "..."}), this will return the object instead of a resolved string, breaking category display.

🛠️ Suggested fix
  const categories = await client.fetch(
    groq`
      *[_type == "category"] | order(orderRank asc) {
        _id,
-       title
+       "title": coalesce(title[$locale], title[lower($locale)], title.uk, title.en, title.pl, title)
      }
-   `
+   `,
+   { locale }
  );
🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/blog/page.tsx around lines 65 - 72, The categories
query returns the raw title object instead of a locale-resolved string; update
the groq passed to client.fetch (the categories query that assigns to
categories) to project a locale-aware title using coalesce with the current
locale (e.g., title: coalesce(title[locale], title.en, /* fallback */)) so the
returned categories have a string title for display; ensure you reference the
same locale variable used elsewhere in page.tsx.

@ViktorSvertoka ViktorSvertoka merged commit 0109871 into develop Jan 15, 2026
8 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sanity branch January 15, 2026 22:11
This was referenced Jan 18, 2026
This was referenced Jan 25, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 23, 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