From 5367013dc68574d80d329a1a1bd64272ddc08dff Mon Sep 17 00:00:00 2001 From: sofiaaaanala Date: Wed, 17 Jun 2026 09:53:56 -0300 Subject: [PATCH] popup de prenda nueva --- app/api/prendas/route.js | 162 ++++++++++++---- app/closet/nueva/page.tsx | 389 ++++++++++++++++++++++++++++++++++++++ dev.db | Bin 36864 -> 36864 bytes 3 files changed, 510 insertions(+), 41 deletions(-) create mode 100644 app/closet/nueva/page.tsx diff --git a/app/api/prendas/route.js b/app/api/prendas/route.js index 59fa84d..27b7f75 100644 --- a/app/api/prendas/route.js +++ b/app/api/prendas/route.js @@ -1,40 +1,115 @@ +// import { NextResponse } from 'next/server'; +// import { Prenda, syncDatabase } from '@/lib/models/index.js'; +// import { verifyToken } from '@/lib/auth.js'; + +// let dbInitialized = false; +// const initDB = async () => { +// if (!dbInitialized) { +// await syncDatabase(); +// dbInitialized = true; +// } +// }; + +// // GET: Obtener todas las prendas del usuario autenticado +// export async function GET(request) { +// try { +// await initDB(); + +// // Obtener token del header +// const authHeader = request.headers.get('authorization'); +// if (!authHeader || !authHeader.startsWith('Bearer ')) { +// return NextResponse.json({ error: 'No autorizado' }, { status: 401 }); +// } + +// const token = authHeader.split(' ')[1]; +// const decoded = verifyToken(token); + +// if (!decoded) { +// return NextResponse.json({ error: 'Token inválido' }, { status: 401 }); +// } + +// const prendas = await Prenda.findAll({ +// where: { usuario_id: decoded.id }, +// order: [['createdAt', 'DESC']], +// }); + +// return NextResponse.json({ prendas }); + +// } catch (error) { +// console.error('Error GET prendas:', error); +// return NextResponse.json({ error: 'Error interno' }, { status: 500 }); +// } +// } + +// // POST: Crear una nueva prenda +// export async function POST(request) { +// try { +// await initDB(); + +// const authHeader = request.headers.get('authorization'); +// if (!authHeader || !authHeader.startsWith('Bearer ')) { +// return NextResponse.json({ error: 'No autorizado' }, { status: 401 }); +// } + +// const token = authHeader.split(' ')[1]; +// const decoded = verifyToken(token); + +// if (!decoded) { +// return NextResponse.json({ error: 'Token inválido' }, { status: 401 }); +// } + +// const { nombre, descripcion, imagen_url, categoria, talle, color, etiquetas } = await request.json(); + +// if (!nombre || !imagen_url) { +// return NextResponse.json({ error: 'Nombre e imagen son obligatorios' }, { status: 400 }); +// } + +// const prenda = await Prenda.create({ +// nombre, +// descripcion, +// imagen_url, +// categoria: categoria || 'otro', +// talle, +// color, +// etiquetas: etiquetas || [], +// usuario_id: decoded.id, +// }); + +// return NextResponse.json({ prenda }, { status: 201 }); + +// } catch (error) { +// console.error('Error POST prenda:', error); +// return NextResponse.json({ error: error.message }, { status: 500 }); +// } +// } + + import { NextResponse } from 'next/server'; -import { Prenda, syncDatabase } from '@/lib/models/index.js'; +import prisma from '@/lib/prisma'; import { verifyToken } from '@/lib/auth.js'; -let dbInitialized = false; -const initDB = async () => { - if (!dbInitialized) { - await syncDatabase(); - dbInitialized = true; - } -}; - // GET: Obtener todas las prendas del usuario autenticado export async function GET(request) { try { - await initDB(); - - // Obtener token del header const authHeader = request.headers.get('authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return NextResponse.json({ error: 'No autorizado' }, { status: 401 }); } - + const token = authHeader.split(' ')[1]; const decoded = verifyToken(token); - + if (!decoded) { return NextResponse.json({ error: 'Token inválido' }, { status: 401 }); } - - const prendas = await Prenda.findAll({ - where: { usuario_id: decoded.id }, - order: [['createdAt', 'DESC']], + + const prendas = await prisma.prendas.findMany({ + where: { id_usuario: decoded.id }, + orderBy: { createdAt: 'desc' }, }); - + return NextResponse.json({ prendas }); - + } catch (error) { console.error('Error GET prendas:', error); return NextResponse.json({ error: 'Error interno' }, { status: 500 }); @@ -44,39 +119,44 @@ export async function GET(request) { // POST: Crear una nueva prenda export async function POST(request) { try { - await initDB(); - const authHeader = request.headers.get('authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return NextResponse.json({ error: 'No autorizado' }, { status: 401 }); } - + const token = authHeader.split(' ')[1]; const decoded = verifyToken(token); - + if (!decoded) { return NextResponse.json({ error: 'Token inválido' }, { status: 401 }); } - - const { nombre, descripcion, imagen_url, categoria, talle, color, etiquetas } = await request.json(); - - if (!nombre || !imagen_url) { - return NextResponse.json({ error: 'Nombre e imagen son obligatorios' }, { status: 400 }); + + const formData = await request.formData(); + const nombre = formData.get('nombre'); + const tipo = formData.get('tipo'); + const talle = formData.get('talle'); + const color = formData.get('color'); + const descripcion = formData.get('descripcion') || ''; + const etiquetas = formData.get('etiquetas') || '[]'; + + if (!nombre || !tipo || !talle || !color) { + return NextResponse.json({ error: 'Nombre, tipo, talle y color son obligatorios' }, { status: 400 }); } - - const prenda = await Prenda.create({ - nombre, - descripcion, - imagen_url, - categoria: categoria || 'otro', - talle, - color, - etiquetas: etiquetas || [], - usuario_id: decoded.id, + + const prenda = await prisma.prendas.create({ + data: { + nombre, + tipo, + talle, + color, + descripcion, + etiquetas, + id_usuario: decoded.id, + }, }); - + return NextResponse.json({ prenda }, { status: 201 }); - + } catch (error) { console.error('Error POST prenda:', error); return NextResponse.json({ error: error.message }, { status: 500 }); diff --git a/app/closet/nueva/page.tsx b/app/closet/nueva/page.tsx new file mode 100644 index 0000000..6b96b3d --- /dev/null +++ b/app/closet/nueva/page.tsx @@ -0,0 +1,389 @@ +"use client"; + +import { useState, useRef } from "react"; + +const CATEGORIAS_PREDEFINIDAS = [ + "Remera", "Camisa", "Vestido", "Pantalón", "Jean", "Pollera", + "Abrigo", "Campera", "Sweater", "Shorts", "Calzado", "Accesorio" +]; + +const ETIQUETAS_PREDEFINIDAS = [ + "Favorita", "Verano", "Invierno", "Primavera", "Otoño", + "Casual", "Formal", "Deportivo", "Fiesta", "Trabajo" +]; + +const TALLES_ROPA = ["XS", "S", "M", "L", "XL", "XXL", "34", "36", "38", "40", "42", "44"]; + +interface Props { + onClose: () => void; +} + +export default function NuevaPrenda({ onClose }: Props) { + const [imagen, setImagen] = useState(null); + const [preview, setPreview] = useState(null); + const [nombre, setNombre] = useState(""); + const [categoria, setCategoria] = useState(""); + const [categoriaCustom, setCategoriaCustom] = useState(""); + const [talle, setTalle] = useState(""); + const [color, setColor] = useState(""); + const [descripcion, setDescripcion] = useState(""); + const [etiquetas, setEtiquetas] = useState([]); + const [etiquetaCustom, setEtiquetaCustom] = useState(""); + const [cargando, setCargando] = useState(false); + const [exito, setExito] = useState(false); + const [error, setError] = useState(""); + const fileRef = useRef(null); + + const handleImagen = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + const permitidos = ["image/jpeg", "image/png", "image/webp"]; + if (!permitidos.includes(file.type)) { + setError("Formato no válido. Usá JPG, PNG o WEBP."); + return; + } + if (file.size > 5 * 1024 * 1024) { + setError("La imagen no puede superar 5MB."); + return; + } + setError(""); + setImagen(file); + setPreview(URL.createObjectURL(file)); + }; + + const toggleEtiqueta = (e: string) => { + setEtiquetas(prev => prev.includes(e) ? prev.filter(x => x !== e) : [...prev, e]); + }; + + const agregarEtiquetaCustom = () => { + const tag = etiquetaCustom.trim(); + if (tag && !etiquetas.includes(tag)) { + setEtiquetas(prev => [...prev, tag]); + setEtiquetaCustom(""); + } + }; + + const handleSubmit = async () => { + if (!nombre || !categoria || !talle || !color) { + setError("Completá nombre, categoría, talle y color."); + return; + } + setCargando(true); + setError(""); + try { + const formData = new FormData(); + if (imagen) formData.append("imagen", imagen); + formData.append("nombre", nombre); + formData.append("tipo", categoriaCustom || categoria); + formData.append("talle", talle); + formData.append("color", color); + formData.append("descripcion", descripcion); + formData.append("etiquetas", JSON.stringify(etiquetas)); + + const res = await fetch("/api/prendas", { method: "POST", body: formData }); + if (!res.ok) throw new Error("Error al guardar"); + setExito(true); + setTimeout(() => onClose(), 1800); + } catch { + setError("Algo salió mal. Intentá de nuevo."); + } finally { + setCargando(false); + } + }; + + const inputStyle = { + width: "100%", + boxSizing: "border-box" as const, + background: "#fafaf7", + border: "1px solid #e8e4de", + borderRadius: "10px", + padding: "10px 14px", + fontSize: "13px", + color: "#1a1a1a", + outline: "none", + fontFamily: "inherit", + }; + + const labelStyle = { + display: "block", + fontSize: "10px", + fontWeight: 500, + letterSpacing: "0.15em", + textTransform: "uppercase" as const, + color: "#6b6b60", + marginBottom: "6px", + }; + + if (exito) { + return ( +
+
+ 🌿 +
+

