├── .eslintrc.json ├── src ├── app │ ├── globals.css │ ├── favicon.ico │ ├── not-found.tsx │ ├── SessionProvider.tsx │ ├── opengraph-image.png │ ├── loading.tsx │ ├── error.tsx │ ├── layout.tsx │ ├── cart │ │ ├── page.tsx │ │ ├── actions.ts │ │ └── CartEntry.tsx │ ├── products │ │ └── [id] │ │ │ ├── actions.ts │ │ │ ├── AddToCartButton.tsx │ │ │ └── page.tsx │ ├── api │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── Footer.tsx │ ├── search │ │ └── page.tsx │ ├── Navbar │ │ ├── Navbar.tsx │ │ ├── UserMenuButton.tsx │ │ └── ShoppingCartButton.tsx │ ├── page.tsx │ └── add-product │ │ └── page.tsx ├── assets │ ├── logo.png │ └── profile-pic-placeholder.png ├── lib │ ├── format.ts │ ├── env.ts │ └── db │ │ ├── prisma.ts │ │ └── cart.ts └── components │ ├── PriceTag.tsx │ ├── FormSubmitButton.tsx │ ├── ProductCard.tsx │ └── PaginationBar.tsx ├── prettier.config.js ├── postcss.config.js ├── @types └── next-auth.d.ts ├── next.config.js ├── README.md ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── tailwind.config.js ├── package.json └── prisma └── schema.prisma /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-ecommerce/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-ecommerce/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("prettier-plugin-tailwindcss")], 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFoundPage() { 2 | return
Page not found.
; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/SessionProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export { SessionProvider as default } from "next-auth/react"; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-ecommerce/HEAD/src/app/opengraph-image.png -------------------------------------------------------------------------------- /src/assets/profile-pic-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codinginflow/nextjs-ecommerce/HEAD/src/assets/profile-pic-placeholder.png -------------------------------------------------------------------------------- /src/app/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function LoadingPage() { 2 | return ; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export default function ErrorPage() { 4 | return
Something went wrong. Please refresh the page.
; 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/format.ts: -------------------------------------------------------------------------------- 1 | export function formatPrice(price: number) { 2 | return (price / 100).toLocaleString("en-US", { 3 | style: "currency", 4 | currency: "USD", 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /@types/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultSession } from "next-auth"; 2 | 3 | declare module "next-auth" { 4 | interface Session { 5 | user: { 6 | id: string; 7 | } & DefaultSession["user"]; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/PriceTag.tsx: -------------------------------------------------------------------------------- 1 | import { formatPrice } from "@/lib/format"; 2 | 3 | interface PriceTagProps { 4 | price: number; 5 | className?: string; 6 | } 7 | 8 | export default function PriceTag({ price, className }: PriceTagProps) { 9 | return {formatPrice(price)}; 10 | } 11 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { hostname: "images.unsplash.com" }, 6 | { hostname: "lh3.googleusercontent.com" }, 7 | ], 8 | }, 9 | experimental: { 10 | serverActions: true, 11 | }, 12 | }; 13 | 14 | module.exports = nextConfig; 15 | -------------------------------------------------------------------------------- /src/lib/env.ts: -------------------------------------------------------------------------------- 1 | import zod from "zod"; 2 | 3 | const envSchema = zod.object({ 4 | DATABASE_URL: zod.string().nonempty(), 5 | GOOGLE_CLIENT_ID: zod.string().nonempty(), 6 | GOOGLE_CLIENT_SECRET: zod.string().nonempty(), 7 | NEXTAUTH_URL: zod.string().nonempty(), 8 | NEXTAUTH_SECRET: zod.string().nonempty(), 9 | }); 10 | 11 | export const env = envSchema.parse(process.env); 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.JS 13.4 E-Commerce App 2 | 3 | An e-commerce website similar to Amazon that makes heavy use of **Next.js server actions**. 4 | 5 | Includes user sign-in with **Next-Auth**, **anonymous shopping carts**, **Prisma client extensions**, and more. Written in **TypeScript**. UI built with **TailwindCSS** & **DaisyUI**. 6 | 7 | Learn how to build this project: https://www.youtube.com/watch?v=AaiijESQH5o 8 | 9 | ![thumbnail](https://github.com/codinginflow/nextjs-ecommerce/assets/52977034/7367010c-abc3-4e11-acdf-03b98f7f0f70) 10 | -------------------------------------------------------------------------------- /.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 | # My additions 38 | .env -------------------------------------------------------------------------------- /src/lib/db/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const globalForPrisma = globalThis as unknown as { 4 | prisma: PrismaClient | undefined; 5 | }; 6 | 7 | const prismaBase = globalForPrisma.prisma ?? new PrismaClient(); 8 | 9 | export const prisma = prismaBase.$extends({ 10 | query: { 11 | cart: { 12 | async update({ args, query }) { 13 | args.data = { ...args.data, updatedAt: new Date() }; 14 | return query(args); 15 | }, 16 | }, 17 | }, 18 | }); 19 | 20 | if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prismaBase; 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/FormSubmitButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ComponentProps } from "react"; 4 | import { experimental_useFormStatus as useFormStatus } from "react-dom"; 5 | 6 | type FormSubmitButtonProps = { 7 | children: React.ReactNode; 8 | className?: string; 9 | } & ComponentProps<"button">; 10 | 11 | export default function FormSubmitButton({ 12 | children, 13 | className, 14 | ...props 15 | }: FormSubmitButtonProps) { 16 | const { pending } = useFormStatus(); 17 | 18 | return ( 19 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 5 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 7 | ], 8 | plugins: [require("daisyui")], 9 | daisyui: { 10 | themes: [ 11 | { 12 | lightTheme: { 13 | primary: "#f4aa3a", 14 | secondary: "#f4f4a1", 15 | accent: "#1be885", 16 | neutral: "#272136", 17 | "base-100": "#ffffff", 18 | info: "#778ad4", 19 | success: "#23b893", 20 | warning: "#f79926", 21 | error: "#ea535a", 22 | body: { 23 | "background-color": "#e3e6e6", 24 | }, 25 | }, 26 | }, 27 | ], 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "./Footer"; 2 | import Navbar from "./Navbar/Navbar"; 3 | import "./globals.css"; 4 | import { Inter } from "next/font/google"; 5 | import SessionProvider from "./SessionProvider"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata = { 10 | title: "Flowmazon", 11 | description: "We make your wallet cry", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | 24 |
{children}
25 |