├── public
├── favicon.ico
├── vercel.svg
├── thirteen.svg
└── next.svg
├── postcss.config.js
├── jsconfig.json
├── utils
├── error.js
├── db.js
└── Store.js
├── next.config.js
├── tailwind.config.js
├── pages
├── api
│ ├── keys
│ │ └── paypal.js
│ ├── admin
│ │ ├── cloudinary-sign.js
│ │ ├── users
│ │ │ ├── index.js
│ │ │ └── [id].js
│ │ ├── orders
│ │ │ ├── index.js
│ │ │ └── [id]
│ │ │ │ └── deliver.js
│ │ ├── summary.js
│ │ └── products
│ │ │ ├── index.js
│ │ │ └── [id].js
│ ├── orders
│ │ ├── [id]
│ │ │ ├── index.js
│ │ │ └── pay.js
│ │ ├── history.js
│ │ └── index.js
│ ├── auth
│ │ ├── signup.js
│ │ ├── update.js
│ │ └── [...nextauth].js
│ └── products
│ │ └── [id].js
├── unauthorized.js
├── _app.js
├── index.js
├── payment.js
├── login.js
├── order-history.js
├── product
│ └── [slug].js
├── shipping.js
├── register.js
├── cart.js
├── profile.js
├── admin
│ ├── orders.js
│ ├── dashboard.js
│ ├── users.js
│ ├── products.js
│ └── product
│ │ └── [id].js
├── placeorder.js
├── search.js
└── order
│ └── [id].js
├── components
├── DropdownLink.js
├── CheckoutWizard.js
├── ProductItem.js
└── Layout.js
├── .gitignore
├── models
├── User.js
├── Product.js
└── Order.js
├── styles
└── globals.css
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wildhunter4137/E-commerce-next/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/utils/error.js:
--------------------------------------------------------------------------------
1 | export const getError = (error) =>
2 | error.response && error.response.data && error.response.data.message
3 | ? error.response.data.message
4 | : error.message;
5 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | images: {
5 | domains: ["res.cloudinary.com", "www.w3.org"],
6 | },
7 | };
8 |
9 | module.exports = nextConfig;
10 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx}",
5 | "./components/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | };
12 |
--------------------------------------------------------------------------------
/pages/api/keys/paypal.js:
--------------------------------------------------------------------------------
1 | import { getSession } from "next-auth/react";
2 |
3 | const handler = async (req, res) => {
4 | const session = await getSession({ req });
5 | if (!session) {
6 | return res.status(401).send("Signin required");
7 | }
8 |
9 | res.send(process.env.PAYPAL_CLIENT_ID || "sb");
10 | };
11 |
12 | export default handler;
13 |
--------------------------------------------------------------------------------
/pages/api/admin/cloudinary-sign.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require("cloudinary").v2;
2 |
3 | export default function signature(req, res) {
4 | const timestamp = Math.round(new Date().getTime() / 1000);
5 | const signature = cloudinary.utils.api_sign_request(
6 | {
7 | timestamp: timestamp,
8 | },
9 | process.env.CLOUDINARY_SECRET
10 | );
11 | res.statsCode = 200;
12 | res.json({
13 | signature,
14 | timestamp,
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/components/DropdownLink.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React from "react";
3 |
4 | function DropdownLink(props) {
5 | const { href, logout, children, ...rest } = props;
6 |
7 | return (
8 | {
12 | if (logout) logout();
13 | }}
14 | >
15 | {children}
16 |
17 | );
18 | }
19 |
20 | export default DropdownLink;
21 |
--------------------------------------------------------------------------------
/pages/api/orders/[id]/index.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session) {
8 | res.status(401).send("Signin required");
9 | }
10 | await db.connect();
11 | const order = await Order.findById(req.query.id);
12 | await db.disconnect();
13 | res.send(order);
14 | };
15 |
16 | export default handler;
17 |
--------------------------------------------------------------------------------
/.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 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # env files
32 | .env
33 |
34 | # vercel
35 | .vercel
36 |
--------------------------------------------------------------------------------
/pages/api/admin/users/index.js:
--------------------------------------------------------------------------------
1 | import User from "@/models/User";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 | await db.connect();
11 | const users = await User.find({});
12 | await db.disconnect();
13 | res.send(users);
14 | };
15 |
16 | export default handler;
17 |
--------------------------------------------------------------------------------
/pages/unauthorized.js:
--------------------------------------------------------------------------------
1 | import Layout from '@/components/Layout';
2 | import { useRouter } from 'next/router';
3 | import React from 'react';
4 |
5 | function Unauthorized() {
6 | const router = useRouter();
7 | const { message } = router.query;
8 |
9 | return (
10 |
11 | Access Denied
12 |
13 |
14 | );
15 | }
16 |
17 | export default Unauthorized;
18 |
--------------------------------------------------------------------------------
/pages/api/orders/history.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session) {
8 | res.status(401).send("Signin required");
9 | }
10 | const { user } = session;
11 | await db.connect();
12 | const orders = await Order.find({
13 | user: user._id,
14 | });
15 | await db.disconnect();
16 | res.send(orders);
17 | };
18 |
19 | export default handler;
20 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | email: {
10 | type: String,
11 | required: true,
12 | unique: true,
13 | },
14 | password: {
15 | type: String,
16 | required: true,
17 | },
18 | isAdmin: {
19 | type: Boolean,
20 | required: true,
21 | default: false,
22 | },
23 | },
24 | {
25 | timestamps: true,
26 | }
27 | );
28 |
29 | const User = mongoose.models.User || mongoose.model("User", userSchema);
30 | export default User;
31 |
--------------------------------------------------------------------------------
/components/CheckoutWizard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function CheckoutWizard({ activeStep = 0 }) {
4 | return (
5 |
6 | {["User Login", "Shipping Address", "Payment Method", "Place Order"].map(
7 | (step, index) => (
8 |
16 | {step}
17 |
18 | )
19 | )}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/pages/api/admin/orders/index.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 | if (req.method === "GET") {
11 | await db.connect();
12 | const orders = await Order.find({}).populate("user", "name");
13 | await db.disconnect();
14 | console.log(orders);
15 | res.send(orders);
16 | } else {
17 | return res.status(400).send({ message: "Method not allowed" });
18 | }
19 | };
20 |
21 | export default handler;
22 |
--------------------------------------------------------------------------------
/pages/api/orders/index.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session) {
8 | res.status(401).send("Signin required");
9 | }
10 | const { user } = session;
11 | await db.connect();
12 | const newOrder = new Order({
13 | ...req.body,
14 | isPaid: false,
15 | paidAt: Date.now(),
16 | paymentResult: {
17 | id: "",
18 | status: "",
19 | email_address: "",
20 | },
21 | user: user._id,
22 | });
23 | const order = await newOrder.save();
24 | await db.disconnect();
25 | res.status(201).send(order);
26 | };
27 |
28 | export default handler;
29 |
--------------------------------------------------------------------------------
/pages/api/admin/orders/[id]/deliver.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 |
11 | await db.connect();
12 | const order = await Order.findById(req.query.id);
13 | if (order) {
14 | order.isDelivered = true;
15 | order.deliveredAt = Date.now();
16 | const devliveredOrder = await order.save();
17 | await db.disconnect();
18 | res.send({
19 | message: "Order delivered successfully",
20 | order: devliveredOrder,
21 | });
22 | } else {
23 | await db.disconnect();
24 | res.status(404).send({
25 | message: "Error: order not found",
26 | });
27 | }
28 | };
29 |
30 | export default handler;
31 |
--------------------------------------------------------------------------------
/pages/api/admin/users/[id].js:
--------------------------------------------------------------------------------
1 | import User from "@/models/User";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 | if (req.method === "DELETE") {
11 | return deleteHandler(req, res);
12 | } else {
13 | return res.status(400).send({ message: "Method not allowed" });
14 | }
15 | };
16 |
17 | const deleteHandler = async (req, res) => {
18 | await db.connect();
19 | const user = await User.findById(req.query.id);
20 | if (user) {
21 | await user.remove();
22 | await db.disconnect();
23 | return res.send({ message: "User deleted successfully" });
24 | } else {
25 | await db.disconnect();
26 | return res.status(404).send({ message: "User Not Found" });
27 | }
28 | };
29 |
30 | export default handler;
31 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap');
6 |
7 | @layer base {
8 | html {
9 | font-family: 'Fredoka One', cursive;
10 | }
11 | }
12 |
13 | .card{
14 | @apply mb-5 block rounded-lg border border-gray-200 shadow-md
15 | }
16 |
17 | .primary-button{
18 | @apply rounded bg-blue-500 py-2 shadow-lg outline-none hover:bg-blue-700 active:bg-blue-800 p-2
19 | }
20 |
21 | .default-button{
22 | @apply rounded bg-gray-100 py-2 shadow outline-none hover:bg-gray-100 active:bg-gray-300 p-2
23 | }
24 |
25 | input,
26 | select,
27 | textarea{
28 | @apply rounded border p-2 outline-none ring-indigo-400 focus:ring
29 | }
30 |
31 | .dropdown-link{
32 | @apply flex p-2 hover:bg-gray-400;
33 | }
34 |
35 | .alert-error{
36 | @apply my-3 rounded-lg bg-red-100 p-3 text-red-700;
37 | }
38 |
39 | .alert-success{
40 | @apply my-3 rounded-lg bg-green-100 p-3 text-green-700;
41 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-shop",
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 | "@headlessui/react": "^1.7.11",
13 | "@heroicons/react": "^2.0.15",
14 | "@next/font": "13.1.6",
15 | "@paypal/react-paypal-js": "^7.8.2",
16 | "axios": "^1.3.3",
17 | "bcryptjs": "^2.4.3",
18 | "chart.js": "^4.2.1",
19 | "cloudinary": "^1.34.0",
20 | "js-cookie": "^3.0.1",
21 | "mongoose": "^6.9.1",
22 | "next": "13.1.6",
23 | "next-auth": "^4.19.2",
24 | "react": "18.2.0",
25 | "react-chartjs-2": "^5.2.0",
26 | "react-dom": "18.2.0",
27 | "react-hook-form": "^7.43.1",
28 | "react-rating-stars-component": "^2.2.0",
29 | "react-responsive-carousel": "^3.2.23",
30 | "react-toastify": "^9.1.1"
31 | },
32 | "devDependencies": {
33 | "autoprefixer": "^10.4.13",
34 | "postcss": "^8.4.21",
35 | "tailwindcss": "^3.2.6"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/db.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const connection = {};
4 |
5 | async function connect() {
6 | if (connection.isConnected) {
7 | return;
8 | }
9 | if (mongoose.connections.length > 0) {
10 | connection.isConnected = mongoose.connections[0].readyState;
11 | if (connection.isConnected === 1) {
12 | return;
13 | }
14 | await mongoose.disconnect();
15 | }
16 | const db = await mongoose.connect(process.env.MONGODO_URL);
17 | connection.isConnected = db.connections[0].readyState;
18 | }
19 |
20 | async function disconnect() {
21 | if (connection.isConnected) {
22 | if (process.env.NODE_ENV === "production") {
23 | await mongoose.disconnect();
24 | connection.isConnected = false;
25 | } else {
26 | console.log("not disconnected");
27 | }
28 | }
29 | }
30 |
31 | function convertDocToObj(doc) {
32 | doc._id = doc._id.toString();
33 | doc.createdAt = doc.createdAt.toString();
34 | doc.updatedAt = doc.updatedAt.toString();
35 | return doc;
36 | }
37 |
38 | const db = { connect, disconnect, convertDocToObj };
39 | export default db;
40 |
--------------------------------------------------------------------------------
/pages/api/orders/[id]/pay.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session) {
8 | res.status(401).send("Signin required");
9 | }
10 | await db.connect();
11 | const order = await Order.findById(req.query.id);
12 | if (order) {
13 | if (order.isPaid) {
14 | return res.status(400).send({ message: "Error: order is already paid" });
15 | }
16 | order.isPaid = true;
17 | order.paidAt = Date.now();
18 | order.paymentResult = {
19 | id: req.body.id,
20 | status: req.body.status,
21 | email_address: req.body.email_address,
22 | };
23 |
24 | const paidOrder = await order.save();
25 | await db.disconnect();
26 | res.send({
27 | message: "Order paid successfully",
28 | order: paidOrder,
29 | });
30 | } else {
31 | await db.disconnect();
32 | res.status(404).send({
33 | message: "Error: order not found",
34 | });
35 | }
36 | };
37 |
38 | export default handler;
39 |
--------------------------------------------------------------------------------
/pages/api/auth/signup.js:
--------------------------------------------------------------------------------
1 | import User from "@/models/User";
2 | import db from "@/utils/db";
3 | import bcryptjs from "bcryptjs";
4 |
5 | const handler = async (req, res) => {
6 | if (req.method !== "POST") {
7 | return;
8 | }
9 |
10 | const { name, email, password } = req.body;
11 |
12 | if (
13 | !name ||
14 | !email ||
15 | !email.includes("@") ||
16 | !password ||
17 | password.trim().length < 5
18 | ) {
19 | res.status(422).json({
20 | message: "Validation error",
21 | });
22 | return;
23 | }
24 | await db.connect();
25 | const existingUser = await User.findOne({ email: email });
26 |
27 | if (existingUser) {
28 | res.status(422).json({
29 | message: "User exists already",
30 | });
31 | await db.disconnect();
32 | return;
33 | }
34 |
35 | const newUser = await User({
36 | name,
37 | email,
38 | password: bcryptjs.hashSync(password),
39 | isAdmin: false,
40 | });
41 |
42 | const user = await newUser.save();
43 |
44 | await db.disconnect();
45 |
46 | res.status(201).send({
47 | message: "Created user",
48 | _id: user._id,
49 | name: user.name,
50 | email: user.email,
51 | isAdmin: user.isAdmin,
52 | });
53 | };
54 |
55 | export default handler;
56 |
--------------------------------------------------------------------------------
/pages/api/auth/update.js:
--------------------------------------------------------------------------------
1 | import bcryptjs from "bcryptjs";
2 | import User from "@/models/User";
3 | import db from "@/utils/db";
4 | import { getSession } from "next-auth/react";
5 |
6 | const handler = async (req, res) => {
7 | if (req.method != "PUT") {
8 | return res.status(400).send({
9 | message: `${req.method} not supported`,
10 | });
11 | }
12 |
13 | const session = await getSession({ req });
14 | if (!session) {
15 | res.status(401).send("Signin required");
16 | }
17 | const { user } = session;
18 | const { name, email, password } = req.body;
19 |
20 | if (
21 | !name ||
22 | !email ||
23 | !email.includes("@") ||
24 | !password ||
25 | password.trim().length < 5
26 | ) {
27 | res.status(422).json({
28 | message: "Validation error",
29 | });
30 | return;
31 | }
32 |
33 | await db.connect();
34 |
35 | const toUpdateUser = await User.findById(user._id);
36 | toUpdateUser.name = name;
37 | toUpdateUser.email = email;
38 | if (password) {
39 | toUpdateUser.password = bcryptjs.hashSync(password);
40 | }
41 |
42 | await toUpdateUser.save();
43 |
44 | await db.disconnect();
45 |
46 | res.send({
47 | message: "User updated",
48 | });
49 | };
50 |
51 | export default handler;
52 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "@/styles/globals.css";
2 | import { StoreProvide } from "@/utils/Store";
3 | import { PayPalScriptProvider } from "@paypal/react-paypal-js";
4 | import { SessionProvider, useSession } from "next-auth/react";
5 | import { useRouter } from "next/router";
6 |
7 | export default function App({
8 | Component,
9 | pageProps: { session, ...pageProps },
10 | }) {
11 | return (
12 |
13 |
14 |
15 | {Component.auth ? (
16 |
17 |
18 |
19 | ) : (
20 |
21 | )}
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | function Auth({ children, adminOnly }) {
29 | const router = useRouter();
30 | const { status, data: session } = useSession({
31 | required: true,
32 | onUnauthenticated() {
33 | router.push("/unauthorized?message=loging required");
34 | },
35 | });
36 |
37 | if (status === "loading") {
38 | return Loading...
;
39 | }
40 |
41 | if (adminOnly && !session.user.isAdmin) {
42 | router.push("/unauthorized?message=admin login required");
43 | }
44 |
45 | return children;
46 | }
47 |
--------------------------------------------------------------------------------
/components/ProductItem.js:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import React from "react";
4 | import ReactStars from "react-rating-stars-component";
5 |
6 | export default function ProductItem({ product, addToCartHandler }) {
7 | const ratingChanged = () => {};
8 |
9 | return (
10 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
24 |
{product.name}
25 |
26 |
{product.brand}
27 |
35 |
{product.price}₹
36 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/pages/api/admin/summary.js:
--------------------------------------------------------------------------------
1 | import Order from "@/models/Order";
2 | import Product from "@/models/Product";
3 | import User from "@/models/User";
4 | import db from "@/utils/db";
5 | import { getSession } from "next-auth/react";
6 |
7 | const handler = async (req, res) => {
8 | const session = await getSession({ req });
9 | if (!session || (session && !session.user.isAdmin)) {
10 | res.status(401).send("Signin required");
11 | }
12 |
13 | await db.connect();
14 |
15 | const ordersCount = await Order.countDocuments();
16 | const productsCount = await Product.countDocuments();
17 | const usersCount = await User.countDocuments();
18 |
19 | const ordersPriceGroup = await Order.aggregate([
20 | {
21 | $group: {
22 | _id: null,
23 | sales: { $sum: "$totalPrice" },
24 | },
25 | },
26 | ]);
27 |
28 | const ordersPrice =
29 | ordersPriceGroup.length > 0 ? ordersPriceGroup[0].sales : 0;
30 |
31 | const salesDate = await Order.aggregate([
32 | {
33 | $group: {
34 | _id: {
35 | $dateToString: {
36 | format: "%Y-%m",
37 | date: "$createdAt",
38 | },
39 | },
40 | totalSales: { $sum: "$totalPrice" },
41 | },
42 | },
43 | ]);
44 |
45 | await db.disconnect();
46 |
47 | res.send({
48 | ordersPrice,
49 | ordersCount,
50 | productsCount,
51 | usersCount,
52 | salesDate,
53 | });
54 | };
55 |
56 | export default handler;
57 |
--------------------------------------------------------------------------------
/pages/api/admin/products/index.js:
--------------------------------------------------------------------------------
1 | import Product from "@/models/Product";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 | if (req.method === "GET") {
11 | await db.connect();
12 | const products = await Product.find({});
13 | await db.disconnect();
14 | res.send(products);
15 | }
16 | if (req.method === "POST") {
17 | return postHandler(req, res);
18 | } else {
19 | return res.status(400).send({ message: "Method not allowed" });
20 | }
21 | };
22 |
23 | const postHandler = async (req, res) => {
24 | await db.connect();
25 | const newProduct = new Product({
26 | name: "sample name",
27 | slug: "sample-name-" + Math.random(),
28 | image: "/image/default-image.svg",
29 | price: 0,
30 | category: "sample category",
31 | brand: "sample brand",
32 | countInStock: 0,
33 | description: "sample description",
34 | rating: 0,
35 | ratings: [0, 0, 0, 0, 0],
36 | totalRatings: 0,
37 | numReviews: 0,
38 | reviews: [],
39 | isFeatured: false,
40 | banner: "",
41 | });
42 |
43 | console.log("post called....");
44 | console.log(newProduct);
45 |
46 | const product = await newProduct.save();
47 | await db.disconnect();
48 | res.send({
49 | message: "Product created succssfully",
50 | product,
51 | });
52 | };
53 |
54 | export default handler;
55 |
--------------------------------------------------------------------------------
/models/Product.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const productSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | slug: {
10 | type: String,
11 | required: true,
12 | unique: true,
13 | },
14 | category: {
15 | type: String,
16 | required: true,
17 | },
18 | image: {
19 | type: String,
20 | required: true,
21 | },
22 | price: {
23 | type: Number,
24 | required: true,
25 | },
26 | brand: {
27 | type: String,
28 | required: true,
29 | },
30 | rating: {
31 | type: Number,
32 | required: true,
33 | default: 0,
34 | },
35 | ratings: [
36 | {
37 | type: Number,
38 | required: true,
39 | default: 0,
40 | },
41 | ],
42 | totalRatings: {
43 | type: Number,
44 | required: true,
45 | default: 0,
46 | },
47 | numReviews: {
48 | type: Number,
49 | required: true,
50 | default: 0,
51 | },
52 | reviews: [
53 | {
54 | type: String,
55 | required: true,
56 | },
57 | ],
58 | countInStock: {
59 | type: Number,
60 | required: true,
61 | default: 0,
62 | },
63 | description: {
64 | type: String,
65 | required: true,
66 | },
67 | isFeatured: {
68 | type: Boolean,
69 | default: false,
70 | },
71 | banner: String,
72 | },
73 | {
74 | timestamps: true,
75 | }
76 | );
77 |
78 | const Product =
79 | mongoose.models.Product || mongoose.model("Product", productSchema);
80 | export default Product;
81 |
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import User from "@/models/User";
2 | import db from "@/utils/db";
3 | import NextAuth from "next-auth/next";
4 | import CredentialsProvider from "next-auth/providers/credentials";
5 | import bcryptjs from "bcryptjs";
6 |
7 | export default NextAuth({
8 | session: {
9 | strategy: "jwt",
10 | },
11 | callbacks: {
12 | async jwt({ token, user }) {
13 | if (user?._id) {
14 | token._id = user._id;
15 | }
16 |
17 | if (user?.isAdmin) {
18 | token.isAdmin = user.isAdmin;
19 | }
20 |
21 | return token;
22 | },
23 | async session({ session, token }) {
24 | if (token?._id) {
25 | session.user._id = token._id;
26 | }
27 |
28 | if (token?.isAdmin) {
29 | session.user.isAdmin = token.isAdmin;
30 | }
31 |
32 | return session;
33 | },
34 | },
35 | providers: [
36 | CredentialsProvider({
37 | async authorize(creadentials) {
38 | console.log("authorize called");
39 | console.log("connect running...");
40 | await db.connect();
41 |
42 | console.log("findOne called");
43 | console.log("findOne running...");
44 | const user = await User.findOne({
45 | email: creadentials.email,
46 | });
47 |
48 | console.log("user is:-", user);
49 |
50 | console.log("disconnect called");
51 | console.log("disconnect running...");
52 | await db.disconnect();
53 |
54 | console.log("retruning...");
55 | if (
56 | user &&
57 | bcryptjs.compareSync(creadentials.password, user.password)
58 | ) {
59 | return {
60 | _id: user._id,
61 | name: user.name,
62 | email: user.email,
63 | image: "image",
64 | isAdmin: user.isAdmin,
65 | };
66 | }
67 |
68 | throw new Error("Invalid email or password");
69 | },
70 | }),
71 | ],
72 | });
73 |
--------------------------------------------------------------------------------
/pages/api/admin/products/[id].js:
--------------------------------------------------------------------------------
1 | import Product from "@/models/Product";
2 | import db from "@/utils/db";
3 | import { getSession } from "next-auth/react";
4 |
5 | const handler = async (req, res) => {
6 | const session = await getSession({ req });
7 | if (!session || (session && !session.user.isAdmin)) {
8 | res.status(401).send("Admin signin required");
9 | }
10 | if (req.method === "GET") {
11 | return getHandler(req, res);
12 | } else if (req.method === "PUT") {
13 | return putHandler(req, res);
14 | } else if (req.method === "DELETE") {
15 | return deleteHandler(req, res);
16 | } else {
17 | return res.status(400).send({ message: "Method not allowed" });
18 | }
19 | };
20 |
21 | const deleteHandler = async (req, res) => {
22 | await db.connect();
23 | const product = await Product.findById(req.query.id);
24 | if (product) {
25 | await product.remove();
26 | await db.disconnect();
27 | return res.send({ message: "Product deleted successfully" });
28 | } else {
29 | await db.disconnect();
30 | return res.status(404).send({ message: "Product not found" });
31 | }
32 | };
33 |
34 | const getHandler = async (req, res) => {
35 | await db.connect();
36 | const product = await Product.findById(req.query.id);
37 | await db.disconnect();
38 | res.send(product);
39 | };
40 |
41 | const putHandler = async (req, res) => {
42 | await db.connect();
43 | const product = await Product.findById(req.query.id);
44 | if (product) {
45 | product.name = req.body.name;
46 | product.slug = req.body.slug;
47 | product.price = req.body.price;
48 | product.category = req.body.category;
49 | product.image = req.body.image;
50 | product.brand = req.body.brand;
51 | product.countInStock = req.body.countInStock;
52 | product.description = req.body.description;
53 | await product.save();
54 | await db.disconnect();
55 | return res.send({ message: "Product updated successfully" });
56 | } else {
57 | await db.disconnect();
58 | return res.status(404).send({ message: "Product not found" });
59 | }
60 | };
61 |
62 | export default handler;
63 |
--------------------------------------------------------------------------------
/pages/api/products/[id].js:
--------------------------------------------------------------------------------
1 | import Product from "@/models/Product";
2 | import db from "@/utils/db";
3 |
4 | const handler = async (req, res) => {
5 | if (req.method === "GET") {
6 | return getHandler(req, res);
7 | } else if (req.method === "PUT") {
8 | return putHandler(req, res);
9 | } else {
10 | return res.status(400).send({ message: "Method not allowed" });
11 | }
12 | };
13 |
14 | const getHandler = async (req, res) => {
15 | await db.connect();
16 | const product = await Product.findById(req.query.id);
17 | await db.disconnect();
18 | res.send(product);
19 | };
20 |
21 | function calculateRatings(star5, star4, star3, star2, star1) {
22 | return (
23 | (5 * star5 + 4 * star4 + 3 * star3 + 2 * star2 + 1 * star1) /
24 | (star5 + star4 + star3 + star2 + star1)
25 | );
26 | }
27 |
28 | const putHandler = async (req, res) => {
29 | await db.connect();
30 | console.log("put called....");
31 | const product = await Product.findById(req.query.id);
32 | console.log("product is:-", product);
33 | if (product) {
34 | const star5 = product.ratings[0];
35 | const star4 = product.ratings[1];
36 | const star3 = product.ratings[2];
37 | const star2 = product.ratings[3];
38 | const star1 = product.ratings[4];
39 |
40 | if (req.body.rating === 5) {
41 | product.ratings[0] = star5 + 1;
42 | } else if (req.body.rating === 4) {
43 | product.ratings[1] = star4 + 1;
44 | } else if (req.body.rating === 3) {
45 | product.ratings[2] = star3 + 1;
46 | } else if (req.body.rating === 2) {
47 | product.ratings[3] = star2 + 1;
48 | } else {
49 | product.ratings[4] = star1 + 1;
50 | }
51 |
52 | product.totalRatings = product.totalRatings + 1;
53 |
54 | product.rating = calculateRatings(
55 | product.ratings[0],
56 | product.ratings[1],
57 | product.ratings[2],
58 | product.ratings[3],
59 | product.ratings[4]
60 | );
61 |
62 | console.log("final product", product);
63 | await product.save();
64 | await db.disconnect();
65 | return res.send({ message: "Ratings updated successfully" });
66 | } else {
67 | await db.disconnect();
68 | return res.status(404).send({ message: "Product not found" });
69 | }
70 | };
71 |
72 | export default handler;
73 |
--------------------------------------------------------------------------------
/models/Order.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const orderSchema = new mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | ref: "User",
8 | required: true,
9 | },
10 | orderItems: [
11 | {
12 | name: {
13 | type: String,
14 | required: true,
15 | },
16 | quantity: {
17 | type: Number,
18 | required: true,
19 | },
20 | image: {
21 | type: String,
22 | required: true,
23 | },
24 | price: {
25 | type: Number,
26 | required: true,
27 | },
28 | },
29 | ],
30 | shippingAddress: {
31 | fullName: {
32 | type: String,
33 | required: true,
34 | },
35 | address: {
36 | type: String,
37 | required: true,
38 | },
39 | city: {
40 | type: String,
41 | required: true,
42 | },
43 | postalCode: {
44 | type: String,
45 | required: true,
46 | },
47 | country: {
48 | type: String,
49 | required: true,
50 | },
51 | },
52 | paymentMethod: {
53 | type: String,
54 | required: true,
55 | },
56 | paymentResult: {
57 | id: String,
58 | status: String,
59 | email_address: String,
60 | },
61 | itemsPrice: {
62 | type: Number,
63 | required: true,
64 | },
65 | shippingPrice: {
66 | type: Number,
67 | required: true,
68 | },
69 | taxPrice: {
70 | type: Number,
71 | required: true,
72 | },
73 | totalPrice: {
74 | type: Number,
75 | required: true,
76 | },
77 | isPaid: {
78 | type: Boolean,
79 | required: true,
80 | default: false,
81 | },
82 | isDelivered: {
83 | type: Boolean,
84 | required: true,
85 | default: false,
86 | },
87 | paidAt: {
88 | type: Date,
89 | },
90 | deliveredAt: {
91 | type: Date,
92 | },
93 | },
94 | {
95 | timestamps: true,
96 | }
97 | );
98 |
99 | const Order = mongoose.models.Order || mongoose.model("Order", orderSchema);
100 | export default Order;
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## :star2: About the Project
2 |
3 | MyShop is an Ecommerce web application built with Next.js and MongoDB.
4 |
5 | ## LIVE DEMO 💥
6 |
7 |
8 |
9 | ### :camera: Screenshots
10 |
11 | 
12 |
13 | ##
14 |
15 | 
16 |
17 | ##
18 |
19 | 
20 |
21 | ##
22 |
23 | 
24 |
25 | ### :space_invader: Tech Stack
26 |
27 |
28 | Client
29 |
33 |
34 |
35 |
36 | Database
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | |
48 |
49 |
50 | |
51 |
52 |
53 | |
54 |
55 |
56 | |
57 |
58 |
59 |
60 | # 🏃♀️ Running
61 |
62 | - Clone repo Run `git clone https://github.com/wildhunter4137/E-commerce-next.git`
63 | - Run `npm install`
64 | - Run `npm run dev`
65 | - See `http://localhost:3000`
66 |
67 |
68 |
69 | ## Contact
70 |
71 | Project Link: https://github.com/wildhunter4137/E-commerce-next
72 |
--------------------------------------------------------------------------------
/utils/Store.js:
--------------------------------------------------------------------------------
1 | import Cookies from "js-cookie";
2 | const { createContext, useReducer } = require("react");
3 | export const Store = createContext();
4 |
5 | const initialStore = {
6 | cart: Cookies.get("cart")
7 | ? JSON.parse(Cookies.get("cart"))
8 | : { cartItems: [], shippingAddress: {}, paymentMethod: "" },
9 | };
10 |
11 | function reducer(state, action) {
12 | switch (action.type) {
13 | case "CART_ADD_ITEM": {
14 | const newItem = action.payload;
15 | const existItem = state.cart.cartItems.find(
16 | (item) => item.slug === newItem.slug
17 | );
18 | const cartItems = existItem
19 | ? state.cart.cartItems.map((item) =>
20 | item.name === existItem.name ? newItem : item
21 | )
22 | : [...state.cart.cartItems, newItem];
23 |
24 | Cookies.set("cart", JSON.stringify({ ...state.cart, cartItems }));
25 |
26 | return { ...state, cart: { ...state.cart, cartItems } };
27 | }
28 | case "CART_REMOVE_ITEM": {
29 | const cartItems = state.cart.cartItems.filter(
30 | (item) => item.slug != action.payload.slug
31 | );
32 |
33 | Cookies.set("cart", JSON.stringify({ ...state.cart, cartItems }));
34 |
35 | return { ...state, cart: { ...state.cart, cartItems } };
36 | }
37 | case "CART_RESET": {
38 | return {
39 | ...state,
40 | cart: {
41 | cartItems: [],
42 | shippingAddress: {
43 | location: {},
44 | },
45 | paymentMethod: "",
46 | },
47 | };
48 | }
49 | case "SAVE_SHIPPING_ADDRESS": {
50 | return {
51 | ...state,
52 | cart: {
53 | ...state.cart,
54 | shippingAddress: {
55 | ...state.cart.shippingAddress,
56 | ...action.payload,
57 | },
58 | },
59 | };
60 | }
61 | case "SAVE_PAYMENT_METHOD": {
62 | return {
63 | ...state,
64 | cart: {
65 | ...state.cart,
66 | paymentMethod: action.payload,
67 | },
68 | };
69 | }
70 | case "CART_CLEAR_ITEMS": {
71 | return { ...state, cart: { ...state.cart, cartItems: [] } };
72 | }
73 | default:
74 | return state;
75 | }
76 | }
77 |
78 | export function StoreProvide({ children }) {
79 | const [state, dispatch] = useReducer(reducer, initialStore);
80 | const value = { state, dispatch };
81 | return {children};
82 | }
83 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import ProductItem from "@/components/ProductItem";
3 | import Product from "@/models/Product";
4 | import db from "@/utils/db";
5 | import { Store } from "@/utils/Store";
6 | import axios from "axios";
7 | import Link from "next/link";
8 | import { useContext } from "react";
9 | import "react-responsive-carousel/lib/styles/carousel.min.css";
10 | import { Carousel } from "react-responsive-carousel";
11 | import { toast } from "react-toastify";
12 |
13 | export default function Home({ featuredProducts, products }) {
14 | const { state, dispatch } = useContext(Store);
15 | const { cart } = state;
16 |
17 | const addToCartHandler = async (product) => {
18 | const existItem = cart.cartItems.find((item) => item.slug === product.slug);
19 | const quantity = existItem ? existItem.quantity + 1 : 1;
20 |
21 | const { data } = await axios.get(`/api/products/${product._id}`);
22 |
23 | if (data.countInStock < quantity) {
24 | toast.error("Sorry. Product is out of stock");
25 | return;
26 | }
27 |
28 | dispatch({
29 | type: "CART_ADD_ITEM",
30 | payload: { ...product, quantity: quantity },
31 | });
32 |
33 | toast.success("Product added to the cart");
34 | };
35 |
36 | return (
37 |
38 |
39 |
40 | {featuredProducts.map((product) => (
41 |
42 |
43 |
44 |

45 |
46 |
47 |
48 | ))}
49 |
50 |
51 | Latest Products
52 |
53 | {products.map((product) => (
54 |
59 | ))}
60 |
61 |
62 | );
63 | }
64 |
65 | export async function getServerSideProps() {
66 | await db.connect();
67 | const products = await Product.find().lean();
68 | const featuredProducts = products.filter(
69 | (product) => product.isFeatured === true
70 | );
71 | return {
72 | props: {
73 | featuredProducts: featuredProducts.map(db.convertDocToObj),
74 | products: products.map(db.convertDocToObj),
75 | },
76 | };
77 | }
78 |
--------------------------------------------------------------------------------
/pages/payment.js:
--------------------------------------------------------------------------------
1 | import CheckoutWizard from "@/components/CheckoutWizard";
2 | import Layout from "@/components/Layout";
3 | import { Store } from "@/utils/Store";
4 | import Cookies from "js-cookie";
5 | import { useRouter } from "next/router";
6 | import React, { useContext, useEffect, useState } from "react";
7 | import { toast } from "react-toastify";
8 |
9 | export default function Payment() {
10 | const router = useRouter();
11 |
12 | const [selectedPaymentMethod, setSelectedPaymentMethod] = useState("");
13 |
14 | const { state, dispatch } = useContext(Store);
15 | const { cart } = state;
16 | const { shippingAddress, paymentMethod } = cart;
17 |
18 | useEffect(() => {
19 | if (!shippingAddress.address) {
20 | router.push("/shipping");
21 | }
22 | setSelectedPaymentMethod(paymentMethod || "");
23 | }, [paymentMethod, router, shippingAddress.address]);
24 |
25 | const submitHandler = (e) => {
26 | e.preventDefault();
27 | if (!selectedPaymentMethod) {
28 | return toast.error("Payment method is required");
29 | }
30 |
31 | dispatch({ type: "SAVE_PAYMENT_METHOD", payload: selectedPaymentMethod });
32 |
33 | Cookies.set(
34 | "cart",
35 | JSON.stringify({
36 | ...cart,
37 | paymentMethod: selectedPaymentMethod,
38 | })
39 | );
40 |
41 | router.push("/placeorder");
42 | };
43 |
44 | return (
45 |
46 |
47 |
76 |
77 | );
78 | }
79 |
80 | Payment.auth = true;
81 |
--------------------------------------------------------------------------------
/pages/login.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import Link from "next/link";
3 | import { signIn, useSession } from "next-auth/react";
4 | import React, { useEffect } from "react";
5 | import { useForm } from "react-hook-form";
6 | import { getError } from "@/utils/error";
7 | import { toast } from "react-toastify";
8 | import { useRouter } from "next/router";
9 |
10 | export default function Login() {
11 | const { data: session } = useSession();
12 | const router = useRouter();
13 | const { redirect } = router.query;
14 |
15 | useEffect(() => {
16 | if (session?.user) {
17 | router.push(redirect || "/");
18 | }
19 | }, [router, session, redirect]);
20 |
21 | const {
22 | register,
23 | handleSubmit,
24 | watch,
25 | formState: { errors },
26 | } = useForm();
27 |
28 | const submitHandler = async ({ email, password }) => {
29 | try {
30 | const result = await signIn("credentials", {
31 | redirect: false,
32 | email,
33 | password,
34 | });
35 |
36 | if (result.error) {
37 | toast.error(result.error);
38 | }
39 | } catch (error) {
40 | toast.error(getError(error));
41 | }
42 | };
43 |
44 | return (
45 |
46 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/pages/order-history.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import Link from "next/link";
5 | import React, { useEffect, useReducer } from "react";
6 |
7 | function reducer(state, action) {
8 | switch (action.type) {
9 | case "FETCH_REQUEST": {
10 | return { ...state, loading: true, error: "" };
11 | }
12 | case "FETCH_SUCCESS": {
13 | return { ...state, loading: false, error: "", orders: action.payload };
14 | }
15 | case "FETCH_FAIL": {
16 | return { ...state, loading: false, error: action.payload };
17 | }
18 | default: {
19 | return state;
20 | }
21 | }
22 | }
23 |
24 | export default function OrderHistory() {
25 | const [{ loading, error, orders }, dispatch] = useReducer(reducer, {
26 | loading: true,
27 | error: "",
28 | orders: [],
29 | });
30 |
31 | useEffect(() => {
32 | const fetchOrders = async () => {
33 | try {
34 | dispatch({ type: "FETCH_REQUEST" });
35 | const { data } = await axios.get("api/orders/history");
36 | dispatch({ type: "FETCH_SUCCESS", payload: data });
37 | } catch (error) {
38 | dispatchEvent({ type: "FETCH_FAIL", payload: getError(error) });
39 | }
40 | };
41 | fetchOrders();
42 | }, []);
43 |
44 | return (
45 |
46 | Order History
47 | {loading ? (
48 | Loading...
49 | ) : error ? (
50 | {error}
51 | ) : (
52 |
53 |
54 |
55 |
56 | | ID |
57 | DATE |
58 | TOTAL |
59 | PAID |
60 | DELIVERED |
61 | ACTION |
62 |
63 |
64 |
65 | {orders.map((order) => (
66 |
67 | | {order._id.substring(20, 24)} |
68 | {order.createdAt.substring(0, 10)} |
69 | {order.totalPrice} ₹ |
70 |
71 | {order.isPaid
72 | ? `${order.paidAt.substring(0, 10)}`
73 | : "not paid"}
74 | |
75 |
76 | {order.isDelivered
77 | ? `${order.deliveredAt.substring(0, 10)}`
78 | : "not delivered"}
79 | |
80 |
81 |
82 | Details
83 |
84 | |
85 |
86 | ))}
87 |
88 |
89 |
90 | )}
91 |
92 | );
93 | }
94 |
95 | OrderHistory.auth = true;
96 |
--------------------------------------------------------------------------------
/pages/product/[slug].js:
--------------------------------------------------------------------------------
1 | import Layout from '@/components/Layout';
2 | import Product from '@/models/Product';
3 | import db from '@/utils/db';
4 | import { getError } from '@/utils/error';
5 | import { Store } from '@/utils/Store';
6 | import axios from 'axios';
7 | import Image from 'next/image';
8 | import Link from 'next/link';
9 | import { useRouter } from 'next/router';
10 | import React, { useContext, useState } from 'react';
11 | import ReactStars from 'react-rating-stars-component';
12 | import { toast } from 'react-toastify';
13 |
14 | export default function ProductDetail(props) {
15 | const { product } = props;
16 |
17 | const { state, dispatch } = useContext(Store);
18 |
19 | const router = useRouter();
20 |
21 | if (!product) {
22 | return (
23 |
24 | Product Not Found
25 |
26 | );
27 | }
28 |
29 | const addToCartHandler = async () => {
30 | const existItem = state.cart.cartItems.find((item) => item.slug === product.slug);
31 | const quantity = existItem ? existItem.quantity + 1 : 1;
32 |
33 | const { data } = await axios.get(`/api/products/${product._id}`);
34 |
35 | if (data.countInStock < quantity) {
36 | toast.error('Sorry. Product is out of stock');
37 | return;
38 | }
39 |
40 | dispatch({
41 | type: 'CART_ADD_ITEM',
42 | payload: { ...product, quantity: quantity },
43 | });
44 |
45 | router.push('/cart');
46 | };
47 |
48 | const ratingChanged = async (productId, count) => {
49 | try {
50 | await axios.put(`/api/products/${productId}`, {
51 | rating: count,
52 | });
53 | toast.success('We really appreciate you taking the time to share your rating with us');
54 | } catch (error) {
55 | dispatch({ type: 'UPDATE_FAUL', payload: getError(error) });
56 | toast.error(getError(error));
57 | }
58 | };
59 |
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
95 |
96 |
97 |
98 |
Price
99 |
{product.price} ₹
100 |
101 |
102 |
Status
103 |
{product.countInStock > 0 ? 'In Stock' : 'Unavailable'}
104 |
105 |
108 |
109 |
110 |
111 |
112 | );
113 | }
114 |
115 | export async function getServerSideProps(context) {
116 | const { params } = context;
117 | const { slug } = params;
118 | await db.connect();
119 | const product = await Product.findOne({
120 | slug: slug,
121 | }).lean();
122 | await db.disconnect();
123 | return {
124 | props: {
125 | product: product ? db.convertDocToObj(product) : null,
126 | },
127 | };
128 | }
129 |
--------------------------------------------------------------------------------
/pages/shipping.js:
--------------------------------------------------------------------------------
1 | import CheckoutWizard from '@/components/CheckoutWizard';
2 | import Layout from '@/components/Layout';
3 | import { Store } from '@/utils/Store';
4 | import Cookies from 'js-cookie';
5 | import { useRouter } from 'next/router';
6 | import React, { useContext, useEffect } from 'react';
7 | import { useForm } from 'react-hook-form';
8 |
9 | export default function Shipping() {
10 | const {
11 | register,
12 | handleSubmit,
13 | setValue,
14 | getValues,
15 | formState: { errors },
16 | } = useForm();
17 |
18 | const { state, dispatch } = useContext(Store);
19 | const { cart } = state;
20 | const { shippingAddress } = cart;
21 |
22 | useEffect(() => {
23 | setValue('fullName', shippingAddress.fullName);
24 | setValue('address', shippingAddress.address);
25 | setValue('city', shippingAddress.city);
26 | setValue('postalCode', shippingAddress.postalCode);
27 | setValue('country', shippingAddress.country);
28 | }, [setValue, shippingAddress]);
29 |
30 | const router = useRouter();
31 |
32 | const submitHandler = ({ fullName, address, city, postalCode, country }) => {
33 | dispatch({
34 | type: 'SAVE_SHIPPING_ADDRESS',
35 | payload: {
36 | fullName,
37 | address,
38 | city,
39 | postalCode,
40 | country,
41 | },
42 | });
43 |
44 | Cookies.set(
45 | 'cart',
46 | JSON.stringify({
47 | ...cart,
48 | shippingAddress: {
49 | fullName,
50 | address,
51 | city,
52 | postalCode,
53 | country,
54 | },
55 | })
56 | );
57 |
58 | router.push('/payment');
59 | };
60 |
61 | return (
62 |
63 |
64 |
141 |
142 | );
143 | }
144 |
145 | Shipping.auth = true;
146 |
--------------------------------------------------------------------------------
/pages/register.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import { signIn, useSession } from "next-auth/react";
5 | import Link from "next/link";
6 | import { useRouter } from "next/router";
7 | import React, { useEffect } from "react";
8 | import { useForm } from "react-hook-form";
9 | import { toast } from "react-toastify";
10 |
11 | export default function Register() {
12 | const { data: session } = useSession();
13 | const router = useRouter();
14 | const { redirect } = router.query;
15 |
16 | useEffect(() => {
17 | if (session?.user) {
18 | router.push(redirect || "/");
19 | }
20 | }, [router, session, redirect]);
21 |
22 | const {
23 | register,
24 | handleSubmit,
25 | watch,
26 | getValues,
27 | formState: { errors },
28 | } = useForm();
29 |
30 | const submitHandler = async ({ name, email, password }) => {
31 | try {
32 | await axios.post("/api/auth/signup", {
33 | redirect: false,
34 | name,
35 | email,
36 | password,
37 | });
38 |
39 | const result = await signIn("credentials", {
40 | redirect: false,
41 | email,
42 | password,
43 | });
44 |
45 | if (result.error) {
46 | toast.error(result.error);
47 | }
48 | } catch (error) {
49 | toast.error(getError(error));
50 | }
51 | };
52 |
53 | return (
54 |
55 |
145 |
146 | );
147 | }
148 |
--------------------------------------------------------------------------------
/pages/cart.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { XCircleIcon } from "@heroicons/react/24/outline";
3 | import { Store } from "@/utils/Store";
4 | import Link from "next/link";
5 | import React, { useContext } from "react";
6 | import Image from "next/image";
7 | import { useRouter } from "next/router";
8 | import dynamic from "next/dynamic";
9 | import axios from "axios";
10 | import { toast } from "react-toastify";
11 |
12 | function Cart() {
13 | const router = useRouter();
14 | const { state, dispatch } = useContext(Store);
15 |
16 | const {
17 | cart: { cartItems },
18 | } = state;
19 |
20 | const removeItemHandler = (item) => {
21 | dispatch({ type: "CART_REMOVE_ITEM", payload: item });
22 | };
23 |
24 | const updateCartHandler = async (item, qty) => {
25 | const quantity = Number(qty);
26 | const { data } = await axios.get(`/api/products/${item._id}`);
27 |
28 | if (data.countInStock < quantity) {
29 | toast.error("Sorry. Product is out of stock");
30 | return;
31 | }
32 |
33 | dispatch({
34 | type: "CART_ADD_ITEM",
35 | payload: { ...item, quantity: quantity },
36 | });
37 |
38 | toast.success("Product updated in the cart");
39 | };
40 |
41 | return (
42 |
43 | Shopping Cart
44 | {cartItems.length === 0 ? (
45 |
46 | Cart is empty. Go shopping
47 |
48 | ) : (
49 |
50 |
51 |
52 |
53 |
54 | | Item |
55 | Quantity |
56 | Price |
57 | Action |
58 |
59 |
60 |
61 | {cartItems.map((item) => (
62 |
63 | |
64 |
65 |
66 |
73 | {item.name}
74 |
75 |
76 | |
77 |
78 |
90 | |
91 | {item.price}₹ |
92 |
93 |
96 | |
97 |
98 | ))}
99 |
100 |
101 |
102 |
103 |
104 | -
105 |
106 | Subtotal (
107 | {cartItems.reduce((total, item) => total + item.quantity, 0)}){" "}
108 | :{" "}
109 | {cartItems.reduce(
110 | (total, item) => total + item.quantity * item.price,
111 | 0
112 | )}{" "}
113 | ₹
114 |
115 |
116 | -
117 |
123 |
124 |
125 |
126 |
127 | )}
128 |
129 | );
130 | }
131 |
132 | export default dynamic(() => Promise.resolve(Cart), { ssr: false });
133 |
--------------------------------------------------------------------------------
/pages/profile.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import { Store } from "@/utils/Store";
4 | import axios from "axios";
5 | import Cookies from "js-cookie";
6 | import { signIn, signOut, useSession } from "next-auth/react";
7 | import React, { useContext, useEffect } from "react";
8 | import { useForm } from "react-hook-form";
9 | import { toast } from "react-toastify";
10 |
11 | export default function Profile() {
12 | const { data: session } = useSession();
13 |
14 | const {
15 | handleSubmit,
16 | register,
17 | getValues,
18 | setValue,
19 | formState: { errors },
20 | } = useForm();
21 |
22 | useEffect(() => {
23 | setValue("name", session.user.name);
24 | setValue("email", session.user.email);
25 | }, []);
26 |
27 | const submitHandler = async ({ name, email, password }) => {
28 | try {
29 | await axios.put("/api/auth/update", {
30 | name,
31 | email,
32 | password,
33 | });
34 |
35 | const result = await signIn("credentials", {
36 | redirect: false,
37 | email,
38 | password,
39 | });
40 |
41 | toast.success("Profile updated successfully");
42 |
43 | logoutClickHandler();
44 |
45 | if (result.error) {
46 | toast.error(result.error);
47 | }
48 | } catch (error) {
49 | toast.error(getError(error));
50 | }
51 | };
52 |
53 | const { state, dispatch } = useContext(Store);
54 |
55 | const logoutClickHandler = () => {
56 | Cookies.remove("cart");
57 | dispatch({ type: "CART_RESET" });
58 | signOut({ callbackUrl: "/login" });
59 | };
60 |
61 | return (
62 |
63 |
154 |
155 | );
156 | }
157 |
158 | Profile.auth = true;
159 |
--------------------------------------------------------------------------------
/pages/admin/orders.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import Link from "next/link";
5 | import React, { useEffect, useReducer } from "react";
6 |
7 | function reducer(state, action) {
8 | switch (action.type) {
9 | case "FETCH_REQUEST": {
10 | return { ...state, loading: true, error: "" };
11 | }
12 | case "FETCH_SUCCESS": {
13 | return { ...state, loading: false, orders: action.payload, error: "" };
14 | }
15 | case "FETCH_FAIL": {
16 | return { ...state, loading: false, error: action.payload };
17 | }
18 | default: {
19 | return state;
20 | }
21 | }
22 | }
23 |
24 | export default function Orders() {
25 | const [{ loading, error, orders }, dispatch] = useReducer(reducer, {
26 | loading: true,
27 | orders: [],
28 | error: "",
29 | });
30 |
31 | useEffect(() => {
32 | const fetchData = async () => {
33 | try {
34 | dispatch({ type: "FETCH_REQUEST" });
35 | const { data } = await axios.get(`/api/admin/orders`);
36 | console.log(data);
37 | dispatch({ type: "FETCH_SUCCESS", payload: data });
38 | } catch (err) {
39 | dispatch({ type: "FETCH_FAIL", payload: getError(err) });
40 | }
41 | };
42 | fetchData();
43 | }, []);
44 |
45 | console.log(orders);
46 |
47 | return (
48 |
49 |
50 |
51 |
52 | -
53 | Dashboard
54 |
55 | -
56 |
57 | Orders
58 |
59 | {""}
60 |
74 |
75 | -
76 | Products
77 |
78 | -
79 | Users
80 |
81 |
82 |
83 |
84 |
Admin Dashboard
85 | {loading ? (
86 |
Loading...
87 | ) : error ? (
88 |
{error}
89 | ) : (
90 |
91 |
92 |
93 |
94 | | ID |
95 | USER |
96 | DATE |
97 | TOTAL |
98 | PAID |
99 | DELIVERED |
100 | ACTION |
101 |
102 |
103 |
104 | {orders.map((order) => (
105 |
106 | | {order._id.substring(20, 24)} |
107 |
108 |
109 | {order.user ? order.user.name : "DELETED USER"}
110 | |
111 |
112 |
113 | {order.createdAt.substring(0, 10)}
114 | |
115 | {order.totalPrice} ₹ |
116 |
117 | {order.isPaid
118 | ? `${order.paidAt.substring(0, 10)}`
119 | : "not paid"}
120 | |
121 |
122 | {order.isDelivered
123 | ? `${order.deliveredAt.substring(0, 10)}`
124 | : "not delivered"}
125 | |
126 |
127 |
128 | Details
129 |
130 | |
131 |
132 | ))}
133 |
134 |
135 |
136 | )}
137 |
138 |
139 |
140 | );
141 | }
142 |
143 | Orders.auth = { adminOnly: true };
144 |
--------------------------------------------------------------------------------
/pages/admin/dashboard.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import {
5 | BarElement,
6 | CategoryScale,
7 | Chart as ChartJS,
8 | Legend,
9 | LinearScale,
10 | Title,
11 | Tooltip,
12 | } from "chart.js";
13 | import Link from "next/link";
14 | import React, { useEffect, useReducer } from "react";
15 | import { Bar } from "react-chartjs-2";
16 |
17 | ChartJS.register(
18 | CategoryScale,
19 | LinearScale,
20 | BarElement,
21 | Title,
22 | Tooltip,
23 | Legend
24 | );
25 |
26 | export const options = {
27 | responsive: true,
28 | plugins: {
29 | legend: {
30 | postion: "top",
31 | },
32 | },
33 | };
34 |
35 | function reducer(state, action) {
36 | switch (action.type) {
37 | case "FETCH_REQUEST": {
38 | return { ...state, loading: true, error: "" };
39 | }
40 | case "FETCH_SUCCESS": {
41 | return { ...state, loading: false, summay: action.payload, error: "" };
42 | }
43 | case "FETCH_FAIL": {
44 | return { ...state, loading: false, error: action.payload };
45 | }
46 | default: {
47 | return state;
48 | }
49 | }
50 | }
51 |
52 | export default function Dashboard() {
53 | const [{ loading, error, summay }, dispatch] = useReducer(reducer, {
54 | loading: true,
55 | summay: { salesDate: [] },
56 | error: "",
57 | });
58 |
59 | useEffect(() => {
60 | const fetchData = async () => {
61 | try {
62 | dispatch({ type: "FETCH_REQUEST" });
63 | const { data } = await axios.get("/api/admin/summary");
64 | dispatch({ type: "FETCH_SUCCESS", payload: data });
65 | } catch (error) {
66 | dispatch({ type: "FETCH_FAIL", payload: getError(error) });
67 | }
68 | };
69 | fetchData();
70 | }, []);
71 |
72 | const data = {
73 | labels: summay.salesDate.map((yearAndMont) => yearAndMont._id),
74 | datasets: [
75 | {
76 | label: "Sales",
77 | backgroundColor: "rgba(162,222,208,1)",
78 | data: summay.salesDate.map((yearAndMont) => yearAndMont.totalSales),
79 | },
80 | ],
81 | };
82 |
83 | return (
84 |
85 |
86 |
87 |
88 | -
89 | Dashboard
90 | {""}
91 |
105 |
106 | -
107 | Orders
108 |
109 | -
110 | Products
111 |
112 | -
113 | Users
114 |
115 |
116 |
117 |
118 |
Admin Dashboard
119 | {loading ? (
120 |
Loading...
121 | ) : error ? (
122 |
{error}
123 | ) : (
124 |
125 |
126 |
127 |
{summay.ordersPrice} ₹
128 |
Sales
129 |
View sales
130 |
131 |
132 |
133 |
{summay.ordersCount}
134 |
Orders
135 |
View orders
136 |
137 |
138 |
139 |
{summay.productsCount}
140 |
Products
141 |
View products
142 |
143 |
144 |
145 |
{summay.usersCount}
146 |
Users
147 |
View users
148 |
149 |
150 |
Sales Report
151 |
157 |
158 | )}
159 |
160 |
161 |
162 | );
163 | }
164 |
165 | Dashboard.auth = { adminOnly: true };
166 |
--------------------------------------------------------------------------------
/pages/admin/users.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import Link from "next/link";
5 | import { useRouter } from "next/router";
6 | import React, { useEffect, useReducer } from "react";
7 | import { toast } from "react-toastify";
8 |
9 | function reducer(state, action) {
10 | switch (action.type) {
11 | case "FETCH_REQUEST": {
12 | return { ...state, loading: true, error: "" };
13 | }
14 | case "FETCH_SUCCESS": {
15 | return { ...state, loading: false, users: action.payload, error: "" };
16 | }
17 | case "FETCH_FAIL": {
18 | return { ...state, loading: false, error: action.payload };
19 | }
20 |
21 | case "DELETE_REQUEST": {
22 | return { ...state, loadingDelete: true };
23 | }
24 | case "DELETE_SUCCESS": {
25 | return { ...state, loadingDelete: false, successDelete: true };
26 | }
27 | case "DELETE_FAIL": {
28 | return { ...state, loadingDelete: false };
29 | }
30 | case "DELETE_RESET": {
31 | return { ...state, loadingDelete: false, successDelete: false };
32 | }
33 |
34 | default: {
35 | return state;
36 | }
37 | }
38 | }
39 |
40 | export default function Users() {
41 | const [{ loading, error, users, loadingDelete, successDelete }, dispatch] =
42 | useReducer(reducer, {
43 | loading: true,
44 | users: [],
45 | error: "",
46 | });
47 |
48 | useEffect(() => {
49 | const fetchData = async () => {
50 | try {
51 | dispatch({ type: "FETCH_REQUEST" });
52 | const { data } = await axios.get(`/api/admin/users`);
53 | dispatch({ type: "FETCH_SUCCESS", payload: data });
54 | } catch (err) {
55 | dispatch({ type: "FETCH_FAIL", payload: getError(err) });
56 | }
57 | };
58 |
59 | if (successDelete) {
60 | dispatch({ type: "DELETE_RESET" });
61 | } else {
62 | fetchData();
63 | }
64 | }, [successDelete]);
65 |
66 | const deletHandler = async (userId) => {
67 | if (!window.confirm("Are you sure?")) {
68 | return;
69 | }
70 | try {
71 | dispatch({ type: "DELETE_REQUEST" });
72 | await axios.delete(`/api/admin/users/${userId}`);
73 | dispatch({ type: "DELETE_SUCCESS" });
74 | toast.success("User deleted succssfully");
75 | } catch (error) {
76 | dispatch({ type: "DELETE_FAIL" });
77 | toast.error(getError(error));
78 | }
79 | };
80 |
81 | return (
82 |
83 |
84 |
85 |
86 | -
87 | Dashboard
88 |
89 | -
90 | Orders
91 |
92 | -
93 | Products
94 |
95 | -
96 |
97 | Users
98 |
99 | {""}
100 |
114 |
115 |
116 |
117 |
118 |
Users
119 | {loadingDelete &&
Deleting...
}
120 | {loading ? (
121 |
Loading...
122 | ) : error ? (
123 |
{error}
124 | ) : (
125 |
126 |
127 |
128 |
129 | | ID |
130 | NAME |
131 | EMAIL |
132 | ADMIN |
133 | ACTION |
134 |
135 |
136 |
137 | {users.map((user) => (
138 |
139 | | {user._id.substring(20, 24)} |
140 | {user.name} |
141 | {user.email} |
142 | {user.isAdmin ? "YES" : "NO"} |
143 |
144 |
148 | Edit
149 |
150 |
151 |
157 | |
158 |
159 | ))}
160 |
161 |
162 |
163 | )}
164 |
165 |
166 |
167 | );
168 | }
169 |
170 | Users.auth = { adminOnly: true };
171 |
--------------------------------------------------------------------------------
/pages/placeorder.js:
--------------------------------------------------------------------------------
1 | import CheckoutWizard from "@/components/CheckoutWizard";
2 | import Layout from "@/components/Layout";
3 | import { getError } from "@/utils/error";
4 | import { Store } from "@/utils/Store";
5 | import axios from "axios";
6 | import Cookies from "js-cookie";
7 | import Image from "next/image";
8 | import Link from "next/link";
9 | import { useRouter } from "next/router";
10 | import React, { useContext, useEffect, useState } from "react";
11 | import { toast } from "react-toastify";
12 |
13 | export default function PlaceOrder() {
14 | const router = useRouter();
15 |
16 | const { state, dispatch } = useContext(Store);
17 | const { cart } = state;
18 | const { cartItems, shippingAddress, paymentMethod } = cart;
19 |
20 | const itemsPrice = Number(
21 | cartItems
22 | .reduce((total, item) => total + item.quantity * item.price, 0)
23 | .toFixed(2)
24 | );
25 |
26 | const shippingPrice = itemsPrice < 400 ? 0 : 120;
27 |
28 | const taxPrice = Number((itemsPrice * 0.05).toFixed(2));
29 |
30 | const totalPrice = Number((itemsPrice + shippingPrice + taxPrice).toFixed(2));
31 |
32 | useEffect(() => {
33 | if (!paymentMethod) {
34 | router.push("/payment");
35 | }
36 | }, [paymentMethod, router]);
37 |
38 | const [loading, setLoading] = useState(false);
39 |
40 | const placeOrderHandler = async () => {
41 | try {
42 | setLoading(true);
43 | const { data } = await axios.post("/api/orders", {
44 | orderItems: cartItems,
45 | shippingAddress,
46 | paymentMethod,
47 | itemsPrice,
48 | shippingPrice,
49 | taxPrice,
50 | totalPrice,
51 | });
52 |
53 | dispatch({ type: "CART_CLEAR_ITEMS" });
54 |
55 | Cookies.set(
56 | "cart",
57 | JSON.stringify({
58 | ...cart,
59 | cartItems: [],
60 | })
61 | );
62 |
63 | setLoading(false);
64 | router.push(`/order/${data._id}`);
65 | } catch (error) {
66 | setLoading(false);
67 | toast.error(getError(error));
68 | }
69 | };
70 |
71 | return (
72 |
73 |
74 | Place Order
75 | {cartItems.length === 0 ? (
76 |
77 | Cart is empty. Go Shopping
78 |
79 | ) : (
80 |
81 |
82 |
83 |
Shipping Address
84 |
85 | {shippingAddress.fullName},{shippingAddress.address},{" "}
86 | {shippingAddress.city},{shippingAddress.postalCode},{" "}
87 | {shippingAddress.country}
88 |
89 |
90 | Edit
91 |
92 |
93 |
94 |
95 |
Payment Method
96 |
{paymentMethod}
97 |
98 | Edit
99 |
100 |
101 |
102 |
103 |
Order Items
104 |
105 |
106 |
107 | | Item |
108 | Quantity |
109 | Price |
110 | Action |
111 |
112 |
113 |
114 | {cartItems.map((item) => (
115 |
116 | |
117 |
118 |
119 |
126 | {item.name}
127 |
128 |
129 | |
130 | {item.quantity} |
131 | {item.price}₹ |
132 |
133 | {item.quantity * item.price}₹
134 | |
135 |
136 | ))}
137 |
138 |
139 |
140 | Edit
141 |
142 |
143 |
144 |
145 |
Order Summary
146 |
147 | -
148 |
149 |
Items
150 |
{itemsPrice}₹
151 |
152 |
153 | -
154 |
155 |
Tax
156 |
{taxPrice}₹
157 |
158 |
159 | -
160 |
161 |
Shipping
162 |
{shippingPrice}₹
163 |
164 |
165 | -
166 |
167 |
Total
168 |
{totalPrice}₹
169 |
170 |
171 | -
172 |
179 |
180 |
181 |
182 |
183 | )}
184 |
185 | );
186 | }
187 |
188 | PlaceOrder.auth = true;
189 |
--------------------------------------------------------------------------------
/pages/admin/products.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import Link from "next/link";
5 | import { useRouter } from "next/router";
6 | import React, { useEffect, useReducer } from "react";
7 | import { toast } from "react-toastify";
8 |
9 | function reducer(state, action) {
10 | switch (action.type) {
11 | case "FETCH_REQUEST": {
12 | return { ...state, loading: true, error: "" };
13 | }
14 | case "FETCH_SUCCESS": {
15 | return { ...state, loading: false, products: action.payload, error: "" };
16 | }
17 | case "FETCH_FAIL": {
18 | return { ...state, loading: false, error: action.payload };
19 | }
20 |
21 | case "CREATE_REQUEST": {
22 | return { ...state, loadingCreate: true };
23 | }
24 | case "CREATE_SUCCESS": {
25 | return { ...state, loadingCreate: false };
26 | }
27 | case "CREATE_FAIL": {
28 | return { ...state, loadingCreate: false };
29 | }
30 |
31 | case "DELETE_REQUEST": {
32 | return { ...state, loadingDelete: true };
33 | }
34 | case "DELETE_SUCCESS": {
35 | return { ...state, loadingDelete: false, successDelete: true };
36 | }
37 | case "DELETE_FAIL": {
38 | return { ...state, loadingDelete: false };
39 | }
40 | case "DELETE_RESET": {
41 | return { ...state, loadingDelete: false, successDelete: false };
42 | }
43 |
44 | default: {
45 | return state;
46 | }
47 | }
48 | }
49 |
50 | export default function Products() {
51 | const [
52 | { loading, error, products, loadingCreate, loadingDelete, successDelete },
53 | dispatch,
54 | ] = useReducer(reducer, {
55 | loading: true,
56 | products: [],
57 | error: "",
58 | successDelete: false,
59 | });
60 |
61 | const router = useRouter();
62 |
63 | useEffect(() => {
64 | const fetchData = async () => {
65 | try {
66 | dispatch({ type: "FETCH_REQUEST" });
67 | const { data } = await axios.get(`/api/admin/products`);
68 | dispatch({ type: "FETCH_SUCCESS", payload: data });
69 | } catch (err) {
70 | dispatch({ type: "FETCH_FAIL", payload: getError(err) });
71 | }
72 | };
73 |
74 | if (successDelete) {
75 | dispatch({ type: "DELETE_RESET" });
76 | } else {
77 | fetchData();
78 | }
79 | }, [successDelete]);
80 |
81 | const createHandler = async () => {
82 | if (!window.confirm("Are you sure")) {
83 | return;
84 | }
85 | try {
86 | dispatch({ type: "CREATE_REQUEST" });
87 | const { data } = await axios.post(`/api/admin/products`);
88 | dispatch({ type: "CREATE_SUCCESS" });
89 | toast.success("Product created succssfully");
90 | router.push(`/admin/product/${data.product._id}`);
91 | } catch (error) {
92 | dispatch({ type: "CREATE_FAIL" });
93 | toast.error(getError(error));
94 | }
95 | };
96 |
97 | const deletHandler = async (productId) => {
98 | if (!window.confirm("Are you sure?")) {
99 | return;
100 | }
101 | try {
102 | dispatch({ type: "DELETE_REQUEST" });
103 | await axios.delete(`/api/admin/products/${productId}`);
104 | dispatch({ type: "DELETE_SUCCESS" });
105 | toast.success("Product deleted succssfully");
106 | } catch (error) {
107 | dispatch({ type: "DELETE_FAIL" });
108 | toast.error(getError(error));
109 | }
110 | };
111 |
112 | return (
113 |
114 |
115 |
116 |
117 | -
118 | Dashboard
119 |
120 | -
121 | Orders
122 |
123 | -
124 |
125 | Products
126 |
127 | {""}
128 |
142 |
143 | -
144 | Users
145 |
146 |
147 |
148 |
149 |
150 |
Admin Dashboard
151 | {loadingDelete &&
Deleting item...
}
152 |
159 |
160 |
161 | {loading ? (
162 |
Loading...
163 | ) : error ? (
164 |
{error}
165 | ) : (
166 |
167 |
168 |
169 |
170 | | ID |
171 | NAME |
172 | PRICE |
173 | CATEGORY |
174 | COUNT |
175 | RATING |
176 | ACTION |
177 |
178 |
179 |
180 | {products.map((product) => (
181 |
182 | | {product._id.substring(20, 24)} |
183 | {product.name} |
184 | {product.price} |
185 | {product.category} ₹ |
186 | {product.countInStock} |
187 | {product.rating} |
188 |
189 |
193 | Edit
194 |
195 |
196 |
203 | |
204 |
205 | ))}
206 |
207 |
208 |
209 | )}
210 |
211 |
212 |
213 | );
214 | }
215 |
216 | Products.auth = { adminOnly: true };
217 |
--------------------------------------------------------------------------------
/pages/search.js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import Product from "@/models/Product";
3 | import db from "@/utils/db";
4 | import { Store } from "@/utils/Store";
5 | import axios from "axios";
6 | import { useRouter } from "next/router";
7 | import React, { useContext } from "react";
8 | import { toast } from "react-toastify";
9 | import { XCircleIcon } from "@heroicons/react/24/outline";
10 | import ProductItem from "@/components/ProductItem";
11 |
12 | const PAGE_SIZE = 3;
13 |
14 | const prices = [
15 | {
16 | name: "200₹ to 600₹",
17 | value: "200-600",
18 | },
19 | {
20 | name: "601₹ to 1200₹",
21 | value: "601-1200",
22 | },
23 | {
24 | name: "1201₹ to 1600₹",
25 | value: "1201-1600",
26 | },
27 | ];
28 |
29 | const ratings = [1, 2, 3, 4, 5];
30 |
31 | export default function Search(props) {
32 | const { countProducts, products, categories, brands, pages } = props;
33 |
34 | const router = useRouter();
35 |
36 | const {
37 | query = "all",
38 | category = "all",
39 | brand = "all",
40 | price = "all",
41 | rating = "all",
42 | sort = "featured",
43 | page = 1,
44 | } = router.query;
45 |
46 | const { state, dispatch } = useContext(Store);
47 | const { cart } = state;
48 |
49 | const addToCartHandler = async (product) => {
50 | const existItem = cart.cartItems.find((item) => item.slug === product.slug);
51 | const quantity = existItem ? existItem.quantity + 1 : 1;
52 |
53 | const { data } = await axios.get(`/api/products/${product._id}`);
54 |
55 | if (data.countInStock < quantity) {
56 | toast.error("Sorry. Product is out of stock");
57 | return;
58 | }
59 |
60 | dispatch({
61 | type: "CART_ADD_ITEM",
62 | payload: { ...product, quantity: quantity },
63 | });
64 |
65 | toast.success("Product added to the cart");
66 | };
67 |
68 | const filterSearch = ({
69 | category,
70 | page,
71 | brand,
72 | sort,
73 | min,
74 | max,
75 | searchQuery,
76 | price,
77 | rating,
78 | }) => {
79 | const { query } = router;
80 | if (page) query.page = page;
81 | if (searchQuery) query.searchQuery = searchQuery;
82 | if (sort) query.sort = sort;
83 | if (category) query.category = category;
84 | if (brand) query.brand = brand;
85 | if (price) query.price = price;
86 | if (rating) query.rating = rating;
87 | if (min) query.min ? query.min : query.min === 0 ? 0 : min;
88 | if (max) query.max ? query.max : query.max === 0 ? 0 : max;
89 |
90 | router.push({
91 | pathname: router.pathname,
92 | query: query,
93 | });
94 | };
95 |
96 | const categoryHandler = (e) => {
97 | filterSearch({ category: e.target.value });
98 | };
99 |
100 | const pageHandler = (page) => {
101 | filterSearch({ page: page });
102 | };
103 |
104 | const brandHandler = (e) => {
105 | filterSearch({ brand: e.target.value });
106 | };
107 |
108 | const sortHandler = (e) => {
109 | filterSearch({ sort: e.target.value });
110 | };
111 |
112 | const priceHandler = (e) => {
113 | filterSearch({ price: e.target.value });
114 | };
115 |
116 | const ratingHandler = (e) => {
117 | filterSearch({ rating: e.target.value });
118 | };
119 |
120 | return (
121 |
122 |
123 |
124 |
125 |
Categories
126 |
139 |
140 |
141 |
142 |
Brands
143 |
152 |
153 |
154 |
155 |
Prices
156 |
165 |
166 |
167 |
168 |
Rating
169 |
178 |
179 |
180 |
181 |
182 |
183 | {products.length === 0 ? "No" : countProducts}
184 | Results
185 | {query !== "all" && query !== "" && " : " + query}
186 | {category !== "all" && category !== "" && " : " + category}
187 | {brand !== "all" && brand !== "" && " : " + brand}
188 | {price !== "all" && price !== "" && " : " + price}
189 | {rating !== "all" && rating !== "" && " : " + rating}
190 |
191 | {(query !== "all" && query !== "") ||
192 | category !== "all" ||
193 | brand !== "all" ||
194 | rating !== "all" ||
195 | price !== "all" ? (
196 |
199 | ) : null}
200 |
201 |
202 | Sort by{" "}
203 |
210 |
211 |
212 |
213 |
214 | {products.map((product) => (
215 |
220 | ))}
221 |
222 |
223 | {products.length > 0 &&
224 | [...Array(pages).keys()].map((pageNumber) => (
225 | -
226 |
234 |
235 | ))}
236 |
237 |
238 |
239 |
240 |
241 | );
242 | }
243 |
244 | export async function getServerSideProps({ query }) {
245 | const pageSize = query.pageSize || PAGE_SIZE;
246 | const page = query.page || 1;
247 | const category = query.category || "";
248 | const brand = query.brand || "";
249 | const price = query.price || "";
250 | const rating = query.rating || "";
251 | const sort = query.sort || "";
252 | const searchQuery = query.query || "";
253 |
254 | const queryFilter =
255 | searchQuery && searchQuery !== "all"
256 | ? {
257 | name: {
258 | $regex: searchQuery,
259 | $options: "i",
260 | },
261 | }
262 | : {};
263 | const categoryFilter =
264 | category && category !== "all"
265 | ? {
266 | category,
267 | }
268 | : {};
269 |
270 | const brandFilter =
271 | brand && brand !== "all"
272 | ? {
273 | brand,
274 | }
275 | : {};
276 |
277 | const ratingFilter =
278 | rating && rating !== "all"
279 | ? {
280 | rating: {
281 | $gte: Number(rating),
282 | },
283 | }
284 | : {};
285 |
286 | const priceFilter =
287 | price && price !== "all"
288 | ? {
289 | price: {
290 | $gte: Number(price.split("-")[0]),
291 | $lte: Number(price.split("-")[1]),
292 | },
293 | }
294 | : {};
295 |
296 | const order =
297 | sort === "featured"
298 | ? { isFeatured: -1 }
299 | : sort === "lowest"
300 | ? { price: 1 }
301 | : sort === "highest"
302 | ? { price: -1 }
303 | : sort === "toprated"
304 | ? { rating: -1 }
305 | : sort === "newest"
306 | ? { createdAt: -1 }
307 | : { _id: -1 };
308 |
309 | await db.connect();
310 | const categories = await Product.find().distinct("category");
311 | const brands = await Product.find().distinct("brand");
312 | const productDocs = await Product.find({
313 | ...queryFilter,
314 | ...categoryFilter,
315 | ...priceFilter,
316 | ...brandFilter,
317 | ...ratingFilter,
318 | })
319 | .sort(order)
320 | .skip(pageSize * (page - 1))
321 | .limit(pageSize)
322 | .lean();
323 |
324 | const countProducts = await Product.countDocuments({
325 | ...queryFilter,
326 | ...categoryFilter,
327 | ...priceFilter,
328 | ...brandFilter,
329 | ...ratingFilter,
330 | });
331 |
332 | const products = productDocs.map(db.convertDocToObj);
333 |
334 | await db.disconnect();
335 |
336 | return {
337 | props: {
338 | products,
339 | countProducts,
340 | page,
341 | pages: Math.ceil(countProducts / pageSize),
342 | categories,
343 | brands,
344 | },
345 | };
346 | }
347 |
--------------------------------------------------------------------------------
/pages/order/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
4 | import axios from "axios";
5 | import { useSession } from "next-auth/react";
6 | import Image from "next/image";
7 | import Link from "next/link";
8 | import { useRouter } from "next/router";
9 | import React, { useEffect, useReducer } from "react";
10 | import { toast } from "react-toastify";
11 |
12 | function reducer(state, action) {
13 | switch (action.type) {
14 | case "FETCH_REQUEST": {
15 | return { ...state, loading: true, error: "" };
16 | }
17 | case "FETCH_SUCCESS": {
18 | return { ...state, loading: false, order: action.payload, error: "" };
19 | }
20 | case "FETCH_FAIL": {
21 | return { ...state, loading: false, error: action.payload };
22 | }
23 |
24 | case "PAY_REQUEST": {
25 | return { ...state, loadingPay: true };
26 | }
27 | case "PAY_SUCCESS": {
28 | return { ...state, loadingPay: false, successPay: true };
29 | }
30 | case "PAY_FAIL": {
31 | return { ...state, loadingPay: false, errorPay: action.payload };
32 | }
33 | case "PAY_RESET": {
34 | return { ...state, loadingPay: false, errorPay: "" };
35 | }
36 |
37 | case "DELIVER_REQUEST": {
38 | return { ...state, loadingDeliver: true, successDeliver: false };
39 | }
40 | case "DELIVER_SUCCESS": {
41 | return { ...state, loadingDeliver: false, successDeliver: true };
42 | }
43 | case "DELIVER_FAIL": {
44 | return { ...state, loadingDeliver: false };
45 | }
46 | case "DELIVER_RESET": {
47 | return { ...state, loadingDeliver: false, successDeliver: false };
48 | }
49 |
50 | default: {
51 | return state;
52 | }
53 | }
54 | }
55 |
56 | export default function Order() {
57 | const [{ isPending }, paypalDispatch] = usePayPalScriptReducer();
58 |
59 | const { data: session } = useSession();
60 |
61 | const { query } = useRouter();
62 | const orderId = query.id;
63 |
64 | const [
65 | {
66 | loading,
67 | error,
68 | order,
69 | successPay,
70 | loadingPay,
71 | errorPay,
72 | loadingDeliver,
73 | successDeliver,
74 | },
75 | dispatch,
76 | ] = useReducer(reducer, {
77 | loading: true,
78 | order: {},
79 | error: "",
80 | });
81 |
82 | useEffect(() => {
83 | const fetchOrder = async () => {
84 | try {
85 | dispatch({ type: "FETCH_REQUEST" });
86 | const { data } = await axios.get(`/api/orders/${orderId}`);
87 | dispatch({ type: "FETCH_SUCCESS", payload: data });
88 | } catch (error) {
89 | dispatch({ type: "FETCH_FAIL", payload: getError(error) });
90 | }
91 | };
92 |
93 | if (
94 | !order._id ||
95 | successPay ||
96 | successDeliver ||
97 | (order._id && order._id != orderId)
98 | ) {
99 | fetchOrder();
100 | if (successPay) {
101 | dispatch({ type: "PAY_RESET" });
102 | }
103 | if (successDeliver) {
104 | dispatch({ type: "DELIVER_RESET" });
105 | }
106 | } else {
107 | const loadPaypalScript = async () => {
108 | const { data: clientId } = await axios.get("/api/keys/paypal");
109 |
110 | paypalDispatch({
111 | type: "resetOptions",
112 | value: {
113 | "client-id": clientId,
114 | currency: "USD",
115 | },
116 | });
117 |
118 | paypalDispatch({
119 | type: "setLoadingStatus",
120 | value: "pending",
121 | });
122 | };
123 | loadPaypalScript();
124 | }
125 | }, [order, orderId, paypalDispatch, successPay, successDeliver]);
126 |
127 | const {
128 | shippingAddress,
129 | paymentMethod,
130 | orderItems,
131 | itemsPrice,
132 | taxPrice,
133 | shippingPrice,
134 | totalPrice,
135 | isPaid,
136 | paidAt,
137 | isDelivered,
138 | deliveredAt,
139 | } = order;
140 |
141 | const createOrder = (data, actions) => {
142 | return actions.order
143 | .create({
144 | purchase_units: [
145 | {
146 | amount: { value: totalPrice },
147 | },
148 | ],
149 | })
150 | .then((orderId) => {
151 | return orderId;
152 | });
153 | };
154 |
155 | const onApprove = (data, actions) => {
156 | return actions.order.capture().then(async function (details) {
157 | try {
158 | dispatch({ type: "PAY_REQUEST" });
159 | const { data } = await axios.put(
160 | `api/orders/${order._id}/pay`,
161 | details
162 | );
163 |
164 | dispatch({ type: "PAY_SUCCESS", payload: data });
165 | toast.success("Order is paid successfully");
166 | } catch (error) {
167 | dispatch({ type: "PAY_FAIL", payload: getError(error) });
168 | toast.error(getError(error));
169 | }
170 | });
171 | };
172 |
173 | const onError = (error) => {
174 | toast.error(getError(error));
175 | };
176 |
177 | const deliverOrderHandler = async () => {
178 | try {
179 | dispatch({ type: "DELIVER_REQUEST" });
180 | const { data } = await axios.put(
181 | `/api/admin/orders/${order._id}/deliver`,
182 | {}
183 | );
184 | dispatch({ type: "DELIVER_SUCCESS", payload: data });
185 | toast.success("Order is delivered");
186 | } catch (error) {
187 | dispatch({ type: "DELIVER_FAIL", payload: getError(error) });
188 | toast.error(getError(error));
189 | }
190 | };
191 |
192 | return (
193 |
194 | {`Order ${orderId}`}
195 | {loading ? (
196 | Loading...
197 | ) : error ? (
198 | {error}
199 | ) : (
200 |
201 |
202 |
203 |
Shipping Address
204 |
205 | {shippingAddress.fullName},{shippingAddress.address},{" "}
206 | {shippingAddress.city},{shippingAddress.postalCode},{" "}
207 | {shippingAddress.country}
208 |
209 | {isDelivered ? (
210 |
Delivered at {deliveredAt}
211 | ) : (
212 |
Not Delivered
213 | )}
214 |
215 |
216 |
217 |
Payment Method
218 |
{paymentMethod}
219 |
220 | {isPaid ? (
221 |
Paid at {paidAt}
222 | ) : (
223 |
Not Paid
224 | )}
225 |
226 |
227 |
228 |
Order Items
229 |
230 |
231 |
232 | | Item |
233 | Quantity |
234 | Price |
235 | Action |
236 |
237 |
238 |
239 | {orderItems.map((item) => (
240 |
241 | |
242 |
243 |
244 |
250 | {item.name}
251 |
252 |
253 | |
254 | {item.quantity} |
255 | {item.price}₹ |
256 |
257 | {item.quantity * item.price}₹
258 | |
259 |
260 | ))}
261 |
262 |
263 |
264 |
265 |
266 |
267 |
Order Summary
268 |
269 | -
270 |
271 |
Items
272 |
{itemsPrice}₹
273 |
274 |
275 | -
276 |
277 |
Tax
278 |
{taxPrice}₹
279 |
280 |
281 | -
282 |
283 |
Shipping
284 |
{shippingPrice}₹
285 |
286 |
287 | -
288 |
289 |
Total
290 |
{totalPrice}₹
291 |
292 |
293 | {!isPaid && (
294 | -
295 | {isPending ? (
296 |
Loading...
297 | ) : (
298 |
305 | )}
306 | {loadingPay && Loading...
}
307 |
308 | )}
309 | {session.user.isAdmin && order.isPaid && !order.isDelivered && (
310 | -
311 | {loadingDeliver &&
Loading...
}
312 |
318 |
319 | )}
320 |
321 |
322 |
323 |
324 | )}
325 |
326 | );
327 | }
328 |
329 | Order.auth = true;
330 |
--------------------------------------------------------------------------------
/pages/admin/product/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from "@/components/Layout";
2 | import { getError } from "@/utils/error";
3 | import axios from "axios";
4 | import Link from "next/link";
5 | import { useRouter } from "next/router";
6 | import React, { useEffect, useReducer } from "react";
7 | import { useForm } from "react-hook-form";
8 | import { toast } from "react-toastify";
9 |
10 | function reducer(state, action) {
11 | switch (action.type) {
12 | case "FETCH_REQUEST": {
13 | return { ...state, loading: true, error: "" };
14 | }
15 | case "FETCH_SUCCESS": {
16 | return { ...state, loading: false, error: "" };
17 | }
18 | case "FETCH_FAIL": {
19 | return { ...state, loading: false, error: action.payload };
20 | }
21 |
22 | case "UPDATE_REQUEST": {
23 | return { ...state, loadingUpdate: true, errorUpdate: "" };
24 | }
25 | case "UPDATE_SUCCESS": {
26 | return { ...state, loadingUpdate: false, errorUpdate: "" };
27 | }
28 | case "UPDATE_FAIL": {
29 | return { ...state, loadingUpdate: false, errorUpdate: action.payload };
30 | }
31 |
32 | case "UPLOAD_REQUEST": {
33 | return { ...state, loadingUpload: true, errorUpload: "" };
34 | }
35 | case "UPLOAD_SUCCESS": {
36 | return { ...state, loadingUpload: false, errorUpload: "" };
37 | }
38 | case "UPLOAD_FAIL": {
39 | return { ...state, loadingUpload: false, errorUpload: action.payload };
40 | }
41 |
42 | default: {
43 | return state;
44 | }
45 | }
46 | }
47 |
48 | export default function ProductEditScreen() {
49 | const { query } = useRouter();
50 | const productId = query.id;
51 | const [{ loading, error, loadingUpdate, loadingUpload }, dispatch] =
52 | useReducer(reducer, {
53 | loading: true,
54 | error: "",
55 | });
56 |
57 | const {
58 | register,
59 | handleSubmit,
60 | setValue,
61 | formState: { errors },
62 | } = useForm();
63 |
64 | useEffect(() => {
65 | const fetchData = async () => {
66 | try {
67 | dispatch({ type: "FETCH_REQUEST" });
68 | const { data } = await axios.get(`/api/admin/products/${productId}`);
69 | dispatch({ type: "FETCH_SUCCESS" });
70 | setValue("name", data.name);
71 | setValue("slug", data.slug);
72 | setValue("price", data.price);
73 | setValue("image", data.image);
74 | setValue("category", data.category);
75 | setValue("brand", data.brand);
76 | setValue("countInStock", data.countInStock);
77 | setValue("description", data.description);
78 | } catch (error) {
79 | dispatch({ type: "FETCH_FAIL", payload: getError(error) });
80 | }
81 | };
82 | fetchData();
83 | }, [productId, setValue]);
84 |
85 | const router = useRouter();
86 |
87 | const submitHandler = async ({
88 | name,
89 | slug,
90 | price,
91 | category,
92 | image,
93 | brand,
94 | countInStock,
95 | description,
96 | }) => {
97 | try {
98 | dispatch({ type: "UPDATE_REQUEST" });
99 | await axios.put(`/api/admin/products/${productId}`, {
100 | name,
101 | slug,
102 | price,
103 | category,
104 | image,
105 | brand,
106 | countInStock,
107 | description,
108 | });
109 | dispatch({ type: "UPDATE_SUCCESS" });
110 | toast.success("Product updated successfully");
111 | router.push("/admin/products");
112 | } catch (error) {
113 | dispatch({ type: "UPDATE_FAUL", payload: getError(error) });
114 | toast.error(getError(error));
115 | }
116 | };
117 |
118 | const uploadHandler = async (e, imageField = "image") => {
119 | const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`;
120 |
121 | try {
122 | dispatch({ type: "UPLOAD_REQUEST" });
123 | const {
124 | data: { signature, timestamp },
125 | } = await axios("/api/admin/cloudinary-sign");
126 |
127 | const file = e.target.files[0];
128 |
129 | const formData = new FormData();
130 |
131 | formData.append("file", file);
132 | formData.append("signature", signature);
133 | formData.append("timestamp", timestamp);
134 | formData.append("api_key", process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY);
135 |
136 | const { data } = await axios.post(url, formData);
137 |
138 | dispatch({ type: "UPLOAD_SUCCESS" });
139 |
140 | setValue(imageField, data.secure_url);
141 |
142 | console.log("image url is:-", data.secure_url);
143 |
144 | toast.success("File uploaded successfully");
145 | } catch (error) {
146 | dispatch({ type: "UPLOAD_FAIL", payload: getError(error) });
147 | toast.error(getError(error));
148 | }
149 | };
150 |
151 | return (
152 |
153 |
154 |
155 |
156 | -
157 | Dashboard
158 |
159 | -
160 | Orders
161 |
162 | -
163 |
164 | Products
165 |
166 | {""}
167 |
181 |
182 | -
183 | Users
184 |
185 |
186 |
187 |
188 | {loading ? (
189 |
Loading...
190 | ) : error ? (
191 |
{error}
192 | ) : (
193 |
329 | )}
330 |
331 |
332 |
333 | );
334 | }
335 |
336 | ProductEditScreen.auth = { adminOnly: true };
337 |
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import { Store } from "@/utils/Store";
2 | import { Menu } from "@headlessui/react";
3 | import Cookies from "js-cookie";
4 | import { signOut, useSession } from "next-auth/react";
5 | import Head from "next/head";
6 | import Link from "next/link";
7 | import { useRouter } from "next/router";
8 | import React, { useContext, useEffect, useState } from "react";
9 | import { ToastContainer } from "react-toastify";
10 | import "react-toastify/dist/ReactToastify.css";
11 | import DropdownLink from "./DropdownLink";
12 |
13 | function Layout({ title, children }) {
14 | const router = useRouter();
15 | const { status, data: session } = useSession();
16 |
17 | const { state, dispatch } = useContext(Store);
18 | const { cart } = state;
19 |
20 | const [cardItemsCount, setCardItemsCount] = useState(0);
21 |
22 | useEffect(() => {
23 | setCardItemsCount(cart.cartItems.reduce((a, c) => a + c.quantity, 0));
24 | }, [cart.cartItems]);
25 |
26 | const logoutClickHandler = () => {
27 | Cookies.remove("cart");
28 | dispatch({ type: "CART_RESET" });
29 | signOut({ callbackUrl: "/login" });
30 | };
31 |
32 | const [query, setQuery] = useState("");
33 |
34 | const submitHandler = (e) => {
35 | e.preventDefault();
36 | router.push(`/search/?query=${query}`);
37 | };
38 |
39 | const [toggle, setToggle] = useState(false);
40 |
41 | return (
42 | <>
43 |
44 | {title ? title : "MyShop"}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
278 |
279 | {children}
280 |
281 |
284 |
285 | >
286 | );
287 | }
288 |
289 | export default Layout;
290 |
--------------------------------------------------------------------------------