From 7c269fc154ac5d2ecaa0930ce54713a74c7a84ed Mon Sep 17 00:00:00 2001 From: Udoh Jeremiah Date: Mon, 1 Apr 2024 17:25:29 +0100 Subject: [PATCH 1/2] Add /orders route. --- .../migrations/20240401155831_/migration.sql | 30 +++++ prisma/schema.prisma | 40 +++++-- src/app/(dashboard)/[storeId]/orders/page.tsx | 105 ++++++++++++++++++ src/components/columns/OrderColumns.tsx | 93 ++++++++++++++++ src/data/routes.ts | 5 + 5 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 prisma/migrations/20240401155831_/migration.sql create mode 100644 src/app/(dashboard)/[storeId]/orders/page.tsx create mode 100644 src/components/columns/OrderColumns.tsx diff --git a/prisma/migrations/20240401155831_/migration.sql b/prisma/migrations/20240401155831_/migration.sql new file mode 100644 index 0000000..ac499ae --- /dev/null +++ b/prisma/migrations/20240401155831_/migration.sql @@ -0,0 +1,30 @@ +-- CreateTable +CREATE TABLE "Order" ( + "id" TEXT NOT NULL, + "storeId" TEXT NOT NULL, + "isPaid" BOOLEAN NOT NULL DEFAULT false, + "phone" TEXT NOT NULL DEFAULT '', + "address" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Order_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OrderItem" ( + "id" TEXT NOT NULL, + "orderId" TEXT NOT NULL, + "productId" TEXT NOT NULL, + + CONSTRAINT "OrderItem_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Order" ADD CONSTRAINT "Order_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderItem" ADD CONSTRAINT "OrderItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6610ee5..bb4f16a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,6 +25,7 @@ model Store { Size Size[] Color Color[] Product Product[] + Order Order[] } model Billboard { @@ -73,22 +74,23 @@ model Color { } model Product { - id String @id @default(uuid()) + id String @id @default(uuid()) storeId String categoryId String sizeId String colorId String name String price Decimal - isFeatured Boolean @default(false) - isArchived Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - Store Store @relation(fields: [storeId], references: [id]) - Category Category @relation(fields: [categoryId], references: [id]) - Size Size @relation(fields: [sizeId], references: [id]) - Color Color @relation(fields: [colorId], references: [id]) + isFeatured Boolean @default(false) + isArchived Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Store Store @relation(fields: [storeId], references: [id]) + Category Category @relation(fields: [categoryId], references: [id]) + Size Size @relation(fields: [sizeId], references: [id]) + Color Color @relation(fields: [colorId], references: [id]) Image Image[] + OrderItem OrderItem[] } model Image { @@ -99,3 +101,23 @@ model Image { updatedAt DateTime @updatedAt Product Product @relation(fields: [productId], references: [id], onDelete: Cascade) } + +model Order { + id String @id @default(uuid()) + storeId String + isPaid Boolean @default(false) + phone String @default("") + address String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Store Store @relation(fields: [storeId], references: [id]) + OrderItem OrderItem[] +} + +model OrderItem { + id String @id @default(uuid()) + orderId String + productId String + Order Order @relation(fields: [orderId], references: [id]) + Product Product @relation(fields: [productId], references: [id]) +} diff --git a/src/app/(dashboard)/[storeId]/orders/page.tsx b/src/app/(dashboard)/[storeId]/orders/page.tsx new file mode 100644 index 0000000..32f9ace --- /dev/null +++ b/src/app/(dashboard)/[storeId]/orders/page.tsx @@ -0,0 +1,105 @@ +import type { Metadata } from "next"; + +import { redirect } from "next/navigation"; + +import { auth } from "@clerk/nextjs"; +import { format } from "date-fns"; + +import DataTable from "@/components/DataTable"; +import Heading from "@/components/Heading"; +import { OrderColumn, columns } from "@/components/columns/OrderColumns"; +import { Separator } from "@/components/ui/separator"; + +import { cn } from "@/lib/utils"; +import prisma from "@/lib/prisma"; + +interface OrdersPageProps { + params: { storeId: string }; +} + +export async function generateMetadata({ + params, +}: { + params: { storeId: string }; +}): Promise { + const { userId } = auth(); + + if (!userId) { + return {}; + } + + const store = await prisma.store.findUnique({ + where: { id: params.storeId, userId }, + }); + + return { + title: `${store?.name} Store Orders | E-Commerce CMS`, + description: `Manage the orders for your ${store?.name} store.`, + }; +} + +export default async function OrdersPage({ params }: OrdersPageProps) { + const { userId } = auth(); + + if (!userId) { + redirect("/login"); + } + + const store = await prisma.store.findFirst({ + where: { id: params.storeId, userId }, + }); + + if (!store) { + redirect("/"); + } + + const orders = await prisma.order.findMany({ + where: { storeId: store.id }, + include: { + OrderItem: { + include: { Product: true }, + }, + }, + orderBy: { createdAt: "desc" }, + }); + + const formattedOrders: OrderColumn[] = orders.map((order) => ({ + id: order.id, + isPaid: order.isPaid, + phone: order.phone, + address: order.address, + products: order.OrderItem.map((orderItem) => orderItem.Product.name).join( + ", ", + ), + totalPrice: new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format( + order.OrderItem.reduce( + (total, orderItem) => total + Number(orderItem.Product.price), + 0, + ), + ), + createdAt: format(order.createdAt, "MMMM do, yyyy"), + })); + + return ( +
+ + + +
+ ); +} diff --git a/src/components/columns/OrderColumns.tsx b/src/components/columns/OrderColumns.tsx new file mode 100644 index 0000000..f22884f --- /dev/null +++ b/src/components/columns/OrderColumns.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { ArrowUpDown } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +export type OrderColumn = { + id: string; + isPaid: boolean; + phone: string; + address: string; + products: string; + totalPrice: string; + createdAt: string; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: "products", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "phone", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "address", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "totalPrice", + header: ({ column }) => { + return ( + + ); + }, + }, + { + accessorKey: "isPaid", + header: "Paid", + }, + { + accessorKey: "createdAt", + header: ({ column }) => { + return ( + + ); + }, + }, +]; diff --git a/src/data/routes.ts b/src/data/routes.ts index dddf901..8e2b98f 100644 --- a/src/data/routes.ts +++ b/src/data/routes.ts @@ -29,6 +29,11 @@ export const routes = [ label: "Products", active: (pathName: string, route: string) => pathName.startsWith(route), }, + { + href: "orders", + label: "Orders", + active: (pathName: string, route: string) => pathName.startsWith(route), + }, { href: "settings", label: "Settings", From c7761468a13f56d85e9c1bb4d40a8be452496b20 Mon Sep 17 00:00:00 2001 From: Udoh Jeremiah Date: Mon, 1 Apr 2024 17:36:02 +0100 Subject: [PATCH 2/2] Fix mobile nav. --- src/components/layout/MobileNav.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/layout/MobileNav.tsx b/src/components/layout/MobileNav.tsx index 2f25111..abb8d47 100644 --- a/src/components/layout/MobileNav.tsx +++ b/src/components/layout/MobileNav.tsx @@ -43,7 +43,7 @@ export default function MobileNav() { href="/" onClick={() => setOpen(false)} className={cn( - "flex items-center gap-2 text-lg font-bold", + "flex w-max items-center gap-2 text-lg font-bold", "md:text-base", )} > @@ -58,7 +58,7 @@ export default function MobileNav() { href={`/${params.storeId}/${href}`} onClick={() => setOpen(false)} className={cn( - "text-muted-foreground transition-colors", + "w-max text-muted-foreground transition-colors", "hover:text-foreground", active(pathName, `/${params.storeId}${href && `/${href}`}`) && "text-foreground",