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 && (