import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; import { z } from 'zod'; interface RouteContext { params: Promise<{ id: string }>; } // Validation schema for renewal const renewMembershipSchema = z.object({ planId: z.string().cuid().optional(), // Optional: change plan during renewal extendMonths: z.number().int().min(1).optional(), // Override default extension }); // POST /api/memberships/[id]/renew - Renew membership export async function POST( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Check if user has appropriate role const allowedRoles = ['SUPER_ADMIN', 'ORG_ADMIN', 'SITE_ADMIN', 'RECEPTIONIST']; if (!allowedRoles.includes(session.user.role)) { return NextResponse.json( { error: 'Forbidden: Insufficient permissions' }, { status: 403 } ); } const { id } = await context.params; // Verify membership exists and belongs to organization const existingMembership = await db.membership.findFirst({ where: { id, plan: { organizationId: session.user.organizationId, }, }, include: { plan: true, client: { select: { id: true, firstName: true, lastName: true, }, }, }, }); if (!existingMembership) { return NextResponse.json( { error: 'Membership not found' }, { status: 404 } ); } // Cannot renew a cancelled membership if (existingMembership.status === 'CANCELLED') { return NextResponse.json( { error: 'Cannot renew a cancelled membership. Create a new membership instead.' }, { status: 400 } ); } // Parse optional body let body = {}; try { body = await request.json(); } catch { // Empty body is acceptable } // Validate input const validationResult = renewMembershipSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Invalid renewal data', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { planId, extendMonths } = validationResult.data; // Determine which plan to use let targetPlan = existingMembership.plan; if (planId && planId !== existingMembership.planId) { // Verify new plan exists and is active const newPlan = await db.membershipPlan.findFirst({ where: { id: planId, organizationId: session.user.organizationId, isActive: true, }, }); if (!newPlan) { return NextResponse.json( { error: 'New membership plan not found or inactive' }, { status: 404 } ); } targetPlan = newPlan; } // Calculate new end date // Extend from current endDate (not from now) to avoid losing time const currentEndDate = existingMembership.endDate; const now = new Date(); // If membership is already expired, extend from now instead const baseDate = currentEndDate > now ? currentEndDate : now; const extensionMonths = extendMonths || targetPlan.durationMonths; const newEndDate = new Date(baseDate); newEndDate.setMonth(newEndDate.getMonth() + extensionMonths); // Update membership const membership = await db.membership.update({ where: { id }, data: { planId: targetPlan.id, status: 'ACTIVE', endDate: newEndDate, remainingHours: targetPlan.courtHours || null, // Reset hours // Keep startDate as is - it's the original start }, include: { plan: { select: { id: true, name: true, price: true, durationMonths: true, courtHours: true, discountPercent: true, benefits: true, }, }, client: { select: { id: true, firstName: true, lastName: true, email: true, phone: true, }, }, }, }); // Calculate computed fields const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); const membershipWithDetails = { ...membership, renewed: true, previousEndDate: existingMembership.endDate, previousPlan: existingMembership.planId !== targetPlan.id ? existingMembership.plan.name : undefined, isExpiring: membership.endDate <= sevenDaysFromNow, daysUntilExpiry: Math.ceil((membership.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)), benefitsSummary: { freeHours: membership.plan.courtHours || 0, hoursRemaining: membership.remainingHours || 0, bookingDiscount: membership.plan.discountPercent ? Number(membership.plan.discountPercent) : 0, extraBenefits: membership.plan.benefits || [], }, }; return NextResponse.json(membershipWithDetails); } catch (error) { console.error('Error renewing membership:', error); return NextResponse.json( { error: 'Failed to renew membership' }, { status: 500 } ); } }