protecting routes for auth
This commit is contained in:
@@ -17,7 +17,7 @@ import { validateToken } from './middleware/auth.js'
|
||||
|
||||
import { register, login } from './routes/auth.js'
|
||||
import { deleteUser } from './routes/user.js'
|
||||
import { createOffer, deleteOffer } from './routes/offer.js'
|
||||
import { createOffer, deleteOffer, getOffers } from './routes/offer.js'
|
||||
|
||||
/* Test route so nxckwc can test axios*/
|
||||
app.get('/users', async (req, res) => {
|
||||
@@ -26,12 +26,14 @@ app.get('/users', async (req, res) => {
|
||||
res.json(user)
|
||||
})
|
||||
|
||||
|
||||
app.post('/login', login)
|
||||
app.post('/register', register)
|
||||
|
||||
app.delete('/user/:userId', deleteUser)
|
||||
|
||||
app.post('/crear-oferta', validateToken, createOffer)
|
||||
app.get('/offers', getOffers)
|
||||
app.delete('/oferta/:projectId', validateToken, deleteOffer)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Oferta" ALTER COLUMN "url" SET DEFAULT '';
|
||||
@@ -45,7 +45,7 @@ model Oferta {
|
||||
salario String @default("0")
|
||||
contrato String
|
||||
descripcion String
|
||||
url String
|
||||
url String @default("")
|
||||
authorId Int
|
||||
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
|
||||
@@ -16,7 +16,7 @@ export const login = async (req, res) => {
|
||||
const user = await prisma.user.findFirst({ where: { email } });
|
||||
|
||||
if (!user || !await bcrypt.compare(password, user.password)) {
|
||||
return res.status(400).json({ error: 'Invalid credentials' })
|
||||
return res.status(400).json({ error: 'Credenciales erroneas' })
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
@@ -35,31 +35,32 @@ export const login = async (req, res) => {
|
||||
export const register = async (req, res) => {
|
||||
|
||||
// Get data
|
||||
const {username, email, password } = req.body
|
||||
const {username, email, password, type } = req.body
|
||||
|
||||
// Check is not empty
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({ error: 'All fields are required' })
|
||||
return res.status(400).json({ error: 'Todos los campos son obligatorios' })
|
||||
}
|
||||
|
||||
// Validate data
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (username.length < 3 ||username.length > 14) { return res.status(400).json({ error: 'Username must be between 3 and 14 characters' })}
|
||||
if (email.length > 30 || !emailRegex.test(email)) { return res.status(400).json({ error: 'Email is not valid' })}
|
||||
if (password.length < 6 || password.length > 32) { return res.status(400).json({ error: 'Password must be between 6 and 32 characters' })}
|
||||
|
||||
if (username.length < 3 ||username.length > 14) { return res.status(400).json({ error: 'El nombre de usuario debe tener entre 3 y 14 caracteres' })}
|
||||
if (email.length > 30 || !emailRegex.test(email)) { return res.status(400).json({ error: 'Correo electronico no valido' })}
|
||||
if (password.length < 6 || password.length > 32) { return res.status(400).json({ error: 'La contraseña debe tener entre 6 y 32 caracteres' })}
|
||||
if (type !== "candidato" && type !== "reclutador") { return res.status(400).json({ error: 'El tipo de cuenta no es valido' })}
|
||||
|
||||
// Check email and username doesnt exists
|
||||
const userExists = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [ { email }, { username } ]
|
||||
OR: [ { email } ]
|
||||
}
|
||||
});
|
||||
|
||||
// If username or email exists, send error
|
||||
if (userExists) {
|
||||
return res.status(409).json({
|
||||
error: 'User already exists'
|
||||
error: 'Este usuario ya existe'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ export const register = async (req, res) => {
|
||||
|
||||
// Create user
|
||||
const user = await prisma.user.create({
|
||||
data: { username, email, password: hashedPassword, type: "candidato" }
|
||||
data: { username, email, password: hashedPassword, type }
|
||||
})
|
||||
|
||||
// Generates token
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
import { prisma } from '../prisma.js'
|
||||
|
||||
export const getOffers = async(req, res) => {
|
||||
const offers = await prisma.oferta.findMany()
|
||||
|
||||
res.json(offers)
|
||||
}
|
||||
|
||||
export const createOffer = async (req, res) => {
|
||||
const { titulo, empresa, ubicacion, salario, contrato, descripcion } = req.body;
|
||||
const authorId = req.user.id
|
||||
|
||||
if (!titulo || !empresa || !ubicacion || !salario || !contrato || !descripcion) return res.status(400).json({ error: ""})
|
||||
|
||||
if (titulo.length < 3 || titulo.length > 64) { return res.status(400).json({ error: 'El titulo debe tener entre 3 y 64 caracteres' })}
|
||||
if (empresa.length < 2 || empresa.length > 32) { return res.status(400).json({ error: 'El nombre de la empresa debe tener entre 2 y 32 caracteres' })}
|
||||
if (ubicacion.length < 3 || ubicacion.length > 16) { return res.status(400).json({ error: 'La ubicacion debe tener entre 3 y 16 caracteres' })}
|
||||
if (salario.length < 3 || salario.length > 10) { return res.status(400).json({ error: 'El salario debe seguir este formato: 500€ o 20€/hora' })}
|
||||
if (descripcion.length < 10 || descripcion.length > 256) { return res.status(400).json({ error: 'La descripcion debe tener entre 10 y 256 caracteres' })}
|
||||
|
||||
const contratosValidos = ["freelance", "completo", "medio"]
|
||||
if (!contratosValidos.includes(contrato)) return res.status(400).json({ error: "contrato no valido" })
|
||||
|
||||
try {
|
||||
const offer = await prisma.oferta.create({
|
||||
data: {
|
||||
titulo, empresa, ubicacion, salario, descripcion
|
||||
titulo, empresa, ubicacion, salario, descripcion, contrato, authorId
|
||||
}
|
||||
})
|
||||
res.status(201).json(offer)
|
||||
|
||||
@@ -3,9 +3,12 @@ import { BrowserRouter, Routes, Route} from 'react-router-dom'
|
||||
|
||||
import Navbar from './components/Navbar'
|
||||
import Home from './pages/Home'
|
||||
import NuevaVacante from './pages/NuevaVacante'
|
||||
import NuevaOferta from './pages/NuevaOferta'
|
||||
import './index.css'
|
||||
import Login from './pages/Login'
|
||||
import PublicRoute from './components/PublicRoute'
|
||||
import PrivateRoute from './components/PrivateRoute'
|
||||
import Register from './pages/Register'
|
||||
|
||||
function App() {
|
||||
|
||||
@@ -15,8 +18,9 @@ function App() {
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/nueva-oferta" element={<NuevaVacante />} />
|
||||
<Route path="/login" element={<PublicRoute><Login /></PublicRoute>} />
|
||||
<Route path="/register" element={<PublicRoute><Register /></PublicRoute>} />
|
||||
<Route path="/nueva-oferta" element={<PrivateRoute><NuevaOferta /></PrivateRoute>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AuthContext } from "../context/AuthContext"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
function Navbar() {
|
||||
const { user } = useContext(AuthContext)
|
||||
const { user, logout } = useContext(AuthContext)
|
||||
|
||||
return (
|
||||
<nav className="py-10 flex justify-between">
|
||||
@@ -20,7 +20,7 @@ function Navbar() {
|
||||
) : (
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-white">Hola, {user.username}</span>
|
||||
<button className="text-red-400 font-bold uppercase">Cerrar sesión</button>
|
||||
<button onClick={logout} className="text-red-400 font-bold uppercase">Cerrar sesión</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
14
frontend/src/components/PrivateRoute.jsx
Normal file
14
frontend/src/components/PrivateRoute.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useContext } from "react"
|
||||
import { AuthContext } from "../context/AuthContext"
|
||||
import { Navigate, useLocation } from "react-router-dom"
|
||||
|
||||
const PrivateRoute = ({children}) => {
|
||||
const { user, isLoading } = useContext(AuthContext)
|
||||
const location = useLocation()
|
||||
|
||||
if (isLoading) return (null)
|
||||
|
||||
return user ? children : <Navigate to="/login" state={{ from: location.pathname }} />
|
||||
}
|
||||
|
||||
export default PrivateRoute
|
||||
11
frontend/src/components/PublicRoute.jsx
Normal file
11
frontend/src/components/PublicRoute.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useContext } from "react"
|
||||
import { AuthContext } from "../context/AuthContext"
|
||||
import { Navigate } from "react-router-dom"
|
||||
|
||||
const PublicRoute = ({children}) => {
|
||||
const { user } = useContext(AuthContext)
|
||||
|
||||
return user ? <Navigate to="/" /> : children
|
||||
}
|
||||
|
||||
export default PublicRoute
|
||||
1
frontend/src/config.js
Normal file
1
frontend/src/config.js
Normal file
@@ -0,0 +1 @@
|
||||
export const API_URL = "http://localhost:5000"
|
||||
@@ -4,10 +4,12 @@ export const AuthContext = createContext()
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const user = localStorage.getItem('user')
|
||||
if (user) setUser(JSON.parse(user))
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
const login = (data) => {
|
||||
@@ -23,7 +25,7 @@ export function AuthProvider({ children }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, login, logout }}>
|
||||
<AuthContext.Provider value={{ user, login, logout, isLoading }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { useEffect } from "react"
|
||||
import { API_URL } from "../config"
|
||||
import { useState } from "react"
|
||||
|
||||
function Home() {
|
||||
|
||||
const [offers, setOffers] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_URL}/offers`)
|
||||
.then(res => res.json())
|
||||
.then(data => setOffers(data))
|
||||
}, [])
|
||||
|
||||
const contratos = {
|
||||
freelance: "Freelance",
|
||||
completo: "Jornada completa",
|
||||
medio: "Media jornada"
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="py-5 md:py-20 flex flex-col gap-4 border-b border-b-gray-700">
|
||||
<div className="py-5 md:py-20 flex flex-col gap-4 border-b border-b-gray-700">
|
||||
<h2 className="text-4xl font-semibold">HadiJobs</h2>
|
||||
<p>Encuentra y publica trabajos para desarrolladores</p>
|
||||
<a className="bg-[#00A4B6] py-3 px-10 rounded uppercase font-extrabold tracking-wider w-fit"
|
||||
@@ -12,29 +29,31 @@ function Home() {
|
||||
<div className="py-5 md:py-20">
|
||||
<h2 className="text-4xl font-semibold pb-5">Lista de ofertas</h2>
|
||||
|
||||
<div className="flex flex-row justify-between">
|
||||
{offers.length === 0 && <p className="text-gray-500">No hay ofertas disponibles</p>}
|
||||
|
||||
{offers.map(offer => (
|
||||
<div key={offer.id} className="flex flex-row items-center justify-between border-b border-gray-700 py-6 hover:bg-gray-800 cursor-pointer rounded px-4 transition">
|
||||
<div className="flex flex-col flex-1">
|
||||
<h3 className="text-xl ">{offer.empresa}</h3>
|
||||
<p className="text-gray-500 ">{offer.titulo}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-xl ">Facebook</h3>
|
||||
<p className="text-gray-500 ">React developer</p>
|
||||
<div className="flex flex-col flex-1">
|
||||
<h3 className="text-gray-500 ">Ubicacion</h3>
|
||||
<p className="">{offer.ubicacion}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1">
|
||||
<h3 className="text-gray-500">Contrato</h3>
|
||||
<p>{contratos[offer.contrato]}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<a className="bg-[#00A4B6] py-3 px-10 rounded uppercase font-extrabold tracking-wider w-fit"
|
||||
href="#">Info</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-gray-500 ">Ubicacion</h3>
|
||||
<p className="">Remoto</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-gray-500">Contrato</h3>
|
||||
<p>Tiempo completo</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<a className="bg-[#00C897] py-3 px-10 rounded uppercase font-extrabold tracking-wider w-fit"
|
||||
href="#">Info</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,13 @@ import { useContext } from "react"
|
||||
import { useState } from "react"
|
||||
import { AuthContext } from "../context/AuthContext"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { API_URL } from "../config"
|
||||
|
||||
function Login() {
|
||||
|
||||
const [email, setEmail] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const { login } = useContext(AuthContext)
|
||||
const navigate = useNavigate()
|
||||
@@ -14,7 +16,7 @@ function Login() {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const res = await fetch('http://localhost:5000/login', {
|
||||
const res = await fetch(`${API_URL}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password })
|
||||
@@ -26,6 +28,7 @@ function Login() {
|
||||
login(data)
|
||||
navigate('/')
|
||||
} else {
|
||||
setError(data.error)
|
||||
console.error(data.error)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +41,8 @@ function Login() {
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h3 className="text-center text-5xl mb-16">Inicia sesion</h3>
|
||||
|
||||
{ error && <p className="text-red-400">{error}</p> }
|
||||
|
||||
<div className="flex flex-col mb-8">
|
||||
<label className="w-[9rem] pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Correo
|
||||
@@ -53,13 +58,13 @@ function Login() {
|
||||
Contraseña
|
||||
</label>
|
||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Escribe su contraseña..." required
|
||||
placeholder="Escriba su contraseña..." required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Acceder" class="bg-[#00A4B6] hover:bg-[#027481] transition cursor-pointer py-3 px-10 rounded uppercase font-extrabold tracking-wider w-full" />
|
||||
<input type="submit" value="Acceder" className="bg-[#00A4B6] hover:bg-[#027481] transition cursor-pointer py-3 px-10 rounded uppercase font-extrabold tracking-wider w-full" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -1,23 +1,59 @@
|
||||
import { useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { API_URL } from "../config"
|
||||
|
||||
function NuevaVacante() {
|
||||
function NuevaOferta() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [titulo, setTitulo] = useState("")
|
||||
const [empresa, setEmpresa] = useState("")
|
||||
const [salario, setSalario] = useState("")
|
||||
const [contrato, setContrato] = useState("")
|
||||
const [ubicacion, setUbicacion] = useState("")
|
||||
const [descripcion, setDescripcion] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
|
||||
e.preventDefault()
|
||||
setError("")
|
||||
|
||||
const res = await fetch(`${API_URL}/crear-oferta`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${localStorage.getItem("token")}`
|
||||
},
|
||||
body: JSON.stringify({titulo, empresa, ubicacion, salario, contrato, descripcion})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (res.ok) {
|
||||
navigate('/')
|
||||
} else {
|
||||
setError(data.error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="py-5 md:py-20 flex flex-col gap-4 border-b border-b-gray-700">
|
||||
<h2 className="text-4xl font-semibold">Nueva vacante</h2>
|
||||
<h2 className="text-4xl font-semibold">Nueva oferta</h2>
|
||||
<p>Rellena el formulario para publicar tu oferta de trabajo</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="max-w-[800px] mx-auto p-10">
|
||||
<form>
|
||||
<form onSubmit={handleSubmit} >
|
||||
<h3 className="text-gray-500 mb-8">Informacion general</h3>
|
||||
|
||||
{ error && <p className="text-red-500">{error}</p>}
|
||||
<div className="flex mb-8">
|
||||
<label className="w-[9rem] pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Titulo
|
||||
</label>
|
||||
<input type="text" name="titulo"
|
||||
<input type="text" name="titulo" value={titulo} onChange={(e) => setTitulo(e.target.value)}
|
||||
placeholder="Ej: React Developer" required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
@@ -27,7 +63,7 @@ function NuevaVacante() {
|
||||
<label className="w-[9rem] pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Empresa
|
||||
</label>
|
||||
<input type="text" name="empresa"
|
||||
<input type="text" name="empresa" value={empresa} onChange={(e) => setEmpresa(e.target.value)}
|
||||
placeholder="Ej: HadiES" required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
@@ -37,7 +73,7 @@ function NuevaVacante() {
|
||||
<label className="w-[9rem] pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Ubicacion
|
||||
</label>
|
||||
<input type="text" name="ubicacion"
|
||||
<input type="text" name="ubicacion" value={ubicacion} onChange={(e) => setUbicacion(e.target.value)}
|
||||
placeholder="Ej: España... Global..." required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
@@ -48,7 +84,7 @@ function NuevaVacante() {
|
||||
Salario (EUR)
|
||||
</label>
|
||||
<input type="text" name="salario"
|
||||
placeholder="Ej: 50€/hora o 200€" required
|
||||
placeholder="Ej: 50€/hora o 200€" required value={salario} onChange={(e) => setSalario(e.target.value)}
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
</div>
|
||||
@@ -58,8 +94,9 @@ function NuevaVacante() {
|
||||
Contrato
|
||||
</label>
|
||||
|
||||
<select name="contrato" className="bg-gray-200 text-black w-full text-center font-normal">
|
||||
<option value="" selected disabled>-- Selecciona una opcion --</option>
|
||||
<select name="contrato" defaultValue={0} value={contrato} onChange={(e) => setContrato(e.target.value)}
|
||||
className="bg-gray-200 text-black w-full text-center font-normal">
|
||||
<option value="" disabled>-- Selecciona una opcion --</option>
|
||||
<option value="freelance">Freelance</option>
|
||||
<option value="completo">Jornada completa</option>
|
||||
<option value="medio">Media jornada</option>
|
||||
@@ -69,14 +106,14 @@ function NuevaVacante() {
|
||||
<div className="flex flex-col gap-4 mb-8">
|
||||
<h3 className="font-bold uppercase">Descripcion del puesto</h3>
|
||||
|
||||
<textarea name="descripcion"
|
||||
<textarea name="descripcion" value={descripcion} onChange={(e) => setDescripcion(e.target.value)}
|
||||
placeholder="Describe el puesto..."
|
||||
className="bg-gray-200 flex-1 px-5 py-3 rounded text-black border-none font-normal min-h-[200px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<input type="submit" value="Publicar" class="bg-[#00A4B6] py-3 px-10 rounded uppercase font-extrabold tracking-wider w-full" />
|
||||
<div className="">
|
||||
<input type="submit" value="Publicar" className="bg-[#00A4B6] hover:bg-[#027481] transition py-3 px-10 rounded uppercase font-extrabold tracking-wider w-full cursor-pointer" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -85,4 +122,4 @@ function NuevaVacante() {
|
||||
)
|
||||
}
|
||||
|
||||
export default NuevaVacante
|
||||
export default NuevaOferta
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useContext } from "react"
|
||||
import { useState } from "react"
|
||||
import { AuthContext } from "../context/AuthContext"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { API_URL } from "../config"
|
||||
|
||||
function Register() {
|
||||
|
||||
const [username, setUsername] = useState("")
|
||||
const [email, setEmail] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [type, setType] = useState("")
|
||||
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const { login } = useContext(AuthContext)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!type) return setError("Debes seleccionar un tipo de cuenta ('Busco trabajo / Ofrezco trabajo')")
|
||||
|
||||
const res = await fetch(`${API_URL}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, email, password })
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (res.ok) {
|
||||
login(data)
|
||||
navigate('/')
|
||||
} else {
|
||||
setError(data.error)
|
||||
console.error(data.error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<div className="max-w-[800px] mx-auto p-20">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h3 className="text-center text-5xl mb-16">Registrate</h3>
|
||||
|
||||
{ error && <p className="text-red-400">{error}</p> }
|
||||
<div className="flex flex-col mb-8">
|
||||
<label className="pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Nombre de usuario
|
||||
</label>
|
||||
<input type="username" value={username} onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="Escriba su nombre de usuario..." required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mb-8">
|
||||
<label className="pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Correo
|
||||
</label>
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Escriba su email..." required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mb-8">
|
||||
<label className="pt-2 text-white font-bold shrink-0 uppercase">
|
||||
Contraseña
|
||||
</label>
|
||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Escriba su contraseña..." required
|
||||
className="bg-gray-200 flex-1 px-5 py-1 rounded text-black w-full border-none font-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mb-8">
|
||||
<label className="pt-2 text-white font-bold shrink-0 uppercase">
|
||||
¿Buscas trabajo o quieres publicar ofertas?
|
||||
</label>
|
||||
<div className="flex gap-5 justify-between my-5">
|
||||
<button className={`w-1/2 py-3 transition font-bold uppercase hover:bg-[#00A4B6] active:brightness-125 ${type === "candidato" ? "bg-[#00A4B6]" : "bg-gray-600"}`}
|
||||
type="button" onClick={() => setType("candidato")}>Busco</button>
|
||||
<button className={`w-1/2 py-3 transition font-bold uppercase hover:bg-[#00A4B6] active:brightness-125 ${type === "reclutador" ? "bg-[#00A4B6]" : "bg-gray-600"}`}
|
||||
type="button" onClick={() => setType("reclutador")}>Publico</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Crear cuenta" className="bg-[#00A4B6] hover:bg-[#027481] transition cursor-pointer py-3 px-10 rounded uppercase font-extrabold tracking-wider w-full" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Register
|
||||
|
||||
Reference in New Issue
Block a user