feat: search integration with backend catalogue#250
Conversation
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughReplaces the placeholder public explore page with a client-side ExplorePage that reads URL params, fetches a paginated catalogue via useQuery, and adds header, category bar, and WhatsApp FAB into a new public layout; also adds debounce hook and several new UI components. (≤50 words) Changes
Sequence DiagramsequenceDiagram
participant User
participant PublicHeader
participant useDebounce
participant Router as BrowserRouter
participant ExplorePage
participant productApi
participant UI as ProductGrid
User->>PublicHeader: types/search input
PublicHeader->>useDebounce: provide value
Note over useDebounce: debounce (≈500ms)
useDebounce->>Router: push/replace /explore?search=...
Router->>ExplorePage: URL params update (search,category,page)
ExplorePage->>productApi: useQuery(fetch catalogue with params)
productApi-->>ExplorePage: returns paginated data
ExplorePage->>UI: render grid / empty / error / pagination
UI-->>User: display results / actions
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
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📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/web/src/components/layout/public-header.tsx (1)
90-96: Camera search button is non-functional.The button has an
aria-label="Search by image"but noonClickhandler. Consider either removing it for now or adding a TODO/disabled state to clarify it's a future feature.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/layout/public-header.tsx` around lines 90 - 96, The Camera search button in the PublicHeader is non-functional because the <button> with aria-label "Search by image" and the Camera component inside has no onClick handler or disabled/placeholder state; either remove the button, mark it visually and semantically disabled (add disabled attribute and aria-disabled or a TODO tooltip/aria-describedby) or add a temporary onClick stub that opens a placeholder modal/toast indicating the feature is pending; update the element around the Camera component (the <button> in public-header.tsx / PublicHeader) accordingly so accessibility labels match the disabled/placeholder behavior.apps/web/src/components/shared/whatsapp-fab.tsx (1)
7-9: Move placeholder phone number to configuration.The hardcoded phone number with "Replace with actual business number" comment should be moved to environment variables or a configuration file to avoid deploying placeholder values to production.
- const whatsappNumber = "+2348012345678"; // Replace with actual business number + const whatsappNumber = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || "+2348012345678";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/shared/whatsapp-fab.tsx` around lines 7 - 9, The whatsappNumber is hardcoded in the whatsapp-fab component (const whatsappNumber) creating a risk of shipping placeholder values; move this value into configuration by reading it from an environment/config variable (e.g., process.env.NEXT_PUBLIC_WHATSAPP_NUMBER or a config utility) and fall back to a safe default or disable the FAB if not set, then update whatsappUrl construction (const whatsappUrl) to use the configured number and keep message (const message) as-is or also configurable if desired; ensure any build-time/public env variable uses the NEXT_PUBLIC prefix for client-side access and add a short comment documenting the new env var.apps/web/src/app/(public)/explore/page.tsx (2)
105-128: Pagination doesn't scale well for many pages.Rendering a button for every page becomes unwieldy with large catalogs. Consider truncating with ellipsis (e.g.,
1 2 ... 5 6 7 ... 41 42) or using prev/next buttons with a page indicator.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(public)/explore/page.tsx around lines 105 - 128, The pagination currently renders a button per page (Array.from({ length: totalPages }) ...) which doesn't scale; replace that full list with a truncated pagination UI: compute a visible window of page numbers around the current page (use symbols page and totalPages) and render first/last pages with ellipses between gaps, plus Prev/Next buttons that use the existing router.push logic and searchParams updates; update the rendering in the same component (where totalPages, page, searchParams, router are used) to map only the computed pages and ellipsis tokens to buttons/elements so large catalogs show something like "1 2 ... 5 6 7 ... 41 42" while preserving the existing onClick behavior and active styling.
38-40: Category slug formatting doesn't handle hyphenated names well.For slugs like
"home-kitchen", the current formatting produces"Home-kitchen"instead of a proper display name. Consider using a lookup map or proper title-casing.+const formatCategoryName = (slug: string) => + slug.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' & '); + <h1 className="text-2xl font-black tracking-tight text-foreground md:text-3xl"> - {search ? `Results for "${search}"` : category && category !== "all" ? `${category.charAt(0).toUpperCase() + category.slice(1)}` : "Explore Products"} + {search ? `Results for "${search}"` : category && category !== "all" ? formatCategoryName(category) : "Explore Products"} </h1>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/`(public)/explore/page.tsx around lines 38 - 40, The heading currently capitalizes only the first character of the category slug (category.charAt(0).toUpperCase() + category.slice(1)), which leaves hyphenated slugs like "home-kitchen" formatted as "Home-kitchen"; replace that logic with a slug-to-title formatter: add a helper (e.g., formatCategorySlug) in page.tsx that splits the category on hyphens, capitalizes each segment, and joins them with spaces (or use a lookup map for custom display names), then use formatCategorySlug(category) inside the <h1> instead of the existing one-char capitalization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/app/`(public)/explore/page.tsx:
- Around line 1-14: The page component ExplorePage uses useSearchParams (and
useRouter) and must be rendered inside a React Suspense boundary to satisfy
Next.js App Router prerender rules; update ExplorePage's JSX to wrap the UI that
relies on useSearchParams (the return value of the ExplorePage function) in a
<React.Suspense fallback={...}> (use an appropriate fallback such as the
existing <Skeleton /> or simple placeholder) so that useSearchParams is executed
within the Suspense boundary; keep the existing import of React (import * as
React from "react") and reference React.Suspense when wrapping the content.
In `@apps/web/src/components/layout/category-bar.tsx`:
- Around line 8-19: Update the hardcoded CATEGORIES constant so its slug set
matches the backend seed data: replace/remove frontend-only slugs
("phones-gadgets", "sports-fitness", "baby-kids") and add the missing backend
slugs ("auto-parts", "agriculture", "office-stationery"), ensuring each entry in
the CATEGORIES array (symbol name: CATEGORIES) pairs the human-friendly name
with the exact slug used in backend seed.ts; verify spelling and casing against
apps/backend/src/prisma/seed.ts and adjust the display names if needed so UI
labels map correctly to backend categories.
In `@apps/web/src/components/layout/public-header.tsx`:
- Around line 23-29: The effect currently reads searchQuery but doesn't include
it in the dependency array, causing an ESLint warning; replace that dependency
pattern by tracking the last-seen URL value in a ref so the effect only depends
on searchParams: add a ref (e.g., const prevUrlRef = React.useRef<string>(''))
near the top of the component, then inside the React.useEffect that reads
searchParams compute const urlSearch = searchParams.get("search") || "" and
compare to prevUrlRef.current — if different call setSearchQuery(urlSearch) and
set prevUrlRef.current = urlSearch; keep the effect’s dependency array to
[searchParams] (no searchQuery) so the loop is avoided and the linter warning is
resolved.
In `@apps/web/src/components/shared/whatsapp-fab.tsx`:
- Around line 39-45: The tooltip never shows because the ancestor wrapper lacks
the required Tailwind "group" class and the tooltip relies on group-hover to
change opacity; update the outer wrapper element in the whatsapp-fab component
(the top-level container in WhatsAppFab / whatsapp-fab.tsx) to include the
"group" class, and adjust the tooltip div's classes: remove the conditional
"hidden" + "group-hover:block" pattern and instead use persistent layout (e.g.,
keep "lg:block") with "opacity-0 transition-opacity group-hover:opacity-100" so
the tooltip becomes visible on group hover.
In `@apps/web/src/components/ui/logo.tsx`:
- Line 8: The Logo component currently accepts a variant?: "light" | "dark" prop
(LogoProps / function Logo) but never uses it, causing wrong text colors; update
the Logo component to read variant (default "light") from props and apply
conditional classes when rendering the wordmark and subtitle (e.g., use
"text-foreground" for light and a light/white text class for dark) by switching
the className via your cn/classnames helper or a conditional template string
inside the Logo render; ensure both the main wordmark element and any small
subtitle/descriptor elements use the conditional class so callers passing
variant="dark" (e.g., login/register/admin pages) get proper contrast.
---
Nitpick comments:
In `@apps/web/src/app/`(public)/explore/page.tsx:
- Around line 105-128: The pagination currently renders a button per page
(Array.from({ length: totalPages }) ...) which doesn't scale; replace that full
list with a truncated pagination UI: compute a visible window of page numbers
around the current page (use symbols page and totalPages) and render first/last
pages with ellipses between gaps, plus Prev/Next buttons that use the existing
router.push logic and searchParams updates; update the rendering in the same
component (where totalPages, page, searchParams, router are used) to map only
the computed pages and ellipsis tokens to buttons/elements so large catalogs
show something like "1 2 ... 5 6 7 ... 41 42" while preserving the existing
onClick behavior and active styling.
- Around line 38-40: The heading currently capitalizes only the first character
of the category slug (category.charAt(0).toUpperCase() + category.slice(1)),
which leaves hyphenated slugs like "home-kitchen" formatted as "Home-kitchen";
replace that logic with a slug-to-title formatter: add a helper (e.g.,
formatCategorySlug) in page.tsx that splits the category on hyphens, capitalizes
each segment, and joins them with spaces (or use a lookup map for custom display
names), then use formatCategorySlug(category) inside the <h1> instead of the
existing one-char capitalization.
In `@apps/web/src/components/layout/public-header.tsx`:
- Around line 90-96: The Camera search button in the PublicHeader is
non-functional because the <button> with aria-label "Search by image" and the
Camera component inside has no onClick handler or disabled/placeholder state;
either remove the button, mark it visually and semantically disabled (add
disabled attribute and aria-disabled or a TODO tooltip/aria-describedby) or add
a temporary onClick stub that opens a placeholder modal/toast indicating the
feature is pending; update the element around the Camera component (the <button>
in public-header.tsx / PublicHeader) accordingly so accessibility labels match
the disabled/placeholder behavior.
In `@apps/web/src/components/shared/whatsapp-fab.tsx`:
- Around line 7-9: The whatsappNumber is hardcoded in the whatsapp-fab component
(const whatsappNumber) creating a risk of shipping placeholder values; move this
value into configuration by reading it from an environment/config variable
(e.g., process.env.NEXT_PUBLIC_WHATSAPP_NUMBER or a config utility) and fall
back to a safe default or disable the FAB if not set, then update whatsappUrl
construction (const whatsappUrl) to use the configured number and keep message
(const message) as-is or also configurable if desired; ensure any
build-time/public env variable uses the NEXT_PUBLIC prefix for client-side
access and add a short comment documenting the new env var.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 70f92be7-8ac9-4314-9de1-72e1a407a145
📒 Files selected for processing (7)
apps/web/src/app/(public)/explore/page.tsxapps/web/src/app/(public)/layout.tsxapps/web/src/components/layout/category-bar.tsxapps/web/src/components/layout/public-header.tsxapps/web/src/components/shared/whatsapp-fab.tsxapps/web/src/components/ui/logo.tsxapps/web/src/hooks/use-debounce.ts
| const CATEGORIES = [ | ||
| { name: "All", slug: "all" }, | ||
| { name: "Electronics", slug: "electronics" }, | ||
| { name: "Fashion", slug: "fashion" }, | ||
| { name: "Home & Kitchen", slug: "home-kitchen" }, | ||
| { name: "Health & Beauty", slug: "health-beauty" }, | ||
| { name: "Food & Groceries", slug: "food-groceries" }, | ||
| { name: "Phones & Gadgets", slug: "phones-gadgets" }, | ||
| { name: "Sports & Fitness", slug: "sports-fitness" }, | ||
| { name: "Baby & Kids", slug: "baby-kids" }, | ||
| { name: "Other", slug: "other" }, | ||
| ]; |
There was a problem hiding this comment.
Category slugs mismatch with backend database.
The hardcoded CATEGORIES array contains slugs that don't exist in the backend database, and is missing slugs that do exist. Based on the backend seed data at apps/backend/src/prisma/seed.ts:
Frontend has but backend doesn't:
phones-gadgetssports-fitnessbaby-kids
Backend has but frontend doesn't:
auto-partsagricultureoffice-stationery
Users selecting mismatched categories will get no results, and some backend categories are completely inaccessible.
Proposed fix to align with backend
const CATEGORIES = [
{ name: "All", slug: "all" },
{ name: "Electronics", slug: "electronics" },
{ name: "Fashion", slug: "fashion" },
{ name: "Home & Kitchen", slug: "home-kitchen" },
{ name: "Health & Beauty", slug: "health-beauty" },
+ { name: "Auto Parts", slug: "auto-parts" },
+ { name: "Agriculture", slug: "agriculture" },
+ { name: "Office & Stationery", slug: "office-stationery" },
{ name: "Food & Groceries", slug: "food-groceries" },
- { name: "Phones & Gadgets", slug: "phones-gadgets" },
- { name: "Sports & Fitness", slug: "sports-fitness" },
- { name: "Baby & Kids", slug: "baby-kids" },
+ { name: "Books & Media", slug: "books-media" },
{ name: "Other", slug: "other" },
];📝 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 CATEGORIES = [ | |
| { name: "All", slug: "all" }, | |
| { name: "Electronics", slug: "electronics" }, | |
| { name: "Fashion", slug: "fashion" }, | |
| { name: "Home & Kitchen", slug: "home-kitchen" }, | |
| { name: "Health & Beauty", slug: "health-beauty" }, | |
| { name: "Food & Groceries", slug: "food-groceries" }, | |
| { name: "Phones & Gadgets", slug: "phones-gadgets" }, | |
| { name: "Sports & Fitness", slug: "sports-fitness" }, | |
| { name: "Baby & Kids", slug: "baby-kids" }, | |
| { name: "Other", slug: "other" }, | |
| ]; | |
| const CATEGORIES = [ | |
| { name: "All", slug: "all" }, | |
| { name: "Electronics", slug: "electronics" }, | |
| { name: "Fashion", slug: "fashion" }, | |
| { name: "Home & Kitchen", slug: "home-kitchen" }, | |
| { name: "Health & Beauty", slug: "health-beauty" }, | |
| { name: "Auto Parts", slug: "auto-parts" }, | |
| { name: "Agriculture", slug: "agriculture" }, | |
| { name: "Office & Stationery", slug: "office-stationery" }, | |
| { name: "Food & Groceries", slug: "food-groceries" }, | |
| { name: "Books & Media", slug: "books-media" }, | |
| { name: "Other", slug: "other" }, | |
| ]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/layout/category-bar.tsx` around lines 8 - 19, Update
the hardcoded CATEGORIES constant so its slug set matches the backend seed data:
replace/remove frontend-only slugs ("phones-gadgets", "sports-fitness",
"baby-kids") and add the missing backend slugs ("auto-parts", "agriculture",
"office-stationery"), ensuring each entry in the CATEGORIES array (symbol name:
CATEGORIES) pairs the human-friendly name with the exact slug used in backend
seed.ts; verify spelling and casing against apps/backend/src/prisma/seed.ts and
adjust the display names if needed so UI labels map correctly to backend
categories.
| // Sync state with URL when search param changes externally (e.g. back button) | ||
| React.useEffect(() => { | ||
| const urlSearch = searchParams.get("search") || ""; | ||
| if (urlSearch !== searchQuery) { | ||
| setSearchQuery(urlSearch); | ||
| } | ||
| }, [searchParams]); |
There was a problem hiding this comment.
Fix missing dependency causing ESLint warning.
The pipeline flags searchQuery as a missing dependency. Adding it directly would create an infinite loop since the effect sets searchQuery. Use a ref to track the previous URL value and avoid the warning.
Proposed fix
+ const prevUrlSearchRef = React.useRef(searchParams.get("search") || "");
+
// Sync state with URL when search param changes externally (e.g. back button)
React.useEffect(() => {
const urlSearch = searchParams.get("search") || "";
- if (urlSearch !== searchQuery) {
+ if (urlSearch !== prevUrlSearchRef.current) {
+ prevUrlSearchRef.current = urlSearch;
setSearchQuery(urlSearch);
}
- }, [searchParams]);
+ }, [searchParams]);Alternatively, you can disable the lint rule for this specific line with an explanatory comment:
React.useEffect(() => {
const urlSearch = searchParams.get("search") || "";
if (urlSearch !== searchQuery) {
setSearchQuery(urlSearch);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- Only sync when URL changes externally
}, [searchParams]);📝 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.
| // Sync state with URL when search param changes externally (e.g. back button) | |
| React.useEffect(() => { | |
| const urlSearch = searchParams.get("search") || ""; | |
| if (urlSearch !== searchQuery) { | |
| setSearchQuery(urlSearch); | |
| } | |
| }, [searchParams]); | |
| const prevUrlSearchRef = React.useRef(searchParams.get("search") || ""); | |
| // Sync state with URL when search param changes externally (e.g. back button) | |
| React.useEffect(() => { | |
| const urlSearch = searchParams.get("search") || ""; | |
| if (urlSearch !== prevUrlSearchRef.current) { | |
| prevUrlSearchRef.current = urlSearch; | |
| setSearchQuery(urlSearch); | |
| } | |
| }, [searchParams]); |
🧰 Tools
🪛 GitHub Actions: CI
[warning] 29-29: React Hook React.useEffect has a missing dependency: 'searchQuery'. (react-hooks/exhaustive-deps)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/layout/public-header.tsx` around lines 23 - 29, The
effect currently reads searchQuery but doesn't include it in the dependency
array, causing an ESLint warning; replace that dependency pattern by tracking
the last-seen URL value in a ref so the effect only depends on searchParams: add
a ref (e.g., const prevUrlRef = React.useRef<string>('')) near the top of the
component, then inside the React.useEffect that reads searchParams compute const
urlSearch = searchParams.get("search") || "" and compare to prevUrlRef.current —
if different call setSearchQuery(urlSearch) and set prevUrlRef.current =
urlSearch; keep the effect’s dependency array to [searchParams] (no searchQuery)
so the loop is avoided and the linter warning is resolved.
| {/* Tooltip */} | ||
| <div className="absolute bottom-full right-0 mb-3 hidden lg:block"> | ||
| <div className="rounded-lg bg-foreground px-3 py-1.5 text-sm font-medium text-background opacity-0 shadow-xl transition-opacity hover:opacity-100 group-hover:block"> | ||
| Need help? Chat with us | ||
| <div className="absolute left-1/2 top-full -translate-x-1/2 border-8 border-transparent border-t-foreground"></div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Tooltip hover is broken - missing group class.
The tooltip uses group-hover:block but no ancestor element has the group class, so the tooltip will never become visible on hover.
Proposed fix
- <div className="fixed bottom-6 right-6 z-50 flex items-center justify-center">
+ <div className="group fixed bottom-6 right-6 z-50 flex items-center justify-center">And fix the tooltip opacity transition:
- <div className="rounded-lg bg-foreground px-3 py-1.5 text-sm font-medium text-background opacity-0 shadow-xl transition-opacity hover:opacity-100 group-hover:block">
+ <div className="rounded-lg bg-foreground px-3 py-1.5 text-sm font-medium text-background opacity-0 shadow-xl transition-opacity group-hover:opacity-100">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/shared/whatsapp-fab.tsx` around lines 39 - 45, The
tooltip never shows because the ancestor wrapper lacks the required Tailwind
"group" class and the tooltip relies on group-hover to change opacity; update
the outer wrapper element in the whatsapp-fab component (the top-level container
in WhatsAppFab / whatsapp-fab.tsx) to include the "group" class, and adjust the
tooltip div's classes: remove the conditional "hidden" + "group-hover:block"
pattern and instead use persistent layout (e.g., keep "lg:block") with
"opacity-0 transition-opacity group-hover:opacity-100" so the tooltip becomes
visible on group hover.
| size?: "xs" | "sm" | "md" | "lg"; | ||
| showWordmark?: boolean; | ||
| className?: string; | ||
| variant?: "light" | "dark"; |
There was a problem hiding this comment.
variant prop is declared but never used, causing visual regression.
The variant prop exists in the interface and is defaulted to "light", but it's completely ignored in the rendering logic. Multiple callers pass variant="dark" expecting white/light-colored text on dark backgrounds:
apps/web/src/app/(auth)/register/page.tsx(dark gradient panel)apps/web/src/app/(auth)/login/page.tsx(dark gradient panel)apps/web/src/app/(auth)/admin/login/page.tsx(dark panel)
The wordmark always renders with text-foreground, which will have poor contrast on these dark backgrounds.
Proposed fix to implement variant styling
{showWordmark && (
<span
className={cn(
font,
- "font-bold tracking-tight text-foreground font-inter"
+ "font-bold tracking-tight font-inter",
+ variant === "dark" ? "text-white" : "text-foreground"
)}
>
Twizrr
</span>
)}Also applies to: 15-15, 43-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/ui/logo.tsx` at line 8, The Logo component currently
accepts a variant?: "light" | "dark" prop (LogoProps / function Logo) but never
uses it, causing wrong text colors; update the Logo component to read variant
(default "light") from props and apply conditional classes when rendering the
wordmark and subtitle (e.g., use "text-foreground" for light and a light/white
text class for dark) by switching the className via your cn/classnames helper or
a conditional template string inside the Logo render; ensure both the main
wordmark element and any small subtitle/descriptor elements use the conditional
class so callers passing variant="dark" (e.g., login/register/admin pages) get
proper contrast.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Summary by CodeRabbit
New Features
Refactor
Style