Moved register and login to user folder. Setup basic login, register and JWT
This commit is contained in:
7
src/app/api/auth/login/route.ts
Normal file
7
src/app/api/auth/login/route.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { login } from '../../../../../src/controllers/authController'; // Import the login function from authController
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
// Delegate the request to the login function in the controller
|
||||
return login(req);
|
||||
}
|
||||
63
src/app/api/auth/register/route.ts
Normal file
63
src/app/api/auth/register/route.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import bcrypt from 'bcrypt';
|
||||
import prisma from '../../../models/prismaClient';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
console.log('Request Body:', body); // Log the request body
|
||||
|
||||
const { name, username, email, password, role, company, address, phone, privatePhone, cvr } = body;
|
||||
|
||||
// Check if the username already exists
|
||||
const existingUserByUsername = await prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
if (existingUserByUsername) {
|
||||
console.log('Username already exists');
|
||||
return NextResponse.json({ error: 'Username already exists' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Check if the email already exists
|
||||
const existingUserByEmail = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
if (existingUserByEmail) {
|
||||
console.log('Email already exists');
|
||||
return NextResponse.json({ error: 'Email already exists' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create the user
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
name,
|
||||
username,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
role,
|
||||
address,
|
||||
phone,
|
||||
company: role === 'BUSINESS' ? company : null,
|
||||
privatePhone: role === 'BUSINESS' ? privatePhone : null,
|
||||
cvr: role === 'BUSINESS' ? cvr : null,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('User created successfully:', newUser);
|
||||
return NextResponse.json({ user: newUser }, { status: 201 });
|
||||
|
||||
} catch (error) {
|
||||
// Check if the error is an instance of Error and log the details
|
||||
if (error instanceof Error) {
|
||||
console.error('Error message:', error.message);
|
||||
console.error('Full error stack:', error.stack);
|
||||
} else {
|
||||
console.error('Unknown error:', error); // Log if the error is not of type `Error`
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,11 @@ const Header: React.FC = () => {
|
||||
Contact
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/account" className='hover:text-gray-400'>
|
||||
Account
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
5
src/app/models/prismaClient.ts
Normal file
5
src/app/models/prismaClient.ts
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
export default prisma;
|
||||
91
src/app/user/login/page.tsx
Normal file
91
src/app/user/login/page.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const LoginPage = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
const res = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
|
||||
// Check if the response has a body to parse
|
||||
let data = null;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch (error) {
|
||||
// If the response body is empty or not valid JSON, handle it gracefully
|
||||
console.error('Error parsing response JSON:', error);
|
||||
}
|
||||
|
||||
// Handle successful login
|
||||
if (res.ok && data?.token) {
|
||||
localStorage.setItem('token', data.token); // Store JWT token in localStorage
|
||||
router.push('/'); // Redirect to a protected page (e.g., dashboard)
|
||||
} else {
|
||||
// Handle login errors, ensuring data is valid
|
||||
setError(data?.error || 'Failed to log in');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-screen bg-gray-100">
|
||||
<div className="bg-white p-6 rounded-lg shadow-md w-96">
|
||||
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
||||
|
||||
{error && <p className="text-red-500 mb-4">{error}</p>}
|
||||
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
224
src/app/user/register/page.tsx
Normal file
224
src/app/user/register/page.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
const RegisterPage = () => {
|
||||
const [role, setRole] = useState<'PRIVATE' | 'BUSINESS' | null>(null); // Initially, no role is selected
|
||||
const [name, setName] = useState('');
|
||||
const [company, setCompany] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [privatePhone, setPrivatePhone] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [cvr, setCvr] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [username, setUsername] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const userData: any = {
|
||||
name,
|
||||
address,
|
||||
phone,
|
||||
email,
|
||||
password,
|
||||
username,
|
||||
role,
|
||||
};
|
||||
|
||||
if (role === 'BUSINESS') {
|
||||
userData.company = company;
|
||||
userData.privatePhone = privatePhone;
|
||||
userData.cvr = cvr;
|
||||
}
|
||||
|
||||
console.log('Submitting user data:', userData); // Log form data before submission
|
||||
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
router.push('/user/login');
|
||||
} else {
|
||||
const errorData = await res.json();
|
||||
console.error('Failed to register:', errorData); // Log the error response from the server
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center h-screen bg-gray-100">
|
||||
{/* Registration Header */}
|
||||
<div className="mb-8 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4">Register Here</h2>
|
||||
<p className="text-gray-600">Please select whether you're a Private or Business user to register.</p>
|
||||
</div>
|
||||
|
||||
{/* Role Selection */}
|
||||
{role === null && (
|
||||
<div className="flex space-x-4 mb-6">
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={() => setRole('PRIVATE')}
|
||||
>
|
||||
Private
|
||||
</button>
|
||||
<button
|
||||
className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline"
|
||||
onClick={() => setRole('BUSINESS')}
|
||||
>
|
||||
Business
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Registration Form */}
|
||||
{role && (
|
||||
<div className="bg-white p-8 rounded-lg shadow-md w-96">
|
||||
<h3 className="text-2xl font-bold mb-4">{role === 'PRIVATE' ? 'Private Registration' : 'Business Registration'}</h3>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{role === 'BUSINESS' && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="company" className="block text-sm font-medium text-gray-700">Company</label>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
value={company}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required={role === 'BUSINESS'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="privatePhone" className="block text-sm font-medium text-gray-700">Private Phone</label>
|
||||
<input
|
||||
type="text"
|
||||
id="privatePhone"
|
||||
value={privatePhone}
|
||||
onChange={(e) => setPrivatePhone(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required={role === 'BUSINESS'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="cvr" className="block text-sm font-medium text-gray-700">CVR</label>
|
||||
<input
|
||||
type="text"
|
||||
id="cvr"
|
||||
value={cvr}
|
||||
onChange={(e) => setCvr(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required={role === 'BUSINESS'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="address" className="block text-sm font-medium text-gray-700">Address</label>
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-700">Phone</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Already Registered */}
|
||||
<div className="mt-6">
|
||||
<p className="text-gray-600">
|
||||
Already registered?{' '}
|
||||
<Link href="/user/login" className="text-blue-500 hover:underline">
|
||||
Login here
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
1
src/config/config.ts
Normal file
1
src/config/config.ts
Normal file
@ -0,0 +1 @@
|
||||
export const SECRET_KEY = process.env.SECRET_KEY || 'f850bce14191c4e9d1d2c9b06701e5ad';
|
||||
0
src/controllers/auctionController.ts
Normal file
0
src/controllers/auctionController.ts
Normal file
36
src/controllers/authController.ts
Normal file
36
src/controllers/authController.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import bcrypt from 'bcrypt';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import prisma from '../app/models/prismaClient';
|
||||
import { SECRET_KEY } from '../config/config'; // Load from config.ts or directly from process.env
|
||||
|
||||
export async function login(req: NextRequest) {
|
||||
try {
|
||||
// Parse the request body
|
||||
const body = await req.json();
|
||||
const { username, password } = body;
|
||||
|
||||
// Find the user by username
|
||||
const user = await prisma.user.findUnique({ where: { username } });
|
||||
|
||||
// If user not found or password is incorrect, return an error
|
||||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Ensure SECRET_KEY is defined
|
||||
if (!SECRET_KEY) {
|
||||
console.error('SECRET_KEY is missing');
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Generate JWT token with user ID
|
||||
const token = jwt.sign({ userId: user.id }, SECRET_KEY, { expiresIn: '1h' });
|
||||
|
||||
// Return the token in the response
|
||||
return NextResponse.json({ token }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
0
src/controllers/userController.ts
Normal file
0
src/controllers/userController.ts
Normal file
23
src/middlewares/authMiddleware.ts
Normal file
23
src/middlewares/authMiddleware.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { NextApiRequest, NextApiResponse, NextApiHandler } from 'next';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { SECRET_KEY } from '../config/config';
|
||||
|
||||
const authMiddleware = (handler: NextApiHandler) => {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'No token provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, SECRET_KEY);
|
||||
(req as any).user = decoded;
|
||||
return handler(req, res);
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default authMiddleware;
|
||||
31
src/utils/axiosInstance.ts
Normal file
31
src/utils/axiosInstance.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Axios Interceptor Instance
|
||||
const AxiosInstance = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_BASE_URL || 'http://10.0.0.123:3000', // Default to localhost if NEXT_PUBLIC_BASE_URL is not set
|
||||
timeout: 1000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
// You can add interceptors here if needed
|
||||
AxiosInstance.interceptors.request.use(
|
||||
(config) => {
|
||||
// Modify request config here if needed
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
AxiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
// Modify response data here if needed
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default AxiosInstance;
|
||||
6
src/utils/jwtUtils.ts
Normal file
6
src/utils/jwtUtils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { SECRET_KEY } from '../config/config';
|
||||
|
||||
export const generateToken = (userId: number) => {
|
||||
return jwt.sign({ userId }, SECRET_KEY, { expiresIn: '1h' });
|
||||
};
|
||||
Reference in New Issue
Block a user