feat(special-editor): add manual save button with unsaved changes indicator

This commit is contained in:
Hördle Bot
2025-11-23 01:07:53 +01:00
parent 23c2697424
commit e06e0d2919

View File

@@ -36,6 +36,8 @@ export default function SpecialEditorPage() {
const [selectedSongId, setSelectedSongId] = useState<number | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [pendingStartTime, setPendingStartTime] = useState<number | null>(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
useEffect(() => {
fetchSpecial();
@@ -49,6 +51,8 @@ export default function SpecialEditorPage() {
setSpecial(data);
if (data.songs.length > 0) {
setSelectedSongId(data.songs[0].songId);
// Initialize pendingStartTime with the current startTime of the first song
setPendingStartTime(data.songs[0].startTime);
}
}
} catch (error) {
@@ -58,15 +62,20 @@ export default function SpecialEditorPage() {
}
};
const handleStartTimeChange = async (songId: number, newStartTime: number) => {
if (!special) return;
const handleStartTimeChange = (newStartTime: number) => {
setPendingStartTime(newStartTime);
setHasUnsavedChanges(true);
};
const handleSave = async () => {
if (!special || !selectedSongId || pendingStartTime === null) return;
setSaving(true);
try {
const res = await fetch(`/api/specials/${specialId}/songs`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ songId, startTime: newStartTime })
body: JSON.stringify({ songId: selectedSongId, startTime: pendingStartTime })
});
if (res.ok) {
@@ -76,10 +85,12 @@ export default function SpecialEditorPage() {
return {
...prev,
songs: prev.songs.map(ss =>
ss.songId === songId ? { ...ss, startTime: newStartTime } : ss
ss.songId === selectedSongId ? { ...ss, startTime: pendingStartTime } : ss
)
};
});
setHasUnsavedChanges(false);
setPendingStartTime(null); // Reset pending state after saving
}
} catch (error) {
console.error('Error updating start time:', error);
@@ -176,21 +187,35 @@ export default function SpecialEditorPage() {
Curate: {selectedSpecialSong.song.title}
</h2>
<div style={{ background: '#f9fafb', padding: '1.5rem', borderRadius: '0.5rem' }}>
<p style={{ fontSize: '0.875rem', color: '#666', marginBottom: '1rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<p style={{ fontSize: '0.875rem', color: '#666', margin: 0 }}>
Click on the waveform to select where the puzzle should start. The highlighted region shows what players will hear.
</p>
<button
onClick={handleSave}
disabled={!hasUnsavedChanges || saving}
style={{
padding: '0.5rem 1.5rem',
background: hasUnsavedChanges ? '#10b981' : '#e5e7eb',
color: hasUnsavedChanges ? 'white' : '#9ca3af',
border: 'none',
borderRadius: '0.5rem',
cursor: hasUnsavedChanges && !saving ? 'pointer' : 'not-allowed',
fontWeight: 'bold',
fontSize: '0.875rem',
whiteSpace: 'nowrap'
}}
>
{saving ? '💾 Saving...' : hasUnsavedChanges ? '💾 Save Changes' : '✓ Saved'}
</button>
</div>
<WaveformEditor
audioUrl={`/uploads/${selectedSpecialSong.song.filename}`}
startTime={selectedSpecialSong.startTime}
startTime={pendingStartTime ?? selectedSpecialSong.startTime}
duration={totalDuration}
unlockSteps={unlockSteps}
onStartTimeChange={(newStartTime) => handleStartTimeChange(selectedSpecialSong.songId, newStartTime)}
onStartTimeChange={handleStartTimeChange}
/>
{saving && (
<div style={{ marginTop: '1rem', color: '#4f46e5', fontSize: '0.875rem' }}>
Saving...
</div>
)}
</div>
</div>
)}