You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
6.7 KiB
TypeScript
199 lines
6.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { listPlans, deletePlan, getEntitlements } from '@/src/lib/api/facility-admin';
|
|
import type { MembershipPlan, PlanTemplate, PlanEntitlements } from '@/src/types/facility-admin';
|
|
import PlanCard from '@/src/components/plans/PlanCard';
|
|
import PlanFormModal from '@/src/components/plans/PlanFormModal';
|
|
import PlanListSkeleton from '@/src/components/plans/PlanListSkeleton';
|
|
import TemplatePicker from '@/src/components/plans/TemplatePicker';
|
|
import EntitlementsConfigModal from '@/src/components/plans/EntitlementsConfigModal';
|
|
|
|
interface MembershipPlansComponentProps {
|
|
facilityId: number;
|
|
}
|
|
|
|
export default function MembershipPlansComponent({ facilityId }: MembershipPlansComponentProps) {
|
|
const [plans, setPlans] = useState<MembershipPlan[]>([]);
|
|
const [entitlementsMap, setEntitlementsMap] = useState<Record<number, PlanEntitlements>>({});
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
|
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
|
const [selectedTemplate, setSelectedTemplate] = useState<PlanTemplate | null>(null);
|
|
const [editingPlan, setEditingPlan] = useState<MembershipPlan | null>(null);
|
|
const [configuringPlan, setConfiguringPlan] = useState<MembershipPlan | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchPlans();
|
|
}, [facilityId]);
|
|
|
|
async function fetchPlans() {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const result = await listPlans(facilityId, { include_inactive: false });
|
|
|
|
if (result.success) {
|
|
setPlans(result.data);
|
|
// Fetch entitlements for all plans in parallel
|
|
await fetchAllEntitlements(result.data);
|
|
} else {
|
|
setError(result.error.detail || 'Failed to load plans');
|
|
}
|
|
|
|
setLoading(false);
|
|
}
|
|
|
|
async function fetchAllEntitlements(plansList: MembershipPlan[]) {
|
|
const entitlementPromises = plansList.map(async (plan) => {
|
|
const result = await getEntitlements(facilityId, plan.facility_membership_plan_id);
|
|
if (result.success) {
|
|
return { planId: plan.facility_membership_plan_id, entitlements: result.data };
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const results = await Promise.all(entitlementPromises);
|
|
const newEntitlementsMap: Record<number, PlanEntitlements> = {};
|
|
|
|
for (const result of results) {
|
|
if (result) {
|
|
newEntitlementsMap[result.planId] = result.entitlements;
|
|
}
|
|
}
|
|
|
|
setEntitlementsMap(newEntitlementsMap);
|
|
}
|
|
|
|
async function handleDelete(planId: number, planName: string) {
|
|
if (!confirm(`Are you sure you want to deactivate "${planName}"?`)) return;
|
|
|
|
const result = await deletePlan(facilityId, planId);
|
|
|
|
if (result.success) {
|
|
fetchPlans();
|
|
} else {
|
|
alert(result.error.detail || 'Failed to delete plan');
|
|
}
|
|
}
|
|
|
|
function handleCreateClick() {
|
|
// Open template picker first
|
|
setTemplatePickerOpen(true);
|
|
}
|
|
|
|
function handleTemplateSelect(template: PlanTemplate | null) {
|
|
setSelectedTemplate(template);
|
|
setTemplatePickerOpen(false);
|
|
setCreateModalOpen(true);
|
|
}
|
|
|
|
function handleCreateModalClose() {
|
|
setCreateModalOpen(false);
|
|
setSelectedTemplate(null);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{/* Header */}
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-900">Membership Plans</h1>
|
|
<p className="text-slate-600 mt-1">Create and manage subscription plans for your facility</p>
|
|
</div>
|
|
<button
|
|
onClick={handleCreateClick}
|
|
className="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md transition-all duration-200"
|
|
>
|
|
+ Create Plan
|
|
</button>
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-6">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Plans Grid */}
|
|
{loading ? (
|
|
<PlanListSkeleton count={3} />
|
|
) : plans.length === 0 ? (
|
|
<div className="text-center py-16">
|
|
<div className="text-6xl mb-4">📋</div>
|
|
<h2 className="text-2xl font-semibold text-slate-900 mb-2">No membership plans yet</h2>
|
|
<p className="text-slate-600 mb-6">Create your first plan to get started</p>
|
|
<button
|
|
onClick={handleCreateClick}
|
|
className="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white px-6 py-3 rounded-lg font-semibold shadow-md transition-all duration-200"
|
|
>
|
|
+ Create Your First Plan
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{plans.map(plan => (
|
|
<PlanCard
|
|
key={plan.facility_membership_plan_id}
|
|
plan={plan}
|
|
entitlements={entitlementsMap[plan.facility_membership_plan_id]}
|
|
onEdit={() => setEditingPlan(plan)}
|
|
onDelete={() => handleDelete(plan.facility_membership_plan_id, plan.name)}
|
|
onConfigureEntitlements={() => setConfiguringPlan(plan)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Template Picker Modal */}
|
|
<TemplatePicker
|
|
isOpen={templatePickerOpen}
|
|
onClose={() => setTemplatePickerOpen(false)}
|
|
onSelect={handleTemplateSelect}
|
|
/>
|
|
|
|
{/* Create Modal */}
|
|
{createModalOpen && (
|
|
<PlanFormModal
|
|
isOpen={createModalOpen}
|
|
onClose={handleCreateModalClose}
|
|
facilityId={facilityId}
|
|
initialValues={selectedTemplate ? {
|
|
name: selectedTemplate.name,
|
|
billing_period: selectedTemplate.billing_period,
|
|
price_cents: selectedTemplate.suggested_price_cents,
|
|
sport_id: selectedTemplate.sport_id,
|
|
is_active: true
|
|
} : undefined}
|
|
templateName={selectedTemplate?.name}
|
|
onSuccess={fetchPlans}
|
|
/>
|
|
)}
|
|
|
|
{/* Edit Modal */}
|
|
{editingPlan && (
|
|
<PlanFormModal
|
|
isOpen={!!editingPlan}
|
|
onClose={() => setEditingPlan(null)}
|
|
facilityId={facilityId}
|
|
plan={editingPlan}
|
|
onSuccess={fetchPlans}
|
|
/>
|
|
)}
|
|
|
|
{/* Entitlements Config Modal */}
|
|
{configuringPlan && (
|
|
<EntitlementsConfigModal
|
|
isOpen={!!configuringPlan}
|
|
onClose={() => setConfiguringPlan(null)}
|
|
facilityId={facilityId}
|
|
plan={configuringPlan}
|
|
onSuccess={fetchPlans}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|