diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/customers/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/customers/page.tsx
index 5aa3796e..04caeb59 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/customers/page.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/customers/page.tsx
@@ -29,7 +29,7 @@ export default async function CustomersPage({
params,
searchParams,
}: CustomersPageProps) {
- const storeId = Number(params.storeId)
+ const storeId = decodeURIComponent(params.storeId)
const { page, per_page, sort, email, from, to } =
customersSearchParamsSchema.parse(searchParams)
diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/layout.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/layout.tsx
index 86c3c8ad..59332a58 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/layout.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/layout.tsx
@@ -4,7 +4,7 @@ import { stores } from "@/db/schema"
import { eq } from "drizzle-orm"
import { getCacheduser } from "@/lib/actions/auth"
-import { getSubscriptionPlan } from "@/lib/fetchers/stripe"
+import { getSubscriptionPlan } from "@/lib/actions/stripe"
import { getDashboardRedirectPath } from "@/lib/subscription"
import {
PageHeader,
@@ -25,7 +25,7 @@ export default async function StoreLayout({
children,
params,
}: StoreLayoutProps) {
- const storeId = Number(params.storeId)
+ const storeId = decodeURIComponent(params.storeId)
const user = await getCacheduser()
diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/orders/[orderId]/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/orders/[orderId]/page.tsx
index b61bba81..1d002126 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/orders/[orderId]/page.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/orders/[orderId]/page.tsx
@@ -30,8 +30,8 @@ interface OrderPageProps {
}
export default async function OrderPage({ params }: OrderPageProps) {
- const storeId = Number(params.storeId)
- const orderId = Number(params.orderId)
+ const storeId = decodeURIComponent(params.storeId)
+ const orderId = decodeURIComponent(params.orderId)
const order = await db.query.orders.findFirst({
where: and(eq(orders.id, orderId), eq(products.storeId, storeId)),
diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/orders/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/orders/page.tsx
index 29815fa0..ef84ca22 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/orders/page.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/orders/page.tsx
@@ -30,7 +30,7 @@ export default async function OrdersPage({
params,
searchParams,
}: OrdersPageProps) {
- const storeId = Number(params.storeId)
+ const storeId = decodeURIComponent(params.storeId)
const { page, per_page, sort, customer, status, from, to } =
ordersSearchParamsSchema.parse(searchParams)
diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/products/[productId]/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/products/[productId]/page.tsx
index 8bfb21a9..d2669ee9 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/products/[productId]/page.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/products/[productId]/page.tsx
@@ -31,8 +31,8 @@ interface UpdateProductPageProps {
export default async function UpdateProductPage({
params,
}: UpdateProductPageProps) {
- const storeId = Number(params.storeId)
- const productId = Number(params.productId)
+ const storeId = decodeURIComponent(params.storeId)
+ const productId = decodeURIComponent(params.productId)
const product = await db.query.products.findFirst({
where: and(eq(products.id, productId), eq(products.storeId, storeId)),
diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/products/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/products/page.tsx
index b0060aa0..a467c021 100644
--- a/src/app/(dashboard)/dashboard/stores/[storeId]/products/page.tsx
+++ b/src/app/(dashboard)/dashboard/stores/[storeId]/products/page.tsx
@@ -3,7 +3,7 @@ import { type Metadata } from "next"
import { unstable_noStore as noStore } from "next/cache"
import { notFound } from "next/navigation"
import { db } from "@/db"
-import { products, stores, type Product } from "@/db/schema"
+import { categories, products, stores, type Product } from "@/db/schema"
import { env } from "@/env.js"
import type { SearchParams } from "@/types"
import { and, asc, desc, eq, gte, inArray, like, lte, sql } from "drizzle-orm"
@@ -30,7 +30,7 @@ export default async function ProductsPage({
params,
searchParams,
}: ProductsPageProps) {
- const storeId = Number(params.storeId)
+ const storeId = decodeURIComponent(params.storeId)
// Parse search params using zod schema
const { page, per_page, sort, name, category, from, to } =
@@ -60,7 +60,7 @@ export default async function ProductsPage({
"asc" | "desc" | undefined,
]) ?? ["createdAt", "desc"]
- const categories = (category?.split(".") as Product["category"][]) ?? []
+ const categoryIds = category?.split(".") ?? []
const fromDay = from ? new Date(from) : undefined
const toDay = to ? new Date(to) : undefined
@@ -73,7 +73,7 @@ export default async function ProductsPage({
.select({
id: products.id,
name: products.name,
- category: products.category,
+ category: categories.name,
price: products.price,
inventory: products.inventory,
rating: products.rating,
@@ -82,14 +82,15 @@ export default async function ProductsPage({
.from(products)
.limit(limit)
.offset(offset)
+ .leftJoin(categories, eq(products.categoryId, categories.id))
.where(
and(
eq(products.storeId, storeId),
// Filter by name
name ? like(products.name, `%${name}%`) : undefined,
// Filter by category
- categories.length > 0
- ? inArray(products.category, categories)
+ categoryIds.length > 0
+ ? inArray(products.categoryId, categoryIds)
: undefined,
// Filter by createdAt
fromDay && toDay
@@ -119,8 +120,8 @@ export default async function ProductsPage({
// Filter by name
name ? like(products.name, `%${name}%`) : undefined,
// Filter by category
- categories.length > 0
- ? inArray(products.category, categories)
+ categoryIds.length > 0
+ ? inArray(products.categoryId, categoryIds)
: undefined,
// Filter by createdAt
fromDay && toDay
diff --git a/src/app/(lobby)/_components/category-card-skeleton.tsx b/src/app/(lobby)/_components/category-card-skeleton.tsx
new file mode 100644
index 00000000..c2748398
--- /dev/null
+++ b/src/app/(lobby)/_components/category-card-skeleton.tsx
@@ -0,0 +1,18 @@
+import { Card, CardContent, CardHeader } from "@/components/ui/card"
+import { Skeleton } from "@/components/ui/skeleton"
+
+export function CategoryCardSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/(lobby)/_components/category-card.tsx b/src/app/(lobby)/_components/category-card.tsx
index 927bd2b1..d668ad45 100644
--- a/src/app/(lobby)/_components/category-card.tsx
+++ b/src/app/(lobby)/_components/category-card.tsx
@@ -1,8 +1,8 @@
import * as React from "react"
import Link from "next/link"
-import type { Category } from "@/types"
+import { BoxIcon } from "@radix-ui/react-icons"
-import { getProductCount } from "@/lib/actions/product"
+import { getProductCount, type getCategories } from "@/lib/actions/product"
import {
Card,
CardContent,
@@ -13,23 +13,25 @@ import {
import { Skeleton } from "@/components/ui/skeleton"
interface CategoryCardProps {
- category: Category
+ category: Awaited>[number]
}
export function CategoryCard({ category }: CategoryCardProps) {
- const productCountPromise = getProductCount({ category })
+ const productCountPromise = getProductCount({
+ categoryName: category.name,
+ })
return (
-
- {category.title}
+
+ {category.name}
-
+
- {category.title}
+ {category.name}
}>
@@ -46,5 +48,5 @@ interface ProductCountProps {
async function ProductCount({ productCountPromise }: ProductCountProps) {
const { data } = await productCountPromise
- return {data} products
+ return {data.count} products
}
diff --git a/src/app/(lobby)/_components/lobby-skeleton.tsx b/src/app/(lobby)/_components/lobby-skeleton.tsx
index 2ee5612f..0d0698a9 100644
--- a/src/app/(lobby)/_components/lobby-skeleton.tsx
+++ b/src/app/(lobby)/_components/lobby-skeleton.tsx
@@ -1,6 +1,5 @@
import Link from "next/link"
-import { productCategories } from "@/config/product"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import { ContentSection } from "@/components/shells/content-section"
@@ -8,7 +7,7 @@ import { Shell } from "@/components/shells/shell"
import { ProductCardSkeleton } from "@/components/skeletons/product-card-skeleton"
import { StoreCardSkeleton } from "@/components/skeletons/store-card-skeleton"
-import { CategoryCard } from "./category-card"
+import { CategoryCardSkeleton } from "./category-card-skeleton"
export function LobbySkeleton() {
return (
@@ -39,8 +38,8 @@ export function LobbySkeleton() {
- {productCategories.map((category) => (
-
+ {Array.from({ length: 4 }).map((_, i) => (
+
))}
productsPromise: ReturnType
+ categoriesPromise: ReturnType
storesPromise: ReturnType
- githubStarsPromise: ReturnType
}
export async function Lobby({
+ githubStarsPromise,
productsPromise,
+ categoriesPromise,
storesPromise,
- githubStarsPromise,
}: LobbyProps) {
// See the "Parallel data fetching" docs: https://nextjs.org/docs/app/building-your-application/data-fetching/patterns#parallel-data-fetching
- const [products, stores, githubStars] = await Promise.all([
+ const [githubStars, products, categories, stores] = await Promise.all([
+ githubStarsPromise,
productsPromise,
+ categoriesPromise,
storesPromise,
- githubStarsPromise,
])
return (
@@ -80,8 +82,8 @@ export async function Lobby({
- {productCategories.map((category) => (
-
+ {categories.map((category) => (
+
))}
}>
-
+
)
}
diff --git a/src/components/cards/product-card.tsx b/src/components/cards/product-card.tsx
index c3415462..413ea78e 100644
--- a/src/components/cards/product-card.tsx
+++ b/src/components/cards/product-card.tsx
@@ -8,7 +8,7 @@ import { CheckIcon, EyeOpenIcon, PlusIcon } from "@radix-ui/react-icons"
import { toast } from "sonner"
import { addToCart } from "@/lib/actions/cart"
-import { catchError, cn, formatPrice } from "@/lib/utils"
+import { cn, formatPrice } from "@/lib/utils"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Button, buttonVariants } from "@/components/ui/button"
import {
@@ -23,10 +23,9 @@ import { Icons } from "@/components/icons"
import { PlaceholderImage } from "@/components/placeholder-image"
interface ProductCardProps extends React.HTMLAttributes {
- product: Pick<
- Product,
- "id" | "name" | "price" | "images" | "category" | "inventory"
- >
+ product: Pick & {
+ category: string | null
+ }
variant?: "default" | "switchable"
isAddedToCart?: boolean
onSwitch?: () => Promise
@@ -40,7 +39,7 @@ export function ProductCard({
className,
...props
}: ProductCardProps) {
- const [isAddingToCart, startAddingToCart] = React.useTransition()
+ const [isUpdatePending, startUpdateTransition] = React.useTransition()
return (
{
- startAddingToCart(async () => {
- try {
- await addToCart({
- productId: product.id,
- quantity: 1,
- })
- toast.success("Added to cart.")
- } catch (err) {
- catchError(err)
- }
+ onClick={async () => {
+ startUpdateTransition(() => {})
+ const { error } = await addToCart({
+ productId: product.id,
+ quantity: 1,
})
+
+ if (error) {
+ toast.error(error)
+ }
}}
- disabled={isAddingToCart}
+ disabled={isUpdatePending}
>
- {isAddingToCart && (
+ {isUpdatePending && (
{
- startAddingToCart(async () => {
- await onSwitch?.()
- })
+ onClick={async () => {
+ startUpdateTransition(async () => {})
+ await onSwitch?.()
}}
- disabled={isAddingToCart}
+ disabled={isUpdatePending}
>
- {isAddingToCart ? (
+ {isUpdatePending ? (
- storeId: number
+ storeId: string
}
export function CustomersTableShell({
diff --git a/src/components/shells/products-table-shell.tsx b/src/components/shells/products-table-shell.tsx
index 5928bd60..b04615f4 100644
--- a/src/components/shells/products-table-shell.tsx
+++ b/src/components/shells/products-table-shell.tsx
@@ -8,7 +8,8 @@ import { type ColumnDef } from "@tanstack/react-table"
import { toast } from "sonner"
import { deleteProduct } from "@/lib/actions/product"
-import { catchError, formatDate, formatPrice } from "@/lib/utils"
+import { getErrorMessage } from "@/lib/handle-error"
+import { formatDate, formatPrice } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
@@ -25,7 +26,7 @@ import { DataTableColumnHeader } from "@/components/data-table/data-table-column
type AwaitedProduct = Pick<
Product,
- "id" | "name" | "category" | "price" | "inventory" | "rating" | "createdAt"
+ "id" | "name" | "categoryId" | "price" | "inventory" | "rating" | "createdAt"
>
interface ProductsTableShellProps {
@@ -33,7 +34,7 @@ interface ProductsTableShellProps {
data: AwaitedProduct[]
pageCount: number
}>
- storeId: number
+ storeId: string
}
export function ProductsTableShell({
@@ -43,7 +44,7 @@ export function ProductsTableShell({
const { data, pageCount } = React.use(promise)
const [isPending, startTransition] = React.useTransition()
- const [selectedRowIds, setSelectedRowIds] = React.useState([])
+ const [selectedRowIds, setSelectedRowIds] = React.useState([])
// Memoize the columns so they don't re-render on every render
const columns = React.useMemo[]>(
@@ -94,7 +95,7 @@ export function ProductsTableShell({
),
cell: ({ cell }) => {
const categories = Object.values(products.category.enumValues)
- const category = cell.getValue() as Product["category"]
+ const category = cell.getValue() as string
if (!categories.includes(category)) return null
@@ -170,7 +171,7 @@ export function ProductsTableShell({
{
loading: "Deleting...",
success: () => "Product deleted successfully.",
- error: (err: unknown) => catchError(err),
+ error: (err: unknown) => getErrorMessage(err),
}
)
})
@@ -206,7 +207,7 @@ export function ProductsTableShell({
},
error: (err: unknown) => {
setSelectedRowIds([])
- return catchError(err)
+ return getErrorMessage(err)
},
}
)
diff --git a/src/db/schema.ts b/src/db/schema.ts
index d3ff8b1a..7d5331e6 100644
--- a/src/db/schema.ts
+++ b/src/db/schema.ts
@@ -46,6 +46,7 @@ export const categories = pgTable("categories", {
})
export const categoriesRelations = relations(categories, ({ many }) => ({
+ products: many(products),
subcategories: many(subcategories),
}))
@@ -120,6 +121,10 @@ export const products = pgTable(
export const productsRelations = relations(products, ({ one }) => ({
store: one(stores, { fields: [products.storeId], references: [stores.id] }),
+ category: one(categories, {
+ fields: [products.categoryId],
+ references: [categories.id],
+ }),
subcategory: one(subcategories, {
fields: [products.subcategoryId],
references: [subcategories.id],
diff --git a/src/lib/actions/cart.ts b/src/lib/actions/cart.ts
index 4a9f6127..468d9851 100644
--- a/src/lib/actions/cart.ts
+++ b/src/lib/actions/cart.ts
@@ -3,7 +3,7 @@
import { unstable_noStore as noStore, revalidatePath } from "next/cache"
import { cookies } from "next/headers"
import { db } from "@/db"
-import { carts, products, stores } from "@/db/schema"
+import { carts, categories, products, stores, subcategories } from "@/db/schema"
import type { CartLineItem } from "@/types"
import { and, asc, desc, eq, inArray, sql } from "drizzle-orm"
import { type z } from "zod"
@@ -43,8 +43,8 @@ export async function getCart(input?: {
id: products.id,
name: products.name,
images: products.images,
- category: products.category,
- subcategory: products.subcategory,
+ category: categories.name,
+ subcategory: subcategories.name,
price: products.price,
inventory: products.inventory,
storeId: products.storeId,
@@ -53,6 +53,8 @@ export async function getCart(input?: {
})
.from(products)
.leftJoin(stores, eq(stores.id, products.storeId))
+ .leftJoin(categories, eq(categories.id, products.categoryId))
+ .leftJoin(subcategories, eq(subcategories.id, products.subcategoryId))
.where(
and(
inArray(products.id, uniqueProductIds),
@@ -160,7 +162,10 @@ export async function addToCart(rawInput: z.infer) {
cookieStore.set("cartId", String(cart[0]?.insertedId))
revalidatePath("/")
- return
+ return {
+ data: [input],
+ error: null,
+ }
}
const cart = await db.query.carts.findFirst({
@@ -194,7 +199,10 @@ export async function addToCart(rawInput: z.infer) {
cookieStore.set("cartId", String(newCart[0]?.insertedId))
revalidatePath("/")
- return
+ return {
+ data: [input],
+ error: null,
+ }
}
const cartItem = cart.items?.find(
diff --git a/src/lib/actions/product.ts b/src/lib/actions/product.ts
index ff74294f..25cf6456 100644
--- a/src/lib/actions/product.ts
+++ b/src/lib/actions/product.ts
@@ -6,7 +6,13 @@ import {
revalidatePath,
} from "next/cache"
import { db } from "@/db"
-import { products, stores, type Product } from "@/db/schema"
+import {
+ categories,
+ products,
+ stores,
+ subcategories,
+ type Product,
+} from "@/db/schema"
import type { SearchParams, StoredFile } from "@/types"
import {
and,
@@ -38,7 +44,7 @@ export async function getFeaturedProducts() {
id: products.id,
name: products.name,
images: products.images,
- category: products.category,
+ category: categories.name,
price: products.price,
inventory: products.inventory,
stripeAccountId: stores.stripeAccountId,
@@ -46,7 +52,8 @@ export async function getFeaturedProducts() {
.from(products)
.limit(8)
.leftJoin(stores, eq(products.storeId, stores.id))
- .groupBy(products.id, stores.stripeAccountId)
+ .leftJoin(categories, eq(products.categoryId, categories.id))
+ .groupBy(products.id, stores.stripeAccountId, categories.name)
.orderBy(
desc(sql`count(${stores.stripeAccountId})`),
desc(sql`count(${products.images})`),
@@ -64,6 +71,7 @@ export async function getFeaturedProducts() {
// See the unstable_noStore API docs: https://nextjs.org/docs/app/api-reference/functions/unstable_noStore
export async function getProducts(input: SearchParams) {
noStore()
+
try {
const search = getProductsSchema.parse(input)
@@ -75,9 +83,8 @@ export async function getProducts(input: SearchParams) {
"asc" | "desc" | undefined,
]) ?? ["createdAt", "desc"]
const [minPrice, maxPrice] = search.price_range?.split("-") ?? []
- const categories =
- (search.categories?.split(".") as Product["category"][]) ?? []
- const subcategories = search.subcategories?.split(".") ?? []
+ const categoryIds = search.categories?.split(".") ?? []
+ const subcategoryIds = search.subcategories?.split(".") ?? []
const storeIds = search.store_ids?.split(".") ?? []
const transaction = await db.transaction(async (tx) => {
@@ -87,8 +94,8 @@ export async function getProducts(input: SearchParams) {
name: products.name,
description: products.description,
images: products.images,
- category: products.category,
- subcategory: products.subcategory,
+ category: categories.name,
+ subcategory: subcategories.name,
price: products.price,
inventory: products.inventory,
rating: products.rating,
@@ -102,13 +109,15 @@ export async function getProducts(input: SearchParams) {
.limit(limit)
.offset(offset)
.leftJoin(stores, eq(products.storeId, stores.id))
+ .leftJoin(categories, eq(products.categoryId, categories.id))
+ .leftJoin(subcategories, eq(products.subcategoryId, subcategories.id))
.where(
and(
- categories.length
- ? inArray(products.category, categories)
+ categoryIds.length > 0
+ ? inArray(products.categoryId, categoryIds)
: undefined,
- subcategories.length
- ? inArray(products.subcategory, subcategories)
+ subcategoryIds.length > 0
+ ? inArray(products.subcategoryId, subcategoryIds)
: undefined,
minPrice ? gte(products.price, minPrice) : undefined,
maxPrice ? lte(products.price, maxPrice) : undefined,
@@ -134,11 +143,11 @@ export async function getProducts(input: SearchParams) {
.from(products)
.where(
and(
- categories.length
- ? inArray(products.category, categories)
+ categoryIds.length > 0
+ ? inArray(products.categoryId, categoryIds)
: undefined,
- subcategories.length
- ? inArray(products.subcategory, subcategories)
+ subcategoryIds.length > 0
+ ? inArray(products.subcategoryId, subcategoryIds)
: undefined,
minPrice ? gte(products.price, minPrice) : undefined,
maxPrice ? lte(products.price, maxPrice) : undefined,
@@ -158,7 +167,6 @@ export async function getProducts(input: SearchParams) {
return transaction
} catch (err) {
- console.error(err)
return {
data: [],
pageCount: 0,
@@ -166,30 +174,95 @@ export async function getProducts(input: SearchParams) {
}
}
-export async function getProductCount({ category }: { category: Category }) {
+export async function getProductCount({
+ categoryName,
+}: {
+ categoryName: string
+}) {
noStore()
+
try {
const count = await db
.select({
count: sql`count(*)`.mapWith(Number),
})
.from(products)
- .where(eq(products.category, category.title))
+ .where(eq(products.name, categoryName))
.execute()
.then((res) => res[0]?.count ?? 0)
return {
- data: count,
+ data: {
+ count,
+ },
error: null,
}
} catch (err) {
return {
- data: 0,
+ data: {
+ count: 0,
+ },
error: getErrorMessage(err),
}
}
}
+export async function getCategories() {
+ return await cache(
+ async () => {
+ return db
+ .selectDistinct({
+ name: categories.name,
+ })
+ .from(categories)
+ },
+ ["categories"],
+ {
+ revalidate: 3600, // every hour
+ tags: ["categories"],
+ }
+ )()
+}
+
+export async function getSubcategories() {
+ return await cache(
+ async () => {
+ return db
+ .selectDistinct({
+ name: subcategories.name,
+ })
+ .from(subcategories)
+ },
+ ["subcategories"],
+ {
+ revalidate: 3600, // every hour
+ tags: ["subcategories"],
+ }
+ )()
+}
+
+export async function getSubcategoriesByCategory({
+ categoryId,
+}: {
+ categoryId: string
+}) {
+ return await cache(
+ async () => {
+ return db
+ .selectDistinct({
+ name: subcategories.name,
+ })
+ .from(subcategories)
+ .where(eq(subcategories.categoryId, categoryId))
+ },
+ [`subcategories-${categoryId}`],
+ {
+ revalidate: 3600, // every hour
+ tags: [`subcategories-${categoryId}`],
+ }
+ )()
+}
+
export async function filterProducts({ query }: { query: string }) {
noStore()
@@ -201,28 +274,24 @@ export async function filterProducts({ query }: { query: string }) {
}
}
- const filteredProducts = await db
- .select({
- id: products.id,
- name: products.name,
- category: products.category,
- })
- .from(products)
- .where(like(products.name, `%${query}%`))
- .orderBy(desc(products.createdAt))
- .limit(10)
-
- const productsByCategory = Object.values(products.category.enumValues).map(
- (category) => ({
- category,
- products: filteredProducts.filter(
- (product) => product.category === category
- ),
- })
- )
+ const categoriesWithProducts = await db.query.categories.findMany({
+ columns: {
+ id: true,
+ name: true,
+ },
+ with: {
+ products: {
+ columns: {
+ id: true,
+ name: true,
+ },
+ },
+ },
+ where: like(categories.name, `%${query}%`),
+ })
return {
- data: productsByCategory,
+ data: categoriesWithProducts,
error: null,
}
} catch (err) {
diff --git a/src/lib/validations/cart.ts b/src/lib/validations/cart.ts
index cefdbd3f..c6f7e9a0 100644
--- a/src/lib/validations/cart.ts
+++ b/src/lib/validations/cart.ts
@@ -22,8 +22,8 @@ export const cartLineItemSchema = z.object({
)
.optional()
.nullable(),
- categoryId: z.string(),
- subcategoryId: z.string().optional().nullable(),
+ category: z.string().optional().nullable(),
+ subcategory: z.string().optional().nullable(),
price: z.string().regex(/^\d+(\.\d{1,2})?$/),
inventory: z.number().default(0),
quantity: z.number(),
diff --git a/src/lib/validations/product.ts b/src/lib/validations/product.ts
index 966d164b..c132de8c 100644
--- a/src/lib/validations/product.ts
+++ b/src/lib/validations/product.ts
@@ -1,4 +1,3 @@
-import { products } from "@/db/schema"
import * as z from "zod"
export const addProductSchema = z.object({
@@ -6,12 +5,8 @@ export const addProductSchema = z.object({
message: "Must be at least 1 character",
}),
description: z.string().optional(),
- category: z
- .enum(products.category.enumValues, {
- required_error: "Must be a valid category",
- })
- .default(products.category.enumValues[0]),
- subcategory: z.string().optional().nullable(),
+ categoryId: z.string(),
+ subcategoryId: z.string().optional().nullable(),
price: z.string().regex(/^\d+(\.\d{1,2})?$/, {
message: "Must be a valid price",
}),