Skip to content

Sanity#176

Merged
ViktorSvertoka merged 2 commits into
developfrom
sanity
Jan 21, 2026
Merged

Sanity#176
ViktorSvertoka merged 2 commits into
developfrom
sanity

Conversation

@KomrakovaAnna
Copy link
Copy Markdown
Collaborator

@KomrakovaAnna KomrakovaAnna commented Jan 21, 2026

Adding brend colours
Adding main posts to blog and category pages
Fix for the text rendering on the post details page
changing UI in general

Summary by CodeRabbit

Release Notes

  • New Features

    • Clickable links now appear within blog post text content
    • Featured post display added to blog category pages
    • Author names are now interactive links for filtering posts by author
    • Category badges added to blog cards
  • Improvements

    • Breadcrumb navigation added to individual blog posts
    • Recommended posts section redesigned with enhanced layout and content previews
    • Blog card styling refined with improved typography and borders
    • Blog post sorting improved with better date handling and fallback logic
    • Navigation refined with persistent home link in blog menu

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

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 21, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 0cf755c
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/6970bbadcc54020008d4127e
😎 Deploy Preview https://deploy-preview-176--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 21, 2026

📝 Walkthrough

Walkthrough

This PR enhances the blog section UI/UX by introducing interactive author links, linkified URLs in post text, featured post sections in category pages, improved post filtering with resolved author parameters, and post ordering changes with caching adjustments.

Changes

Cohort / File(s) Summary
Post Details & Category Layout
frontend/app/[locale]/blog/[slug]/PostDetails.tsx, frontend/app/[locale]/blog/category/[category]/page.tsx
PostDetails now includes linkified text conversion, breadcrumb navigation (Blog > Title), interactive author links, and reworked recommended posts with grid layout and body previews. Category page adds featured post section with image, gradient overlay, and moves remaining posts to a grid layout.
Blog Components - Props & Propagation
frontend/components/blog/BlogCard.tsx, frontend/components/blog/BlogGrid.tsx, frontend/components/blog/BlogCategoryGrid.tsx
Added optional disableHoverColor prop to BlogCard and BlogGrid; BlogCategoryGrid propagates this prop through. BlogCard updates include styled category badges, reduced author avatar size, improved author display linking, and excerpt joined with newlines.
Blog Filtering & Navigation
frontend/components/blog/BlogFilters.tsx, frontend/components/blog/BlogCategoryLinks.tsx
BlogFilters refactored with resolvedAuthor logic derived from URL params, updated filtering and display logic, and clearAll now resets author/category query params. BlogCategoryLinks adds persistent home link as first navigation item.
Blog Page & Query Logic
frontend/app/[locale]/blog/page.tsx
Added noStore() to disable caching; changed post ordering to coalesce publishedAt with _createdAt fallback.
Configuration & Localization
frontend/client.ts, frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Updated Sanity client to disable CDN (useCdn: false). Added readMore translation key across English, Polish, and Ukrainian message files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Sanity #145: Modifies the same blog-area components (PostDetails, BlogCard, BlogGrid, BlogFilters) with overlapping changes to post rendering, layout, and query logic.
  • Sanity #155: Changes PostDetails.tsx and multiple blog components with similar category label mapping and post rendering enhancements.
  • Sanity #119: Modifies PostDetails.tsx with changes to post data fetching and author/body UI presentation.

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 URLs now dance in clickable delight,
Authors shine as links, oh what a sight!
Featured posts gleam with gradient's glow,
Breadcrumbs guide readers through the flow,
Blog reimagined, fresh and bright! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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 pull request title 'Sanity' is vague and does not clearly summarize the main changes; it lacks descriptive information about what was actually modified. Revise the title to be more descriptive, such as 'Add featured posts and improve blog UI styling' or 'Enhance blog pages with featured posts and unified styling', reflecting the primary changes outlined in the PR objectives.
✅ 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

🤖 Fix all issues with AI agents
In `@frontend/app/`[locale]/blog/category/[category]/page.tsx:
- Around line 80-146: The current logic always treats posts[0] as featuredPost
then only renders it if featuredPost.mainImage exists, which drops the first
post from the grid when it has no image; update the selection so featuredPost is
the first post in posts with a mainImage (e.g., find index via posts.findIndex(p
=> p.mainImage)), set restPosts to posts with that featured item removed (if
found) or to the full posts array when no featured post exists, and keep the
featuredDate logic tied to the chosen featuredPost; ensure BlogCategoryGrid
receives restPosts (or posts when no featured found) and adjust any rendering
guards that assume featuredPost is posts[0].
🧹 Nitpick comments (5)
frontend/client.ts (1)

6-6: Consider the performance implications of disabling CDN globally.

Setting useCdn: false at the client level affects all Sanity queries across the application. While this ensures fresh data, it increases latency and API usage for every request.

Given that the blog page already uses .withConfig({ useCdn: false }) to override this per-query, consider keeping useCdn: true as the default and only disabling it for specific queries that require real-time data. This would improve performance for pages that can tolerate slight staleness.

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

