Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions apps/next-frontend/app/api/mercadopago/create-preference/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,28 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Se requiere external_reference' }, { status: 400 });
}

// Obtener datos del organizador (vendedor)
// El organizador se obtiene del metadata (eventId)
const eventId = metadata?.eventoid;
if (!eventId) {
return NextResponse.json({ error: 'Se requiere eventoid en metadata' }, { status: 400 });
}

// Obtener el evento y su organizador
const event = await prisma.eventos.findUnique({
where: { eventoid: eventId },
select: {
creadorid: true,
titulo: true,
},
});

if (!event) {
return NextResponse.json({ error: 'Evento no encontrado' }, { status: 404 });
}

// Obtener datos del organizador (vendedor) - NO del usuario autenticado
const seller = await prisma.user.findUnique({
where: { id: session.user.id },
where: { id: event.creadorid },
select: {
id: true,
mercado_pago_user_id: true,
Expand All @@ -58,7 +77,7 @@ export async function POST(request: NextRequest) {

if (!seller || !seller.wallet_linked || !seller.mercado_pago_access_token) {
return NextResponse.json(
{ error: 'Debe vincular su cuenta de Mercado Pago primero' },
{ error: 'El organizador debe vincular su cuenta de Mercado Pago' },
{ status: 403 },
);
}
Expand Down Expand Up @@ -170,13 +189,21 @@ export async function POST(request: NextRequest) {
amount: total,
marketplace_fee: marketplaceFee,
status: 'pending',
metadata: metadata || {},
metadata: {
...metadata,
seller_id: seller.id,
buyer_id: session.user.id,
event_id: eventId,
marketplace_fee: marketplaceFee,
},
},
});

console.log('[Create Preference] Order saved to database:', {
preferenceId: preferenceData.id,
external_reference,
sellerId: seller.id,
buyerId: session.user.id,
});
} catch (dbError) {
console.error('[Create Preference] Error saving order to database:', dbError);
Expand Down
94 changes: 80 additions & 14 deletions apps/next-frontend/app/colaborador/scanner/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { QRScanner } from '@/components/qr-scanner';
import { QRScannerFreeEvent } from '@/components/qr-scanner-free-event';
import { InviteCodeModal } from '@/components/invite-code-modal';
import { useRoleProtection } from '@/hooks/use-role-protection';
import { useRouter } from 'next/navigation';
import { Loader2 } from 'lucide-react';
Expand All @@ -14,6 +15,9 @@ export default function ScannerPage() {
const [eventos, setEventos] = useState<any[]>([]);
const [eventInfo, setEventInfo] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [showCodeModal, setShowCodeModal] = useState(false);
const [inviteCodeInput, setInviteCodeInput] = useState('');
const [isUsingCode, setIsUsingCode] = useState(false);

// Cargar eventos del colaborador
useEffect(() => {
Expand Down Expand Up @@ -46,6 +50,46 @@ export default function ScannerPage() {
}
};

const handleUseInviteCode = async () => {
if (!inviteCodeInput.trim()) {
toast.error('Por favor ingresa un código de invitación');
return;
}

setIsUsingCode(true);
try {
const response = await fetch('/api/administrador/invitados/usar-codigo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
codigo: inviteCodeInput.trim().toUpperCase(),
}),
});

const data = await response.json();

if (!response.ok) {
toast.error(data.message || 'Error al validar código');
return;
}

toast.success('¡Código validado! Uniendo al evento...');
setInviteCodeInput('');
setShowCodeModal(false);

// Recargar eventos después de usar el código
setTimeout(() => {
loadEventos();
}, 1500);
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Error al validar código');
} finally {
setIsUsingCode(false);
}
};

// Detectar si el evento es gratis
const isEventFree = eventInfo?.stock_entrada?.every((stock: any) => Number(stock.precio) === 0);

Expand All @@ -70,21 +114,43 @@ export default function ScannerPage() {

if (!eventos || eventos.length === 0) {
return (
<div className="min-h-screen bg-gradient-to-br from-stone-900 to-stone-800 flex items-center justify-center text-white">
<div className="text-center">
<h1 className="text-3xl font-bold mb-4">Sin Evento Asignado</h1>
<p className="text-stone-400 mb-8">
No tienes un evento asignado como colaborador. Por favor, contacta al organizador del
evento.
</p>
<button
onClick={() => router.push('/')}
className="px-6 py-3 bg-stone-700 hover:bg-stone-600 rounded-lg font-semibold transition-colors"
>
Volver al inicio
</button>
<>
<div className="min-h-screen bg-gradient-to-br from-stone-900 to-stone-800 flex items-center justify-center text-white">
<div className="text-center">
<h1 className="text-3xl font-bold mb-4">Sin Evento Asignado</h1>
<p className="text-stone-400 mb-8">
No tienes un evento asignado como colaborador. Ingresa un código de invitación o
contacta al organizador del evento.
</p>
<div className="flex gap-4 justify-center">
<button
onClick={() => setShowCodeModal(true)}
className="px-6 py-3 bg-orange-600 hover:bg-orange-700 rounded-lg font-semibold transition-colors"
>
Ingresar Código de Invitación
</button>
<button
onClick={() => router.push('/')}
className="px-6 py-3 bg-stone-700 hover:bg-stone-600 rounded-lg font-semibold transition-colors"
>
Volver al inicio
</button>
</div>
</div>
</div>
</div>

<InviteCodeModal
isOpen={showCodeModal}
onClose={() => {
setShowCodeModal(false);
setInviteCodeInput('');
}}
onSubmit={handleUseInviteCode}
codeValue={inviteCodeInput}
onCodeChange={setInviteCodeInput}
isLoading={isUsingCode}
/>
</>
);
}

Expand Down
11 changes: 10 additions & 1 deletion apps/next-frontend/app/eventos/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useState, useEffect } from 'react';
import {
Calendar,
MapPin,
Expand Down Expand Up @@ -246,6 +246,15 @@ export default function EventosPage() {
const deleteEventMutation = useDeleteEvent();
const updateEventMutation = useUpdateEvent();

// Log para debuggear cambios de data
useEffect(() => {
console.log('[EventosPage] Data de eventos cambió, total eventos:', events.length);
console.log(
'[EventosPage] Eventos:',
events.map((e) => ({ id: e.eventoid, titulo: e.titulo })),
);
}, [events]);

// filter events
const proximosEvents = events.filter((event) => !isEventPast(event));
const pasadosEvents = events.filter((event) => isEventPast(event));
Expand Down
4 changes: 4 additions & 0 deletions apps/next-frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ body {
}

@layer base {
html {
height: 100%;
overflow-y: scroll;
}
* {
@apply border-border;
}
Expand Down
29 changes: 15 additions & 14 deletions apps/next-frontend/components/create-event-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,20 +417,21 @@ export default function CreateEventForm() {
}

return (
<>
{eventImages.length > 0 ? (
<div
style={{ backgroundImage: `url(${eventImages[0]})` }}
className="fixed left-0 top-0 z-10 h-full w-full bg-cover bg-center opacity-30 blur-lg filter"
/>
) : (
<div className="fixed left-0 top-0 z-10 h-full w-full bg-gradient-to-b from-neutral-950 to-neutral-900" />
)}

<div className="relative z-20 min-h-screen overflow-hidden text-zinc-200 transition-all duration-500">
<div
style={
eventImages.length > 0
? { backgroundImage: `url(${eventImages[0]})` }
: { background: 'linear-gradient(to bottom, rgb(16, 15, 15), rgb(23, 23, 23))' }
}
className="min-h-screen bg-cover bg-center"
>
{/* Background blur overlay */}
<div className="fixed left-0 top-0 w-full h-screen backdrop-blur-lg bg-black/60 pointer-events-none z-0" />

<div className="relative z-20 min-h-screen text-zinc-200 transition-all duration-500">
<Navbar />

<div className="mx-auto max-w-5xl space-y-2 px-4 md:px-20 pb-3 pt-6 md:pt-10">
<div className="mx-auto max-w-5xl space-y-2 px-4 md:px-20 pb-24 pt-6 md:pt-10">
<div className="grid gap-4 md:gap-8 md:grid-cols-[330px,1fr]">
<div className="space-y-2">
<Dialog open={isImageDialogOpen} onOpenChange={setIsImageDialogOpen}>
Expand Down Expand Up @@ -824,7 +825,7 @@ export default function CreateEventForm() {
<Button
onClick={handleCreateEvent}
size="md"
className="w-full rounded-lg bg-white py-3 text-base font-medium text-black shadow-lg hover:bg-stone-200 disabled:cursor-not-allowed disabled:opacity-50"
className="w-full rounded-lg pb-6 bg-white py-3 text-base font-medium text-black shadow-lg hover:bg-stone-200 disabled:cursor-not-allowed disabled:opacity-50"
disabled={createEventMutation.isPending || !session}
>
{!session
Expand All @@ -837,6 +838,6 @@ export default function CreateEventForm() {
</div>
</div>
</div>
</>
</div>
);
}
21 changes: 11 additions & 10 deletions apps/next-frontend/components/evento-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,17 @@ export function EventoContent({ event, eventId }: EventoContentProps) {
undefined;

return (
<main className="min-h-screen py-16">
<main
style={
coverImage
? { backgroundImage: `url(${coverImage})` }
: { background: 'linear-gradient(to bottom, rgb(16, 15, 15), rgb(23, 23, 23))' }
}
className="min-h-screen py-16 bg-cover bg-center"
>
{/* Background blur overlay */}
<div className="fixed left-0 top-0 w-full h-screen backdrop-blur-lg bg-black/30 pointer-events-none z-0" />

{/* Banner de reserva temporal */}
{isReserved && isReservationActive(id) && timeLeft > 0 && (
<div className="fixed top-0 left-0 right-0 z-50 bg-gradient-to-r from-yellow-100 to-orange-100 border-b-2 border-yellow-400 shadow-lg">
Expand All @@ -258,15 +268,6 @@ export function EventoContent({ event, eventId }: EventoContentProps) {
</div>
)}

{coverImage ? (
<div
style={{ backgroundImage: `url(${coverImage})` }}
className="fixed left-0 top-0 z-5 h-full w-full bg-cover bg-center opacity-30 blur-lg filter"
/>
) : (
<div className="fixed left-0 top-0 z-5 h-full w-full bg-gradient-to-b from-neutral-950 to-neutral-900" />
)}

<div
className={`relative z-20 min-h-screen text-zinc-200 transition-all duration-500 ${isReserved && isReservationActive(id) && timeLeft > 0 ? 'pt-20' : ''}`}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/next-frontend/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Footer = () => {
const currentYear = new Date().getFullYear();

return (
<footer className="bg-stone-900 text-white relative z-[100]">
<footer className="bg-stone-900 text-white relative z-50">
<div className="max-w-7xl mx-auto px-6 py-12">
{/* Contenido principal del footer */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
Expand Down
Loading
Loading