Add Tiptap enhancements and webinar date/time fields

- Add text alignment controls to Tiptap editor (left, center, right, justify)
- Add horizontal rule/spacer button to Tiptap toolbar
- Add event_start and duration_minutes fields to webinar products
- Add webinar status badges (Recording Available, Coming Soon, Ended)
- Install @tiptap/extension-text-align package

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-25 13:41:51 +07:00
parent fa274bd8cc
commit eea3a1f8d8
5 changed files with 125 additions and 4 deletions

View File

@@ -345,8 +345,17 @@ export default function ProductDetail() {
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4 mb-6">
<div>
<h1 className="text-4xl font-bold mb-2">{product.title}</h1>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 flex-wrap">
<Badge className="bg-primary text-primary-foreground capitalize">{product.type}</Badge>
{product.type === 'webinar' && product.recording_url && (
<Badge className="bg-secondary text-primary">Rekaman Tersedia</Badge>
)}
{product.type === 'webinar' && !product.recording_url && product.event_start && new Date(product.event_start) > new Date() && (
<Badge className="bg-brand-accent text-white">Segera Hadir</Badge>
)}
{product.type === 'webinar' && !product.recording_url && product.event_start && new Date(product.event_start) <= new Date() && (
<Badge className="bg-muted text-primary">Telah Selesai</Badge>
)}
{hasAccess && <Badge className="bg-primary text-primary-foreground">Anda memiliki akses</Badge>}
</div>
</div>

View File

@@ -29,6 +29,8 @@ interface Product {
content: string;
meeting_link: string | null;
recording_url: string | null;
event_start: string | null;
duration_minutes: number | null;
price: number;
sale_price: number | null;
is_active: boolean;
@@ -42,6 +44,8 @@ const emptyProduct = {
content: '',
meeting_link: '',
recording_url: '',
event_start: null as string | null,
duration_minutes: null as number | null,
price: 0,
sale_price: null as number | null,
is_active: true,
@@ -84,6 +88,8 @@ export default function AdminProducts() {
content: product.content || '',
meeting_link: product.meeting_link || '',
recording_url: product.recording_url || '',
event_start: product.event_start,
duration_minutes: product.duration_minutes,
price: product.price,
sale_price: product.sale_price,
is_active: product.is_active,
@@ -113,6 +119,8 @@ export default function AdminProducts() {
content: form.content,
meeting_link: form.meeting_link || null,
recording_url: form.recording_url || null,
event_start: form.event_start || null,
duration_minutes: form.duration_minutes || null,
price: form.price,
sale_price: form.sale_price || null,
is_active: form.is_active,
@@ -322,6 +330,29 @@ export default function AdminProducts() {
<Input value={form.recording_url} onChange={(e) => setForm({ ...form, recording_url: e.target.value })} placeholder="https://youtube.com/..." className="border-2" />
</div>
</div>
{form.type === 'webinar' && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Tanggal & Waktu Webinar</Label>
<Input
type="datetime-local"
value={form.event_start || ''}
onChange={(e) => setForm({ ...form, event_start: e.target.value || null })}
className="border-2"
/>
</div>
<div className="space-y-2">
<Label>Durasi (menit)</Label>
<Input
type="number"
value={form.duration_minutes || ''}
onChange={(e) => setForm({ ...form, duration_minutes: e.target.value ? parseInt(e.target.value) : null })}
placeholder="60"
className="border-2"
/>
</div>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Harga *</Label>