7-7: Redundant caching opt-outs.

Both export const revalidate = 0 and noStore() are used to disable caching. While this works, it's redundant—either one alone is sufficient. Consider using only noStore() since it's the more explicit approach for dynamic data fetching.

Also applies to: 28-28

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

9-15: Consider memoizing the empty callback to avoid unnecessary re-renders.

The inline () => {} creates a new function reference on each render. If BlogGrid or its children are memoized, this could cause unnecessary re-renders.

Suggested optimization
 'use client';

+import { useCallback } from 'react';
 import BlogGrid from '@/components/blog/BlogGrid';
 import type { Post } from '@/components/blog/BlogFilters';

+const noop = () => {};
+
 export function BlogCategoryGrid({ posts }: { posts: Post[] }) {
   if (!posts.length) return null;

   return (
     <BlogGrid
       posts={posts}
-      onAuthorSelect={() => {}}
+      onAuthorSelect={noop}
       disableHoverColor
     />
   );
 }
frontend/components/blog/BlogFilters.tsx (1)

132-140: clearAll leaves the search param behind.

If the intent is to reset all filters, consider removing search as well—or rename the action to reflect partial clearing.

🔧 Optional tweak
 const params = new URLSearchParams(searchParams?.toString() || '');
 params.delete('author');
 params.delete('category');
+params.delete('search');
 const nextPath = params.toString() ? `${pathname}?${params}` : pathname;
 router.replace(nextPath);
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)

48-67: Avoid stateful RegExp.test with a global regex.

test() on a global regex carries lastIndex state, which can behave inconsistently across parts. Safer to use a non-global check.

