From 4aa071eeebe6f93c29b596d16205a906c0bd5e14 Mon Sep 17 00:00:00 2001 From: Senko-san Date: Sun, 14 Jun 2026 01:36:07 +0300 Subject: [PATCH] =?UTF-8?q?feat(upload):=20persistent=20"Recently=20upload?= =?UTF-8?q?ed"=20list=20(=C2=A7A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transient client-side upload queue vanished on refresh, so a just- uploaded track seemed to disappear. Add a server-backed "Recently uploaded" section (source=upload, newest first) that survives refresh and auto-refreshes after each upload (the upload mutation already invalidates the `Track` tag this query provides). - api: `source` filter on `LibraryFilters` → `GET /tracks?source=` - i18n: `upload.recent.*` (en + ru); loading/error/empty states Co-Authored-By: Claude Opus 4.8 --- src/api/endpoints/library.ts | 1 + src/api/types.ts | 2 ++ src/features/upload/UploadPage.tsx | 47 +++++++++++++++++++++++++++++- src/i18n/locales/en.ts | 4 +++ src/i18n/locales/ru.ts | 4 +++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/api/endpoints/library.ts b/src/api/endpoints/library.ts index 3dfec9a..164b874 100644 --- a/src/api/endpoints/library.ts +++ b/src/api/endpoints/library.ts @@ -42,6 +42,7 @@ function trackParams(f: LibraryFilters) { q: f.search, artist_id: f.artistId, album_id: f.albumId, + source: f.source, sort_by: f.sortBy ? SORT_BY[f.sortBy] : undefined, order: f.sortOrder, ...paging(f.page, f.pageSize), diff --git a/src/api/types.ts b/src/api/types.ts index 019b1b2..8feea2e 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -177,6 +177,8 @@ export interface LibraryFilters { genre?: string; artistId?: string; albumId?: string; + /** Filter by ingest origin, e.g. `upload`, `youtube`, `local`. */ + source?: string; liked?: boolean; page?: number; pageSize?: number; diff --git a/src/features/upload/UploadPage.tsx b/src/features/upload/UploadPage.tsx index 7f5124e..bc9ca03 100644 --- a/src/features/upload/UploadPage.tsx +++ b/src/features/upload/UploadPage.tsx @@ -6,8 +6,23 @@ import { buildUploadFormData, useUploadTrackMutation, } from '../../api/endpoints/upload'; -import { useGetTrackQuery } from '../../api/endpoints/library'; +import { + useGetTrackQuery, + useGetTracksQuery, +} from '../../api/endpoints/library'; import { MetadataStatusBadge } from '../../components/track/MetadataStatusBadge'; +import { TrackRow } from '../../components/track/TrackRow'; +import { LoadingSkeleton } from '../../components/common/LoadingSkeleton'; +import { ErrorState } from '../../components/common/ErrorState'; + +/** A8 "Recently uploaded": server-backed list (source=upload, newest first) so + * it survives a page refresh — unlike the transient client-side queue above. */ +const RECENT_UPLOADS = { + source: 'upload', + sortBy: 'dateAdded', + sortOrder: 'desc', + pageSize: 20, +} as const; /** Pure client-side state — this is a transient upload queue, never server data. */ type ItemStatus = 'queued' | 'uploading' | 'done' | 'duplicate' | 'error'; @@ -40,6 +55,10 @@ export function UploadPage() { const [items, setItems] = useState([]); const [dragging, setDragging] = useState(false); + // Persisted view of past uploads. Auto-refreshes after each upload because the + // upload mutation invalidates the `Track` tag this query provides. + const recentQuery = useGetTracksQuery(RECENT_UPLOADS); + const inputRef = useRef(null); const idCounter = useRef(0); const activeCount = useRef(0); @@ -228,6 +247,32 @@ export function UploadPage() { ))} )} + +
+ + {t('upload.recent.title')} + + {recentQuery.isLoading && } + {recentQuery.isError && ( + recentQuery.refetch()} /> + )} + {recentQuery.data && recentQuery.data.items.length === 0 && ( +

+ {t('upload.recent.empty')} +

+ )} + {recentQuery.data?.items.map((track, i) => ( + + ))} +
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 9a8be0b..8420c4e 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -310,6 +310,10 @@ const en = { clearCompleted: 'Clear completed', retry: 'Retry', editMetadata: 'Edit metadata', + recent: { + title: 'Recently uploaded', + empty: 'Nothing uploaded yet.', + }, metadataPending: 'Uploaded tracks land as “Unknown Artist” with metadata pending — enrich them afterwards.', unknownArtist: 'Unknown Artist · metadata pending', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index b3644d7..ed2a5f0 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -312,6 +312,10 @@ const ru: Translations = { clearCompleted: 'Убрать завершённые', retry: 'Повторить', editMetadata: 'Изменить метаданные', + recent: { + title: 'Недавно загруженные', + empty: 'Пока ничего не загружено.', + }, metadataPending: 'Загруженные треки появляются как «Unknown Artist» с метаданными в ожидании — дозаполните их позже.', unknownArtist: 'Unknown Artist · метаданные в ожидании',