¡Listo!

+

Prenda agregada

+

Ya forma parte de tu closet sostenible ✦

+
+ ); + } + + return ( +
+ {/* Header del modal */} +
+
+

Tu closet

+

Agregar prenda

+
+ +
+ + {/* Body */} +
+ + {/* Upload imagen */} +
+ +
fileRef.current?.click()} + style={{ + border: "1.5px dashed #e8e4de", + borderRadius: "14px", + padding: "1.5rem", + textAlign: "center", + cursor: "pointer", + background: preview ? "#fafaf7" : "transparent", + transition: "border-color 0.2s", + }} + onMouseEnter={e => (e.currentTarget.style.borderColor = "#A8C5A0")} + onMouseLeave={e => (e.currentTarget.style.borderColor = "#e8e4de")} + > + {preview ? ( + preview + ) : ( + <> +

📷

+

Tocá para subir una foto

+

JPG, PNG o WEBP · Máx 5MB

+ + )} +
+ +
+ + {/* Nombre */} +
+ + setNombre(e.target.value)} + onFocus={e => (e.target.style.borderColor = "#A8C5A0")} + onBlur={e => (e.target.style.borderColor = "#e8e4de")} + /> +
+ + {/* Categoría */} +
+ +
+ {CATEGORIAS_PREDEFINIDAS.map(cat => ( + + ))} +
+ { setCategoriaCustom(e.target.value); setCategoria(""); }} + onFocus={e => (e.target.style.borderColor = "#A8C5A0")} + onBlur={e => (e.target.style.borderColor = "#e8e4de")} + /> +
+ + {/* Talle y color en fila */} +
+
+ + +
+
+ + setColor(e.target.value)} + onFocus={e => (e.target.style.borderColor = "#A8C5A0")} + onBlur={e => (e.target.style.borderColor = "#e8e4de")} + /> +
+
+ + {/* Descripción */} +
+ +