connected with database, created credential provider and google auth

This commit is contained in:
Juthatip McDevitt 2024-05-14 16:17:49 -05:00
parent eb83d21e23
commit e755ac6619
16 changed files with 1207 additions and 52 deletions

View file

@ -1,5 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.env
# dependencies
/node_modules
/.pnp

File diff suppressed because it is too large Load diff

View file

@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View 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 }

View 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);

View 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);
}

View file

@ -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;
}

View file

@ -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}>
<AppProvider>
<Header/>
{children}
</AppProvider>
</body>
</html>
);

View 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

View file

@ -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 (

View file

@ -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>
)
}

View 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

View file

@ -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>
{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 && (

View 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

View 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"
]
}