feat(upload): wire A8 local track upload to backend

Implement the A8 upload screen against the existing /upload contract:
- UploadResponse type ({track_id, title, already_exists}) + mutation typed to it
- buildUploadFormData helper (single file under field `file`, per FastAPI)
- UploadPage: drag-and-drop + file picker, client-side queue with
  concurrency cap (3), per-file status badges, retry on error,
  already_exists -> "Already in library", deep-link to A7 metadata editor
- i18n upload.* section (en/ru) incl. "metadata pending" hint

Indeterminate spinner per file; percent progress is a follow-up
(needs an XHR baseQuery — fetchBaseQuery gives no upload progress).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Senko-san
2026-06-07 18:47:59 +03:00
parent aed0572071
commit 61dbb1abd2
5 changed files with 385 additions and 5 deletions
+22
View File
@@ -179,6 +179,28 @@ const en = {
description: "This screen doesn't exist yet.",
backToLibrary: 'Back to library',
},
upload: {
title: 'Upload files',
dropzone: {
title: 'Drag & drop audio files here',
hint: 'or click to choose files — one or many at a time',
button: 'Choose files',
},
queueTitle: 'Uploads ({{completed}}/{{total}})',
clearCompleted: 'Clear completed',
retry: 'Retry',
editMetadata: 'Edit metadata',
metadataPending:
'Uploaded tracks land as “Unknown Artist” with metadata pending — enrich them afterwards.',
unknownArtist: 'Unknown Artist · metadata pending',
status: {
queued: 'Queued',
uploading: 'Uploading',
done: 'Uploaded',
duplicate: 'Already in library',
error: 'Failed',
},
},
} as const;
export default en;
+22
View File
@@ -181,6 +181,28 @@ const ru: Translations = {
description: 'Этого экрана пока нет.',
backToLibrary: 'Вернуться в библиотеку',
},
upload: {
title: 'Загрузка файлов',
dropzone: {
title: 'Перетащите аудиофайлы сюда',
hint: 'или нажмите, чтобы выбрать файлы — по одному или сразу несколько',
button: 'Выбрать файлы',
},
queueTitle: 'Загрузки ({{completed}}/{{total}})',
clearCompleted: 'Убрать завершённые',
retry: 'Повторить',
editMetadata: 'Изменить метаданные',
metadataPending:
'Загруженные треки появляются как «Unknown Artist» с метаданными в ожидании — дозаполните их позже.',
unknownArtist: 'Unknown Artist · метаданные в ожидании',
status: {
queued: 'В очереди',
uploading: 'Загрузка',
done: 'Загружено',
duplicate: 'Уже в библиотеке',
error: 'Ошибка',
},
},
};
export default ru;