Skip to content

Commit

Permalink
Merge pull request #9 from udohjeremiah/dev
Browse files Browse the repository at this point in the history
Add /products and /products/[productId] routes.
  • Loading branch information
udohjeremiah authored Apr 1, 2024
2 parents 4175f0b + b97ec21 commit 6711e8a
Show file tree
Hide file tree
Showing 29 changed files with 2,327 additions and 193 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@hookform/resolvers": "^3.3.4",
"@prisma/client": "^5.11.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
Expand Down
34 changes: 34 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions prisma/migrations/20240331171819_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"storeId" TEXT NOT NULL,
"categoryId" TEXT NOT NULL,
"sizeId" TEXT NOT NULL,
"colorId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"price" DECIMAL(65,30) NOT NULL,
"isFeatured" BOOLEAN NOT NULL DEFAULT false,
"isArchived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Image" (
"id" TEXT NOT NULL,
"productId" TEXT NOT NULL,
"publicId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Image_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_sizeId_fkey" FOREIGN KEY ("sizeId") REFERENCES "Size"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_colorId_fkey" FOREIGN KEY ("colorId") REFERENCES "Color"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Image" ADD CONSTRAINT "Image_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE;
10 changes: 10 additions & 0 deletions prisma/migrations/20240401002817_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Warnings:
- You are about to drop the column `publicId` on the `Image` table. All the data in the column will be lost.
- Added the required column `imagePublicId` to the `Image` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Image" DROP COLUMN "publicId",
ADD COLUMN "imagePublicId" TEXT NOT NULL;
50 changes: 41 additions & 9 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ datasource db {

model Store {
id String @id @default(uuid())
name String
userId String
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Billboard Billboard[]
Category Category[]
Size Size[]
Color Color[]
Product Product[]
}

model Billboard {
Expand All @@ -46,24 +47,55 @@ model Category {
updatedAt DateTime @updatedAt
Store Store @relation(fields: [storeId], references: [id])
Billboard Billboard @relation(fields: [billboardId], references: [id])
Product Product[]
}

model Size {
id String @id @default(uuid())
id String @id @default(uuid())
storeId String
name String
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Store Store @relation(fields: [storeId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Store Store @relation(fields: [storeId], references: [id])
Product Product[]
}

model Color {
id String @id @default(uuid())
id String @id @default(uuid())
storeId String
name String
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Store Store @relation(fields: [storeId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Store Store @relation(fields: [storeId], references: [id])
Product Product[]
}

model Product {
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])
Image Image[]
}

model Image {
id String @id @default(uuid())
productId String
imagePublicId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
}
158 changes: 158 additions & 0 deletions src/app/(dashboard)/[storeId]/products/[productId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import type { Metadata } from "next";

import { redirect } from "next/navigation";

import { auth } from "@clerk/nextjs";
import { format } from "date-fns";

import APIList from "@/components/APIList";
import Heading from "@/components/Heading";
import DeleteProductDialog from "@/components/dialogs/DeleteProductDialog";
import ProductForm from "@/components/forms/ProductForm";
import { Separator } from "@/components/ui/separator";

import { cn } from "@/lib/utils";
import prisma from "@/lib/prisma";

interface ProductPageProps {
params: { storeId: string; productId: string };
}

export async function generateMetadata({
params,
}: {
params: { storeId: string; productId: string };
}): Promise<Metadata> {
const { userId } = auth();

if (!userId) {
return {};
}

const store = await prisma.store.findUnique({
where: { id: params.storeId, userId },
});

const product = await prisma.product.findUnique({
where: { id: params.productId, storeId: params.storeId },
});

return {
title: `${store?.name} Store ${product?.name} Product | E-Commerce CMS`,
description: `Manage the ${product?.name} product for your ${store?.name} store.`,
};
}

export default async function ProductPage({ params }: ProductPageProps) {
const { userId } = auth();

if (!userId) {
redirect("/login");
}

const store = await prisma.store.findFirst({
where: { id: params.storeId, userId },
});

if (!store) {
redirect("/");
}

const categories = await prisma.category.findMany({
where: { storeId: params.storeId },
});

const sizes = await prisma.size.findMany({
where: { storeId: params.storeId },
});

const colors = await prisma.color.findMany({
where: { storeId: params.storeId },
});

const product = await prisma.product.findUnique({
where: { id: params.productId, storeId: store.id },
include: {
Category: true,
Size: true,
Color: true,
Image: true,
},
});

if (!product) {
redirect(`/${store.id}/products`);
}

return (
<main
className={cn(
"container flex flex-1 flex-col gap-4 py-4",
"md:gap-8 md:py-8",
)}
>
<div
className={cn(
"flex flex-col gap-4",
"md:flex-row md:items-center md:justify-between",
)}
>
<Heading
title={`${store?.name} Store Product`}
description={`Manage this product for your ${store?.name} store.`}
/>
<DeleteProductDialog
product={{
id: product.id,
name: product.name,
isFeatured: product.isFeatured,
isArchived: product.isArchived,
price: new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(product.price.toNumber()),
category: product.Category.name,
size: product.Size.name,
color: product.Color.value,
createdAt: format(product.createdAt, "MMMM do, yyyy"),
}}
triggerBtnClassName="w-max"
/>
</div>
<Separator />
<ProductForm
product={product}
images={product.Image}
categories={categories}
sizes={sizes}
colors={colors}
/>
<Separator />
<Heading title="API" description="API calls for product" />
<APIList
apis={[
{
title: "NEXT_PUBLIC_API_URL",
variant: "public",
route: "",
},
{
title: "GET",
variant: "public",
route: `products/{productId}`,
},
{
title: "PATCH",
variant: "admin",
route: `products/{productId}`,
},
{
title: "DELETE",
variant: "admin",
route: `products/{productId}`,
},
]}
/>
</main>
);
}
Loading

0 comments on commit 6711e8a

Please sign in to comment.