diff --git a/apps/web/__tests__/unit/share-transcript-rendering.test.ts b/apps/web/__tests__/unit/share-transcript-rendering.test.ts index 900746d2c68..14802a4b03b 100644 --- a/apps/web/__tests__/unit/share-transcript-rendering.test.ts +++ b/apps/web/__tests__/unit/share-transcript-rendering.test.ts @@ -296,10 +296,12 @@ describe("share transcript rendering", () => { await click(button("Save")); expect(button("Save").disabled).toBe(true); expect(button("Cancel").disabled).toBe(true); - await editCue(3); - expect(container.querySelector("textarea")?.value).toMatch(/^Cue 3:/); - expect(button("Save").disabled).toBe(true); - expect(button("Cancel").disabled).toBe(true); + expect(container.querySelector("textarea")?.disabled).toBe(true); + expect( + cue(3).parentElement?.querySelector( + 'button[title="Edit transcript entry"]', + ), + ).toBeNull(); await act(async () => finish({ success: true })); expect(container.querySelector("textarea")).toBeNull(); expect(container.textContent).toContain("Saved after waiting"); diff --git a/apps/web/app/s/[videoId]/_components/tabs/Transcript.tsx b/apps/web/app/s/[videoId]/_components/tabs/Transcript.tsx index 5bfb575bc18..88f52a0b46f 100644 --- a/apps/web/app/s/[videoId]/_components/tabs/Transcript.tsx +++ b/apps/web/app/s/[videoId]/_components/tabs/Transcript.tsx @@ -250,52 +250,65 @@ export const Transcript: React.FC = ({ data, onSeek }) => { const handleTranscriptClick = useCallback( (entry: TranscriptEntry) => { - if (editingEntry === entry.id) { + if (isSaving || editingEntry === entry.id) { return; } setSelectedEntry(entry.id); onSeek?.(entry.startTime); }, - [editingEntry, onSeek], + [editingEntry, isSaving, onSeek], ); - const startEditing = useCallback((entry: TranscriptEntry) => { - setEditingEntry(entry.id); - }, []); + const startEditing = useCallback( + (entry: TranscriptEntry) => { + if (isSaving) { + return; + } + setEditingEntry(entry.id); + }, + [isSaving], + ); - const cancelEditing = () => { + const cancelEditing = useCallback(() => { + if (isSaving) { + return; + } setEditingEntry(null); - }; + }, [isSaving]); const saveEdit = async (editText: string) => { const normalizedEditText = normalizeTranscriptCueText(editText); - if (!editingEntry || !normalizedEditText) { + if (!editingEntry || !normalizedEditText || isSaving) { return; } + const entryIdToSave = editingEntry; + setIsSaving(true); try { const result = await editTranscriptEntry( data.id, - editingEntry, + entryIdToSave, normalizedEditText, ); if (result.success) { setTranscriptData((prev) => prev.map((entry) => - entry.id === editingEntry + entry.id === entryIdToSave ? { ...entry, text: normalizedEditText } : entry, ), ); - setEditingEntry(null); + setEditingEntry((current) => + current === entryIdToSave ? null : current, + ); invalidateTranscript(data.id); } else { console.error("[Transcript] Failed to save transcript edit:", { - entryId: editingEntry, + entryId: entryIdToSave, videoId: data.id, errorMessage: result.message, result, @@ -303,7 +316,7 @@ export const Transcript: React.FC = ({ data, onSeek }) => { } } catch (error) { console.error("[Transcript] Error saving transcript edit:", { - entryId: editingEntry, + entryId: entryIdToSave, videoId: data.id, error: error instanceof Error ? error.message : error, stack: error instanceof Error ? error.stack : undefined, @@ -808,7 +821,7 @@ export const Transcript: React.FC = ({ data, onSeek }) => { key={entry.id} entry={entry} selected={selectedEntry === entry.id} - canEdit={canEdit} + canEdit={canEdit && !isSaving} onSelect={handleTranscriptClick} onEdit={startEditing} /> diff --git a/apps/web/app/s/[videoId]/_components/tabs/transcript-row.tsx b/apps/web/app/s/[videoId]/_components/tabs/transcript-row.tsx index c0bb2d4eb47..f044b876966 100644 --- a/apps/web/app/s/[videoId]/_components/tabs/transcript-row.tsx +++ b/apps/web/app/s/[videoId]/_components/tabs/transcript-row.tsx @@ -69,7 +69,8 @@ export function TranscriptEditor({