diff --git a/src/app/(protected)/dashboard/org/my-event/[my-eventId]/page.jsx b/src/app/(protected)/dashboard/org/my-event/[my-eventId]/page.jsx index 9f9c711..b39aacc 100644 --- a/src/app/(protected)/dashboard/org/my-event/[my-eventId]/page.jsx +++ b/src/app/(protected)/dashboard/org/my-event/[my-eventId]/page.jsx @@ -28,7 +28,8 @@ import { Minus, Plus, Megaphone, - Tag + Tag, + Landmark } from "lucide-react"; import toast from "react-hot-toast"; import { getImageUrl, getErrorMessage } from "../../../../../../lib/utils"; @@ -46,6 +47,7 @@ import useTempBookingStore from "@/store/tempBookingStore"; function BookForAttendeeModal({ isOpen, onClose, event, eventId, onSuccess, onManualPaymentRequired }) { const router = useRouter(); const [loading, setLoading] = useState(false); + const [paymentMethod, setPaymentMethod] = useState('paystack'); const [formData, setFormData] = useState({ firstname: '', lastname: '', @@ -112,14 +114,14 @@ function BookForAttendeeModal({ isOpen, onClose, event, eventId, onSuccess, onMa ] }; if (isPaidEvent) { - payload.payment_method = 'paystack'; + payload.payment_method = paymentMethod; } const response = await api.post('/tickets/organizer/book-for-attendee/', payload); const result = response.data; // Paid event: store booking with base subtotal so checkout page adds platform + Paystack fees - if (isPaidEvent && result.booking_id != null && result.payment_url != null) { + if (isPaidEvent && result.booking_id != null) { const price = selectedCategory?.price ?? 0; const subtotal = price * formData.quantity; const items = [{ @@ -139,6 +141,7 @@ function BookForAttendeeModal({ isOpen, onClose, event, eventId, onSuccess, onMa payment_url: result.payment_url || null, payment_reference: result.payment_reference || null, tickets: result.tickets || [], + payment_method: paymentMethod, created_at: new Date().toISOString(), organizer_booking: { returnUrl: window.location.pathname, @@ -150,7 +153,10 @@ function BookForAttendeeModal({ isOpen, onClose, event, eventId, onSuccess, onMa onSuccess?.(); onClose(); toast.success('Proceeding to checkout...'); - router.push(`/checkout/payment/${result.booking_id}`); + const checkoutUrl = paymentMethod === 'manual_bank_transfer' + ? `/checkout/payment/${result.booking_id}?method=bank_transfer` + : `/checkout/payment/${result.booking_id}`; + router.push(checkoutUrl); return; } @@ -287,6 +293,40 @@ function BookForAttendeeModal({ isOpen, onClose, event, eventId, onSuccess, onMa + {/* Payment Method Selection */} + {isPaidEvent && ( +
+
+ + Payment Method +
+
+ + +
+
+ )} + {/* Ticket Selection Card */}
diff --git a/src/app/(protected)/dashboard/user/events/[event_id]/page.jsx b/src/app/(protected)/dashboard/user/events/[event_id]/page.jsx index bf4fd3b..d028914 100644 --- a/src/app/(protected)/dashboard/user/events/[event_id]/page.jsx +++ b/src/app/(protected)/dashboard/user/events/[event_id]/page.jsx @@ -377,13 +377,18 @@ const EventDetailsPage = () => { if (validReferral) clearReferral(); - toast.success("Booking created! Redirecting to checkout...", { id: toastId }); - router.push(`/checkout/payment/${bookingId}`); + if (checkoutPaymentMethod === "manual_bank_transfer") { + toast.success("Booking created! Please complete the bank transfer.", { id: toastId }); + router.push(`/checkout/payment/${bookingId}?method=bank_transfer`); + } else { + toast.success("Booking created! Redirecting to checkout...", { id: toastId }); + router.push(`/checkout/payment/${bookingId}`); + } return; } // Fallback: If there's a direct payment URL, redirect to it - if (response.data.payment_url) { + if (response.data.payment_url && checkoutPaymentMethod !== "manual_bank_transfer") { toast.success("Redirecting to payment...", { id: toastId }); window.location.href = response.data.payment_url; return; diff --git a/src/app/checkout/payment/[booking_id]/page.jsx b/src/app/checkout/payment/[booking_id]/page.jsx index df4cd08..d4a247e 100644 --- a/src/app/checkout/payment/[booking_id]/page.jsx +++ b/src/app/checkout/payment/[booking_id]/page.jsx @@ -49,6 +49,14 @@ export default function CheckoutPaymentPage() { const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("paystack"); + // Sync active tab with URL parameters for direct redirection (e.g. from organizer dashboard) + useEffect(() => { + const method = searchParams.get('method'); + if (method === 'bank_transfer') { + setActiveTab('manual_bank_transfer'); + } + }, [searchParams]); + useEffect(() => { const fetchBookingData = async () => { if (!booking_id) { diff --git a/src/app/events/[event_id]/EventDetailsClient.jsx b/src/app/events/[event_id]/EventDetailsClient.jsx index 25b4476..66ccaf2 100644 --- a/src/app/events/[event_id]/EventDetailsClient.jsx +++ b/src/app/events/[event_id]/EventDetailsClient.jsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; -import { Loader2, MapPin, Calendar, Clock, Ticket, Info, Share2, Copy, Check, X, Maximize2, Plus, Minus, ShoppingCart } from "lucide-react"; +import { Loader2, MapPin, Calendar, Clock, Ticket, Info, Share2, Copy, Check, X, Maximize2, Plus, Minus, ShoppingCart, CreditCard, Landmark, Landmark as Bank } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import toast from "react-hot-toast"; import useAuthStore from "@/store/authStore"; @@ -35,6 +35,7 @@ const EventDetailsClient = ({ event_id, initialEvent }) => { const [event, setEvent] = useState(initialEvent || null); const [loading, setLoading] = useState(!initialEvent); const [bookingLoading, setBookingLoading] = useState(false); + const [paymentMethod, setPaymentMethod] = useState("paystack"); // 'paystack' or 'manual_bank_transfer' //Getting booking id for tracking tickets const [bookingID,setBookingID] = useState(null) @@ -183,13 +184,15 @@ const EventDetailsClient = ({ event_id, initialEvent }) => { } }); - // Paystack calculates fee on total amount INCLUDING platform service fee - const paystackFee = subtotal > 0 ? calculatePaystackFee(subtotal + PLATFORM_FEE) : 0; - const platformFee = subtotal > 0 ? PLATFORM_FEE + paystackFee : 0; + // Base platform service fee + const baseServiceFee = subtotal > 0 ? PLATFORM_FEE : 0; + // Paystack processing fee only applies if Paystack is the selected method + const paystackFee = (subtotal > 0 && paymentMethod === "paystack") ? calculatePaystackFee(subtotal + PLATFORM_FEE) : 0; + const platformFee = baseServiceFee + paystackFee; const total = subtotal + platformFee; - return { selectedItems, subtotal, platformFee, total, totalQuantity }; - }, [ticketSelections, categories]); + return { selectedItems, subtotal, platformFee, total, totalQuantity, paystackFee }; + }, [ticketSelections, categories, paymentMethod]); // Handle quantity change for a category const handleQuantityChange = (categoryId, delta) => { @@ -276,6 +279,7 @@ const EventDetailsClient = ({ event_id, initialEvent }) => { const payload = { event_id: eventIdToUse, items: items, + payment_method: paymentMethod, // Scoped referral source for event:TO-56363 ...(eventIdToUse === "event:TO-56363" && { referral: refUsername, // Pass referee username as 'referral' to backend @@ -314,22 +318,15 @@ const EventDetailsClient = ({ event_id, initialEvent }) => { }; localStorage.setItem(`booking_${bookingId}`, JSON.stringify(bookingDataForCheckout)); - toast.success("Booking created! Redirecting to payment...", { id: toastId }); - router.push(`/checkout/payment/${bookingId}`); - return; - } - - // Fallback for cases where booking_id isn't directly returned but payment_url is - if (response.data.payment_url) { - toast.success("Redirecting to payment...", { id: toastId }); - window.location.href = response.data.payment_url; + if (paymentMethod === "manual_bank_transfer") { + toast.success("Booking created! Please complete the bank transfer.", { id: toastId }); + router.push(`/checkout/payment/${bookingId}?method=bank_transfer`); + } else { + toast.success("Booking created! Redirecting to payment...", { id: toastId }); + router.push(`/checkout/payment/${bookingId}`); + } return; } - - toast.success("Ticket booked successfully!", { id: toastId }); - if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent("tickets-updated")); - router.push("/dashboard/user/my-tickets"); - } catch (error) { console.error("Booking error:", error); let errorMessage = error.response?.data?.error || "Failed to book ticket"; @@ -410,7 +407,7 @@ const EventDetailsClient = ({ event_id, initialEvent }) => {
- + {/* Referral Badge */} {refUsername && ( diff --git a/src/components/organizer/BulkBookForAttendeeModal.jsx b/src/components/organizer/BulkBookForAttendeeModal.jsx index 9808194..7dae6d9 100644 --- a/src/components/organizer/BulkBookForAttendeeModal.jsx +++ b/src/components/organizer/BulkBookForAttendeeModal.jsx @@ -410,12 +410,17 @@ export default function BulkBookForAttendeeModal({ items, total_quantity: result.ticket_count ?? ticketCount, subtotal, - payment_method: "manual_bank_transfer", - total_manual_amount: result.total_amount || (subtotal + 80), + payment_url: result.payment_url || null, // Only for online payments payment_reference: result.payment_reference || null, + payment_method: paymentMethod, // Use the selected payment method + total_manual_amount: result.total_amount || (subtotal + 80), // Only for manual transfer, assuming 80 is platform fee tickets: result.tickets || [], created_at: new Date().toISOString(), - organizer_booking: { returnUrl: window.location.pathname }, + organizer_booking: { + returnUrl: window.location.pathname, + attendeeEmail: attendees[0]?.email, + attendeeName: `${attendees[0]?.firstname} ${attendees[0]?.lastname}`, + }, }; localStorage.setItem( `booking_${result.booking_id}`, @@ -463,7 +468,7 @@ export default function BulkBookForAttendeeModal({ return; } - // Free event + // Handle Free Event (Booking is immediate and doesn't require payment routing) toast.success( `Successfully booked ${result.ticket_count} ticket(s) for ${result.unique_attendees ?? result.attendees?.length ?? ticketCount} attendee(s)`, );