remove speaking practice edit controls

Drop the practice-level edit action and modal from the Speaking page while preserving collapsible groups, searchable practice filtering, and question bulk actions.

Made-with: Cursor
This commit is contained in:
Yared Yemane 2026-04-07 04:58:35 -07:00
parent fd0790fe7f
commit 840a64c4e0

View File

@ -1,5 +1,5 @@
import { type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { ArrowLeft, ChevronDown, ChevronRight, Image as ImageIcon, Loader2, Mic, Pencil, Plus, Trash2, Upload } from "lucide-react"
import { ArrowLeft, ChevronDown, ChevronRight, Image as ImageIcon, Loader2, Mic, Plus, Trash2, Upload } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"
import { Button } from "../../components/ui/button"
import { Input } from "../../components/ui/input"
@ -17,7 +17,6 @@ import {
getQuestions,
getPracticeQuestionsByPractice,
getQuestionSets,
updatePractice,
updateQuestion,
} from "../../api/courses.api"
import { resolveFileUrl, uploadAudioFile, uploadImageFile, uploadVideoFile } from "../../api/files.api"
@ -161,11 +160,6 @@ export function SpeakingPage() {
const [selectedQuestionIds, setSelectedQuestionIds] = useState<number[]>([])
const [bulkDeleting, setBulkDeleting] = useState(false)
const [collapsedPracticeIds, setCollapsedPracticeIds] = useState<number[]>([])
const [editingPractice, setEditingPractice] = useState<PracticeFilterOption | null>(null)
const [practiceEditTitle, setPracticeEditTitle] = useState("")
const [practiceEditDescription, setPracticeEditDescription] = useState("")
const [practiceEditPersona, setPracticeEditPersona] = useState("")
const [practiceEditSaving, setPracticeEditSaving] = useState(false)
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [openCreate, setOpenCreate] = useState(false)
@ -1264,41 +1258,6 @@ export function SpeakingPage() {
)
}
const handleOpenPracticeEdit = (practiceId: number | null) => {
if (!practiceId) return
const practice = practiceOptions.find((p) => p.id === practiceId)
if (!practice) return
setEditingPractice(practice)
setPracticeEditTitle(practice.title)
setPracticeEditDescription(practice.description ?? "")
setPracticeEditPersona(practice.persona ?? "")
}
const handleSavePracticeEdit = async () => {
if (!editingPractice) return
if (!practiceEditTitle.trim()) {
toast.error("Practice title is required")
return
}
setPracticeEditSaving(true)
try {
await updatePractice(editingPractice.id, {
title: practiceEditTitle.trim(),
description: practiceEditDescription.trim(),
...(practiceEditPersona.trim() ? { persona: practiceEditPersona.trim() } : {}),
})
toast.success("Practice updated")
setEditingPractice(null)
await fetchPracticeOptions()
await fetchAudioQuestions()
} catch (error) {
console.error("Failed to update practice:", error)
toast.error("Failed to update practice")
} finally {
setPracticeEditSaving(false)
}
}
const groupedAudioQuestions = useMemo(() => {
const groups = new Map<string, { practiceId: number | null; practiceTitle: string; questions: AudioListQuestion[] }>()
for (const q of audioQuestions) {
@ -1475,16 +1434,6 @@ export function SpeakingPage() {
{group.practiceTitle} {group.practiceId ? `(#${group.practiceId})` : ""}
</label>
</div>
<Button
type="button"
variant="outline"
size="sm"
className="h-8 border-grayScale-200 text-grayScale-700 hover:bg-white"
onClick={() => handleOpenPracticeEdit(group.practiceId)}
>
<Pencil className="h-3.5 w-3.5" />
Edit practice
</Button>
</div>
</div>
{(group.practiceId && collapsedPracticeIds.includes(group.practiceId) ? [] : group.questions).map((question, idx) => (
@ -1888,44 +1837,6 @@ export function SpeakingPage() {
</div>
)}
{editingPractice && (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/45 p-4 backdrop-blur-sm">
<div className="w-full max-w-xl overflow-hidden rounded-2xl border border-grayScale-200/80 bg-white shadow-2xl">
<div className="border-b border-grayScale-100 bg-gradient-to-r from-grayScale-50 to-white px-5 py-4">
<h3 className="text-base font-semibold text-grayScale-900">
Edit practice metadata ({editingPractice.id})
</h3>
</div>
<div className="space-y-4 px-5 py-4">
<div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Title</label>
<Input value={practiceEditTitle} onChange={(e) => setPracticeEditTitle(e.target.value)} />
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Description</label>
<Textarea
rows={3}
value={practiceEditDescription}
onChange={(e) => setPracticeEditDescription(e.target.value)}
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Persona (optional)</label>
<Input value={practiceEditPersona} onChange={(e) => setPracticeEditPersona(e.target.value)} />
</div>
</div>
<div className="flex justify-end gap-2 border-t border-grayScale-100 bg-grayScale-50/40 px-5 py-4">
<Button variant="outline" onClick={() => setEditingPractice(null)} disabled={practiceEditSaving}>
Cancel
</Button>
<Button className="bg-brand-500 hover:bg-brand-600" onClick={handleSavePracticeEdit} disabled={practiceEditSaving}>
{practiceEditSaving ? "Saving..." : "Save practice"}
</Button>
</div>
</div>
</div>
)}
{openCreate && (
<div className="space-y-6">
<Card className="overflow-hidden border-grayScale-200/80 shadow-sm">