SAVE
This commit is contained in:
parent
03eae11b3b
commit
3a99ab39c6
|
@ -0,0 +1 @@
|
|||
MONGODB_URI=mongodb+srv://adrianborrageirosmourelos:FOcz19ZnmULvFZeY@cluster0.gf3secu.mongodb.net/draw_dev?retryWrites=true&w=majority&appName=Cluster0
|
|
@ -31,7 +31,7 @@ yarn-error.log*
|
|||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
|
@ -21,11 +21,15 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"autoprefixer": "10",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"postcss": "8",
|
||||
"tailwindcss": "3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,12 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { login } from '@/controllers/authController';
|
||||
import dbConnect from '@/lib/db/connection';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
await dbConnect();
|
||||
return login(req, res);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { loginUser } from "@/controllers/authController";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Llamar a la función loginUser del controlador
|
||||
const { token, user, error, status } = await loginUser(body);
|
||||
|
||||
// Si hay un error, devolverlo con el status correspondiente
|
||||
if (error) {
|
||||
return NextResponse.json({ error }, { status: status || 500 });
|
||||
}
|
||||
|
||||
// Devolver la respuesta con el token y el usuario
|
||||
return NextResponse.json({ token, user }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Error en el login:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Error interno del servidor" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { register } from '@/controllers/authController';
|
||||
import dbConnect from '@/lib/db/connection';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
await dbConnect();
|
||||
return register(req, res);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { register } from "@/controllers/authController";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Llamar a la función register del controlador
|
||||
const { token, user, error, status } = await register(body);
|
||||
|
||||
// Si hay un error, devolverlo con el status correspondiente
|
||||
if (error) {
|
||||
return NextResponse.json({ error }, { status: status || 500 });
|
||||
}
|
||||
|
||||
// Devolver la respuesta con el token y el usuario
|
||||
return NextResponse.json({ token, user }, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("Error en el registro:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Error interno del servidor" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Error al iniciar sesión');
|
||||
}
|
||||
|
||||
localStorage.setItem('token', data.token);
|
||||
router.push('/dashboard');
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
setError('Ha ocurrido un error desconocido');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
|
||||
<div className="max-w-md w-full mx-auto">
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<div className="px-8 pt-8 pb-6 border-b border-gray-200">
|
||||
<h2 className="text-2xl font-normal text-gray-800 mb-2">Iniciar sesión</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Accede a tu cuenta para comenzar a dibujar
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-8 py-6">
|
||||
{error && (
|
||||
<div className="mb-4 px-4 py-3 bg-red-50 text-sm text-red-600 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Correo electrónico
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="tu@ejemplo.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Contraseña
|
||||
</label>
|
||||
<div className="text-xs">
|
||||
<a href="#" className="text-gray-500 hover:text-gray-700">
|
||||
¿Olvidaste tu contraseña?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-blue-600 focus:ring-0 border-gray-300 rounded"
|
||||
/>
|
||||
<label htmlFor="remember-me" className="ml-2 block text-sm text-gray-600">
|
||||
Recordarme
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full flex justify-center items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Iniciando sesión...
|
||||
</>
|
||||
) : 'Iniciar sesión'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="px-8 py-4 bg-gray-50 border-t border-gray-200">
|
||||
<p className="text-sm text-center text-gray-600">
|
||||
¿No tienes una cuenta?{' '}
|
||||
<Link href="/auth/register" className="text-blue-600 hover:text-blue-500 font-medium">
|
||||
Regístrate
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Register() {
|
||||
const router = useRouter();
|
||||
const [formData, setFormData] = useState({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError('Las contraseñas no coinciden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.password.length < 6) {
|
||||
setError('La contraseña debe tener al menos 6 caracteres');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: formData.username,
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Error al registrar usuario');
|
||||
}
|
||||
|
||||
localStorage.setItem('token', data.token);
|
||||
router.push('/dashboard');
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
setError('Ha ocurrido un error desconocido');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center p-6">
|
||||
<div className="max-w-md w-full mx-auto">
|
||||
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
|
||||
<div className="px-8 pt-8 pb-6 border-b border-gray-200">
|
||||
<h2 className="text-2xl font-normal text-gray-800 mb-2">Crear cuenta</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Regístrate para comenzar a dibujar
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-8 py-6">
|
||||
{error && (
|
||||
<div className="mb-4 px-4 py-3 bg-red-50 text-sm text-red-600 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nombre de usuario
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
autoComplete="username"
|
||||
required
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="usuario"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Correo electrónico
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="tu@ejemplo.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Contraseña
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">Mínimo 6 caracteres</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Confirmar contraseña
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 text-sm"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full flex justify-center items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Creando cuenta...
|
||||
</>
|
||||
) : 'Crear cuenta'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="px-8 py-4 bg-gray-50 border-t border-gray-200">
|
||||
<p className="text-sm text-center text-gray-600">
|
||||
¿Ya tienes una cuenta?{' '}
|
||||
<Link href="/auth/login" className="text-blue-600 hover:text-blue-500 font-medium">
|
||||
Inicia sesión
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
"use client"
|
||||
import LoginForm from '@/components/LoginForm';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Login() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
</div>
|
||||
<LoginForm />
|
||||
<div className="text-center">
|
||||
<Link href="/register" className="text-blue-500 hover:text-blue-600">
|
||||
Dont have an account? Register
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
113
src/app/page.tsx
113
src/app/page.tsx
|
@ -1,58 +1,69 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import styles from "./page.module.css";
|
||||
|
||||
interface Drawing {
|
||||
_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
creator: string;
|
||||
createdAt: string;
|
||||
}
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Home() {
|
||||
const [drawings, setDrawings] = useState<Drawing[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDrawings = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/drawings');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setDrawings(data.data);
|
||||
}
|
||||
} catch {
|
||||
console.error('Error fetching drawings');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDrawings();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<h1>Mis Dibujos</h1>
|
||||
<div className="min-h-screen bg-gray-100 flex flex-col">
|
||||
<header className="py-6 bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between items-center">
|
||||
<div className="font-medium text-lg text-gray-900">DrawApp</div>
|
||||
<nav>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm text-gray-700 hover:text-gray-900 mr-4"
|
||||
>
|
||||
Iniciar sesión
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="text-sm bg-blue-600 text-white py-2 px-3 rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Registrarse
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{loading ? (
|
||||
<p>Cargando dibujos...</p>
|
||||
) : drawings.length > 0 ? (
|
||||
<ul>
|
||||
{drawings.map((drawing) => (
|
||||
<li key={drawing._id}>
|
||||
<h2>{drawing.title}</h2>
|
||||
<p>Creado por: {drawing.creator}</p>
|
||||
<p>Fecha: {new Date(drawing.createdAt).toLocaleDateString()}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No hay dibujos disponibles</p>
|
||||
)}
|
||||
<main className="flex-grow flex">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 flex flex-col lg:flex-row items-center">
|
||||
<div className="lg:w-1/2 lg:pr-12 mb-10 lg:mb-0">
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-6">
|
||||
Dibuja lo que imagines
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8">
|
||||
Una aplicación simple y poderosa para expresar tu creatividad mediante dibujos digitales.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-4">
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="inline-flex justify-center items-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none"
|
||||
>
|
||||
Comenzar gratis
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
className="inline-flex justify-center items-center px-6 py-3 border border-gray-300 rounded-md shadow-sm text-base font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none"
|
||||
>
|
||||
Conocer más
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:w-1/2 flex justify-center">
|
||||
<div className="w-full max-w-md h-96 bg-white rounded-lg shadow-md flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1} stroke="currentColor" className="w-24 h-24 text-gray-300">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="bg-white border-t border-gray-200 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<p className="text-sm text-gray-500 text-center">
|
||||
© 2023 DrawApp. Todos los derechos reservados.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
"use client"
|
||||
import RegisterForm from '@/components/RegisterForm';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Register() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Create your account
|
||||
</h2>
|
||||
</div>
|
||||
<RegisterForm />
|
||||
<div className="text-center">
|
||||
<Link href="/login" className="text-blue-500 hover:text-blue-600">
|
||||
Already have an account? Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
"use client"
|
||||
import { useState } from 'react';
|
||||
import { LoginData } from '../types/auth';
|
||||
|
||||
export default function LoginForm() {
|
||||
const [formData, setFormData] = useState<LoginData>({
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Login failed');
|
||||
}
|
||||
|
||||
localStorage.setItem('token', data.token);
|
||||
window.location.href = '/dashboard';
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Login failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
placeholder="Email"
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
placeholder="Password"
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
"use client"
|
||||
import { useState } from 'react';
|
||||
import { RegisterData } from '../types/auth';
|
||||
|
||||
export default function RegisterForm() {
|
||||
const [formData, setFormData] = useState<RegisterData>({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Registration failed');
|
||||
}
|
||||
|
||||
localStorage.setItem('token', data.token);
|
||||
window.location.href = '/dashboard';
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Registration failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
placeholder="Username"
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
placeholder="Email"
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
placeholder="Password"
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
minLength={6}
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -1,17 +1,23 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import User from '../models/User';
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import bcrypt from "bcryptjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import User from "../models/User";
|
||||
import dbConnect from "@/lib/db/connection";
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
|
||||
|
||||
export async function register(req: NextApiRequest, res: NextApiResponse) {
|
||||
export async function register(data: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}) {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
await dbConnect();
|
||||
const { username, email, password } = data;
|
||||
|
||||
const existingUser = await User.findOne({ $or: [{ email }, { username }] });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ error: 'User already exists' });
|
||||
return { error: "User already exists", status: 400 };
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
@ -22,44 +28,42 @@ export async function register(req: NextApiRequest, res: NextApiResponse) {
|
|||
password: hashedPassword,
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
const token = jwt.sign({ userId: user._id }, JWT_SECRET, {
|
||||
expiresIn: "7d",
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
};
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
console.error("Error en el registro:", error);
|
||||
return { error: "Internal server error", status: 500 };
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await dbConnect();
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
return res.status(401).json({ error: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
const token = jwt.sign({ userId: user._id }, JWT_SECRET, {
|
||||
expiresIn: "7d",
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
token,
|
||||
|
@ -69,7 +73,40 @@ export async function login(req: NextApiRequest, res: NextApiResponse) {
|
|||
email: user.email,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
} catch {
|
||||
return res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function loginUser(data: { email: string; password: string }) {
|
||||
try {
|
||||
await dbConnect();
|
||||
const { email, password } = data;
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return { error: "Invalid credentials", status: 401 };
|
||||
}
|
||||
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
if (!isValidPassword) {
|
||||
return { error: "Invalid credentials", status: 401 };
|
||||
}
|
||||
|
||||
const token = jwt.sign({ userId: user._id }, JWT_SECRET, {
|
||||
expiresIn: "7d",
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error en el login:", error);
|
||||
return { error: "Internal server error", status: 500 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
Loading…
Reference in New Issue
Block a user