From 2e42cd8a560781ece7313df9a87fc8bbf03853dd Mon Sep 17 00:00:00 2001 From: Arman Date: Fri, 18 Oct 2024 03:52:22 -0400 Subject: [PATCH] Added FTS for product search --- src/components/search-dropdown.tsx | 83 ++++++++++++------------------ src/lib/actions.ts | 38 ++++++++++++++ 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/components/search-dropdown.tsx b/src/components/search-dropdown.tsx index 574bdf6..b694447 100644 --- a/src/components/search-dropdown.tsx +++ b/src/components/search-dropdown.tsx @@ -1,49 +1,30 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Search, X } from "lucide-react"; import Image from "next/image"; import { cn } from "@/lib/utils"; +import { Product } from "../db/schema"; +import { searchProducts } from "../lib/actions"; +import Link from "next/link"; -type Item = { - id: string; - name: string; - icon: string; -}; - -const items: Item[] = [ - { id: "1", name: "aluminum", icon: "/placeholder.svg?height=40&width=40" }, - { - id: "2", - name: "aluminum extrusions", - icon: "/placeholder.svg?height=40&width=40", - }, - { - id: "3", - name: "aluminum tubing", - icon: "/placeholder.svg?height=40&width=40", - }, - { - id: "4", - name: "allen wrenches", - icon: "/placeholder.svg?height=40&width=40", - }, - { - id: "5", - name: "all thread rods", - icon: "/placeholder.svg?height=40&width=40", - }, -]; +type SearchResult = Product & { href: string }; export function SearchDropdownComponent() { const [searchTerm, setSearchTerm] = useState(""); + const [filteredItems, setFilteredItems] = useState([]); const [isOpen, setIsOpen] = useState(false); - const filteredItems = items.filter((item) => - item.name.toLowerCase().includes(searchTerm.toLowerCase()), - ); + useEffect(() => { + const search = async () => { + const results = await searchProducts(searchTerm); + setFilteredItems(results); + }; + + search(); + }, [searchTerm]); return (
@@ -77,23 +58,25 @@ export function SearchDropdownComponent() {
{filteredItems.map((item) => ( -
{ - setSearchTerm(item.name); - setIsOpen(false); - }} - > - - {item.name} -
+ +
{ + setSearchTerm(item.name); + setIsOpen(false); + }} + > + + {item.name} +
+ ))}
diff --git a/src/lib/actions.ts b/src/lib/actions.ts index d03eb62..0a2c88b 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -1,4 +1,12 @@ "use server"; +import { sql } from "drizzle-orm"; +import { db } from "../db"; +import { + categories, + products, + subcategories, + subcollection, +} from "../db/schema"; import { getCart, updateCart } from "./cart"; export async function addToCart(prevState: unknown, formData: FormData) { @@ -35,3 +43,33 @@ export async function addToCart(prevState: unknown, formData: FormData) { return "Item added to cart"; } + +export async function searchProducts(searchTerm: string) { + const results = await db + .select() + .from(products) + .where( + sql`to_tsvector('english', ${products.name}) @@ plainto_tsquery('english', ${searchTerm})`, + ) + .limit(5) + .innerJoin( + subcategories, + sql`${products.subcategory_slug} = ${subcategories.slug}`, + ) + .innerJoin( + subcollection, + sql`${subcategories.subcollection_id} = ${subcollection.id}`, + ) + .innerJoin( + categories, + sql`${subcollection.category_slug} = ${categories.slug}`, + ); + + return results.map((item) => { + const href = `/products/${item.categories.slug}/${item.subcategories.slug}/${item.products.slug}`; + return { + ...item.products, + href, + }; + }); +}