'use client' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { Bookmark, BookmarkPlus, Clock, Trash2 } from 'lucide-react' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Textarea } from '@/components/ui/textarea' import { formatTime } from '@/lib/utils' export type LessonBookmark = { id: string lessonId: string courseId: string moduleId: string position: number note: string createdAt: string updatedAt: string lesson: { id: string title: string slug: string } } interface LessonBookmarksProps { lessonId: string courseId: string moduleId: string currentTime: number onJumpToTime: (time: number) => void isVideo?: boolean } function formatSavedAt(isoDate: string) { const date = new Date(isoDate) return date.toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', }) } export function LessonBookmarks({ lessonId, courseId, moduleId, currentTime, onJumpToTime, isVideo = true, }: LessonBookmarksProps) { const [bookmarks, setBookmarks] = useState([]) const [note, setNote] = useState('') const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [deletingId, setDeletingId] = useState(null) const sortedBookmarks = useMemo( () => [...bookmarks].sort((a, b) => a.position - b.position || a.createdAt.localeCompare(b.createdAt)), [bookmarks] ) const fetchBookmarks = useCallback(async () => { setLoading(true) setError(null) try { const response = await fetch('/api/bookmarks?lessonId=' + lessonId) const data = await response.json() if (!response.ok) { throw new Error(data?.error || 'Failed to fetch bookmarks') } setBookmarks(data.items || []) } catch (fetchError) { console.error('Failed to fetch bookmarks:', fetchError) setError('Could not load bookmarks. The notebook page got smudged.') } finally { setLoading(false) } }, [lessonId]) useEffect(() => { fetchBookmarks() }, [fetchBookmarks]) const handleSave = async () => { if (!isVideo) return setSaving(true) setError(null) try { const response = await fetch('/api/bookmarks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lessonId, courseId, moduleId, position: Math.max(0, Math.floor(currentTime)), note, }), }) const data = await response.json() if (!response.ok) { throw new Error(data?.error || 'Failed to save bookmark') } setBookmarks(prev => [data.bookmark, ...prev]) setNote('') } catch (saveError) { console.error('Failed to create bookmark:', saveError) setError('Could not save bookmark. The pen ran out of cosmic ink.') } finally { setSaving(false) } } const handleDelete = async (bookmarkId: string) => { setDeletingId(bookmarkId) setError(null) try { const response = await fetch('/api/bookmarks/' + bookmarkId, { method: 'DELETE' }) const data = await response.json() if (!response.ok) { throw new Error(data?.error || 'Failed to delete bookmark') } setBookmarks(prev => prev.filter(bookmark => bookmark.id !== bookmarkId)) } catch (deleteError) { console.error('Failed to delete bookmark:', deleteError) setError('Could not delete bookmark. The eraser bounced off the desk.') } finally { setDeletingId(null) } } return ( Lesson Bookmarks Save a timestamp and note for this lesson so you can jump back to the exact spot later. {!isVideo && (
Time-based bookmarks are available on video lessons.
)} {isVideo && (
Current time: {formatTime(Math.max(0, Math.floor(currentTime)))}