├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── Logo.webp ├── hero │ ├── Featured1.webp │ ├── Featured2.webp │ ├── Featured3.webp │ ├── Featured4.webp │ ├── event1.webp │ ├── event2.webp │ ├── event3.webp │ └── header.webp └── products │ ├── feature.webp │ ├── p1.png │ ├── p2.png │ ├── p3.png │ ├── p4.png │ └── p6.png ├── sanity.cli.ts ├── sanity.config.ts ├── sanity ├── category.ts ├── env.ts ├── lib │ ├── client.ts │ └── image.ts ├── product.ts └── schema.ts ├── src ├── app │ ├── [slug] │ │ └── page.tsx │ ├── api │ │ ├── cart │ │ │ ├── [userId] │ │ │ │ └── route.tsx │ │ │ ├── removeitem │ │ │ │ └── [productId] │ │ │ │ │ └── route.tsx │ │ │ └── route.ts │ │ ├── stripe-session │ │ │ └── route.ts │ │ └── webhooks │ │ │ └── route.ts │ ├── cart │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ ├── products │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── studio │ │ └── [[...index]] │ │ │ └── page.tsx │ └── success │ │ └── page.tsx ├── components │ ├── sections │ │ ├── CheckOut.tsx │ │ ├── Footer.tsx │ │ ├── Hero.tsx │ │ ├── Navbar.tsx │ │ ├── Newsletter.tsx │ │ ├── Products.tsx │ │ ├── Promotion.tsx │ │ ├── Unique.tsx │ │ └── index.ts │ ├── shared │ │ ├── CartItemCard.tsx │ │ ├── Icard.tsx │ │ ├── Menu.tsx │ │ ├── StartShopping.tsx │ │ ├── SwipperSlidder.tsx │ │ ├── Wrapper.tsx │ │ └── addtoCartProduct.tsx │ └── utils │ │ ├── ImageComponent.tsx │ │ └── Providers.tsx ├── interfaces.d.ts ├── lib │ ├── drizzle.ts │ └── stripe.ts ├── middleware.ts └── redux │ ├── features │ └── cartSlice.tsx │ └── store.tsx ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | .env 38 | 39 | .vercel 40 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dsznajder.es7-react-js-snippets" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "typescript.tsdk": "node_modules\\typescript\\lib", 4 | "reactSnippets.settings.importReactOnTop": false 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## HACKATHONE ONE 2 | 3 | For Live site preview, [click here](https://hackathon-ecom.vercel.app/) 4 | 5 | **I have used** 6 | 7 | - Next.js 13.4 (app router) 8 | - TailwindCSS for styling 9 | - Sanity for content management 10 | - React Redux for state management 11 | - Vercel Postgres for cart data management 12 | - Drizzle ORM for database 13 | - Stripe for payment 14 | - Webhooks (which will handle the calls coming from stripe) 15 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | hostname: 'cdn.sanity.io', 7 | }, 8 | ], 9 | }, 10 | } 11 | 12 | module.exports = nextConfig 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hactathon", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^4.21.7", 13 | "@reduxjs/toolkit": "^1.9.5", 14 | "@sanity/image-url": "^1.0.2", 15 | "@sanity/vision": "^3.12.0", 16 | "@stripe/stripe-js": "^1.54.1", 17 | "@types/node": "20.3.1", 18 | "@types/react": "18.2.12", 19 | "@types/react-dom": "18.2.5", 20 | "@vercel/postgres": "^0.3.1", 21 | "autoprefixer": "10.4.14", 22 | "drizzle-orm": "^0.27.0", 23 | "eslint": "8.42.0", 24 | "eslint-config-next": "13.4.5", 25 | "next": "^13.4.5", 26 | "next-sanity": "^4.3.3", 27 | "postcss": "8.4.24", 28 | "react": "18.2.0", 29 | "react-dom": "18.2.0", 30 | "react-hot-toast": "^2.4.1", 31 | "react-icons": "^4.9.0", 32 | "react-redux": "^8.1.0", 33 | "redux-persist": "^6.0.0", 34 | "sanity": "^3.12.0", 35 | "stripe": "^12.10.0", 36 | "swiper": "^9.4.1", 37 | "tailwindcss": "3.3.2", 38 | "typescript": "5.1.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/Logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/Logo.webp -------------------------------------------------------------------------------- /public/hero/Featured1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/Featured1.webp -------------------------------------------------------------------------------- /public/hero/Featured2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/Featured2.webp -------------------------------------------------------------------------------- /public/hero/Featured3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/Featured3.webp -------------------------------------------------------------------------------- /public/hero/Featured4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/Featured4.webp -------------------------------------------------------------------------------- /public/hero/event1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/event1.webp -------------------------------------------------------------------------------- /public/hero/event2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/event2.webp -------------------------------------------------------------------------------- /public/hero/event3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/event3.webp -------------------------------------------------------------------------------- /public/hero/header.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/hero/header.webp -------------------------------------------------------------------------------- /public/products/feature.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/feature.webp -------------------------------------------------------------------------------- /public/products/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/p1.png -------------------------------------------------------------------------------- /public/products/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/p2.png -------------------------------------------------------------------------------- /public/products/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/p3.png -------------------------------------------------------------------------------- /public/products/p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/p4.png -------------------------------------------------------------------------------- /public/products/p6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/public/products/p6.png -------------------------------------------------------------------------------- /sanity.cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This configuration file lets you run `$ sanity [command]` in this folder 3 | * Go to https://www.sanity.io/docs/cli to learn more. 4 | **/ 5 | import { defineCliConfig } from 'sanity/cli' 6 | 7 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 8 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 9 | 10 | export default defineCliConfig({ api: { projectId, dataset } }) 11 | -------------------------------------------------------------------------------- /sanity.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This configuration is used to for the Sanity Studio that’s mounted on the `\src\app\studio\[[...index]]\page.tsx` route 3 | */ 4 | 5 | import {visionTool} from '@sanity/vision' 6 | import {defineConfig} from 'sanity' 7 | import {deskTool} from 'sanity/desk' 8 | 9 | // Go to https://www.sanity.io/docs/api-versioning to learn how API versioning works 10 | import {apiVersion, dataset, projectId} from './sanity/env' 11 | import {schema} from './sanity/schema' 12 | 13 | export default defineConfig({ 14 | basePath: '/studio', 15 | projectId, 16 | dataset, 17 | // Add and edit the content schema in the './sanity/schema' folder 18 | schema, 19 | plugins: [ 20 | deskTool(), 21 | // Vision is a tool that lets you query your content with GROQ in the studio 22 | // https://www.sanity.io/docs/the-vision-plugin 23 | visionTool({defaultApiVersion: apiVersion}), 24 | ], 25 | }) 26 | -------------------------------------------------------------------------------- /sanity/category.ts: -------------------------------------------------------------------------------- 1 | import { defineField, defineType } from "sanity"; 2 | 3 | export default defineType({ 4 | name: "category", 5 | type: "document", 6 | title: "Category", 7 | fields: [ 8 | defineField({ 9 | name: "name", 10 | type: "string", 11 | title: "Category Name", 12 | }), 13 | defineField({ 14 | name: "slug", 15 | type: "slug", 16 | title: "Slug", 17 | options: { 18 | source: "slug", 19 | maxLength: 96, 20 | }, 21 | }), 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /sanity/env.ts: -------------------------------------------------------------------------------- 1 | export const apiVersion = 2 | process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-14' 3 | 4 | export const dataset = assertValue( 5 | process.env.NEXT_PUBLIC_SANITY_DATASET, 6 | 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET' 7 | ) 8 | 9 | export const projectId = assertValue( 10 | process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, 11 | 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID' 12 | ) 13 | 14 | export const useCdn = false 15 | 16 | function assertValue(v: T | undefined, errorMessage: string): T { 17 | if (v === undefined) { 18 | throw new Error(errorMessage) 19 | } 20 | 21 | return v 22 | } 23 | -------------------------------------------------------------------------------- /sanity/lib/client.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "next-sanity"; 2 | 3 | import { apiVersion, dataset, projectId, useCdn } from "../env"; 4 | 5 | export const client = createClient({ 6 | apiVersion, 7 | dataset, 8 | projectId, 9 | useCdn, 10 | token: process.env.SANITY_SECRET_TOKEN, 11 | }); 12 | -------------------------------------------------------------------------------- /sanity/lib/image.ts: -------------------------------------------------------------------------------- 1 | import createImageUrlBuilder from "@sanity/image-url"; 2 | import type { Image } from "sanity"; 3 | 4 | import { client } from "./client"; 5 | 6 | const imageBuilder = createImageUrlBuilder(client); 7 | 8 | export const urlForImage = (source: Image) => { 9 | return imageBuilder?.image(source).auto("format").fit("max"); 10 | }; 11 | -------------------------------------------------------------------------------- /sanity/product.ts: -------------------------------------------------------------------------------- 1 | import { defineType, defineField } from "sanity"; 2 | 3 | export const product = defineType({ 4 | name: "product", 5 | title: "Product", 6 | type: "document", 7 | fields: [ 8 | defineField({ 9 | name: "name", 10 | title: "Name", 11 | type: "string", 12 | }), 13 | 14 | defineField({ 15 | name: "subcat", 16 | title: "Sub Category", 17 | type: "string", 18 | }), 19 | defineField({ 20 | name: "quantity", 21 | title: "Quantity", 22 | type: "number", 23 | }), 24 | defineField({ 25 | name: "price", 26 | title: "Price", 27 | type: "number", 28 | }), 29 | defineField({ 30 | name: "description", 31 | title: "Description", 32 | type: "string", 33 | }), 34 | defineField({ 35 | name: "category", 36 | title: "Product Category", 37 | type: "reference", 38 | 39 | to: [ 40 | { 41 | type: "category", 42 | }, 43 | ], 44 | }), 45 | defineField({ 46 | name: "image", 47 | title: "Image", 48 | type: "array", 49 | of: [ 50 | { 51 | name: "img", 52 | type: "image", 53 | title: "Image", 54 | options: { 55 | hotspot: true, 56 | }, 57 | }, 58 | ], 59 | }), 60 | defineField({ 61 | name: "productcare", 62 | title: "Product Care", 63 | type: "array", 64 | of: [ 65 | { 66 | name: "carelist", 67 | type: "string", 68 | title: "Handle with Care ", 69 | }, 70 | ], 71 | }), 72 | 73 | defineField({ 74 | name: "slug", 75 | title: "Slug", 76 | type: "slug", 77 | options: { 78 | source: "name", 79 | maxLength: 96, 80 | }, 81 | }), 82 | ], 83 | }); 84 | -------------------------------------------------------------------------------- /sanity/schema.ts: -------------------------------------------------------------------------------- 1 | import { type SchemaTypeDefinition } from "sanity"; 2 | import category from "./category"; 3 | import { product } from "./product"; 4 | 5 | export const schema: { types: SchemaTypeDefinition[] } = { 6 | types: [product, category], 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import Wrapper from "@/components/shared/Wrapper"; 2 | import { client } from "../../../sanity/lib/client"; 3 | import Link from "next/link"; 4 | import Image from "next/image"; 5 | import { urlForImage } from "../../../sanity/lib/image"; 6 | import { SanityProducts } from "@/interfaces"; 7 | 8 | type Props = { 9 | params: { 10 | slug: string; 11 | }; 12 | }; 13 | 14 | const getProductsbyCategory = async ({ params }: Props) => { 15 | const str = params.slug; 16 | const str2 = str.charAt(0).toUpperCase() + str.slice(1); 17 | const query = `*[_type == "product" && category -> name == "${str2}"] | order(_createdAt asc) { 18 | _id, 19 | name, 20 | image, 21 | subcat, 22 | price, 23 | slug { 24 | current 25 | }, 26 | }`; 27 | const res = await client.fetch(query); 28 | return res; 29 | }; 30 | 31 | const Categorical = async ({ params }: Props) => { 32 | const products: SanityProducts[] = await getProductsbyCategory({ params }); 33 | return ( 34 | <> 35 | {products.length >= 1 ? ( 36 | 37 |
38 | {products.map((product) => ( 39 |
43 | 44 | {product.name} 50 |

{product.name}

51 |

{product.subcat}

52 |

${product.price}

53 | 54 |
55 | ))} 56 |
57 |
58 | ) : ( 59 | 60 |
61 |

No products found for {params.slug}

62 |
63 |
64 | )} 65 | 66 | ); 67 | }; 68 | 69 | export default Categorical; 70 | 71 | type ICategory = { 72 | slug: { 73 | current: string; 74 | }; 75 | }; 76 | 77 | export async function generateStaticParams() { 78 | const query = `*[_type == "category"] { 79 | slug { 80 | current 81 | } 82 | }`; 83 | const res: ICategory[] = await client.fetch(query); 84 | 85 | return res.map((category) => ({ 86 | slug: category.slug.current, 87 | })); 88 | } 89 | -------------------------------------------------------------------------------- /src/app/api/cart/[userId]/route.tsx: -------------------------------------------------------------------------------- 1 | import { cartTable, db } from "@/lib/drizzle"; 2 | import { eq } from "drizzle-orm"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | 5 | export const GET = async ( 6 | request: NextRequest, 7 | { params: { userId } }: { params: { userId: string } } 8 | ) => { 9 | if (!userId) { 10 | return NextResponse.json({ message: "Invalid User ID" }); 11 | } 12 | try { 13 | const res = await db 14 | .select() 15 | .from(cartTable) 16 | .where(eq(cartTable.user_id, userId)); 17 | const cartItems = res.map((item) => ({ 18 | _id: item.product_id, 19 | name: item.product_name, 20 | price: item.price, 21 | totalPrice: item.price * item.quantity, 22 | subcat: item.subcat, 23 | image: item.image, 24 | userId: item.user_id, 25 | quantity: item.quantity, 26 | })); 27 | const totalQuantity = cartItems.reduce( 28 | (total, item) => total + item.quantity, 29 | 0 30 | ); 31 | const totalAmount = cartItems.reduce( 32 | (total, item) => total + item.totalPrice, 33 | 0 34 | ); 35 | return NextResponse.json({ cartItems, totalQuantity, totalAmount }); 36 | } catch (error) { 37 | console.log(error); 38 | return NextResponse.json({ 39 | Message: (error as { message: string }).message, 40 | }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/api/cart/removeitem/[productId]/route.tsx: -------------------------------------------------------------------------------- 1 | import { cartTable, db } from "@/lib/drizzle"; 2 | import { and, eq } from "drizzle-orm"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | import { auth } from "@clerk/nextjs"; 5 | 6 | export const DELETE = async ( 7 | request: NextRequest, 8 | { params: { productId } }: { params: { productId: string } } 9 | ) => { 10 | const { userId } = auth(); 11 | try { 12 | if (productId && userId) { 13 | const res = await db 14 | .delete(cartTable) 15 | .where( 16 | and( 17 | eq(cartTable.user_id, userId as string), 18 | eq(cartTable.product_id, productId) 19 | ) 20 | ) 21 | .returning(); 22 | return NextResponse.json({ 23 | Message: "Item removed from Cart", 24 | }); 25 | } else { 26 | if (productId) { 27 | throw new Error("Please Login"); 28 | } else { 29 | throw new Error("Incorrect Product Id"); 30 | } 31 | } 32 | } catch (error) { 33 | return NextResponse.json((error as { message: string }).message); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/app/api/cart/route.ts: -------------------------------------------------------------------------------- 1 | import { addToCart, cartTable, db } from "@/lib/drizzle"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { auth } from "@clerk/nextjs"; 4 | import { and, eq } from "drizzle-orm"; 5 | 6 | 7 | export const POST = async (request: NextRequest) => { 8 | const { userId } = auth(); 9 | 10 | const req: addToCart = await request.json(); 11 | 12 | try { 13 | if (req) { 14 | const res = await db 15 | .insert(cartTable) 16 | .values({ 17 | user_id: userId as string, 18 | product_id: req.product_id, 19 | quantity: req.quantity, 20 | image: req.image, 21 | price: req.price, 22 | product_name: req.product_name, 23 | subcat: req.subcat, 24 | total_price: req.price * req.quantity, 25 | }) 26 | .returning(); 27 | return NextResponse.json({ res }); 28 | } else { 29 | throw new Error("Failed to insert Data"); 30 | } 31 | } catch (error) { 32 | console.log(error); 33 | return NextResponse.json({ 34 | Message: "Something went wrong", 35 | }); 36 | } 37 | }; 38 | 39 | export const PUT = async (request: NextRequest) => { 40 | const { userId } = auth(); 41 | 42 | const req: addToCart = await request.json(); 43 | 44 | try { 45 | if (req) { 46 | const res = await db 47 | .update(cartTable) 48 | .set({ 49 | quantity: req.quantity, 50 | total_price: req.price, 51 | }) 52 | .where(and(eq(cartTable.user_id, userId as string), eq(cartTable.product_id, req.product_id))).returning(); 53 | return NextResponse.json({ res }); 54 | } else { 55 | throw new Error("Failed to update Data"); 56 | } 57 | } catch (error) { 58 | console.log(error); 59 | return NextResponse.json({ 60 | Message: "Something went wrong", 61 | }); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/app/api/stripe-session/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Stripe from "stripe"; 3 | import { auth } from "@clerk/nextjs"; 4 | 5 | const key = process.env.STRIPE_SECRET_KEY || ""; 6 | 7 | const stripe = new Stripe(key, { 8 | apiVersion: "2022-11-15", 9 | }); 10 | 11 | export async function POST(request: NextRequest) { 12 | const {userId} = auth(); 13 | 14 | const body = await request.json(); 15 | console.log(body); 16 | const customer = await stripe.customers.create({ 17 | metadata: { 18 | userId: userId, 19 | }, 20 | }) 21 | 22 | try { 23 | if (body.length > 0 && userId) { 24 | 25 | const session = await stripe.checkout.sessions.create({ 26 | submit_type: "pay", 27 | mode: "payment", 28 | payment_method_types: ["card"], 29 | billing_address_collection: "auto", 30 | shipping_options: [ 31 | { shipping_rate: "shr_1NNQEiCWhuWDHCyLI3ZEbn2T" }, 32 | { shipping_rate: "shr_1NNQdtCWhuWDHCyLVxFex7Oe" }, 33 | ], 34 | invoice_creation: { 35 | enabled: true, 36 | }, 37 | 38 | line_items: body.map((item: any) => { 39 | 40 | return { 41 | price_data: { 42 | currency: "usd", 43 | product_data: { 44 | name: item.name, 45 | images: [item.image], 46 | }, 47 | unit_amount: item.price * 100, 48 | }, 49 | quantity: item.quantity, 50 | adjustable_quantity: { 51 | enabled: true, 52 | minimum: 1, 53 | maximum: 10, 54 | }, 55 | }; 56 | }), 57 | customer:customer.id, 58 | phone_number_collection: { 59 | enabled: true, 60 | }, 61 | success_url: `${request.headers.get("origin")}/success`, 62 | cancel_url: `${request.headers.get("origin")}/cart`, 63 | }); 64 | return NextResponse.json({ session }); 65 | } else { 66 | return NextResponse.json({ message: "No Data Found" }); 67 | } 68 | } catch (err: any) { 69 | console.log(err); 70 | return NextResponse.json(err.message); 71 | } 72 | } -------------------------------------------------------------------------------- /src/app/api/webhooks/route.ts: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe"; 2 | import { db, cartTable } from "@/lib/drizzle"; 3 | import { eq } from "drizzle-orm"; 4 | import {headers} from "next/headers" 5 | 6 | const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET as string; 7 | export async function POST(req: any, res: any){ 8 | 9 | // console.log(endpointSecret + "End Point Secret"); 10 | 11 | const headerslist = headers(); 12 | 13 | try { 14 | const rawBody = await req.text(); 15 | const sig = headerslist.get("stripe-signature") 16 | 17 | 18 | const stripe = new Stripe( 19 | process.env.STRIPE_SECRET_KEY as string, 20 | { 21 | apiVersion: '2022-11-15', 22 | } 23 | ) 24 | 25 | // console.log(process.env.STRIPE_SECRET_KEY + "stripe secret key"); 26 | 27 | let event 28 | 29 | try { 30 | if (!sig || !endpointSecret) { 31 | return new Response(`Webhook Signature Or Endpoint Secret is Missing`, { 32 | status: 400, 33 | }) 34 | } 35 | 36 | event = stripe.webhooks.constructEvent( 37 | rawBody.toString(), // Stringify the request for the Stripe library 38 | sig, 39 | endpointSecret 40 | ) 41 | } catch (err: any) { 42 | console.log(`⚠️ Webhook sig`); 43 | return new Response("webhooks signature / endpoint secret missing" , { 44 | status: 400 45 | }) 46 | } 47 | 48 | 49 | if ( 'checkout.session.completed' === event.type ) { 50 | const session = event.data.object; 51 | // @ts-ignore 52 | const customerData = await stripe.customers.retrieve(session.customer) 53 | // @ts-ignore 54 | const userId = customerData.metadata.userId; 55 | 56 | await db.delete(cartTable).where(eq(cartTable.user_id, userId)); 57 | 58 | 59 | console.log( 'payment success-----------------------', session ); 60 | // @ts-ignore 61 | const line_Items = await stripe.checkout.sessions.listLineItems(event.data.object!.id); 62 | 63 | return new Response("Payment Confirmation Router Reciept", { 64 | status: 200 65 | }); 66 | 67 | 68 | } else { 69 | res.setHeader("Allow", "POST"); 70 | // res.status(405).end("Method Not Allowed"); 71 | } 72 | } catch (err: any) { 73 | console.log("Error in webhook----------", err); 74 | // res.status(400).send(`Webhook Error: ${err.message}`); 75 | return; 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/app/cart/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import CartItemCard from "@/components/shared/CartItemCard"; 3 | import Wrapper from "@/components/shared/Wrapper"; 4 | import { useAppSelector } from "@/redux/store"; 5 | import { BiShoppingBag } from "react-icons/bi"; 6 | import { selectIsLoading } from "@/redux/features/cartSlice"; 7 | import StripeCheckOutButton from "@/components/sections/CheckOut"; 8 | import StartShopping from "@/components/shared/StartShopping"; 9 | import { Toaster, toast } from "react-hot-toast"; 10 | 11 | const CartDataLoadingFromApi = () => { 12 | return ( 13 | 14 |
15 |

Loading Data

16 |
17 |
18 | ); 19 | }; 20 | 21 | const LoadedCartData = () => { 22 | const cartItems = useAppSelector((state) => state.cart.items); 23 | const totalItems = useAppSelector((state) => state.cart.totalQuantity); 24 | const totalPrice = useAppSelector((state) => state.cart.totalAmount); 25 | 26 | if (cartItems.length > 0) { 27 | return ( 28 | 29 |

Shopping Cart

30 |
31 |
32 | {cartItems.map((elm) => ( 33 | 34 | ))} 35 |
36 |
37 |
38 |

Order Summary

39 |
40 |
41 |

Quantity

42 |
43 |
44 |

{totalItems}

45 |
46 |
47 |
48 |
49 |

Total Amount

50 |
51 |
52 |

${totalPrice}

53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 | ); 64 | } else { 65 | return ( 66 | 67 |

Shopping Cart

68 | 69 |
70 | 71 |

Your shopping bag is empty

72 | 73 |
74 |
75 | ); 76 | } 77 | }; 78 | 79 | const CartPage = () => { 80 | const isLoading = useAppSelector(selectIsLoading); 81 | 82 | return <>{isLoading ? : }; 83 | }; 84 | 85 | export default CartPage; 86 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdeveloper/ecom-hackathon/ed4ed9f609ecbda2bec321b433d680563d637963/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | 7 | h1 { 8 | @apply lg:text-[3.5rem] md:text-[2.75rem] text-[2rem] font-bold tracking-wider leading-[50px] 9 | } 10 | 11 | h2 { 12 | @apply text-[2.75rem] font-bold tracking-[0.03em] leading-10 13 | } 14 | 15 | h3 { 16 | @apply lg:text-3xl md:text-[1.125rem] text-[1.05rem] leading-[55px] tracking-[0.03em] font-bold 17 | } 18 | 19 | h4 { 20 | @apply text-[1.125rem] leading-5 tracking-[0.03em] font-semibold 21 | } 22 | 23 | h5 { 24 | @apply text-[1.05rem] leading-6 tracking-[0.03em] font-semibold 25 | } 26 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Navbar, Footer } from "@/components/sections"; 2 | import "./globals.css"; 3 | import { Sora } from "next/font/google"; 4 | import Providers from "@/components/utils/Providers"; 5 | 6 | import { ClerkProvider, auth } from "@clerk/nextjs"; 7 | 8 | const sora = Sora({ subsets: ["latin"], style: "normal" }); 9 | 10 | export const metadata = { 11 | title: "Dine Market", 12 | description: "Ecommerce shopping App", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | const { userId } = auth(); 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | {children} 28 |