import { useEffect, useMemo, useState } from 'react'
import { listLessons, listResources, updateResourceLinks } from '../api'
function formatDateHeader(dateStr) {
if (!dateStr) return 'Undated'
const d = new Date(`${dateStr}T00:00:00`)
if (Number.isNaN(d.getTime())) return dateStr
return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })
}
function groupByDate(lessons) {
const groups = {}
for (const l of lessons) {
const key = l.date || 'Undated'
;(groups[key] ||= []).push(l)
}
return Object.entries(groups).sort((a, b) => (a[0] < b[0] ? 1 : -1))
}
function groupByCategory(lessons) {
const groups = {}
for (const l of lessons) {
const key = l.category || 'General'
;(groups[key] ||= []).push(l)
}
return Object.entries(groups).sort((a, b) => a[0].localeCompare(b[0]))
}
function LessonCard({ lesson, onOpen }) {
return (
onOpen(lesson.id)}>
{lesson.title || 'Untitled'}
{lesson.date}
{lesson.category && {lesson.category} }
{lesson.preview && {lesson.preview}
}
)
}
function LessonGroups({ groups, onOpen, openFirst }) {
return groups.map(([label, items], i) => (
{label} ({items.length})
{items.map((lesson) => (
))}
))
}
function domainOf(url) {
try {
const host = new URL(url).hostname
return host.startsWith('www.') ? host.slice(4) : host
} catch {
return url
}
}
function Resources() {
const [resources, setResources] = useState(null)
const [error, setError] = useState('')
const [editingId, setEditingId] = useState(null)
const [draftLinks, setDraftLinks] = useState([])
const [saving, setSaving] = useState(false)
const [saveError, setSaveError] = useState('')
useEffect(() => {
listResources()
.then((data) => setResources(data.resources))
.catch((e) => setError(e.message))
}, [])
if (error) return
Could not load resources: {error}
if (resources === null) return Loading resources…
if (resources.length === 0) {
return (
📚 No resources yet. Save a page that's mostly links or book recommendations
(e.g. "Online Resources", "Books to Read") and it'll show up here, beautifully
laid out and out of your lecture notes.
)
}
const startEdit = (page) => {
setEditingId(page.id)
setDraftLinks(page.links.map((l) => ({ url: l.url, label: l.label })))
setSaveError('')
}
const cancelEdit = () => {
setEditingId(null)
setDraftLinks([])
setSaveError('')
}
const updateDraft = (i, field, value) => {
setDraftLinks((prev) => prev.map((l, idx) => (idx === i ? { ...l, [field]: value } : l)))
}
const removeDraft = (i) => {
setDraftLinks((prev) => prev.filter((_, idx) => idx !== i))
}
const addDraft = () => {
setDraftLinks((prev) => [...prev, { url: '', label: '' }])
}
const saveEdit = async (pageId) => {
setSaving(true)
setSaveError('')
try {
const { links } = await updateResourceLinks(pageId, draftLinks)
setResources((prev) => prev.map((p) => (p.id === pageId ? { ...p, links } : p)))
setEditingId(null)
setDraftLinks([])
} catch (e) {
setSaveError(e.message)
} finally {
setSaving(false)
}
}
return (
{resources.map((page) => (
📚 {page.title}
{editingId === page.id ? (
saveEdit(page.id)} disabled={saving}>
{saving ? 'Saving…' : '💾 Save'}
Cancel
) : (
startEdit(page)}>✏️ Edit links
)}
{editingId === page.id ? (
{draftLinks.map((link, i) => (
updateDraft(i, 'label', e.target.value)}
/>
updateDraft(i, 'url', e.target.value)}
/>
removeDraft(i)}
title="Remove this link"
>
🗑️
))}
+ Add link
{saveError &&
⚠ Could not save: {saveError}
}
) : (
page.links.length > 0 && (
)
)}
{page.books.length > 0 && (
{page.books.map((book, i) => {
const meta = [book.author, book.note].filter(Boolean).join(' · ')
return (
📖
{book.title}
{meta && {meta} }
)
})}
)}
))}
)
}
export default function Lessons({ onOpenLesson }) {
const [lessons, setLessons] = useState(null)
const [error, setError] = useState('')
const [query, setQuery] = useState('')
const [view, setView] = useState('date')
useEffect(() => {
listLessons()
.then((data) => setLessons(data.lessons))
.catch((e) => setError(e.message))
}, [])
const filtered = useMemo(() => {
if (!lessons) return []
const q = query.trim().toLowerCase()
if (!q) return lessons
return lessons.filter((l) => (l.title || '').toLowerCase().includes(q))
}, [lessons, query])
if (error) return Could not load lessons: {error}
if (lessons === null) return Loading lessons…
return (
setQuery(e.target.value)}
/>
setView('date')}>
📅 By Date
setView('topic')}>
🏷️ By Topic
setView('resources')}>
📚 Resources
{view === 'resources' &&
}
{view !== 'resources' && lessons.length === 0 && (
📓 No lessons saved yet. Open the Notebook tab, paste some French text, and save it.
)}
{view !== 'resources' && lessons.length > 0 && filtered.length === 0 && (
No lessons match "{query}".
)}
{view === 'date' && filtered.length > 0 && (
[formatDateHeader(d), items])} onOpen={onOpenLesson} openFirst />
)}
{view === 'topic' && filtered.length > 0 && (
)}
)
}