add booking and payment schema
This commit is contained in:
+13
-11
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { tripService } from "@/server/services/trip.service";
|
||||
import { bookingService } from "@/server/services/booking.service";
|
||||
import { trustService } from "@/server/services/trust.service";
|
||||
import { formatRupiah } from "@/lib/utils";
|
||||
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
||||
@@ -128,11 +129,13 @@ export default async function TripDetailPage({
|
||||
: null;
|
||||
|
||||
const tripIsFree = isFreeTrip(trip);
|
||||
const paymentPendingParticipants = tripIsFree
|
||||
? []
|
||||
: activeParticipants.filter(
|
||||
(p) => p.markedPaidAt && !p.paymentConfirmedAt
|
||||
);
|
||||
|
||||
// Antrian konfirmasi pembayaran: source dari Booking + Payment (B9).
|
||||
// Hanya organizer yang butuh data ini, dan hanya untuk trip berbayar.
|
||||
const paymentPendingBookings =
|
||||
!tripIsFree && isOrganizer
|
||||
? await bookingService.getAwaitingManualForTrip(trip.id)
|
||||
: [];
|
||||
|
||||
const catMeta = categoryMeta(trip.category);
|
||||
|
||||
@@ -412,14 +415,13 @@ export default async function TripDetailPage({
|
||||
/>
|
||||
)}
|
||||
|
||||
{isOrganizer && paymentPendingParticipants.length > 0 && (
|
||||
{isOrganizer && paymentPendingBookings.length > 0 && (
|
||||
<OrganizerPaymentQueue
|
||||
tripId={trip.id}
|
||||
items={paymentPendingParticipants.map((p) => ({
|
||||
id: p.id,
|
||||
user: p.user,
|
||||
joinStatus:
|
||||
p.status === "PENDING" ? ("PENDING" as const) : ("CONFIRMED" as const),
|
||||
items={paymentPendingBookings.map((b) => ({
|
||||
id: b.participantId,
|
||||
user: { name: b.user.name, image: b.user.image },
|
||||
joinStatus: "CONFIRMED" as const,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { tripService } from "@/server/services/trip.service";
|
||||
import { organizerService } from "@/server/services/organizer.service";
|
||||
import { bookingService } from "@/server/services/booking.service";
|
||||
import { formatRupiah } from "@/lib/utils";
|
||||
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
||||
import { isFreeTrip } from "@/lib/trip-pricing";
|
||||
@@ -40,17 +41,18 @@ export default async function PaymentPage({ params }: PageProps) {
|
||||
redirect(`/trips/${id}`);
|
||||
}
|
||||
|
||||
const myParticipation = trip.participants.find(
|
||||
(p) => p.userId === session.user.id && p.status !== "CANCELLED"
|
||||
const booking = await bookingService.getByTripAndUser(
|
||||
trip.id,
|
||||
session.user.id
|
||||
);
|
||||
|
||||
if (!myParticipation || myParticipation.status === "CANCELLED") {
|
||||
return (
|
||||
<NotJoinedNotice tripId={trip.id} title={trip.title} />
|
||||
);
|
||||
if (!booking || booking.status === "CANCELLED") {
|
||||
return <NotJoinedNotice tripId={trip.id} title={trip.title} />;
|
||||
}
|
||||
// Narrowed: status sudah pasti PENDING atau CONFIRMED
|
||||
const activeStatus: "PENDING" | "CONFIRMED" = myParticipation.status;
|
||||
|
||||
const latestManualPayment = booking.payments.find(
|
||||
(p) => p.provider === "MANUAL"
|
||||
);
|
||||
|
||||
const tripIsFree = isFreeTrip(trip);
|
||||
const catMeta = categoryMeta(trip.category);
|
||||
@@ -109,7 +111,7 @@ export default async function PaymentPage({ params }: PageProps) {
|
||||
{tripIsFree ? (
|
||||
<FreeTripSection
|
||||
tripId={trip.id}
|
||||
participationStatus={activeStatus}
|
||||
bookingStatus={booking.status}
|
||||
/>
|
||||
) : (
|
||||
<PaidTripSection
|
||||
@@ -117,9 +119,13 @@ export default async function PaymentPage({ params }: PageProps) {
|
||||
organizerId={trip.organizerId}
|
||||
organizerName={trip.organizer.name}
|
||||
price={trip.price}
|
||||
participationStatus={activeStatus}
|
||||
markedPaidAt={myParticipation.markedPaidAt}
|
||||
paymentConfirmedAt={myParticipation.paymentConfirmedAt}
|
||||
bookingStatus={booking.status}
|
||||
paymentMarkedAt={
|
||||
latestManualPayment?.status === "AWAITING"
|
||||
? latestManualPayment.updatedAt
|
||||
: null
|
||||
}
|
||||
paymentPaidAt={latestManualPayment?.paidAt ?? null}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -148,10 +154,10 @@ function NotJoinedNotice({ tripId, title }: { tripId: string; title: string }) {
|
||||
|
||||
function FreeTripSection({
|
||||
tripId,
|
||||
participationStatus,
|
||||
bookingStatus,
|
||||
}: {
|
||||
tripId: string;
|
||||
participationStatus: "PENDING" | "CONFIRMED";
|
||||
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-emerald-200 bg-emerald-50/60 p-6 text-center shadow-sm sm:p-8">
|
||||
@@ -170,7 +176,7 @@ function FreeTripSection({
|
||||
Status keikutsertaan
|
||||
</p>
|
||||
<p className="text-sm font-bold text-neutral-800">
|
||||
{participationStatus === "CONFIRMED"
|
||||
{bookingStatus === "PAID"
|
||||
? "✅ Terkonfirmasi sebagai peserta"
|
||||
: "⏳ Menunggu persetujuan organizer"}
|
||||
</p>
|
||||
@@ -193,30 +199,33 @@ async function PaidTripSection({
|
||||
organizerId,
|
||||
organizerName,
|
||||
price,
|
||||
participationStatus,
|
||||
markedPaidAt,
|
||||
paymentConfirmedAt,
|
||||
bookingStatus,
|
||||
paymentMarkedAt,
|
||||
paymentPaidAt,
|
||||
}: {
|
||||
tripId: string;
|
||||
organizerId: string;
|
||||
organizerName: string;
|
||||
price: number;
|
||||
participationStatus: "PENDING" | "CONFIRMED";
|
||||
markedPaidAt: Date | null;
|
||||
paymentConfirmedAt: Date | null;
|
||||
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
|
||||
paymentMarkedAt: Date | null;
|
||||
paymentPaidAt: Date | null;
|
||||
}) {
|
||||
const verification = await organizerService.getStatusForUser(organizerId);
|
||||
const bankAvailable = verification?.status === "APPROVED";
|
||||
const canMarkPaid =
|
||||
participationStatus === "CONFIRMED" && !markedPaidAt && !paymentConfirmedAt;
|
||||
const showStatusOnly = !!markedPaidAt;
|
||||
|
||||
const isApproved = bookingStatus === "AWAITING_PAY" || bookingStatus === "PAID";
|
||||
const isPendingApproval = bookingStatus === "PENDING";
|
||||
const hasMarkedPaid = !!paymentMarkedAt || !!paymentPaidAt;
|
||||
const isFullyPaid = bookingStatus === "PAID";
|
||||
const canMarkPaid = bookingStatus === "AWAITING_PAY" && !paymentMarkedAt;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<PaymentTimeline
|
||||
participationStatus={participationStatus}
|
||||
markedPaidAt={markedPaidAt}
|
||||
paymentConfirmedAt={paymentConfirmedAt}
|
||||
approved={isApproved}
|
||||
markedPaid={hasMarkedPaid}
|
||||
confirmedPaid={isFullyPaid}
|
||||
/>
|
||||
|
||||
{!bankAvailable && (
|
||||
@@ -277,7 +286,7 @@ async function PaidTripSection({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{participationStatus === "PENDING" && (
|
||||
{isPendingApproval && (
|
||||
<div className="rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900">
|
||||
Kamu belum disetujui organizer untuk ikut trip ini. Tunggu persetujuan
|
||||
dulu sebelum transfer — supaya tidak perlu refund kalau ditolak.
|
||||
@@ -288,9 +297,9 @@ async function PaidTripSection({
|
||||
<MarkPaidButton tripId={tripId} />
|
||||
)}
|
||||
|
||||
{showStatusOnly && (
|
||||
{hasMarkedPaid && (
|
||||
<div className="rounded-2xl border border-neutral-200 bg-white p-4 text-sm text-neutral-600 shadow-sm sm:p-5">
|
||||
{paymentConfirmedAt ? (
|
||||
{isFullyPaid ? (
|
||||
<p>
|
||||
✅ Pembayaran kamu sudah dikonfirmasi oleh{" "}
|
||||
<span className="font-semibold text-neutral-800">
|
||||
@@ -320,27 +329,18 @@ async function PaidTripSection({
|
||||
}
|
||||
|
||||
function PaymentTimeline({
|
||||
participationStatus,
|
||||
markedPaidAt,
|
||||
paymentConfirmedAt,
|
||||
approved,
|
||||
markedPaid,
|
||||
confirmedPaid,
|
||||
}: {
|
||||
participationStatus: "PENDING" | "CONFIRMED";
|
||||
markedPaidAt: Date | null;
|
||||
paymentConfirmedAt: Date | null;
|
||||
approved: boolean;
|
||||
markedPaid: boolean;
|
||||
confirmedPaid: boolean;
|
||||
}) {
|
||||
const steps = [
|
||||
{
|
||||
label: "Disetujui organizer",
|
||||
done: participationStatus === "CONFIRMED",
|
||||
},
|
||||
{
|
||||
label: "Kamu menandai sudah bayar",
|
||||
done: !!markedPaidAt,
|
||||
},
|
||||
{
|
||||
label: "Organizer konfirmasi pembayaran",
|
||||
done: !!paymentConfirmedAt,
|
||||
},
|
||||
{ label: "Disetujui organizer", done: approved },
|
||||
{ label: "Kamu menandai sudah bayar", done: markedPaid },
|
||||
{ label: "Organizer konfirmasi pembayaran", done: confirmedPaid },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user