Create admin/MembershipInviteForm.jsx
Browse files
frontend/src/components/admin/MembershipInviteForm.jsx
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// frontend/src/components/admin/MembershipInviteForm.jsx
|
| 2 |
+
import React, { useState } from "react";
|
| 3 |
+
import { Send, X } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export default function MembershipInviteForm({
|
| 6 |
+
plans,
|
| 7 |
+
onSend,
|
| 8 |
+
onCancel,
|
| 9 |
+
isLoading,
|
| 10 |
+
}) {
|
| 11 |
+
const [email, setEmail] = useState("parent@email.com");
|
| 12 |
+
const [selectedPlanId, setSelectedPlanId] = useState("");
|
| 13 |
+
const [classDetails, setClassDetails] = useState("");
|
| 14 |
+
|
| 15 |
+
const selectedPlan = plans.find((p) => p.id === parseInt(selectedPlanId));
|
| 16 |
+
|
| 17 |
+
const handleSubmit = (e) => {
|
| 18 |
+
e.preventDefault();
|
| 19 |
+
if (!email.trim() || !selectedPlanId) {
|
| 20 |
+
return;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
onSend({
|
| 24 |
+
email: email.trim(),
|
| 25 |
+
plan_id: parseInt(selectedPlanId),
|
| 26 |
+
class_details: classDetails.trim(),
|
| 27 |
+
invited_by: "admin",
|
| 28 |
+
});
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
return (
|
| 32 |
+
<form onSubmit={handleSubmit} className="space-y-4">
|
| 33 |
+
<div>
|
| 34 |
+
<label className="block text-sm font-medium text-stone-700 mb-1.5">
|
| 35 |
+
Email Address
|
| 36 |
+
</label>
|
| 37 |
+
<input
|
| 38 |
+
type="email"
|
| 39 |
+
required
|
| 40 |
+
value={email}
|
| 41 |
+
onChange={(e) => setEmail(e.target.value)}
|
| 42 |
+
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
|
| 43 |
+
placeholder="parent@email.com"
|
| 44 |
+
/>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<div>
|
| 48 |
+
<label className="block text-sm font-medium text-stone-700 mb-1.5">
|
| 49 |
+
Membership Plan
|
| 50 |
+
</label>
|
| 51 |
+
<select
|
| 52 |
+
required
|
| 53 |
+
value={selectedPlanId}
|
| 54 |
+
onChange={(e) => setSelectedPlanId(e.target.value)}
|
| 55 |
+
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
|
| 56 |
+
>
|
| 57 |
+
<option value="">Select a plan...</option>
|
| 58 |
+
{plans
|
| 59 |
+
.filter((p) => p.is_active)
|
| 60 |
+
.map((plan) => (
|
| 61 |
+
<option key={plan.id} value={plan.id}>
|
| 62 |
+
{plan.name} - ${plan.price}/{plan.billing_period} (Up to{" "}
|
| 63 |
+
{plan.max_students || 1} student{plan.max_students !== 1 ? "s" : ""})
|
| 64 |
+
</option>
|
| 65 |
+
))}
|
| 66 |
+
</select>
|
| 67 |
+
</div>
|
| 68 |
+
|
| 69 |
+
<div>
|
| 70 |
+
<label className="block text-sm font-medium text-stone-700 mb-1.5">
|
| 71 |
+
Class Information (shown to member)
|
| 72 |
+
</label>
|
| 73 |
+
<textarea
|
| 74 |
+
value={classDetails}
|
| 75 |
+
onChange={(e) => setClassDetails(e.target.value)}
|
| 76 |
+
rows={4}
|
| 77 |
+
className="w-full rounded-lg border border-stone-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
|
| 78 |
+
placeholder="e.g., Beginner Karate - Mon & Wed 4:00 PM, Advanced Karate - Fri 5:00 PM"
|
| 79 |
+
/>
|
| 80 |
+
<p className="text-xs text-stone-500 mt-1">
|
| 81 |
+
Describe the classes included so the member knows what they're signing
|
| 82 |
+
up for
|
| 83 |
+
</p>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div className="flex items-center justify-end gap-2 pt-2">
|
| 87 |
+
<button
|
| 88 |
+
type="button"
|
| 89 |
+
onClick={onCancel}
|
| 90 |
+
disabled={isLoading}
|
| 91 |
+
className="inline-flex items-center gap-2 rounded-lg border border-stone-200 px-4 py-2 text-sm font-medium text-stone-700 hover:bg-stone-50 disabled:opacity-50"
|
| 92 |
+
>
|
| 93 |
+
<X className="w-4 h-4" />
|
| 94 |
+
Cancel
|
| 95 |
+
</button>
|
| 96 |
+
<button
|
| 97 |
+
type="submit"
|
| 98 |
+
disabled={isLoading || !email.trim() || !selectedPlanId}
|
| 99 |
+
className="inline-flex items-center gap-2 rounded-lg bg-red-600 hover:bg-red-700 text-white text-sm font-medium px-4 py-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
| 100 |
+
>
|
| 101 |
+
<Send className="w-4 h-4" />
|
| 102 |
+
{isLoading ? "Sending..." : "Send Invite"}
|
| 103 |
+
</button>
|
| 104 |
+
</div>
|
| 105 |
+
</form>
|
| 106 |
+
);
|
| 107 |
+
}
|
| 108 |
+
|