Add product-level video source toggle and improve curriculum UX

- Add video source toggle UI (YouTube/Embed) to product edit form for bootcamps
- Remove Bootcamp menu from admin navigation (curriculum managed via Products page)
- Remove tabs from product add/edit modal (simplified to single form)
- Improve ProductCurriculum layout from 3-column (3|5|4) to 2-column (4|8)
- Modules and lessons now in left sidebar with accordion-style expansion
- Lesson editor takes 67% width instead of 33% for better content editing UX
- Add helpful tip about configuring both video sources for redundancy

🤖 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-30 21:04:40 +07:00
parent da71acb431
commit 94aca1edec
3 changed files with 343 additions and 290 deletions

View File

@@ -12,13 +12,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Switch } from '@/components/ui/switch';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { toast } from '@/hooks/use-toast';
import { Skeleton } from '@/components/ui/skeleton';
import { Plus, Pencil, Trash2, Search, X, BookOpen } from 'lucide-react';
import { CurriculumEditor } from '@/components/admin/CurriculumEditor';
import { RichTextEditor } from '@/components/RichTextEditor';
import { formatIDR } from '@/lib/format';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Info } from 'lucide-react';
interface Product {
id: string;
@@ -34,6 +35,7 @@ interface Product {
price: number;
sale_price: number | null;
is_active: boolean;
video_source?: string;
}
const emptyProduct = {
@@ -49,6 +51,7 @@ const emptyProduct = {
price: 0,
sale_price: null as number | null,
is_active: true,
video_source: 'youtube' as string,
};
export default function AdminProducts() {
@@ -60,7 +63,6 @@ export default function AdminProducts() {
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
const [form, setForm] = useState(emptyProduct);
const [saving, setSaving] = useState(false);
const [activeTab, setActiveTab] = useState('details');
const [searchQuery, setSearchQuery] = useState('');
const [filterType, setFilterType] = useState<string>('all');
const [filterStatus, setFilterStatus] = useState<string>('all');
@@ -116,15 +118,14 @@ export default function AdminProducts() {
price: product.price,
sale_price: product.sale_price,
is_active: product.is_active,
video_source: product.video_source || 'youtube',
});
setActiveTab('details');
setDialogOpen(true);
};
const handleNew = () => {
setEditingProduct(null);
setForm(emptyProduct);
setActiveTab('details');
setDialogOpen(true);
};
@@ -147,6 +148,7 @@ export default function AdminProducts() {
price: form.price,
sale_price: form.sale_price || null,
is_active: form.is_active,
video_source: form.video_source || 'youtube',
};
if (editingProduct) {
@@ -420,97 +422,126 @@ export default function AdminProducts() {
<DialogHeader>
<DialogTitle>{editingProduct ? 'Edit Produk' : 'Produk Baru'}</DialogTitle>
</DialogHeader>
<Tabs value={activeTab} onValueChange={setActiveTab} className="mt-4">
<TabsList className="border-2 border-border">
<TabsTrigger value="details">Detail</TabsTrigger>
{editingProduct && form.type === 'bootcamp' && <TabsTrigger value="curriculum">Kurikulum</TabsTrigger>}
</TabsList>
<TabsContent value="details" className="space-y-4 py-4">
<div className="space-y-4 py-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Judul *</Label>
<Input value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value, slug: generateSlug(e.target.value) })} className="border-2" />
</div>
<div className="space-y-2">
<Label>Slug *</Label>
<Input value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} className="border-2" />
</div>
</div>
<div className="space-y-2">
<Label>Tipe</Label>
<Select value={form.type} onValueChange={(v) => setForm({ ...form, type: v })}>
<SelectTrigger className="border-2"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="webinar">Webinar</SelectItem>
<SelectItem value="bootcamp">Bootcamp</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Deskripsi</Label>
<RichTextEditor content={form.description} onChange={(v) => setForm({ ...form, description: v })} />
</div>
<div className="space-y-2">
<Label>Konten</Label>
<RichTextEditor content={form.content} onChange={(v) => setForm({ ...form, content: v })} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Meeting Link</Label>
<Input value={form.meeting_link} onChange={(e) => setForm({ ...form, meeting_link: e.target.value })} placeholder="https://meet.google.com/..." className="border-2" />
</div>
<div className="space-y-2">
<Label>Recording URL</Label>
<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>Judul *</Label>
<Input value={form.title} onChange={(e) => setForm({ ...form, title: e.target.value, slug: generateSlug(e.target.value) })} className="border-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>Slug *</Label>
<Input value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} className="border-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="space-y-2">
<Label>Tipe</Label>
<Select value={form.type} onValueChange={(v) => setForm({ ...form, type: v })}>
<SelectTrigger className="border-2"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="webinar">Webinar</SelectItem>
<SelectItem value="bootcamp">Bootcamp</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Deskripsi</Label>
<RichTextEditor content={form.description} onChange={(v) => setForm({ ...form, description: v })} />
</div>
<div className="space-y-2">
<Label>Konten</Label>
<RichTextEditor content={form.content} onChange={(v) => setForm({ ...form, content: v })} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Meeting Link</Label>
<Input value={form.meeting_link} onChange={(e) => setForm({ ...form, meeting_link: e.target.value })} placeholder="https://meet.google.com/..." className="border-2" />
</div>
<div className="space-y-2">
<Label>Recording URL</Label>
<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>
<Input type="number" value={form.price} onChange={(e) => setForm({ ...form, price: parseFloat(e.target.value) || 0 })} className="border-2" />
</div>
<div className="space-y-2">
<Label>Harga Promo</Label>
<Input type="number" value={form.sale_price || ''} onChange={(e) => setForm({ ...form, sale_price: e.target.value ? parseFloat(e.target.value) : null })} placeholder="Kosongkan jika tidak promo" className="border-2" />
</div>
</div>
<div className="flex items-center gap-2">
<Switch checked={form.is_active} onCheckedChange={(checked) => setForm({ ...form, is_active: checked })} />
<Label>Aktif</Label>
</div>
<Button onClick={handleSave} className="w-full shadow-sm" disabled={saving}>
{saving ? 'Menyimpan...' : 'Simpan Produk'}
</Button>
</TabsContent>
{editingProduct && form.type === 'bootcamp' && (
<TabsContent value="curriculum" className="py-4">
<CurriculumEditor productId={editingProduct.id} />
</TabsContent>
)}
</Tabs>
{form.type === 'bootcamp' && (
<div className="space-y-4">
<Label className="text-base font-semibold">Video Source Settings</Label>
<RadioGroup
value={form.video_source || 'youtube'}
onValueChange={(value) => setForm({ ...form, video_source: value })}
>
<div className="flex items-center space-x-2 p-3 border-2 border-border rounded-lg">
<RadioGroupItem value="youtube" id="youtube" />
<div className="flex-1">
<Label htmlFor="youtube" className="font-medium cursor-pointer">
YouTube (Primary)
</Label>
<p className="text-sm text-muted-foreground">
Use YouTube URLs for all lessons
</p>
</div>
</div>
<div className="flex items-center space-x-2 p-3 border-2 border-border rounded-lg">
<RadioGroupItem value="embed" id="embed" />
<div className="flex-1">
<Label htmlFor="embed" className="font-medium cursor-pointer">
Custom Embed (Backup)
</Label>
<p className="text-sm text-muted-foreground">
Use custom embed codes (Adilo, Vimeo, etc.) for all lessons
</p>
</div>
</div>
</RadioGroup>
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
This setting affects ALL lessons in this bootcamp. Configure both YouTube URLs and embed codes for each lesson in the curriculum editor. Use this toggle to switch between sources instantly.
</AlertDescription>
</Alert>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Harga *</Label>
<Input type="number" value={form.price} onChange={(e) => setForm({ ...form, price: parseFloat(e.target.value) || 0 })} className="border-2" />
</div>
<div className="space-y-2">
<Label>Harga Promo</Label>
<Input type="number" value={form.sale_price || ''} onChange={(e) => setForm({ ...form, sale_price: e.target.value ? parseFloat(e.target.value) : null })} placeholder="Kosongkan jika tidak promo" className="border-2" />
</div>
</div>
<div className="flex items-center gap-2">
<Switch checked={form.is_active} onCheckedChange={(checked) => setForm({ ...form, is_active: checked })} />
<Label>Aktif</Label>
</div>
<Button onClick={handleSave} className="w-full shadow-sm" disabled={saving}>
{saving ? 'Menyimpan...' : 'Simpan Produk'}
</Button>
</div>
</DialogContent>
</Dialog>
</div>

View File

@@ -314,226 +314,258 @@ export default function ProductCurriculum() {
</div>
<div className="grid grid-cols-12 gap-6">
{/* Left: Modules List (3 columns) */}
<div className="col-span-3">
{/* Left Sidebar: Modules & Lessons (4 columns) */}
<div className="col-span-4 space-y-6">
{/* Modules Card */}
<Card>
<CardHeader>
<CardTitle className="text-base">Modules</CardTitle>
<Button size="sm" onClick={handleAddModule} className="w-full">
<Plus className="w-4 h-4 mr-2" />
Add Module
</Button>
<div className="flex items-center justify-between">
<CardTitle className="text-base">Modules</CardTitle>
<Button size="sm" onClick={handleAddModule}>
<Plus className="w-4 h-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-2">
{modules.map((module, index) => {
const moduleLessons = getLessonsForModule(module.id);
const isSelected = selectedModuleId === module.id;
const isExpanded = expandedModules.has(module.id);
return (
<div
key={module.id}
className={cn(
"p-3 border rounded cursor-pointer transition-colors group",
isSelected ? "border-primary bg-primary/5" : "hover:bg-gray-50 border-border"
<div key={module.id} className="border-2 border-border rounded-lg overflow-hidden">
{/* Module Header */}
<div
className={cn(
"p-3 cursor-pointer transition-colors",
isSelected ? "bg-primary/10" : "bg-muted hover:bg-muted/80"
)}
onClick={() => {
setSelectedModuleId(module.id);
toggleModule(module.id);
}}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 flex-1 min-w-0">
<GripVertical className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="font-medium truncate">{module.title}</span>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation();
moveModule(module.id, 'up');
}}
disabled={index === 0}
>
<ChevronUp className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation();
moveModule(module.id, 'down');
}}
disabled={index === modules.length - 1}
>
<ChevronDown className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation();
handleEditModule(module);
}}
>
<Pencil className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation();
handleDeleteModule(module.id);
}}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
<div className="text-xs text-muted-foreground mt-1 pl-6">
{moduleLessons.length} lesson{moduleLessons.length !== 1 ? 's' : ''}
</div>
</div>
{/* Lessons List (expanded) */}
{isExpanded && (
<div className="border-t-2 border-border bg-card">
<div className="p-2 space-y-1">
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation();
handleAddLesson(module.id);
}}
className="w-full border-dashed text-xs"
>
<Plus className="w-3 h-3 mr-1" />
Add Lesson
</Button>
{moduleLessons.map((lesson, lessonIndex) => {
const isLessonSelected = selectedLessonId === lesson.id;
return (
<div
key={lesson.id}
className={cn(
"p-2 rounded cursor-pointer transition-colors group",
isLessonSelected ? "bg-primary/20 border border-primary" : "hover:bg-muted"
)}
onClick={(e) => {
e.stopPropagation();
handleEditLesson(lesson);
}}
>
<div className="flex items-center justify-between gap-2">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{lesson.title}</p>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-xs text-muted-foreground">
{lessonIndex + 1}.
</span>
{lesson.youtube_url && (
<span className="text-xs text-blue-600">YouTube</span>
)}
{lesson.embed_code && (
<span className="text-xs text-purple-600">Embed</span>
)}
{lesson.content && (
<span className="text-xs text-muted-foreground"> Content</span>
)}
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0"
onClick={(e) => {
e.stopPropagation();
moveLesson(lesson.id, 'up');
}}
disabled={lessonIndex === 0}
>
<ChevronUp className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0"
onClick={(e) => {
e.stopPropagation();
moveLesson(lesson.id, 'down');
}}
disabled={lessonIndex === moduleLessons.length - 1}
>
<ChevronDown className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0"
onClick={(e) => {
e.stopPropagation();
handleDeleteLesson(lesson.id);
}}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
</div>
);
})}
{moduleLessons.length === 0 && (
<div className="text-center text-xs text-muted-foreground py-2">
No lessons yet
</div>
)}
</div>
</div>
)}
>
<div className="flex items-center justify-between">
<div
className="flex items-center gap-2 flex-1 min-w-0"
onClick={() => {
setSelectedModuleId(module.id);
if (expandedModules.has(module.id)) {
toggleModule(module.id);
} else {
const newExpanded = new Set(expandedModules);
newExpanded.add(module.id);
setExpandedModules(newExpanded);
}
}}
>
<GripVertical className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="font-medium truncate">{module.title}</span>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => moveModule(module.id, 'up')}
disabled={index === 0}
>
<ChevronUp className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => moveModule(module.id, 'down')}
disabled={index === modules.length - 1}
>
<ChevronDown className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => handleEditModule(module)}
>
<Pencil className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => handleDeleteModule(module.id)}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
<div className="text-xs text-muted-foreground mt-1 pl-6">
{moduleLessons.length} lesson{moduleLessons.length !== 1 ? 's' : ''}
</div>
</div>
);
})}
{modules.length === 0 && (
<div className="text-center text-sm text-muted-foreground py-4">
No modules yet
No modules yet. Click + to create one.
</div>
)}
</CardContent>
</Card>
</div>
{/* Middle: Lessons List (5 columns) */}
<div className="col-span-5">
<Card>
<CardHeader>
<CardTitle className="text-base">Lessons</CardTitle>
{selectedModuleId && (
<Button size="sm" onClick={() => handleAddLesson(selectedModuleId)} className="w-full">
<Plus className="w-4 h-4 mr-2" />
Add Lesson
</Button>
)}
</CardHeader>
<CardContent>
{!selectedModuleId ? (
<p className="text-muted-foreground text-center py-8 text-sm">
Select a module to view lessons
</p>
) : (
<div className="space-y-2">
{getLessonsForModule(selectedModuleId).map((lesson, index) => {
const isSelected = selectedLessonId === lesson.id;
return (
<div
key={lesson.id}
className={cn(
"p-3 border rounded cursor-pointer transition-colors group",
isSelected ? "border-primary bg-primary/5" : "hover:bg-gray-50 border-border"
)}
>
<div className="flex items-center justify-between">
<div
className="flex-1 min-w-0"
onClick={() => handleEditLesson(lesson)}
>
<p className="font-medium text-sm truncate">{lesson.title}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-muted-foreground">
{index + 1}. {lesson.video_url || lesson.youtube_url || lesson.embed_code ? '✓ Video' : 'No video'}
</span>
{lesson.youtube_url && (
<span className="text-xs text-blue-600">YouTube</span>
)}
{lesson.embed_code && (
<span className="text-xs text-purple-600">Embed</span>
)}
{lesson.content && (
<span className="text-xs text-muted-foreground"> Content</span>
)}
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => moveLesson(lesson.id, 'up')}
disabled={index === 0}
>
<ChevronUp className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => moveLesson(lesson.id, 'down')}
disabled={index === getLessonsForModule(selectedModuleId).length - 1}
>
<ChevronDown className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={() => handleDeleteLesson(lesson.id)}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
</div>
);
})}
{getLessonsForModule(selectedModuleId).length === 0 && (
<div className="text-center text-sm text-muted-foreground py-4">
No lessons yet
</div>
)}
</div>
)}
</CardContent>
</Card>
</div>
{/* Right: Lesson Editor (4 columns) */}
<div className="col-span-4">
{/* Right: Lesson Editor (8 columns - full width for better UX) */}
<div className="col-span-8">
<Card className="sticky top-4">
<CardHeader>
<CardTitle className="text-base">Lesson Editor</CardTitle>
<CardTitle>
{selectedLessonId === 'new' ? 'New Lesson' : editingLesson ? 'Edit Lesson' : 'Lesson Editor'}
</CardTitle>
</CardHeader>
<CardContent>
{!selectedLessonId ? (
<p className="text-muted-foreground text-center py-8 text-sm">
Select a lesson to edit
</p>
<div className="text-center py-16">
<p className="text-muted-foreground mb-4">
Select or create a lesson to start editing
</p>
<p className="text-sm text-muted-foreground">
Click on a module to expand it, then click "Add Lesson" or select an existing lesson.
</p>
</div>
) : (
<div className="space-y-4">
<div className="space-y-6">
<div className="space-y-2">
<Label>Title *</Label>
<Input
value={lessonForm.title}
onChange={(e) => setLessonForm({ ...lessonForm, title: e.target.value })}
placeholder="Lesson title"
className="border-2"
className="border-2 text-base"
/>
</div>
<div className="space-y-2">
<Label>YouTube URL (Primary)</Label>
<Input
value={lessonForm.youtube_url}
onChange={(e) => setLessonForm({ ...lessonForm, youtube_url: e.target.value })}
placeholder="https://www.youtube.com/watch?v=..."
className="border-2"
/>
{lessonForm.youtube_url && (
<p className="text-xs text-green-600"> YouTube configured</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>YouTube URL (Primary)</Label>
<Input
value={lessonForm.youtube_url}
onChange={(e) => setLessonForm({ ...lessonForm, youtube_url: e.target.value })}
placeholder="https://www.youtube.com/watch?v=..."
className="border-2"
/>
{lessonForm.youtube_url && (
<p className="text-xs text-green-600"> YouTube configured</p>
)}
</div>
<div className="space-y-2">
<Label>Release Date (optional)</Label>
<Input
type="date"
value={lessonForm.release_at}
onChange={(e) => setLessonForm({ ...lessonForm, release_at: e.target.value })}
className="border-2"
/>
</div>
</div>
<div className="space-y-2">
@@ -550,9 +582,9 @@ export default function ProductCurriculum() {
)}
</div>
<div className="p-3 bg-muted rounded-md">
<p className="text-xs text-muted-foreground">
💡 <strong>Tip:</strong> Configure both YouTube URL and embed code for redundancy. Use product settings to toggle between sources.
<div className="p-4 bg-muted border-2 border-border rounded-lg">
<p className="text-sm text-muted-foreground">
💡 <strong>Tip:</strong> Configure both YouTube URL and embed code for redundancy. Use product settings to toggle between sources. This setting affects ALL lessons in the bootcamp.
</p>
</div>
@@ -562,25 +594,15 @@ export default function ProductCurriculum() {
content={lessonForm.content}
onChange={(html) => setLessonForm({ ...lessonForm, content: html })}
placeholder="Write your lesson content here... Use code blocks for syntax highlighting."
className="min-h-[300px]"
className="min-h-[400px]"
/>
<p className="text-xs text-muted-foreground">
Supports rich text formatting, code blocks with syntax highlighting, images, and more.
</p>
</div>
<div className="space-y-2">
<Label>Release Date (optional)</Label>
<Input
type="date"
value={lessonForm.release_at}
onChange={(e) => setLessonForm({ ...lessonForm, release_at: e.target.value })}
className="border-2"
/>
</div>
<div className="flex gap-2">
<Button onClick={handleSaveLesson} disabled={saving} className="flex-1">
<div className="flex gap-3 pt-4">
<Button onClick={handleSaveLesson} disabled={saving} className="flex-1 shadow-sm" size="lg">
{saving ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
@@ -598,6 +620,7 @@ export default function ProductCurriculum() {
onClick={() => setSelectedLessonId(null)}
disabled={saving}
className="border-2"
size="lg"
>
Cancel
</Button>