File size: 6,462 Bytes
dab0626 1b8f2e5 dab0626 c6297a9 dab0626 bbad283 dab0626 c6297a9 dab0626 bbad283 dab0626 c6297a9 dab0626 bbad283 dab0626 9f21447 dab0626 9f21447 dab0626 bbad283 dab0626 1b8f2e5 dab0626 1b8f2e5 dab0626 1b8f2e5 dab0626 1b8f2e5 c6297a9 1b8f2e5 c6297a9 dab0626 1b8f2e5 dab0626 bbad283 1b8f2e5 bbad283 1b8f2e5 dab0626 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | // frontend/src/components/admin/PlanForm.jsx
import React, { useEffect, useState } from "react";
const BILLING_OPTIONS = [
{ value: "monthly", label: "Monthly" },
{ value: "quarterly", label: "Quarterly" },
{ value: "half_yearly", label: "Half Yearly" },
{ value: "annual", label: "Annual" },
];
export default function PlanForm({ plan, onSave, onCancel, isLoading }) {
const [name, setName] = useState("");
const [billingPeriod, setBillingPeriod] = useState("monthly");
const [price, setPrice] = useState("");
const [description, setDescription] = useState("");
const [maxStudents, setMaxStudents] = useState("1");
const [isActive, setIsActive] = useState(true);
const [isDefault, setIsDefault] = useState(false);
useEffect(() => {
if (plan) {
setName(plan.name || "");
setBillingPeriod(plan.billing_period || "monthly");
setPrice(plan.price != null ? String(plan.price) : "");
setDescription(plan.description || "");
setMaxStudents(String(plan.max_students || 1));
setIsActive(plan.is_active ?? true);
setIsDefault(plan.is_default ?? false);
} else {
setName("");
setBillingPeriod("monthly");
setPrice("");
setDescription("");
setMaxStudents("1");
setIsActive(true);
setIsDefault(false);
}
}, [plan]);
function handleSubmit(e) {
e.preventDefault();
const priceNumber = parseFloat(price || "0") || 0;
const maxStudentsNumber = parseInt(maxStudents || "1", 10) || 1;
onSave({
name,
billing_period: billingPeriod,
price: priceNumber,
description: description || null,
max_students: maxStudentsNumber,
is_active: isActive,
is_default: isDefault,
});
}
return (
<form
onSubmit={handleSubmit}
className="bg-stone-50 border border-stone-200 rounded-xl p-4 space-y-4"
>
{/* Top Row: Plan Name, Billing Period on left; Price, Max Students on right */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<div>
<label className="block text-xs font-medium text-stone-700 mb-1">
Plan name
</label>
<input
type="text"
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Beginner Monthly"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<label className="block text-xs font-medium text-stone-700 mb-1">
Billing period
</label>
<select
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
value={billingPeriod}
onChange={(e) => setBillingPeriod(e.target.value)}
>
{BILLING_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>
<div className="space-y-2">
<div>
<label className="block text-xs font-medium text-stone-700 mb-1">
Price (CAD)
</label>
<input
type="number"
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="99"
value={price}
onChange={(e) => setPrice(e.target.value)}
min="0"
step="0.01"
/>
</div>
<div>
<label className="block text-xs font-medium text-stone-700 mb-1">
Max Students
</label>
<input
type="number"
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="1"
value={maxStudents}
onChange={(e) => setMaxStudents(e.target.value)}
min="1"
/>
</div>
</div>
</div>
{/* Description - Full Width */}
<div>
<label className="block text-xs font-medium text-stone-700 mb-1">
Description
</label>
<textarea
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 min-h-[60px]"
placeholder="Beginner classes 2x per week."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
{/* Bottom Row: Checkboxes and Buttons */}
<div className="flex items-center justify-between pt-1">
<div className="flex flex-col gap-2">
<label className="inline-flex items-center gap-2 text-xs text-stone-700">
<input
type="checkbox"
className="rounded border-stone-300"
checked={isActive}
onChange={(e) => setIsActive(e.target.checked)}
/>
Active / visible to students
</label>
<label className="inline-flex items-center gap-2 text-xs text-stone-700">
<input
type="checkbox"
className="rounded border-stone-300"
checked={isDefault}
onChange={(e) => setIsDefault(e.target.checked)}
/>
Default membership plan (max 2)
</label>
</div>
<div className="flex gap-2">
<button
type="button"
onClick={onCancel}
className="px-3 py-1.5 rounded-lg border border-stone-200 text-xs font-medium text-stone-700 hover:bg-stone-100"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading}
className="px-3 py-1.5 rounded-lg bg-indigo-600 text-white text-xs font-medium hover:bg-indigo-700 disabled:opacity-60"
>
{isLoading ? "Saving…" : plan ? "Update plan" : "Create plan"}
</button>
</div>
</div>
</form>
);
}
|