Sanity#155
Conversation
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughAdds blog category pages, category navigation, a debounced header search API/UI, and header/menu blog variants; disables CDN for several blog queries and exports Next.js ISR revalidate = 0 on blog pages; normalizes category display (maps "Growth" → "Career") across components. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Browser UI
participant Header as BlogHeaderSearch
participant API as /api/blog-search (Route)
participant Sanity as Sanity Client/DB
User->>Header: open search UI
Header->>API: GET /api/blog-search (no-cache)
API->>Sanity: GROQ query (useCdn: false)
Sanity-->>API: posts JSON
API-->>Header: JSON results
Header->>Header: debounce & filter results (title/body)
User->>Header: select result
Header->>User: navigate to /blog/<slug> or /blog?search=<q>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ 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: 10
🤖 Fix all issues with AI agents
In `@frontend/app/`[locale]/layout.tsx:
- Around line 34-43: The blogCategories fetch can throw and crash the layout;
initialize blogCategories as an empty array and wrap the
client.withConfig(...).fetch(groq`...`) call in a try-catch, assigning the
result to blogCategories on success and logging the error (e.g.,
console.error('Failed to fetch blog categories:', error)) on failure so the
layout continues to render with an empty array; ensure the variable keeps the
same type Array<{ _id: string; title: string }> and reference the existing
blogCategories identifier and the client.withConfig(...).fetch call when making
the change.
In `@frontend/components/blog/BlogCategoryGrid.tsx`:
- Around line 6-9: BlogCategoryGrid currently returns null when posts is empty,
causing inconsistent UX with BlogGrid which shows a "no posts" message; remove
the early return in BlogCategoryGrid (the block checking posts.length) and
always render <BlogGrid posts={posts} onAuthorSelect={...} /> so BlogGrid can
handle the empty-state messaging, ensuring onAuthorSelect remains provided (keep
the existing no-op or forward a real handler if available).
In `@frontend/components/blog/BlogCategoryLinks.tsx`:
- Around line 75-81: Remove the local slugify implementation in
BlogCategoryLinks.tsx and import and reuse the existing, robust slugify from
frontend/lib/shop/slug.ts instead; specifically delete the local function named
slugify and add an import for the exported slugify (or its named export) at the
top of the file, then replace all local calls to slugify (in
functions/components within BlogCategoryLinks) to use the imported slugify so
Unicode normalization, diacritic removal, and dash cleanup are applied
consistently.
In `@frontend/components/blog/BlogNavLinks.tsx`:
- Around line 62-69: The items creation in the useMemo currently uses raw
category.title and query-based URLs which causes two issues: map the "Growth"
title to the display label "Career" and make URL patterns consistent with
BlogCategoryLinks; update the mapping in the useMemo that builds items (same
block using categories, category.title, isActive, currentCategory, isBlogPath)
to include a displayTitle (e.g., map "Growth" => "Career") and set isActive to
compare currentCategory to that mapped value, then update the rendering to use
category.displayTitle instead of category.title and change link generation from
a query param (/blog?category=...) to the path-based format
(/blog/category/<slug>) so both label and routing match BlogCategoryLinks.
- Around line 55-57: The baseLink constant in BlogNavLinks (used for
linkClassName fallback) is missing the focus-visible ring styles present in
BlogCategoryLinks; update the baseLink string in BlogNavLinks to include the
same focus-visible classes (e.g., focus:outline-none focus-visible:ring-2
focus-visible:ring-offset-2 focus-visible:ring-primary or the exact classes used
by BlogCategoryLinks) so keyboard focus shows a visible ring and the components
have consistent accessibility styling.
- Around line 38-53: The client-side fetch in useEffect uses the global
CDN-enabled client (client.fetch(...)) which can diverge from server-side calls;
update the fetch to use a non-CDN client by calling the no-cdn variant (e.g.,
client.withConfig({ useCdn: false }) or a dedicated client instance) when
executing categoriesQuery inside the useEffect so setCategories(result || [])
gets consistent data with server components; keep the existing active
cancellation pattern and error handling (useEffect,
client.fetch/categoriesQuery, setCategories).
In `@frontend/components/header/MainSwitcher.tsx`:
- Line 43: The main wrapper in the MainSwitcher component (the <main> element in
MainSwitcher.tsx with className "mx-auto px-6 min-h-[80vh]") lacks a max-width
constraint; update its className to include the same max-width used by
BlogCategoryPage (e.g., add "max-w-6xl") so the line becomes "mx-auto px-6
max-w-6xl min-h-[80vh]" to ensure consistent content width across pages.
In `@frontend/components/header/UnifiedHeader.tsx`:
- Line 20: The prop enableSearch declared on the UnifiedHeader component is
unused; either remove it from the component props or wire it into the header
logic—locate the UnifiedHeader functional component (and its props/type where
enableSearch?: boolean is declared) and either delete enableSearch from the
props/type or implement conditional rendering/behavior based on enableSearch
(e.g., show/hide the search input/button or enable search-related handlers like
onSearch) so the prop is actually consumed by UnifiedHeader.
In `@frontend/package.json`:
- Line 71: The package.json was changed to pin "ts-node" to an ancient ^1.7.1
which is incompatible with our "typescript": "^5" and will break builds; either
remove the "ts-node" dependency entirely if it's unused (we already use "tsx"
for scripts) or revert/update it to a modern compatible release (e.g., restore
"^10.9.2" or a current v10+ release) in package.json so ts-node matches
TypeScript 5; search for "ts-node" and "typescript" in package.json and update
accordingly and run a quick npm/yarn install + smoke test of any
ts-node-invoking scripts.
- Line 66: Package.json shows an accidental downgrade of the drizzle-kit
dependency to "^0.18.1" which is incompatible with the project’s drizzle-orm
"^0.45.1"; update the "drizzle-kit" entry in frontend/package.json back to the
compatible version (e.g., "^0.31.8" or any release >=0.31.8) or upgrade to a
release compatible with drizzle-orm, then reinstall dependencies and verify the
db-related npm scripts ("db:generate", "db:migrate", "db:studio") still run
correctly.
🧹 Nitpick comments (10)
frontend/components/blog/BlogCard.tsx (1)
41-42: Consider centralizing category label normalization.The "Growth" → "Career" mapping is now duplicated across several components; a small shared helper (e.g.,
normalizeCategoryLabel) would prevent drift and keep category naming consistent across the app.♻️ Possible local refactor (can later move to a shared util)
- const categoryLabel = - post.categories?.[0] === 'Growth' ? 'Career' : post.categories?.[0]; + const normalizeCategoryLabel = (label?: string) => + label === 'Growth' ? 'Career' : label; + const categoryLabel = normalizeCategoryLabel(post.categories?.[0]);frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)
165-165: Consider extracting theGrowth → Careermapping to a shared utility.This mapping is duplicated across multiple files (
BlogFilters.tsx,BlogCategoryLinks.tsx, and here). A shared helper function would improve maintainability and reduce the risk of inconsistencies if more mappings are added later.♻️ Suggested helper
// frontend/lib/blog/categoryLabel.ts export function getCategoryDisplayLabel(category: string): string { if (category === 'Growth') return 'Career'; return category; }frontend/components/blog/BlogCategoryLinks.tsx (1)
60-70: Consider whether "Home" belongs in the blog category navigation.The "Home" link navigates to
/which is outside the blog context. Semantically, it's not a blog category and may confuse users. If this is intentional for navigation convenience, consider placing it in a separate section or using a distinct visual treatment.frontend/app/[locale]/blog/category/[category]/page.tsx (4)
95-101: Duplicateslugifyfunction with inconsistent implementation.A
slugifyutility already exists atfrontend/lib/shop/slug.tswith more robust handling (NFKD normalization, repeated dash cleanup). This local version lacks those safeguards, which could cause category matching failures for titles with diacritics or special characters.♻️ Suggested fix: reuse the existing utility
import groq from 'groq'; import { notFound } from 'next/navigation'; import { getTranslations } from 'next-intl/server'; import { client } from '@/client'; import { BlogCategoryGrid } from '@/components/blog/BlogCategoryGrid'; +import { slugify } from '@/lib/shop/slug'; export const revalidate = 0; // ... rest of file ... -function slugify(value: string) { - return value - .toLowerCase() - .trim() - .replace(/[^a-z0-9\s-]/g, '') - .replace(/\s+/g, '-'); -}
30-35: Duplicate categories query — consider extracting to a shared module.The same GROQ query for fetching categories is defined in both this file and
frontend/app/[locale]/layout.tsx. Centralizing it would reduce duplication and ensure consistency.Also applies to: 45-47
54-55: Hardcoded "Growth" → "Career" mapping is duplicated.This display mapping also appears in
BlogCategoryLinks.tsx. Consider extracting this to a shared utility or constant to maintain consistency:// e.g., in a shared module export const getCategoryDisplayTitle = (title: string) => title === 'Growth' ? 'Career' : title;
21-21: Consider using a more specific type forbody.Using
any[]loses type safety. If you have a Portable Text block type definition, consider using it here.frontend/components/header/AppChrome.tsx (2)
43-45: Blog context bypasses header here but MainSwitcher adds it back.When
isBlogis true, this returns just{children}without a header. However,MainSwitcher(which wraps children inlayout.tsx) will then renderUnifiedHeaderwithvariant="blog". This layered approach works but may be confusing for future maintainers.Consider adding a brief comment explaining this delegation pattern, or consolidating header rendering responsibility to one component.
30-35:blogCategoriespassed to shop variant is unused.The shop variant of
UnifiedHeaderrendersNavLinksinstead ofBlogCategoryLinks, so passingblogCategorieshere has no effect. While harmless, it adds noise to the prop drilling.frontend/components/header/AppMobileMenu.tsx (1)
43-47: Consider using aMapor object lookup instead of chained conditionals.This is a minor readability suggestion for the links resolution:
const VARIANT_LINKS = { shop: NAV_LINKS, platform: SITE_LINKS, blog: [], } as const; const links = useMemo(() => VARIANT_LINKS[variant], [variant]);
| const blogCategories: Array<{ _id: string; title: string }> = await client | ||
| .withConfig({ useCdn: false }) | ||
| .fetch( | ||
| groq` | ||
| *[_type == "category"] | order(orderRank asc) { | ||
| _id, | ||
| title | ||
| } | ||
| ` | ||
| ); |
There was a problem hiding this comment.
Category fetch lacks error handling.
If the Sanity client fetch fails, this will throw and crash the layout. Consider wrapping in try-catch with a fallback to an empty array:
let blogCategories: Array<{ _id: string; title: string }> = [];
try {
blogCategories = await client
.withConfig({ useCdn: false })
.fetch(groq`...`);
} catch (error) {
console.error('Failed to fetch blog categories:', error);
}This ensures the layout remains functional even if the CMS is temporarily unavailable.
🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/layout.tsx around lines 34 - 43, The blogCategories
fetch can throw and crash the layout; initialize blogCategories as an empty
array and wrap the client.withConfig(...).fetch(groq`...`) call in a try-catch,
assigning the result to blogCategories on success and logging the error (e.g.,
console.error('Failed to fetch blog categories:', error)) on failure so the
layout continues to render with an empty array; ensure the variable keeps the
same type Array<{ _id: string; title: string }> and reference the existing
blogCategories identifier and the client.withConfig(...).fetch call when making
the change.
| export function BlogCategoryGrid({ posts }: { posts: Post[] }) { | ||
| if (!posts.length) return null; | ||
|
|
||
| return <BlogGrid posts={posts} onAuthorSelect={() => {}} />; |
There was a problem hiding this comment.
Inconsistent empty-state handling compared to BlogGrid.
BlogGrid displays a "no posts" message when posts is empty (see BlogGrid.tsx lines 14-16), but this component returns null. This creates inconsistent UX between the main blog page and category pages.
Consider delegating to BlogGrid to handle the empty state, or ensure the parent component handles the empty case with appropriate messaging.
💡 Suggested fix
export function BlogCategoryGrid({ posts }: { posts: Post[] }) {
- if (!posts.length) return null;
-
return <BlogGrid posts={posts} onAuthorSelect={() => {}} />;
}📝 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.
| export function BlogCategoryGrid({ posts }: { posts: Post[] }) { | |
| if (!posts.length) return null; | |
| return <BlogGrid posts={posts} onAuthorSelect={() => {}} />; | |
| export function BlogCategoryGrid({ posts }: { posts: Post[] }) { | |
| return <BlogGrid posts={posts} onAuthorSelect={() => {}} />; | |
| } |
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogCategoryGrid.tsx` around lines 6 - 9,
BlogCategoryGrid currently returns null when posts is empty, causing
inconsistent UX with BlogGrid which shows a "no posts" message; remove the early
return in BlogCategoryGrid (the block checking posts.length) and always render
<BlogGrid posts={posts} onAuthorSelect={...} /> so BlogGrid can handle the
empty-state messaging, ensuring onAuthorSelect remains provided (keep the
existing no-op or forward a real handler if available).
| function slugify(value: string) { | ||
| return value | ||
| .toLowerCase() | ||
| .trim() | ||
| .replace(/[^a-z0-9\s-]/g, '') | ||
| .replace(/\s+/g, '-'); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Duplicate slugify function — reuse the existing one from frontend/lib/shop/slug.ts.
There's already a more robust slugify implementation in frontend/lib/shop/slug.ts that handles:
- Unicode normalization (NFKD)
- Diacritic removal
- Consecutive/trailing dash cleanup
This local implementation is simpler but may produce inconsistent slugs for edge cases (e.g., accented characters).
♻️ Suggested fix
'use client';
import { Link, usePathname } from '@/i18n/routing';
import { cn } from '@/lib/utils';
+import { slugify } from '@/lib/shop/slug';
// ... rest of component ...
-function slugify(value: string) {
- return value
- .toLowerCase()
- .trim()
- .replace(/[^a-z0-9\s-]/g, '')
- .replace(/\s+/g, '-');
-}🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogCategoryLinks.tsx` around lines 75 - 81, Remove
the local slugify implementation in BlogCategoryLinks.tsx and import and reuse
the existing, robust slugify from frontend/lib/shop/slug.ts instead;
specifically delete the local function named slugify and add an import for the
exported slugify (or its named export) at the top of the file, then replace all
local calls to slugify (in functions/components within BlogCategoryLinks) to use
the imported slugify so Unicode normalization, diacritic removal, and dash
cleanup are applied consistently.
| useEffect(() => { | ||
| let active = true; | ||
| client | ||
| .fetch<Category[]>(categoriesQuery) | ||
| .then(result => { | ||
| if (!active) return; | ||
| setCategories(result || []); | ||
| }) | ||
| .catch(() => { | ||
| if (!active) return; | ||
| setCategories([]); | ||
| }); | ||
| return () => { | ||
| active = false; | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
Client-side fetch uses CDN while server-side fetches bypass it.
The server components in this PR use withConfig({ useCdn: false }), but this client-side fetch uses the default CDN-enabled client. This could cause inconsistent data between navigation and page content.
🔧 Suggested fix
useEffect(() => {
let active = true;
client
+ .withConfig({ useCdn: false })
.fetch<Category[]>(categoriesQuery)
.then(result => {📝 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.
| useEffect(() => { | |
| let active = true; | |
| client | |
| .fetch<Category[]>(categoriesQuery) | |
| .then(result => { | |
| if (!active) return; | |
| setCategories(result || []); | |
| }) | |
| .catch(() => { | |
| if (!active) return; | |
| setCategories([]); | |
| }); | |
| return () => { | |
| active = false; | |
| }; | |
| }, []); | |
| useEffect(() => { | |
| let active = true; | |
| client | |
| .withConfig({ useCdn: false }) | |
| .fetch<Category[]>(categoriesQuery) | |
| .then(result => { | |
| if (!active) return; | |
| setCategories(result || []); | |
| }) | |
| .catch(() => { | |
| if (!active) return; | |
| setCategories([]); | |
| }); | |
| return () => { | |
| active = false; | |
| }; | |
| }, []); |
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogNavLinks.tsx` around lines 38 - 53, The
client-side fetch in useEffect uses the global CDN-enabled client
(client.fetch(...)) which can diverge from server-side calls; update the fetch
to use a non-CDN client by calling the no-cdn variant (e.g., client.withConfig({
useCdn: false }) or a dedicated client instance) when executing categoriesQuery
inside the useEffect so setCategories(result || []) gets consistent data with
server components; keep the existing active cancellation pattern and error
handling (useEffect, client.fetch/categoriesQuery, setCategories).
| const baseLink = | ||
| linkClassName || | ||
| 'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-secondary hover:text-foreground'; |
There was a problem hiding this comment.
Missing focus-visible styles compared to BlogCategoryLinks.
BlogCategoryLinks includes focus-visible ring styles for accessibility, but this component's baseLink omits them. Consider adding for consistent keyboard navigation UX.
♿ Suggested fix
const baseLink =
linkClassName ||
- 'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-secondary hover:text-foreground';
+ 'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-secondary hover:text-foreground ' +
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ' +
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-background';📝 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.
| const baseLink = | |
| linkClassName || | |
| 'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-secondary hover:text-foreground'; | |
| const baseLink = | |
| linkClassName || | |
| 'rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-secondary hover:text-foreground ' + | |
| 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ' + | |
| 'focus-visible:ring-offset-2 focus-visible:ring-offset-background'; |
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogNavLinks.tsx` around lines 55 - 57, The baseLink
constant in BlogNavLinks (used for linkClassName fallback) is missing the
focus-visible ring styles present in BlogCategoryLinks; update the baseLink
string in BlogNavLinks to include the same focus-visible classes (e.g.,
focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
focus-visible:ring-primary or the exact classes used by BlogCategoryLinks) so
keyboard focus shows a visible ring and the components have consistent
accessibility styling.
| const items = useMemo(() => { | ||
| return categories | ||
| .filter(category => category.title) | ||
| .map(category => ({ | ||
| ...category, | ||
| isActive: isBlogPath && currentCategory === category.title, | ||
| })); | ||
| }, [categories, currentCategory, isBlogPath]); |
There was a problem hiding this comment.
Missing Growth → Career label mapping and inconsistent URL pattern.
Two inconsistencies with BlogCategoryLinks.tsx:
-
Missing label mapping:
BlogCategoryLinkstransforms "Growth" to "Career", but this component displays the rawcategory.title. -
Different URL patterns: This component uses
/blog?category=...(query param) whileBlogCategoryLinksuses/blog/category/...(path-based routing). This could lead to broken navigation or unexpected behavior.
🔧 Suggested fix for label mapping
const items = useMemo(() => {
return categories
.filter(category => category.title)
.map(category => ({
...category,
+ displayTitle: category.title === 'Growth' ? 'Career' : category.title,
isActive: isBlogPath && currentCategory === category.title,
}));
}, [categories, currentCategory, isBlogPath]);Then use category.displayTitle in the render.
Also applies to: 85-87
🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogNavLinks.tsx` around lines 62 - 69, The items
creation in the useMemo currently uses raw category.title and query-based URLs
which causes two issues: map the "Growth" title to the display label "Career"
and make URL patterns consistent with BlogCategoryLinks; update the mapping in
the useMemo that builds items (same block using categories, category.title,
isActive, currentCategory, isBlogPath) to include a displayTitle (e.g., map
"Growth" => "Career") and set isActive to compare currentCategory to that mapped
value, then update the rendering to use category.displayTitle instead of
category.title and change link generation from a query param
(/blog?category=...) to the path-based format (/blog/category/<slug>) so both
label and routing match BlogCategoryLinks.
| showAdminLink={showAdminLink} | ||
| blogCategories={blogCategories} | ||
| /> | ||
| <main className="mx-auto px-6 min-h-[80vh]">{children}</main> |
There was a problem hiding this comment.
Layout inconsistency: missing max-w constraint on blog main wrapper.
The BlogCategoryPage uses max-w-6xl mx-auto, but this main wrapper only has mx-auto px-6. This could cause blog content to appear wider than intended on large screens.
Consider adding a consistent max-width:
-<main className="mx-auto px-6 min-h-[80vh]">{children}</main>
+<main className="mx-auto max-w-6xl px-6 min-h-[80vh]">{children}</main>📝 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.
| <main className="mx-auto px-6 min-h-[80vh]">{children}</main> | |
| <main className="mx-auto max-w-6xl px-6 min-h-[80vh]">{children}</main> |
🤖 Prompt for AI Agents
In `@frontend/components/header/MainSwitcher.tsx` at line 43, The main wrapper in
the MainSwitcher component (the <main> element in MainSwitcher.tsx with
className "mx-auto px-6 min-h-[80vh]") lacks a max-width constraint; update its
className to include the same max-width used by BlogCategoryPage (e.g., add
"max-w-6xl") so the line becomes "mx-auto px-6 max-w-6xl min-h-[80vh]" to ensure
consistent content width across pages.
| variant: UnifiedHeaderVariant; | ||
| userExists: boolean; | ||
| showAdminLink?: boolean; | ||
| enableSearch?: boolean; |
There was a problem hiding this comment.
Unused prop: enableSearch is declared but never referenced.
This prop is added to the type but not used anywhere in the component. Either implement the search functionality or remove the prop to avoid confusion.
🤖 Prompt for AI Agents
In `@frontend/components/header/UnifiedHeader.tsx` at line 20, The prop
enableSearch declared on the UnifiedHeader component is unused; either remove it
from the component props or wire it into the header logic—locate the
UnifiedHeader functional component (and its props/type where enableSearch?:
boolean is declared) and either delete enableSearch from the props/type or
implement conditional rendering/behavior based on enableSearch (e.g., show/hide
the search input/button or enable search-related handlers like onSearch) so the
prop is actually consumed by UnifiedHeader.
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| "drizzle-kit": "^0.31.8", | ||
| "drizzle-kit": "^0.18.1", |
There was a problem hiding this comment.
Critical: drizzle-kit version downgraded from ^0.31.8 to ^0.18.1.
This appears to be an accidental downgrade. [email protected] can be used with [email protected] or higher — your project uses drizzle-orm: "^0.45.1" (line 37), which is incompatible with drizzle-kit 0.18.1. This will break the db:generate, db:migrate, and db:studio scripts.
Revert to the previous version or upgrade to a compatible release.
🐛 Proposed fix
- "drizzle-kit": "^0.18.1",
+ "drizzle-kit": "^0.31.8",📝 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.
| "drizzle-kit": "^0.18.1", | |
| "drizzle-kit": "^0.31.8", |
🤖 Prompt for AI Agents
In `@frontend/package.json` at line 66, Package.json shows an accidental downgrade
of the drizzle-kit dependency to "^0.18.1" which is incompatible with the
project’s drizzle-orm "^0.45.1"; update the "drizzle-kit" entry in
frontend/package.json back to the compatible version (e.g., "^0.31.8" or any
release >=0.31.8) or upgrade to a release compatible with drizzle-orm, then
reinstall dependencies and verify the db-related npm scripts ("db:generate",
"db:migrate", "db:studio") still run correctly.
| "jsdom": "^27.4.0", | ||
| "tailwindcss": "^4", | ||
| "ts-node": "^10.9.2", | ||
| "ts-node": "^1.7.1", |
There was a problem hiding this comment.
Critical: ts-node version downgraded from ^10.9.2 to ^1.7.1.
This is an extreme downgrade. Latest version: 10.9.2, last published: 2 years ago. Version 1.7.1 is from circa 2016 and does not support TypeScript 5 (your project uses "typescript": "^5"). This will cause immediate failures if ts-node is invoked.
Note: Your project already uses tsx for running TypeScript scripts. Consider whether ts-node is needed at all — if not, remove it entirely. Otherwise, revert to the correct version.
🐛 Proposed fix (if ts-node is still needed)
- "ts-node": "^1.7.1",
+ "ts-node": "^10.9.2",Alternative: Remove ts-node entirely if unused
- "ts-node": "^1.7.1",📝 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.
| "ts-node": "^1.7.1", | |
| "ts-node": "^10.9.2", |
| "ts-node": "^1.7.1", |
🤖 Prompt for AI Agents
In `@frontend/package.json` at line 71, The package.json was changed to pin
"ts-node" to an ancient ^1.7.1 which is incompatible with our "typescript": "^5"
and will break builds; either remove the "ts-node" dependency entirely if it's
unused (we already use "tsx" for scripts) or revert/update it to a modern
compatible release (e.g., restore "^10.9.2" or a current v10+ release) in
package.json so ts-node matches TypeScript 5; search for "ts-node" and
"typescript" in package.json and update accordingly and run a quick npm/yarn
install + smoke test of any ts-node-invoking scripts.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/components/blog/BlogFilters.tsx (1)
334-358: “All” doesn’t clear a?category=filter.When the page loads with a category query param,
resolvedCategoryis driven by the URL. Clicking “All” only clears local state, so the filter stays active and the button won’t become selected. Consider removing thecategoryparam on click.🐛 Suggested fix
- <button - type="button" - onClick={() => setSelectedCategory(null)} + <button + type="button" + onClick={() => { + setSelectedCategory(null); + if (searchParams?.get('category')) { + const params = new URLSearchParams(searchParams.toString()); + params.delete('category'); + const nextPath = params.toString() + ? `${pathname}?${params}` + : pathname; + router.replace(nextPath); + } + }} className={ !resolvedCategory ? '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' } >
🤖 Fix all issues with AI agents
In `@frontend/app/globals.css`:
- Line 2: The `@import` line in globals.css pulls Font Awesome directly from an
unversioned raw GitHub master URL which is unstable and a supply-chain risk;
replace that import with a pinned, official CDN URL (e.g., jsDelivr with a
specific version) or remove the import and install/use the local package
`@fortawesome/fontawesome-free` and import its CSS from node_modules instead,
updating the existing `@import` statement (or equivalent stylesheet reference) to
the chosen pinned CDN path or local package path so the dependency is versioned
and reliable.
In `@frontend/components/blog/BlogHeaderSearch.tsx`:
- Around line 38-73: The effect in BlogHeaderSearch that fetches SEARCH_ENDPOINT
can loop forever on errors because items.length remains 0 and isLoading is set
back to false; add a ref or state (e.g., hasFetchedRef or hasFetched state) to
mark that a fetch was attempted and check it in the useEffect so the fetch runs
only once per open (and reset that flag when the panel closes). Update the fetch
effect to set hasFetched to true at start (or in finally) and include hasFetched
in the effect guard (if (!open || items.length || isLoading || hasFetched)
return), and clear hasFetched when open becomes false to allow refetch on
reopen.
♻️ Duplicate comments (1)
frontend/components/header/UnifiedHeader.tsx (1)
17-23:enableSearchprop is still unused.This was already flagged in a previous review; it’s still declared but not consumed. Consider removing it or wiring it to conditional search rendering.
🧹 Nitpick comments (2)
frontend/app/globals.css (1)
1-1: Prefernext/font/googleover CSS@importfor Lato.CSS
@importis render‑blocking and adds a third‑party request. In Next.js 15,next/font/googlecan self‑host Lato with better performance/privacy.frontend/app/api/blog-search/route.ts (1)
5-14: Consider capping the search payload.This endpoint returns all posts (with body content) without any limit, so the response can grow unbounded and slow the header search as content scales. A simple slice cap keeps payloads predictable (and you can later add query-based filtering if needed).
♻️ Suggested diff (add a cap; pick an appropriate limit)
- *[_type == "post" && defined(slug.current)] | order(publishedAt desc) { + *[_type == "post" && defined(slug.current)] | order(publishedAt desc)[0...200] { _id, "title": coalesce(title.en, title.uk, title.pl, title), "body": coalesce(body.en, body.uk, body.pl, body)[]{ ..., children[]{ text } }, slug }
| @@ -1,3 +1,5 @@ | |||
| @import url(https://fonts.googleapis.com/css?family=Lato:100,300,400,700); | |||
| @import url(https://raw.github.com/FortAwesome/Font-Awesome/master/docs/assets/css/font-awesome.min.css); | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /repo && find . -name "globals.css" -o -name "globals.css" | head -5Repository: DevLoversTeam/devlovers.net
Length of output: 126
🏁 Script executed:
cat -n frontend/app/globals.css | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 939
🌐 Web query:
Font Awesome official CDN URL latest stable version free
💡 Result:
Use jsDelivr (official npm package) for the latest free release — CSS minified (v7.1.0):
https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.1.0/css/all.min.css. [1][2]
Sources:
[1] FortAwesome GitHub releases (7.1.0).
[2] jsDelivr package page for @fortawesome/fontawesome-free (v7.1.0).
Don't load Font Awesome from raw GitHub master.
This URL is unversioned and points to the master branch, which can change or break unexpectedly—a supply-chain and stability risk. Use a pinned official CDN URL or install @fortawesome/fontawesome-free locally.
🔧 Example solutions
Official jsDelivr CDN (v7.1.0):
-@import url(https://raw.github.com/FortAwesome/Font-Awesome/master/docs/assets/css/font-awesome.min.css);
+@import url(https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.1.0/css/all.min.css);Or install locally:
-@import url(https://raw.github.com/FortAwesome/Font-Awesome/master/docs/assets/css/font-awesome.min.css);
+@import '@fortawesome/fontawesome-free/css/all.min.css';📝 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.
| @import url(https://raw.github.com/FortAwesome/Font-Awesome/master/docs/assets/css/font-awesome.min.css); | |
| `@import` url(https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.1.0/css/all.min.css); |
🤖 Prompt for AI Agents
In `@frontend/app/globals.css` at line 2, The `@import` line in globals.css pulls
Font Awesome directly from an unversioned raw GitHub master URL which is
unstable and a supply-chain risk; replace that import with a pinned, official
CDN URL (e.g., jsDelivr with a specific version) or remove the import and
install/use the local package `@fortawesome/fontawesome-free` and import its CSS
from node_modules instead, updating the existing `@import` statement (or
equivalent stylesheet reference) to the chosen pinned CDN path or local package
path so the dependency is versioned and reliable.
| export function BlogHeaderSearch() { | ||
| const [open, setOpen] = useState(false); | ||
| const [value, setValue] = useState(''); | ||
| const [items, setItems] = useState<PostSearchItem[]>([]); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
| const inputRef = useRef<HTMLInputElement>(null); | ||
| const router = useRouter(); | ||
| const debounceRef = useRef<number | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| if (!open) return; | ||
| inputRef.current?.focus(); | ||
| }, [open]); | ||
|
|
||
| useEffect(() => { | ||
| if (!open || items.length || isLoading) return; | ||
| let active = true; | ||
| setIsLoading(true); | ||
| fetch(SEARCH_ENDPOINT, { cache: 'no-store' }) | ||
| .then(response => (response.ok ? response.json() : [])) | ||
| .then((result: PostSearchItem[]) => { | ||
| if (!active) return; | ||
| setItems(Array.isArray(result) ? result : []); | ||
| }) | ||
| .catch(() => { | ||
| if (!active) return; | ||
| setItems([]); | ||
| }) | ||
| .finally(() => { | ||
| if (!active) return; | ||
| setIsLoading(false); | ||
| }); | ||
| return () => { | ||
| active = false; | ||
| }; | ||
| }, [open, items.length, isLoading]); |
There was a problem hiding this comment.
Failed fetch can loop indefinitely.
If the request errors, items.length stays 0 and isLoading becomes false, so the effect re-runs and refetches repeatedly while the panel is open. Add a “fetch attempted” guard (and optionally reset it when closed) to prevent infinite retries.
🐛 Suggested fix
export function BlogHeaderSearch() {
const [open, setOpen] = useState(false);
const [value, setValue] = useState('');
const [items, setItems] = useState<PostSearchItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
const debounceRef = useRef<number | null>(null);
+ const didFetchRef = useRef(false);
useEffect(() => {
- if (!open || items.length || isLoading) return;
+ if (!open) {
+ didFetchRef.current = false;
+ return;
+ }
+ if (items.length || isLoading || didFetchRef.current) return;
+ didFetchRef.current = true;
let active = true;
setIsLoading(true);
fetch(SEARCH_ENDPOINT, { cache: 'no-store' })
.then(response => (response.ok ? response.json() : []))
.then((result: PostSearchItem[]) => {
if (!active) return;
setItems(Array.isArray(result) ? result : []);
})
.catch(() => {
if (!active) return;
setItems([]);
})
.finally(() => {
if (!active) return;
setIsLoading(false);
});
return () => {
active = false;
};
- }, [open, items.length, isLoading]);
+ }, [open, items.length, isLoading]);🤖 Prompt for AI Agents
In `@frontend/components/blog/BlogHeaderSearch.tsx` around lines 38 - 73, The
effect in BlogHeaderSearch that fetches SEARCH_ENDPOINT can loop forever on
errors because items.length remains 0 and isLoading is set back to false; add a
ref or state (e.g., hasFetchedRef or hasFetched state) to mark that a fetch was
attempted and check it in the useEffect so the fetch runs only once per open
(and reset that flag when the panel closes). Update the fetch effect to set
hasFetched to true at start (or in finally) and include hasFetched in the effect
guard (if (!open || items.length || isLoading || hasFetched) return), and clear
hasFetched when open becomes false to allow refetch on reopen.
Adding the header to the blog page and category pages
Summary by CodeRabbit
New Features
UI Updates
✏️ Tip: You can customize this high-level summary in your review settings.