| | |
| | 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> |
| | ); |
| | } |
| |
|