🔧 Safer URL check
 function linkifyText(text: string) {
   const urlRegex = /(https?:\/\/[^\s]+)/g;
   const parts = text.split(urlRegex);
   return parts.map((part, index) => {
     if (!part) return null;
-    if (urlRegex.test(part)) {
+    const isUrl = /^https?:\/\/\S+$/.test(part);
+    if (isUrl) {
       return (
         <a
           key={`link-${index}`}

Comment on lines +80 to +146
const featuredPost = posts[0];
const restPosts = posts.slice(1);
const featuredDate = featuredPost?.publishedAt
? new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).format(new Date(featuredPost.publishedAt))
: '';

return (
<main className="max-w-6xl mx-auto px-6 py-12">
<h1 className="text-4xl font-bold mb-4 text-center">
{displayTitle}
{categoryTitle}
</h1>
{featuredPost?.mainImage && (
<section className="mt-10">
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
<Image
src={featuredPost.mainImage}
alt={featuredPost.title}
width={1400}
height={800}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
priority={false}
/>
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-white/95 via-white/70 to-transparent dark:from-black/90 dark:via-black/60 sm:h-64" />
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
{featuredPost.categories?.[0] && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{featuredPost.categories[0]}
</div>
)}
<h2 className="mt-2 text-3xl font-semibold text-gray-900 transition group-hover:text-[var(--accent-primary)] dark:text-white dark:group-hover:text-[var(--accent-primary)] sm:text-4xl">
{featuredPost.title}
</h2>
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
{featuredPost.author?.image && (
<Image
src={featuredPost.author.image}
alt={featuredPost.author.name || 'Author'}
width={28}
height={28}
className="h-7 w-7 rounded-full object-cover"
/>
)}
{featuredPost.author?.name && (
<span>{featuredPost.author.name}</span>
)}
{featuredPost.author?.name && featuredDate && <span>·</span>}
{featuredDate && <span>{featuredDate}</span>}
</div>
<Link
href={`/blog/${featuredPost.slug.current}`}
className="absolute bottom-6 right-6 inline-flex h-11 w-11 items-center justify-center rounded-full bg-[var(--accent-primary)] text-white opacity-0 transition group-hover:opacity-100 hover:brightness-110"
aria-label={featuredPost.title}
>
<span aria-hidden="true">↗</span>
</Link>
</div>
</article>
</section>
)}
<div className="mt-12">
<BlogCategoryGrid posts={posts} />
<BlogCategoryGrid posts={restPosts} />
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

Avoid dropping the first post when it lacks an image.

featuredPost is always posts[0], but the featured section renders only when mainImage exists. If the first post lacks an image, it’s excluded from both the featured section and the grid.

✅ Suggested fix (select first post with an image, otherwise keep full list)
-const featuredPost = posts[0];
-const restPosts = posts.slice(1);
-const featuredDate = featuredPost?.publishedAt
+const featuredPost = posts.find(post => post.mainImage);
+const restPosts = featuredPost
+  ? posts.filter(post => post._id !== featuredPost._id)
+  : posts;
+const featuredDate = featuredPost?.publishedAt
   ? new Intl.DateTimeFormat(locale, {
       day: '2-digit',
       month: '2-digit',
       year: 'numeric',
     }).format(new Date(featuredPost.publishedAt))
   : '';

 ...
-{featuredPost?.mainImage && (
+{featuredPost && (
📝 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
const featuredPost = posts[0];
const restPosts = posts.slice(1);
const featuredDate = featuredPost?.publishedAt
? new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).format(new Date(featuredPost.publishedAt))
: '';
return (
<main className="max-w-6xl mx-auto px-6 py-12">
<h1 className="text-4xl font-bold mb-4 text-center">
{displayTitle}
{categoryTitle}
</h1>
{featuredPost?.mainImage && (
<section className="mt-10">
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
<Image
src={featuredPost.mainImage}
alt={featuredPost.title}
width={1400}
height={800}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
priority={false}
/>
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-white/95 via-white/70 to-transparent dark:from-black/90 dark:via-black/60 sm:h-64" />
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
{featuredPost.categories?.[0] && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{featuredPost.categories[0]}
</div>
)}
<h2 className="mt-2 text-3xl font-semibold text-gray-900 transition group-hover:text-[var(--accent-primary)] dark:text-white dark:group-hover:text-[var(--accent-primary)] sm:text-4xl">
{featuredPost.title}
</h2>
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
{featuredPost.author?.image && (
<Image
src={featuredPost.author.image}
alt={featuredPost.author.name || 'Author'}
width={28}
height={28}
className="h-7 w-7 rounded-full object-cover"
/>
)}
{featuredPost.author?.name && (
<span>{featuredPost.author.name}</span>
)}
{featuredPost.author?.name && featuredDate && <span>·</span>}
{featuredDate && <span>{featuredDate}</span>}
</div>
<Link
href={`/blog/${featuredPost.slug.current}`}
className="absolute bottom-6 right-6 inline-flex h-11 w-11 items-center justify-center rounded-full bg-[var(--accent-primary)] text-white opacity-0 transition group-hover:opacity-100 hover:brightness-110"
aria-label={featuredPost.title}
>
<span aria-hidden="true"></span>
</Link>
</div>
</article>
</section>
)}
<div className="mt-12">
<BlogCategoryGrid posts={posts} />
<BlogCategoryGrid posts={restPosts} />
const featuredPost = posts.find(post => post.mainImage);
const restPosts = featuredPost
? posts.filter(post => post._id !== featuredPost._id)
: posts;
const featuredDate = featuredPost?.publishedAt
? new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).format(new Date(featuredPost.publishedAt))
: '';
return (
<main className="max-w-6xl mx-auto px-6 py-12">
<h1 className="text-4xl font-bold mb-4 text-center">
{categoryTitle}
</h1>
{featuredPost && (
<section className="mt-10">
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
<Image
src={featuredPost.mainImage}
alt={featuredPost.title}
width={1400}
height={800}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
priority={false}
/>
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-white/95 via-white/70 to-transparent dark:from-black/90 dark:via-black/60 sm:h-64" />
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
{featuredPost.categories?.[0] && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{featuredPost.categories[0]}
</div>
)}
<h2 className="mt-2 text-3xl font-semibold text-gray-900 transition group-hover:text-[var(--accent-primary)] dark:text-white dark:group-hover:text-[var(--accent-primary)] sm:text-4xl">
{featuredPost.title}
</h2>
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
{featuredPost.author?.image && (
<Image
src={featuredPost.author.image}
alt={featuredPost.author.name || 'Author'}
width={28}
height={28}
className="h-7 w-7 rounded-full object-cover"
/>
)}
{featuredPost.author?.name && (
<span>{featuredPost.author.name}</span>
)}
{featuredPost.author?.name && featuredDate && <span>·</span>}
{featuredDate && <span>{featuredDate}</span>}
</div>
<Link
href={`/blog/${featuredPost.slug.current}`}
className="absolute bottom-6 right-6 inline-flex h-11 w-11 items-center justify-center rounded-full bg-[var(--accent-primary)] text-white opacity-0 transition group-hover:opacity-100 hover:brightness-110"
aria-label={featuredPost.title}
>
<span aria-hidden="true"></span>
</Link>
</div>
</article>
</section>
)}
<div className="mt-12">
<BlogCategoryGrid posts={restPosts} />
🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/blog/category/[category]/page.tsx around lines 80 -
146, The current logic always treats posts[0] as featuredPost then only renders
it if featuredPost.mainImage exists, which drops the first post from the grid
when it has no image; update the selection so featuredPost is the first post in
posts with a mainImage (e.g., find index via posts.findIndex(p => p.mainImage)),
set restPosts to posts with that featured item removed (if found) or to the full
posts array when no featured post exists, and keep the featuredDate logic tied
to the chosen featuredPost; ensure BlogCategoryGrid receives restPosts (or posts
when no featured found) and adjust any rendering guards that assume featuredPost
is posts[0].

@ViktorSvertoka ViktorSvertoka merged commit fed94e4 into develop Jan 21, 2026
9 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sanity branch January 21, 2026 15:27
This was referenced Jan 21, 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