Seth0330 commited on
Commit
dab0626
·
verified ·
1 Parent(s): 964fb13

Create frontend/src/components/admin/PlanForm.jsx

Browse files
frontend/src/components/admin/PlanForm.jsx ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // frontend/src/components/admin/PlanForm.jsx
2
+ import React, { useEffect, useState } from "react";
3
+
4
+ const BILLING_OPTIONS = [
5
+ { value: "monthly", label: "Monthly" },
6
+ { value: "quarterly", label: "Quarterly" },
7
+ { value: "annual", label: "Annual" },
8
+ ];
9
+
10
+ export default function PlanForm({ plan, onSave, onCancel, isLoading }) {
11
+ const [name, setName] = useState("");
12
+ const [billingPeriod, setBillingPeriod] = useState("monthly");
13
+ const [price, setPrice] = useState("");
14
+ const [stripeLink, setStripeLink] = useState("");
15
+ const [description, setDescription] = useState("");
16
+ const [isActive, setIsActive] = useState(true);
17
+
18
+ useEffect(() => {
19
+ if (plan) {
20
+ setName(plan.name || "");
21
+ setBillingPeriod(plan.billing_period || "monthly");
22
+ setPrice(plan.price != null ? String(plan.price) : "");
23
+ setStripeLink(plan.stripe_link || "");
24
+ setDescription(plan.description || "");
25
+ setIsActive(plan.is_active ?? true);
26
+ } else {
27
+ setName("");
28
+ setBillingPeriod("monthly");
29
+ setPrice("");
30
+ setStripeLink("");
31
+ setDescription("");
32
+ setIsActive(true);
33
+ }
34
+ }, [plan]);
35
+
36
+ function handleSubmit(e) {
37
+ e.preventDefault();
38
+ const priceNumber = parseFloat(price || "0") || 0;
39
+
40
+ onSave({
41
+ name,
42
+ billing_period: billingPeriod,
43
+ price: priceNumber,
44
+ stripe_link: stripeLink || null,
45
+ description: description || null,
46
+ is_active: isActive,
47
+ });
48
+ }
49
+
50
+ return (
51
+ <form
52
+ onSubmit={handleSubmit}
53
+ className="grid grid-cols-1 md:grid-cols-2 gap-4 bg-stone-50 border border-stone-200 rounded-xl p-4"
54
+ >
55
+ <div className="space-y-2">
56
+ <div>
57
+ <label className="block text-xs font-medium text-stone-700 mb-1">
58
+ Plan name
59
+ </label>
60
+ <input
61
+ type="text"
62
+ 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"
63
+ placeholder="Beginner Monthly"
64
+ value={name}
65
+ onChange={(e) => setName(e.target.value)}
66
+ />
67
+ </div>
68
+
69
+ <div>
70
+ <label className="block text-xs font-medium text-stone-700 mb-1">
71
+ Billing period
72
+ </label>
73
+ <select
74
+ 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"
75
+ value={billingPeriod}
76
+ onChange={(e) => setBillingPeriod(e.target.value)}
77
+ >
78
+ {BILLING_OPTIONS.map((opt) => (
79
+ <option key={opt.value} value={opt.value}>
80
+ {opt.label}
81
+ </option>
82
+ ))}
83
+ </select>
84
+ </div>
85
+
86
+ <div>
87
+ <label className="block text-xs font-medium text-stone-700 mb-1">
88
+ Price (CAD)
89
+ </label>
90
+ <input
91
+ type="number"
92
+ 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"
93
+ placeholder="99"
94
+ value={price}
95
+ onChange={(e) => setPrice(e.target.value)}
96
+ min="0"
97
+ step="0.01"
98
+ />
99
+ </div>
100
+ </div>
101
+
102
+ <div className="space-y-2">
103
+ <div>
104
+ <label className="block text-xs font-medium text-stone-700 mb-1">
105
+ Stripe payment link
106
+ </label>
107
+ <input
108
+ type="url"
109
+ 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"
110
+ placeholder="https://buy.stripe.com/..."
111
+ value={stripeLink}
112
+ onChange={(e) => setStripeLink(e.target.value)}
113
+ />
114
+ <p className="text-[11px] text-stone-500 mt-1">
115
+ Paste the Stripe Checkout link for this plan.
116
+ </p>
117
+ </div>
118
+
119
+ <div>
120
+ <label className="block text-xs font-medium text-stone-700 mb-1">
121
+ Description
122
+ </label>
123
+ <textarea
124
+ 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]"
125
+ placeholder="Beginner classes 2x per week."
126
+ value={description}
127
+ onChange={(e) => setDescription(e.target.value)}
128
+ />
129
+ </div>
130
+
131
+ <div className="flex items-center justify-between pt-1">
132
+ <label className="inline-flex items-center gap-2 text-xs text-stone-700">
133
+ <input
134
+ type="checkbox"
135
+ className="rounded border-stone-300"
136
+ checked={isActive}
137
+ onChange={(e) => setIsActive(e.target.checked)}
138
+ />
139
+ Active / visible to students
140
+ </label>
141
+ <div className="flex gap-2">
142
+ <button
143
+ type="button"
144
+ onClick={onCancel}
145
+ className="px-3 py-1.5 rounded-lg border border-stone-200 text-xs font-medium text-stone-700 hover:bg-stone-100"
146
+ >
147
+ Cancel
148
+ </button>
149
+ <button
150
+ type="submit"
151
+ disabled={isLoading}
152
+ className="px-3 py-1.5 rounded-lg bg-indigo-600 text-white text-xs font-medium hover:bg-indigo-700 disabled:opacity-60"
153
+ >
154
+ {isLoading ? "Saving…" : plan ? "Update plan" : "Create plan"}
155
+ </button>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </form>
160
+ );
161
+ }