import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { ArrowLeft, House, Tag, ShieldCheck, Plus, Trash2, Loader2 } from "lucide-react"; import { DateTimePicker } from "@/components/ui/date-time-picker"; import { toast } from "sonner"; import AppBreadcrumb from "@/components/generic/Breadcrumb/AppBreadcrumb"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { PageMeta } from "@/contexts/MetadataContext"; import { useTiers } from "@/contexts/AdminTiersContext"; import api from "@/utils/api.util"; // ─── Helpers ────────────────────────────────────────────────────────────────── function SectionCard({ icon: Icon, title, description, children }) { return (
{Icon && }

{title}

{description &&

{description}

}
{children}
); } const EMPTY_PROMO = { code: "", type: "flat", value: "", currency: "USD", max_discount: "", max_uses: "", expires_at: "", min_amount: "", }; const WINDOW_UNITS = ["minutes", "hours", "days"]; // ─── Page ───────────────────────────────────────────────────────────────────── export default function PaymentPolicy() { const navigate = useNavigate(); const { planId } = useParams(); const { fetchPlan, plan, loading: planLoading } = useTiers(); const [policy, setPolicy] = useState(null); const [policyLoading, setPolicyLoading] = useState(true); const [saving, setSaving] = useState(false); // ── Refund policy fields const [refundAllowed, setRefundAllowed] = useState(true); const [refundWindowValue, setRefundWindowValue] = useState(5); const [refundWindowUnit, setRefundWindowUnit] = useState("minutes"); const [refundReasonReqd, setRefundReasonReqd] = useState(false); // ── Promo rules const [promoRules, setPromoRules] = useState([]); const [addForm, setAddForm] = useState(EMPTY_PROMO); const [showAdd, setShowAdd] = useState(false); // ─── Load ──────────────────────────────────────────────────────────────────── useEffect(() => { fetchPlan(planId); setPolicyLoading(true); api.get(`/admin/tier-policies/plans/${planId}/payment-policy`) .then(({ data }) => { const p = data.data; if (p) { setPolicy(p); const rp = p.refund_policy ?? {}; setRefundAllowed(rp.allowed ?? true); setRefundWindowValue(rp.window_value ?? 5); setRefundWindowUnit(rp.window_unit ?? "minutes"); setRefundReasonReqd(rp.reason_required ?? false); setPromoRules(p.promo_rules ?? []); } }) .catch(() => {}) .finally(() => setPolicyLoading(false)); }, [planId]); // ─── Save ──────────────────────────────────────────────────────────────────── const handleSave = async () => { setSaving(true); try { await api.put(`/admin/tier-policies/plans/${planId}/payment-policy`, { refund_policy: { allowed: refundAllowed, window_value: Number(refundWindowValue), window_unit: refundWindowUnit, reason_required: refundReasonReqd, }, promo_rules: promoRules, }); toast("Payment policy saved."); navigate("/admin/tiers/plans"); } catch (err) { toast(err?.response?.data?.message ?? "Could not save payment policy."); } finally { setSaving(false); } }; // ─── Promo helpers ──────────────────────────────────────────────────────────── const handleAddPromo = () => { const code = addForm.code.trim().toUpperCase(); if (!code) { toast("Code is required."); return; } if (!addForm.value || Number(addForm.value) <= 0) { toast("Value must be greater than 0."); return; } if (promoRules.some((r) => r.code.toUpperCase() === code)) { toast("A rule with this code already exists."); return; } const rule = { code, type: addForm.type, value: Number(addForm.value), ...(addForm.type === "flat" && addForm.currency ? { currency: addForm.currency.trim().toUpperCase() } : {}), ...(addForm.type === "percent" && addForm.max_discount ? { max_discount: Number(addForm.max_discount) } : {}), ...(addForm.max_uses ? { max_uses: Number(addForm.max_uses) } : {}), ...(addForm.expires_at ? { expires_at: addForm.expires_at } : {}), ...(addForm.min_amount ? { min_amount: Number(addForm.min_amount) } : {}), }; setPromoRules((prev) => [...prev, rule]); setAddForm(EMPTY_PROMO); setShowAdd(false); }; const handleRemovePromo = (code) => setPromoRules((prev) => prev.filter((r) => r.code !== code)); // ─── Render ─────────────────────────────────────────────────────────────────── const isLoading = planLoading || policyLoading; return (
, to: "/admin" }, { label: "Plans", to: "/admin/tiers/plans" }, { label: plan?.label ?? `Plan #${planId}`, to: `/admin/tiers/plans/${planId}/view` }, { label: "Payment Policy" }, ]} />

Payment Policy

{plan?.tier} — {plan?.label}

{isLoading ? (
{[...Array(3)].map((_, i) => )}
) : (
{/* ── Refund Policy ─────────────────────────────────────────── */}

Allow Refunds

Users can request a refund within the window below.

{refundAllowed && ( <>
setRefundWindowValue(e.target.value)} placeholder="5" />

Users have {refundWindowValue || "?"} {refundWindowUnit} from payment to request a refund.

Require Reason

User must provide a reason when requesting a refund.

)}
{/* ── Promo Codes ───────────────────────────────────────────── */} {/* Existing rules */} {promoRules.length > 0 ? (
{promoRules.map((rule) => (
{rule.code} {rule.type} {rule.type === "flat" ? `${rule.currency ?? "USD"} ${Number(rule.value).toFixed(2)} off` : `${rule.value}% off${rule.max_discount ? ` (max ${rule.max_discount})` : ""}` } {rule.max_uses && ( · {rule.max_uses} uses max )} {rule.expires_at && ( · expires {new Date(rule.expires_at).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })} )}
))}
) : (
No promo codes configured for this plan.
)} {/* Add form */} {showAdd ? (

New Promo Code

setAddForm((f) => ({ ...f, code: e.target.value.toUpperCase() }))} />
setAddForm((f) => ({ ...f, value: e.target.value }))} />
{addForm.type === "flat" && (
{localizedPrices.length === 0 && (

No localized prices set — only base currency available.

)}
)} {addForm.type === "percent" && (
setAddForm((f) => ({ ...f, max_discount: e.target.value }))} />
)}
setAddForm((f) => ({ ...f, max_uses: e.target.value }))} />
setAddForm((f) => ({ ...f, expires_at: iso ?? "" }))} placeholder="No expiry" />
setAddForm((f) => ({ ...f, min_amount: e.target.value }))} />
) : ( )}
{/* ── Save ─────────────────────────────────────────────────── */}
)}
); }