mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, House, CreditCard, BadgeCheck, User } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb";
|
||||
import { useTiers } from "@/contexts/AdminTiersContext";
|
||||
import { PageMeta } from "@/contexts/MetadataContext";
|
||||
|
||||
const STATUS_BADGE = {
|
||||
pending: "secondary",
|
||||
completed: "default",
|
||||
failed: "destructive",
|
||||
cancelled: "outline",
|
||||
expired: "outline",
|
||||
refunded: "outline",
|
||||
};
|
||||
const TIER_BADGE = { free: "secondary", premium: "default", exclusive: "destructive" };
|
||||
|
||||
// ─── Provider label map (extend as you add more providers) ───────────────────
|
||||
const PROVIDER_LABELS = {
|
||||
paypal: "PayPal",
|
||||
stripe: "Stripe",
|
||||
gcash: "GCash",
|
||||
};
|
||||
|
||||
function InfoRow({ label, children }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-xs text-muted-foreground uppercase tracking-wide">{label}</span>
|
||||
<span className="text-sm font-medium break-all">
|
||||
{children ?? <span className="text-muted-foreground italic">—</span>}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionCard({ icon: Icon, title, children }) {
|
||||
return (
|
||||
<div className="rounded-lg border bg-card p-5 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
|
||||
<h2 className="text-sm font-semibold">{title}</h2>
|
||||
</div>
|
||||
<Separator />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Provider reference section — renders differently per provider ────────────
|
||||
function ProviderReference({ payment }) {
|
||||
const provider = payment.provider ?? "paypal";
|
||||
const payload = payment.provider_payload ?? {};
|
||||
const title = `${PROVIDER_LABELS[provider] ?? provider} Reference`;
|
||||
|
||||
return (
|
||||
<SectionCard icon={CreditCard} title={title}>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
|
||||
{/* PayPal */}
|
||||
{provider === "paypal" && (
|
||||
<>
|
||||
<InfoRow label="Order ID">{payload.order_id}</InfoRow>
|
||||
<InfoRow label="Capture ID">{payload.capture_id}</InfoRow>
|
||||
<InfoRow label="Payer ID">{payload.payer_id}</InfoRow>
|
||||
{payload.checkout && (
|
||||
<>
|
||||
<InfoRow label="Subtotal">
|
||||
{payment.currency} {Number(payload.checkout.subtotal ?? 0).toFixed(2)}
|
||||
</InfoRow>
|
||||
{Number(payload.checkout.discount) > 0 && (
|
||||
<InfoRow label="Promo Applied">
|
||||
{payload.checkout.promo_code} — -{payment.currency}{" "}
|
||||
{Number(payload.checkout.discount).toFixed(2)}
|
||||
</InfoRow>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Stripe — extend when needed */}
|
||||
{provider === "stripe" && (
|
||||
<>
|
||||
<InfoRow label="Payment Intent ID">{payload.payment_intent_id}</InfoRow>
|
||||
<InfoRow label="Charge ID">{payload.charge_id}</InfoRow>
|
||||
<InfoRow label="Customer ID">{payload.customer_id}</InfoRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* GCash / other — generic fallback */}
|
||||
{provider !== "paypal" && provider !== "stripe" && (
|
||||
<>
|
||||
<InfoRow label="Reference ID">{payload.reference_id}</InfoRow>
|
||||
<InfoRow label="Transaction ID">{payload.transaction_id}</InfoRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Cancelled info — shown for any provider */}
|
||||
{payload.cancelled_at && (
|
||||
<InfoRow label="Cancelled At">
|
||||
{new Date(payload.cancelled_at).toLocaleString()}
|
||||
</InfoRow>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</SectionCard>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function ViewPayment() {
|
||||
const navigate = useNavigate();
|
||||
const { paymentId } = useParams();
|
||||
const { fetchPayment, payment, loading } = useTiers();
|
||||
|
||||
useEffect(() => { fetchPayment(paymentId); }, [fetchPayment, paymentId]);
|
||||
|
||||
return (
|
||||
<section className="bg-muted/60 min-h-full">
|
||||
<PageMeta title={payment ? `${payment.plan?.label ?? 'Payment'} – Payment - STARR` : undefined} />
|
||||
<div className="lg:container lg:mx-auto lg:px-0 flex flex-col items-start px-4 py-10">
|
||||
<div className="w-full max-w-2xl mx-auto">
|
||||
|
||||
<div className="flex flex-col gap-2 mb-6">
|
||||
<AppBreadcrumb items={[
|
||||
{ label: "Home", icon: <House className="size-4" />, to: "/admin" },
|
||||
{ label: "Payments", to: "/admin/tiers/payments" },
|
||||
{ label: `Payment #${paymentId}` },
|
||||
]} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Button type="button" variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Payment Details</h1>
|
||||
<p className="text-sm text-muted-foreground">Payment #{paymentId}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading && !payment ? (
|
||||
<div className="space-y-5">
|
||||
{[...Array(3)].map((_, i) => <Skeleton key={i} className="h-32 w-full" />)}
|
||||
</div>
|
||||
) : !payment ? (
|
||||
<p className="text-sm text-muted-foreground">Payment not found.</p>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
|
||||
{/* Transaction */}
|
||||
<SectionCard icon={CreditCard} title="Transaction">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Status">
|
||||
<Badge variant={STATUS_BADGE[payment.status] ?? "outline"} className="capitalize mt-0.5">
|
||||
{payment.status}
|
||||
</Badge>
|
||||
</InfoRow>
|
||||
<InfoRow label="Provider">
|
||||
{PROVIDER_LABELS[payment.provider] ?? payment.provider ?? "—"}
|
||||
</InfoRow>
|
||||
<InfoRow label="Amount">
|
||||
{payment.currency} {Number(payment.amount).toFixed(2)}
|
||||
</InfoRow>
|
||||
{Number(payment.discount) > 0 && (
|
||||
<InfoRow label="Discount">
|
||||
-{payment.currency} {Number(payment.discount).toFixed(2)}
|
||||
{payment.promo_code && ` (${payment.promo_code})`}
|
||||
</InfoRow>
|
||||
)}
|
||||
<InfoRow label="Paid At">
|
||||
{payment.paid_at ? new Date(payment.paid_at).toLocaleString() : "—"}
|
||||
</InfoRow>
|
||||
<InfoRow label="Created At">
|
||||
{payment.createdAt ? new Date(payment.createdAt).toLocaleString() : "—"}
|
||||
</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* User */}
|
||||
<SectionCard icon={User} title="User">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="User ID">{String(payment.user_id)}</InfoRow>
|
||||
<InfoRow label="Email">{payment.user?.email}</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
{/* Plan */}
|
||||
{payment.plan && (
|
||||
<SectionCard icon={BadgeCheck} title="Plan">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoRow label="Label">{payment.plan.label}</InfoRow>
|
||||
<InfoRow label="Tier">
|
||||
<Badge variant={TIER_BADGE[payment.plan.tier] ?? "outline"} className="capitalize mt-0.5">
|
||||
{payment.plan.tier}
|
||||
</Badge>
|
||||
</InfoRow>
|
||||
<InfoRow label="Duration">{payment.plan.duration_days} days</InfoRow>
|
||||
</div>
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{/* Provider reference — dynamic per provider */}
|
||||
<ProviderReference payment={payment} />
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user