Files
yourwillyourwish/components/admin/WebinarModal.tsx
2026-02-06 21:44:04 -06:00

339 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
interface WebinarModalProps {
webinar?: any;
onClose: () => void;
onSave: () => void;
}
export default function WebinarModal({ webinar, onClose, onSave }: WebinarModalProps) {
const isEdit = !!webinar;
const [form, setForm] = useState({
title: "",
description: "",
speaker: "",
startAt: new Date(Date.now() + 86400000).toISOString().slice(0, 16),
duration: 90,
bannerUrl: "",
category: "Basics",
visibility: "PUBLIC",
isActive: true,
capacity: 25,
priceCents: 0,
});
const [learningPoints, setLearningPoints] = useState<string[]>([""]);
const [msg, setMsg] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (webinar) {
setForm({
title: webinar.title || "",
description: webinar.description || "",
speaker: webinar.speaker || "",
startAt: webinar.startAt ? new Date(webinar.startAt).toISOString().slice(0, 16) : "",
duration: webinar.duration || 90,
bannerUrl: webinar.bannerUrl || "",
category: webinar.category || "Basics",
visibility: webinar.visibility || "PUBLIC",
isActive: webinar.isActive ?? true,
capacity: webinar.capacity || 25,
priceCents: webinar.priceCents || 0,
});
// Load learning points if available
if (webinar.learningPoints && Array.isArray(webinar.learningPoints) && webinar.learningPoints.length > 0) {
setLearningPoints(webinar.learningPoints);
} else {
setLearningPoints([""]);
}
}
}, [webinar]);
function addLearningPoint() {
setLearningPoints([...learningPoints, ""]);
}
function removeLearningPoint(index: number) {
if (learningPoints.length > 1) {
setLearningPoints(learningPoints.filter((_, i) => i !== index));
}
}
function updateLearningPoint(index: number, value: string) {
const updated = [...learningPoints];
updated[index] = value;
setLearningPoints(updated);
}
async function handleSubmit() {
setMsg(null);
setLoading(true);
// Validate required fields
if (!form.title.trim()) {
setLoading(false);
setMsg("❌ Title is required");
return;
}
if (!form.description.trim()) {
setLoading(false);
setMsg("❌ Description is required");
return;
}
if (!form.speaker.trim()) {
setLoading(false);
setMsg("❌ Speaker is required");
return;
}
if (form.duration < 15) {
setLoading(false);
setMsg("❌ Duration must be at least 15 minutes");
return;
}
if (form.capacity < 1) {
setLoading(false);
setMsg("❌ Capacity must be at least 1");
return;
}
// Filter out empty learning points
const filteredPoints = learningPoints.filter(p => p.trim() !== "");
const payload = {
...form,
duration: Number(form.duration),
capacity: Number(form.capacity),
priceCents: Number(form.priceCents),
startAt: new Date(form.startAt).toISOString(),
learningPoints: filteredPoints,
};
const res = isEdit
? await fetch(`/api/webinars/${webinar.id}`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
})
: await fetch("/api/webinars", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
const d = await res.json();
setLoading(false);
if (!d.ok) {
setMsg(d.message);
return;
}
setMsg(isEdit ? "Webinar updated!" : "Webinar created!");
setTimeout(() => {
onSave();
onClose();
}, 1000);
}
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2 className="modal-title">
{isEdit ? "✏️ Edit Webinar" : "🎓 Create New Webinar"}
</h2>
<button onClick={onClose} className="modal-close-btn">
</button>
</div>
<div className="modal-body space-y-4">
{msg && (
<div className={`p-4 rounded-xl text-sm font-medium ${msg.includes("!") || msg.includes("✅") ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20" : "bg-red-500/10 text-red-600 dark:text-red-400 border border-red-500/20"}`}>
{msg}
</div>
)}
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">📝 Title *</label>
<input
className="input-field w-full"
placeholder="e.g., Estate Planning Fundamentals"
value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">📄 Description *</label>
<textarea
className="input-field w-full"
rows={4}
placeholder="Describe what participants will learn..."
value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">🎤 Speaker *</label>
<input
className="input-field w-full"
placeholder="e.g., Dr. Jane Smith"
value={form.speaker}
onChange={(e) => setForm({ ...form, speaker: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">🏷 Category *</label>
<select
className="input-field w-full"
value={form.category}
onChange={(e) => setForm({ ...form, category: e.target.value })}
>
<option value="Basics">Basics</option>
<option value="Planning">Planning</option>
<option value="Tax">Tax</option>
<option value="Healthcare">Healthcare</option>
<option value="Advanced">Advanced</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">📅 Start Date & Time *</label>
<input
className="input-field w-full"
type="datetime-local"
value={form.startAt}
onChange={(e) => setForm({ ...form, startAt: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300"> Duration (minutes) *</label>
<input
className="input-field w-full"
type="number"
min="15"
step="15"
value={form.duration}
onChange={(e) => setForm({ ...form, duration: Number(e.target.value) })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">👥 Capacity *</label>
<input
className="input-field w-full"
type="number"
min="1"
value={form.capacity}
onChange={(e) => setForm({ ...form, capacity: Number(e.target.value) })}
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">
💰 Price {form.priceCents === 0 ? <span className="text-green-600 dark:text-green-400">(FREE)</span> : <span className="text-blue-600 dark:text-blue-400">(PAID)</span>}
</label>
<input
className="input-field w-full"
type="number"
min="0"
placeholder="0 for free"
value={form.priceCents}
onChange={(e) => setForm({ ...form, priceCents: Number(e.target.value) })}
/>
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1">
${(form.priceCents / 100).toFixed(2)} {form.priceCents === 0 && "- This webinar will be free"}
</p>
</div>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">🖼 Banner Image URL (optional)</label>
<input
className="input-field w-full"
placeholder="https://example.com/banner.jpg"
value={form.bannerUrl}
onChange={(e) => setForm({ ...form, bannerUrl: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">📚 Learning Points</label>
<div className="space-y-2">
{learningPoints.map((point, index) => (
<div key={index} className="flex gap-2">
<input
className="input-field flex-1"
placeholder={`Learning point ${index + 1}`}
value={point}
onChange={(e) => updateLearningPoint(index, e.target.value)}
/>
<button
type="button"
onClick={() => removeLearningPoint(index)}
className="px-3 py-2 bg-red-500/10 hover:bg-red-500/20 text-red-600 dark:text-red-400 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
disabled={learningPoints.length === 1}
title="Remove point"
>
🗑
</button>
</div>
))}
<button
type="button"
onClick={addLearningPoint}
className="w-full px-4 py-2 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors font-medium text-sm"
>
Add Learning Point
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">👁 Visibility</label>
<select
className="input-field w-full"
value={form.visibility}
onChange={(e) => setForm({ ...form, visibility: e.target.value })}
>
<option value="PUBLIC">Public</option>
<option value="PRIVATE">Private</option>
</select>
</div>
<div className="flex items-end">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="w-5 h-5 rounded border-gray-300 text-primary focus:ring-primary"
checked={form.isActive}
onChange={(e) => setForm({ ...form, isActive: e.target.checked })}
/>
<span className="text-sm font-semibold text-slate-700 dark:text-slate-300"> Active</span>
</label>
</div>
</div>
</div>
<div className="modal-footer">
<button onClick={onClose} className="btn-secondary" disabled={loading}>
Cancel
</button>
<button onClick={handleSubmit} className="btn-primary" disabled={loading}>
{loading ? "⏳ Saving..." : isEdit ? "✅ Update Webinar" : "🎓 Create Webinar"}
</button>
</div>
</div>
</div>
);
}