feat(settings): wire Playback + Scrobbling panels to the backend
Adds src/api/endpoints/settings.ts (getSettings/updateSettings, getScrobbling/updateScrobbling) plus AppSettings/ScrobblingConfig types and a Settings cache tag. Replaces the "coming soon" scaffolds: - Playback: stream-quality preference (SegmentedControl), server-synced. - Scrobbling: enable + provider (Last.fm/ListenBrainz) + username + write-only session key, with a linked/not-linked badge and save states. Built with modern-sk (Window/SegmentedControl/TextField/Button/Callout/ Badge) + the shared Loading/Error states; en + ru strings added. Server state flows through RTK Query hooks only (no fetch in components). Also drops a pre-existing unused TextField import in LibraryPage that was failing `tsc --noEmit`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { api } from '../index';
|
||||
import type {
|
||||
AppSettings,
|
||||
ScrobbleProvider,
|
||||
ScrobblingConfig,
|
||||
StreamQuality,
|
||||
ThemePref,
|
||||
} from '../types';
|
||||
|
||||
// Wire shapes (snake_case) from the backend, mapped to the app's camelCase types.
|
||||
interface RawSettings {
|
||||
theme: ThemePref;
|
||||
stream_quality: StreamQuality;
|
||||
}
|
||||
interface RawScrobbling {
|
||||
enabled: boolean;
|
||||
provider: ScrobbleProvider | null;
|
||||
username: string | null;
|
||||
configured: boolean;
|
||||
}
|
||||
|
||||
const toSettings = (r: RawSettings): AppSettings => ({
|
||||
theme: r.theme,
|
||||
streamQuality: r.stream_quality,
|
||||
});
|
||||
const toScrobbling = (r: RawScrobbling): ScrobblingConfig => ({
|
||||
enabled: r.enabled,
|
||||
provider: r.provider,
|
||||
username: r.username,
|
||||
configured: r.configured,
|
||||
});
|
||||
|
||||
export const settingsApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getSettings: build.query<AppSettings, void>({
|
||||
query: () => '/settings',
|
||||
transformResponse: (raw: RawSettings) => toSettings(raw),
|
||||
providesTags: ['Settings'],
|
||||
}),
|
||||
updateSettings: build.mutation<
|
||||
AppSettings,
|
||||
{ theme?: ThemePref; streamQuality?: StreamQuality }
|
||||
>({
|
||||
query: ({ theme, streamQuality }) => ({
|
||||
url: '/settings',
|
||||
method: 'PATCH',
|
||||
body: { theme, stream_quality: streamQuality },
|
||||
}),
|
||||
transformResponse: (raw: RawSettings) => toSettings(raw),
|
||||
invalidatesTags: ['Settings'],
|
||||
}),
|
||||
getScrobbling: build.query<ScrobblingConfig, void>({
|
||||
query: () => '/settings/scrobbling',
|
||||
transformResponse: (raw: RawScrobbling) => toScrobbling(raw),
|
||||
providesTags: ['Settings'],
|
||||
}),
|
||||
updateScrobbling: build.mutation<
|
||||
ScrobblingConfig,
|
||||
{
|
||||
enabled: boolean;
|
||||
provider: ScrobbleProvider | null;
|
||||
username: string | null;
|
||||
// Write-only: send only when the user typed a new key; omit to keep the
|
||||
// stored one (the server never returns it).
|
||||
sessionKey?: string;
|
||||
}
|
||||
>({
|
||||
query: ({ enabled, provider, username, sessionKey }) => ({
|
||||
url: '/settings/scrobbling',
|
||||
method: 'PUT',
|
||||
body: {
|
||||
enabled,
|
||||
provider,
|
||||
username,
|
||||
session_key: sessionKey,
|
||||
},
|
||||
}),
|
||||
transformResponse: (raw: RawScrobbling) => toScrobbling(raw),
|
||||
invalidatesTags: ['Settings'],
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetSettingsQuery,
|
||||
useUpdateSettingsMutation,
|
||||
useGetScrobblingQuery,
|
||||
useUpdateScrobblingMutation,
|
||||
} = settingsApi;
|
||||
@@ -23,6 +23,7 @@ export const api = createApi({
|
||||
'Like',
|
||||
'User',
|
||||
'Storage',
|
||||
'Settings',
|
||||
],
|
||||
// Tier 2 offline: seed the cache from the persisted snapshot dispatched at
|
||||
// startup (see `store/rtkqPersist.ts`). Returning the saved queries/mutations
|
||||
|
||||
@@ -258,3 +258,24 @@ export interface MetadataEdit {
|
||||
genre?: string;
|
||||
trackNumber?: number;
|
||||
}
|
||||
|
||||
// -- user settings (`/settings`) ---------------------------------------------
|
||||
export type ThemePref = 'system' | 'light' | 'dark';
|
||||
export type StreamQuality = 'original' | 'high' | 'medium' | 'low';
|
||||
|
||||
/** General per-user preferences from `GET /settings`. */
|
||||
export interface AppSettings {
|
||||
theme: ThemePref;
|
||||
streamQuality: StreamQuality;
|
||||
}
|
||||
|
||||
export type ScrobbleProvider = 'lastfm' | 'listenbrainz';
|
||||
|
||||
/** Scrobbling config from `GET /settings/scrobbling`. The session key is
|
||||
* write-only server-side, so it never appears here — only `configured`. */
|
||||
export interface ScrobblingConfig {
|
||||
enabled: boolean;
|
||||
provider: ScrobbleProvider | null;
|
||||
username: string | null;
|
||||
configured: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user