React Next Ecom Free
React Next Ecom Free
md 9/7/2023
// app/layout.js
import "./globals.css";
// app/page.js
export default function Home() {
return (
<main>
<div>
<h1>Home</h1>
</div>
</main>
);
}
// install bootstrap-material
// npm i bootstrap-material-design
// import in layout
import "bootstrap-material-design/dist/css/bootstrap-material-
design.min.css";
2 / 15
ReactNextEcomFree.md 9/7/2023
Create navigation
// components/nav/TopNav.js
import Link from "next/link";
<div className="d-flex">
<Link className="nav-link" href="/login">
Login
</Link>
<Link className="nav-link" href="/register">
Register
</Link>
</div>
</nav>
);
}
// import in layout
import TopNav from "@/components/nav/TopNav";
// ...
// try visiting
// http://localhost:3000/api
3 / 15
ReactNextEcomFree.md 9/7/2023
Signup to mongodb
Signup to mongo atlas to get a connection string A tutorial link
Using ENV variables
Use custom config file along with next.config.js to use env variables so that it works perfectly once
deployed to vercel
// config.js
const DB_URI =
process.env.NODE_ENV === "production"
? "mongodb+srv://ryan:xxx@nextecom.xxx.mongodb.net/?
retryWrites=true&w=majority"
: "mongodb://localhost:27017/nextecom";
module.exports = {
DB_URI,
};
// next.config.js
const config = require("./config");
module.exports = nextConfig;
Connect to mongondb
// npm i mongoose mongoose-unique-validator
// utils/dbConnect.js
import mongoose from "mongoose";
4 / 15
ReactNextEcomFree.md 9/7/2023
userSchema.plugin(uniqueValidator);
// npm i bcrypt
// app/api/register/route.js
import { NextResponse } from "next/server";
import dbConnect from "@/utils/dbConnect";
import User from "@/models/user";
import bcrypt from "bcrypt";
await dbConnect();
try {
const { name, email, password } = body;
Register form
// app/register/page.js
"use client";
import { set } from "mongoose";
import { useState } from "react";
import { FormEvent } from "react";
6 / 15
ReactNextEcomFree.md 9/7/2023
setLoading(true);
console.table({ name, email, password });
} catch (err) {
console.log(err);
setLoading(false);
}
};
return (
<main>
<div className="container">
<div className="row d-flex justify-content-center align-items-
center vh-100">
<div className="col-lg-5 bg-light p-5 shadow">
<h2 className="mb-4">Register</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="form-control mb-4"
placeholder="Your name"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control mb-4"
placeholder="Your email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control mb-4"
placeholder="Your password"
/>
<button
className="btn btn-primary btn-raised"
disabled={loading || !name || !email || !password}
>
{loading ? "Please wait.." : "Submit"}
</button>
</form>
</div>
</div>
</div>
</main>
);
}
7 / 15
ReactNextEcomFree.md 9/7/2023
module.exports = {
// ...
API,
};
// next.config.js
const nextConfig = {
env: {
DB_URI: config.DB_URI,
API: config.API,
},
};
// npm i react-hot-toast
// layout.tsx
import { Toaster } from "react-hot-toast";
// ...
<body>
<TopNav />
<Toaster />
{children}
</body>;
// ...
// app/register/page
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
// ...
const router = useRouter();
8 / 15
ReactNextEcomFree.md 9/7/2023
email,
password,
}),
});
if (!response.ok) {
toast.error(data.err);
setLoading(false);
} else {
toast.success(data.success);
router.push("/login");
}
} catch (err) {
console.log(err);
setLoading(false);
}
};
Login page
// app/login/page
"use client";
import { set } from "mongoose";
import { useState } from "react";
import { FormEvent } from "react";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
return (
<main>
<div className="container">
<div className="row d-flex justify-content-center align-items-
center vh-100">
<div className="col-lg-5 bg-light p-5 shadow">
<h2 className="mb-4">Login</h2>
<form onSubmit={handleSubmit}>
9 / 15
ReactNextEcomFree.md 9/7/2023
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="form-control mb-4"
placeholder="Your email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control mb-4"
placeholder="Your password"
/>
<button
className="btn btn-primary btn-raised"
disabled={loading || !email || !password}
>
{loading ? "Please wait.." : "Submit"}
</button>
</form>
</div>
</div>
</div>
</main>
);
}
// app/login/page
import { signIn } from "next-auth/react";
// ...
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setLoading(false);
if (result?.error) {
toast.error(result?.error);
} else {
10 / 15
ReactNextEcomFree.md 9/7/2023
toast.success("Login success");
router.push("/");
}
};
// without next-auth config, you get redirected to '/api/auth/error'
NextAuth configuration
// config.js
// name "NEXTAUTH_SECRET" is important, dont rename it
const NEXTAUTH_SECRET = "YOUR_SECRET";
// utils/authOptions.js
import CredentialsProvider from "next-auth/providers/credentials";
import User from "@/models/user";
import bcrypt from "bcrypt";
import dbConnect from "@/utils/dbConnect";
if (!user) {
throw new Error("Invalid email or password");
}
if (!isPasswordMatched) {
throw new Error("Invalid email or password");
}
11 / 15
ReactNextEcomFree.md 9/7/2023
return user;
},
}),
],
secret: process.env.NEXT_AUTH_SECRET,
pages: {
signIn: "/login",
},
};
// components/nav/TopNav
import Link from "next/link";
import { useSession, signOut } from "next-auth/react";
// console.log(data, status);
return (
<nav className="nav shadow p-2 justify-content-between mb-3">
<Link className="nav-link" href="/">
🛒 NEXTCOM
</Link>
// globals.css
.pointer {
cursor: pointer;
}
Loading page
This is default loading page in nextjs
13 / 15
ReactNextEcomFree.md 9/7/2023
// app/loading.js
export default function Loading() {
return (
<div className="d-flex justify-content-center align-items-center vh-
100 text-danger">
LOADING
</div>
);
}
User dashboard
// app/dashboard/user/page
export default function UserDashboard() {
return (
14 / 15
ReactNextEcomFree.md 9/7/2023
<div className="container">
<div className="row">
<div className="col">
<p className="lead">Dashboard</p>
<hr />
...
</div>
</div>
</div>
);
}
// this page is accessible to anyone
Protecting pages
Protect dashboard pages
// middleware.js
export { default } from "next-auth/middleware";
export const config = { matcher: ["/dashboard/:path*"] };
// handleSubmit()
router.push(callbackUrl);
What is next?
Unfortunately, Udemy only allows 2 hours of content on free course. But do not worry, If you are enjoying
this course, I will make sure you will have access to the rest of the course materials soon in future.
You can stay in touch with me on Twitter to learn more.
15 / 15