connected with database, created credential provider and google auth
This commit is contained in:
parent
eb83d21e23
commit
e755ac6619
16 changed files with 1207 additions and 52 deletions
2
donutshop_ecommerce/.gitignore
vendored
2
donutshop_ecommerce/.gitignore
vendored
|
@ -1,5 +1,7 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
.env
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
|
|
936
donutshop_ecommerce/package-lock.json
generated
936
donutshop_ecommerce/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -9,8 +9,13 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/mongodb-adapter": "^3.1.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"framer-motion": "^11.1.8",
|
||||
"mongodb": "^6.6.1",
|
||||
"mongoose": "^8.3.4",
|
||||
"next": "14.2.3",
|
||||
"next-auth": "^4.24.7",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-icons": "^5.2.0",
|
||||
|
@ -19,6 +24,8 @@
|
|||
"slick-carousel": "^1.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.12",
|
||||
"@types/react": "18.3.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"postcss": "^8",
|
||||
|
|
BIN
donutshop_ecommerce/public/google.png
Normal file
BIN
donutshop_ecommerce/public/google.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
44
donutshop_ecommerce/src/app/api/auth/[...nextauth]/route.js
Normal file
44
donutshop_ecommerce/src/app/api/auth/[...nextauth]/route.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import * as mongoose from "mongoose"
|
||||
import NextAuth from "next-auth"
|
||||
import CredentialsProvider from "next-auth/providers/credentials"
|
||||
import { User } from "../../models/User"
|
||||
import bcrypt from 'bcrypt'
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { MongoDBAdapter } from "@auth/mongodb-adapter"
|
||||
import clientPromise from "../../../../libs/mongoConnect"
|
||||
|
||||
export const authOptions = {
|
||||
secret: process.env.SECRET,
|
||||
adapter: MongoDBAdapter(clientPromise),
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
}),
|
||||
CredentialsProvider({
|
||||
name: 'Credentials',
|
||||
id: 'credentials',
|
||||
credentials: {
|
||||
username: { label: "Email", type: "email", placeholder: "test@example.com" },
|
||||
password: { label: "Password", type: "password" }
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
const email = credentials?.email;
|
||||
const password = credentials?.password
|
||||
|
||||
mongoose.connect(process.env.MONGO_URL);
|
||||
|
||||
const user = await User.findOne({email});
|
||||
const passwordOk = user && bcrypt.compareSync(password, user.password);
|
||||
if(passwordOk){
|
||||
return user
|
||||
}
|
||||
return null
|
||||
}
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST }
|
28
donutshop_ecommerce/src/app/api/models/User.js
Normal file
28
donutshop_ecommerce/src/app/api/models/User.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Schema, model, models } from "mongoose";
|
||||
import bcrypt from 'bcrypt'
|
||||
|
||||
|
||||
const UserSchema = new Schema({
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
validate: pass => {
|
||||
if (!pass?.length || pass.length < 5) {
|
||||
new Error('Password must be at least five characters');
|
||||
}
|
||||
},
|
||||
},
|
||||
}, {timestamps: true});
|
||||
|
||||
UserSchema.post('validate', function (user) {
|
||||
const passwordNothashed = user.password;
|
||||
const salt = bcrypt.genSaltSync(10);
|
||||
user.password = bcrypt.hashSync(passwordNothashed, salt)
|
||||
});
|
||||
|
||||
export const User = models?.User || model('User', UserSchema);
|
9
donutshop_ecommerce/src/app/api/register/route.js
Normal file
9
donutshop_ecommerce/src/app/api/register/route.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import mongoose from "mongoose";
|
||||
import { User } from "../models/User.js";
|
||||
|
||||
export async function POST(req){
|
||||
const body = await req.json();
|
||||
mongoose.connect(process.env.MONGO_URL);
|
||||
const createdUser = await User.create(body)
|
||||
return Response.json(createdUser);
|
||||
}
|
|
@ -9,6 +9,12 @@ html{
|
|||
body{
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/*===== general setup =====*/
|
||||
input[type="email"], input[type="password"], input[type="text"]{
|
||||
@apply border p-2 border-gray-400 block my-2 w-full rounded-md outline-none border-[#DCA0AE]
|
||||
}
|
||||
|
||||
/*===== strokel =====*/
|
||||
.text-stroke-3 {
|
||||
text-shadow: -1px -1px 0 #95743D, 2px -1px 0 #95743D, -1px 2px 0 #95743D, 1px 1px 0 #95743D;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Header from "../components/layout/Header";
|
||||
import AppProvider from "../components/AppContext";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
|
@ -14,8 +15,10 @@ export default function RootLayout({ children }) {
|
|||
<html lang="en">
|
||||
<link rel="shortcut icon" href="/favicon.png" />
|
||||
<body className={inter.className} suppressHydrationWarning={true}>
|
||||
<Header/>
|
||||
{children}
|
||||
<AppProvider>
|
||||
<Header/>
|
||||
{children}
|
||||
</AppProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
45
donutshop_ecommerce/src/app/login/page.js
Normal file
45
donutshop_ecommerce/src/app/login/page.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
"use client"
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import React, { useState } from 'react'
|
||||
import { signIn } from "next-auth/react";
|
||||
|
||||
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loginInProgress, setLoginInProgreass] = useState(false);
|
||||
|
||||
async function handleFormSubmit(ev) {
|
||||
ev.preventDefault();
|
||||
setLoginInProgreass(true)
|
||||
|
||||
await signIn('credentials', {email, password, callbackUrl:'/'})
|
||||
|
||||
setLoginInProgreass(false)
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='px-5 mt-10'>
|
||||
<p className='text-center text-2xl font-semibold uppercase mb-5 text-[#FF5580]'>Login</p>
|
||||
<form className='block max-w-xs mx-auto' onSubmit={handleFormSubmit}>
|
||||
<input type='email' name="email" placeholder='Enter your email' value={email} onChange={ev => setEmail(ev.target.value)} disabled={loginInProgress} className=' disabled:bg-gray-300 disabled:cursor-not-allowed'/>
|
||||
<input type='password' name='password' placeholder='Enter your password' value={password} onChange={ev => setPassword(ev.target.value)} disabled={loginInProgress} className=' disabled:bg-gray-300 disabled:cursor-not-allowed'/>
|
||||
<button type='submit' disabled={loginInProgress} className='rounded-md bg-[#DCA0AE] text-white hover:opacity-80 duration-300 p-2 font-semibold block w-full disabled:cursor-not-allowed'>Login</button>
|
||||
<div>
|
||||
<p className='text-center text-gray-500 text-sm my-5'>Login with provider</p>
|
||||
</div>
|
||||
<button type='button' onClick={() => signIn('google', {callbackUrl:'/'})} className='w-full p-2 border border-[#DCA0AE] rounded-md hover:bg-gray-200 hover:border-gray-200 duration-300 bg-transparent text-black flex justify-center items-center gap-2'>
|
||||
<Image src='/google.png' width={20} height={20} alt='google-register'/> Login with Google
|
||||
</button>
|
||||
<div className='mt-5 text-center text-sm text-gray-500'>Don't have an account?
|
||||
<Link href={'/register'} className='text-gray-700 font-semibold hover:underline'>Register</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
|
@ -1,13 +1,13 @@
|
|||
"use client"
|
||||
import HomeCustom from '@/components/layout/HomeCustom'
|
||||
import Hero from '@/components/layout/Hero'
|
||||
import HomeCreateBox from '@/components/layout/HomeCreateBox'
|
||||
import HomeMenu from '@/components/layout/HomeMenu'
|
||||
import HomeMonthly from '@/components/layout/HomeMonthly'
|
||||
import HomeCustom from '../components/layout/HomeCustom'
|
||||
import Hero from '../components/layout/Hero'
|
||||
import HomeCreateBox from '../components/layout/HomeCreateBox'
|
||||
import HomeMenu from '../components/layout/HomeMenu'
|
||||
import HomeMonthly from '../components/layout/HomeMonthly'
|
||||
import React from 'react'
|
||||
import { ParallaxProvider } from "react-scroll-parallax";
|
||||
import Footer from '@/components/layout/Footer'
|
||||
import TextAnimation from '@/components/layout/TextAnimation'
|
||||
import Footer from '../components/layout/Footer'
|
||||
import TextAnimation from '../components/layout/TextAnimation'
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
|
|
@ -1,13 +1,63 @@
|
|||
"use client"
|
||||
import React from 'react'
|
||||
import { signIn } from 'next-auth/react';
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link';
|
||||
import React, { useState } from 'react'
|
||||
|
||||
|
||||
const RegisterPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [userCreated, setUserCreated] = useState(false);
|
||||
const [creatingUser, setCreatingUser] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
async function handleFormSubmit(ev){
|
||||
ev.preventDefault();
|
||||
setCreatingUser(true);
|
||||
setError(false);
|
||||
setUserCreated(false)
|
||||
const response = await fetch('/api/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({email, password}),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
});
|
||||
if(response.ok){
|
||||
setUserCreated(true)
|
||||
} else{
|
||||
setError(true)
|
||||
}
|
||||
setCreatingUser(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
This is register page
|
||||
</>
|
||||
<div className='px-5 mt-10'>
|
||||
<p className='text-center text-2xl font-semibold uppercase mb-5 text-[#FF5580]'>Register</p>
|
||||
{userCreated && (
|
||||
<div className='my-4 text-center text-green-600 font-bold'>
|
||||
User created! <br/><Link href='/login' className='font-normal hover:underline'>Login here</Link>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
error && (
|
||||
<div className='my-4 text-center text-red-600 '>
|
||||
Something went wrong, please try again later!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form className='block max-w-xs mx-auto' onSubmit={handleFormSubmit}>
|
||||
<input type='email' placeholder='Enter your email' value={email} onChange={ev => setEmail(ev.target.value)} disabled={creatingUser} className=' disabled:bg-gray-300 disabled:cursor-not-allowed'/>
|
||||
<input type='password' placeholder='Enter your password' value={password} onChange={ev => setPassword(ev.target.value)} disabled={creatingUser} className=' disabled:bg-gray-300 disabled:cursor-not-allowed'/>
|
||||
<button type='submit' disabled={creatingUser} className='rounded-md bg-[#DCA0AE] text-white hover:opacity-80 duration-300 p-2 font-semibold block w-full disabled:cursor-not-allowed'>Register</button>
|
||||
<div>
|
||||
<p className='text-center text-gray-500 text-sm my-5'>Login with provider</p>
|
||||
</div>
|
||||
<button onClick={() => signIn('google', {callbackUrl:'/'})} className='w-full p-2 border border-[#DCA0AE] rounded-md hover:bg-gray-200 hover:border-gray-200 duration-300 bg-transparent text-black flex justify-center items-center gap-2'>
|
||||
<Image src='/google.png' width={20} height={20} alt='google-register'/> Login with Google
|
||||
</button>
|
||||
<div className='mt-5 text-center text-sm text-gray-500'>Already have an account? <Link href={'/login'} className='text-gray-700 font-semibold hover:underline'>Login</Link></div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
13
donutshop_ecommerce/src/components/AppContext.js
Normal file
13
donutshop_ecommerce/src/components/AppContext.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use client"
|
||||
import { SessionProvider } from 'next-auth/react'
|
||||
import React from 'react'
|
||||
|
||||
const AppProvider = ({children}) => {
|
||||
return (
|
||||
<SessionProvider>
|
||||
{children}
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppProvider
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
import React, { useState } from "react";
|
||||
import {signOut, useSession} from 'next-auth/react'
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { IoCloseOutline } from "react-icons/io5";
|
||||
|
@ -12,11 +13,15 @@ const Header = () => {
|
|||
|
||||
const [nav, setNav] = useState(false);
|
||||
|
||||
//use session
|
||||
const session = useSession();
|
||||
const status = session.status;
|
||||
|
||||
|
||||
return (
|
||||
<div className='w-full h-full'>
|
||||
<div className='flex justify-between py-10 px-5 md:px-8 lg:px-10'>
|
||||
<div className='flex justify-center relative px-4'>
|
||||
<Link href='/' className='flex justify-center relative px-4'>
|
||||
<div className='mt-5 md:mt3 flex flex-col items-center'>
|
||||
<Image src='/logo.png' width={120} height={120} alt='' className='w-[70px] md:w-[100px]'/>
|
||||
</div>
|
||||
|
@ -27,7 +32,7 @@ const Header = () => {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<div className='mt-5'>
|
||||
<ul className="hidden md:flex flex-row gap-10 text-xs font-semibold uppercase text-[#95743D]">
|
||||
<Link href='/' className="">Home</Link>
|
||||
|
@ -39,8 +44,19 @@ const Header = () => {
|
|||
<div className='mt-3'>
|
||||
<div className="flex justify-end md:justify-center items-center w-full text-white">
|
||||
<ul className="hidden md:flex flex-row gap-5 text-xs font-semibold uppercase text-[#95743D] items-center">
|
||||
<Link href='/' className="">Login</Link>
|
||||
<Link href='/register' className="px-2 py-2 border border-[#DCA0AE] rounded-full hover:bg-[#DCA0AE] hover:text-white duration-300">Register</Link>
|
||||
{status === 'authenticated' && (
|
||||
<>
|
||||
<Link href={'/profile'}>Profile</Link>
|
||||
<button onClick={() => signOut()} className="px-2 py-2 border border-[#DCA0AE] rounded-full hover:bg-[#DCA0AE] hover:text-white duration-300 uppercase">Logout</button>
|
||||
</>
|
||||
)}
|
||||
{status === 'unauthenticated' && (
|
||||
<>
|
||||
<Link href='/login' className="">Login</Link>
|
||||
<Link href='/register' className="px-2 py-2 border border-[#DCA0AE] rounded-full hover:bg-[#DCA0AE] hover:text-white duration-300">Register</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
</ul>
|
||||
<div onClick={() => setNav(!nav)} className="cursor-pointer pr-4 z-20 text-[#95743D] md:hidden "> {nav ? <IoCloseOutline size={30} /> : <HiBars2 size={30} />}</div>
|
||||
{nav && (
|
||||
|
|
30
donutshop_ecommerce/src/libs/mongoConnect.js
Normal file
30
donutshop_ecommerce/src/libs/mongoConnect.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
|
||||
import { MongoClient } from "mongodb"
|
||||
|
||||
if (!process.env.MONGO_URL) {
|
||||
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
|
||||
}
|
||||
|
||||
const uri = process.env.MONGO_URL
|
||||
const options = {}
|
||||
|
||||
let client
|
||||
let clientPromise;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// In development mode, use a global variable so that the value
|
||||
// is preserved across module reloads caused by HMR (Hot Module Replacement).
|
||||
if (!global._mongoClientPromise) {
|
||||
client = new MongoClient(uri, options)
|
||||
global._mongoClientPromise = client.connect()
|
||||
}
|
||||
clientPromise = global._mongoClientPromise
|
||||
} else {
|
||||
// In production mode, it's best to not use a global variable.
|
||||
client = new MongoClient(uri, options)
|
||||
clientPromise = client.connect()
|
||||
}
|
||||
|
||||
// Export a module-scoped MongoClient promise. By doing this in a
|
||||
// separate module, the client can be shared across functions.
|
||||
export default clientPromise
|
34
donutshop_ecommerce/tsconfig.json
Normal file
34
donutshop_ecommerce/tsconfig.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
".next/types/**/*.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue