Sanity#145
Conversation
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughRefactors 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 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: Duplicatetypeandfieldsdeclarations in the title field definition.The
titlefield has duplicatedtype: 'object'andfieldsarray declarations within the samedefineFieldcall. 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 && nulldoes nothing and adds noise.🧹 Cleanup
- {(post.tags?.length || 0) > 0 && null}frontend/components/blog/BlogFilters.tsx (2)
85-87:plainTextExcerptshould 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-opselectedCategory && null.
This expression is dead and can be deleted.🧹 Cleanup
- {selectedCategory && null}
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonstudio/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
frontend/app/[locale]/blog/[slug]/PostDetails.tsxfrontend/app/[locale]/blog/page.tsxfrontend/components/blog/BlogCard.tsxfrontend/components/blog/BlogFilters.tsxfrontend/components/blog/BlogGrid.tsxfrontend/messages/en.jsonfrontend/messages/pl.jsonfrontend/messages/uk.jsonstudio/package.jsonstudio/sanity.config.tsstudio/schemaTypes/category.tsstudio/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 >?
(parse)
[error] 309-309: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 309-309: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 309-309: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 309-309: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 309-309: Unexpected token. Did you mean {'>'} or >?
(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 >?
(parse)
[error] 94-94: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 94-94: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 94-94: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 94-94: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 94-94: Unexpected token. Did you mean {'>'} or >?
(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 >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 135-135: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 138-138: Unexpected token. Did you mean {'}'} or }?
(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 >?
(parse)
[error] 218-218: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 218-218: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 218-218: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 218-218: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 218-218: Unexpected token. Did you mean {'>'} or >?
(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.encorrectly 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
orderRankFieldis correctly configured withtype: 'category'matching the document type, enabling drag-and-drop ordering in the Sanity Studio.
21-21: LGTM!Registering
orderRankOrderingenables the custom sort order to be applied when viewing categories.studio/sanity.config.ts (2)
4-4: LGTM!Correct import of
orderableDocumentListDeskItemfor the custom desk structure.
14-31: LGTM!The custom structure configuration is well-implemented:
- Correctly passes
S(StructureBuilder) andcontextto the structure function- Standard document types (post, author) use
S.documentTypeListItem()- Category uses
orderableDocumentListDeskItemwith proper parameters for drag-and-drop orderingvisionTool()is retained for GROQ query testingstudio/package.json (1)
18-18: The@sanity/orderable-document-listpackage is properly specified at version1.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
onAuthorSelectprop 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_idandslugkeeps 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.
| {(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> | ||
| )} |
There was a problem hiding this comment.
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.
| <<<<<<< 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 |
There was a problem hiding this comment.
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).
| {!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> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the BlogFilters.tsx file to see imports and current usage
head -50 frontend/components/blog/BlogFilters.tsxRepository: 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 -20Repository: 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.tsxRepository: 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 -20Repository: 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 2Repository: 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 -30Repository: 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 -20Repository: DevLoversTeam/devlovers.net
Length of output: 222
🏁 Script executed:
# Look for where translation keys are defined
rg "all|All" --type json frontend/ | head -20Repository: 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 -10Repository: 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.jsonRepository: 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 -30Repository: 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.
| {!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.
| {!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> | ||
| )} |
There was a problem hiding this comment.
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.
| {!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.
There was a problem hiding this comment.
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 totoLocaleDateString()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 totoLocaleDateString()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
formattedDatecan 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 tonullorfalse, 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
nullregardless 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
⛔ Files ignored due to path filters (2)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonstudio/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (3)
frontend/app/[locale]/blog/[slug]/PostDetails.tsxfrontend/app/[locale]/blog/page.tsxfrontend/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
BlogFiltersare 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
BlogFiltersensure type consistency across components.
31-40: LGTM!Good use of
useMemofor date formatting with proper validation and locale awareness viaIntl.DateTimeFormat.frontend/app/[locale]/blog/[slug]/PostDetails.tsx (3)
46-63: LGTM!The
seededShuffleandhashStringutilities 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
recommendedQuerycorrectly 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
Imageoptimization- Accessible link structure with hover states
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const categories = await client.fetch( | ||
| groq` | ||
| *[_type == "category"] | order(orderRank asc) { | ||
| _id, | ||
| title | ||
| } | ||
| ` | ||
| ); |
There was a problem hiding this comment.
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.
redesign of blog page
Summary by CodeRabbit
New Features
Changed Behavior
Localization
Chores
✏️ Tip: You can customize this high-level summary in your review settings.