- Product listing with search and filter options
- Shopping cart functionality
- User authentication and account management
- Responsive design
- Create product type
- Add details
- Add images
- Render products
- Create product page
- Create 3 columns
- Show images in first column
- Show prduct info in second column
- show add to cart action on third column
- add styles
-
lib/utils.ts
export const round2 = (num: number) => Math.round((num + Number.EPSILON) * 100) / 100;
-
lib/models/OrderModel.ts
export type OrderItem = { name: string; slug: string; qty: number; image: string; price: number; color: string; size: string; };
-
npm install zustand
-
Create a Hook: lib/hooks/userCartStore.ts
-
Create AddToCart component
increase: (item: OrderItem) => { set((state) => { const exist = state.items.find((x) => x.slug === item.slug); const updatedCartItems = exist ? state.items.map((x) => x.slug === item.slug ? { ...exist, qty: exist.qty + 1 } : x ) : [...state.items, { ...item, qty: 1 }]; const { itemsPrice, shippingPrice, taxPrice, totalPrice } = calcPrice(updatedCartItems); return { items: updatedCartItems, itemsPrice, taxPrice, shippingPrice, totalPrice, }; }); },
-
Create Menu component in header folder
decrease: (item: OrderItem) => {
set((state) => {
const exist = state.items.find((x) => x.slug === item.slug);
if (!exist) return state;
const updatedCartItems =
exist.qty === 1
? state.items.filter((x) => x.slug !== item.slug)
: state.items.map((x) =>
x.slug === item.slug ? { ...exist, qty: exist.qty - 1 } : x
);
const { itemsPrice, shippingPrice, taxPrice, totalPrice } =
calcPrice(updatedCartItems);
return {
items: updatedCartItems,
itemsPrice,
taxPrice,
shippingPrice,
totalPrice,
};
});
},
- lin/useCartService.ts
import { persist } from "zustand/middleware";
export const cartStore = create<Cart>()(
persist(() => initialState, {
name: "cartStore",
})
);
- app/(frontend)//cart/cartDetails.ts
- Local MongoDB
- Install MongoDB
- Create .env file and add MONGODB_URI=mongodb://localhost/next-amazon
- OR Atlas Cloud MongoDB
- Create database at MOngoDB Atlas
- Create .env file, add MongoDB_URI=mongodb+srv://your-db-connection
- lib/servrices/productServices.ts
import { cache } from "react";
import dbConnect from "../dbConnect";
import ProductModel, { Product } from "../models/ProductModel";
export const revalidate = 3600;
const getLatest = cache(async () => {
await dbConnect();
const products = await ProductModel.find({})
.sort({ _id: -1 })
.limit(4)
.lean();
return products as Product[];
});
const getFeatured = cache(async () => {
await dbConnect();
const products = await ProductModel.find({ isFeatured: true })
.limit(3)
.lean();
return products as Product[];
});
const getBySlug = cache(async (slug: string) => {
await dbConnect();
const product = await ProductModel.findOne({ slug }).lean();
return product as Product | null;
});
const productService = {
getLatest,
getFeatured,
getBySlug,
};
export default productService;
- app(frontend)/pages.tsx
- lib/dbConnect.ts
import mongoose from "mongoose";
async function dbConnect() {
try {
console.log("Successfully connected to MongoDB.");
await mongoose.connect(process.env.MONGODB_URI!);
} catch (error) {
console.log("Connection error");
throw new Error("Connection Failed!");
}
}
export default dbConnect;
- npm install next-auth@beta react-hook-form
- .env
AUTH_URL=http://localhost:3000
AUTH_SECRET=*******
- lib/auth.ts
import bcrypt from "bcryptjs";
import CredentialsProvider from "next-auth/providers/credentials";
import dbConnect from "./dbConnect";
import UserModel from "./models/UserModel";
import NextAuth from "next-auth";
export const config = {
providers: [
CredentialsProvider({
credentials: {
email: { type: "email" },
password: { type: "password" },
},
async authorize(credentials) {
await dbConnect();
if (credentials == null) return null;
const user = await UserModel.findOne({ email: credentials.email });
if (user) {
const isMatch = await bcrypt.compare(
credentials.password as string,
user.password
);
if (isMatch) {
return user;
}
}
return null;
},
}),
],
pages: {
signIn: "/signin",
newUser: "/register",
error: "/signin",
},
callbacks: {
authorized({ request, auth }: any) {
const protectedPaths = [
/\/shipping/,
/\/payment/,
/\/place-order/,
/\/profile/,
/\/order\/(.*)/,
/\/admin/,
];
const { pathname } = request.nextUrl;
if (protectedPaths.some((p) => p.test(pathname))) return !!auth;
return true;
},
async jwt({ user, trigger, session, token }: any) {
if (user) {
token.user = {
_id: user._id,
email: user.email,
name: user.name,
isAdmin: user.isAdmin,
};
}
if (trigger === "update" && session) {
token.user = {
...token.user,
email: session.user.email,
name: session.user.name,
};
}
return token;
},
session: async ({ session, token }: any) => {
if (token) {
session.user = token.user;
}
return session;
},
},
};
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth(config);
- app/api/auth/register/route.ts
import bcrypt from "bcryptjs";
import dbConnect from "@/lib/dbConnect";
import { NextRequest } from "next/server";
import UserModel from "@/lib/models/UserModel";
export const POST = async (request: NextRequest) => {
const { name, email, password } = await request.json();
await dbConnect();
const hashedPassword = await bcrypt.hash(password, 5);
const newUser = new UserModel({
name,
email,
password: hashedPassword,
});
try {
await newUser.save();
return Response.json(
{ message: "New User has been Created." },
{ status: 201 }
);
} catch (error: any) {
return Response.json({ message: error.message }, { status: 500 });
}
};
- lib/models/OrderModel.ts
export type OrderItem = {
name: string;
slug: string;
qty: number;
image: string;
price: number;
color: string;
size: string;
};
export type ShippingAddress = {
fullName: string;
address: string;
city: string;
postalCode: string;
country: string;
};
- lib/hooks/useCartStore.ts
saveShippingAddress: (shippingAddress: ShippingAddress) => {
cartStore.setState({ shippingAddress });
};
savePaymentMethod: (paymentMethod: string) => {
cartStore.setState({
paymentMethod,
});
};
- components/CheckoutSteps.tsx
const CheckoutSteps = ({ current = 0 }) => {
return (
<ul className="steps steps-vertical lg:steps-horizontal w-full mt-4">
{["User Login", "Shipping Address", "Payment Method", "Place Order"].map(
(step, index) => (
<li
key={step}
className={`step ${index <= current ? "step-primary" : " "}`}
>
{step}
</li>
)
)}
</ul>
);
};
export default CheckoutSteps;
- Create Shippping Form and Shipping Page
- app/frontend/shipping/Form.tsx
- app/frontend/shipping/page.tsx
import { Metadata } from "next";
import Form from "./Form";
export const metadata: Metadata = {
title: "Shipping Address",
};
export default async function ShippingPage() {
return <Form />;
}
- app/frontend/payment/Form.tsx
- app/frontend/payment/page.tsx
import { Metadata } from "next";
import Form from "./Form";
export const metadata: Metadata = {
title: "Payment Method"
}
export default async function PaymentPage() {
return <Form />
-
lib/models/OrderModel.ts
-
app/api/orders/route.ts
-
npm install swr
- SWR is a React Hooks library for data fetching.
- With SWR, components will get a stream of data updates constantly and automatically. Thus, the UI will be always fast and reactive.
- components/ClientProviders.tsx
- wrap the ClientProvider with
<SWRConfig
value={{
onError: (error, key) => {
toast.error(error.message);
},
fetcher: async (resource, init) => {
const res = await fetch(resource, init);
if (!res.ok) {
throw new Error("An error occured while fetching the data.");
}
return res.json();
},
}}
>
<Toaster />
{children}
</SWRConfig>
import { auth } from "@/lib/auth";
import dbConnect from "@/lib/dbConnect";
import OrderModel from "@/lib/models/OrderModel";
export const GET = auth(async (...request: any) => {
const [req, { params }] = request;
if (!req.auth) {
return Response.json({ message: "unauthorized" }, { status: 401 });
}
await dbConnect();
const order = await OrderModel.findById(params.id);
return Response.json(order);
});
- Get paypal client id and app secret
- .env
PAYPAL_API_URL=https://api-m.sandbox.paypal.com
PAYPAL_CLIENT_ID=your client id
PAYPAL_APP_SECRET=your app secret
- lib/paypal.ts
async function handleResponse(response: any) {
if (response.staus === 200 || response.status === 201) {
return response.json();
}
const errorMessage = await response.text();
throw new Error(errorMessage);
}
- app/api/orders/[id]/create-paypal-order/route.ts
// generating api
-
app/api/orders/[id]/capture-paypal-order/route.ts
-
app/(frontend)/order/[id]/OrderDetails.tsx