diff --git a/spotizerr-ui/package.json b/spotizerr-ui/package.json index 38a2f5d..2d55918 100644 --- a/spotizerr-ui/package.json +++ b/spotizerr-ui/package.json @@ -1,7 +1,7 @@ { "name": "spotizerr-ui", "private": true, - "version": "3.0.5", + "version": "3.0.6", "type": "module", "scripts": { "dev": "vite", diff --git a/spotizerr-ui/src/components/config/AccountsTab.tsx b/spotizerr-ui/src/components/config/AccountsTab.tsx index 8db7ba3..eeb6e91 100644 --- a/spotizerr-ui/src/components/config/AccountsTab.tsx +++ b/spotizerr-ui/src/components/config/AccountsTab.tsx @@ -40,11 +40,33 @@ const deleteCredential = async ({ service, name }: { service: Service; name: str return response; }; +// --- Error helpers --- +function extractApiErrorMessage(error: unknown): string { + const fallback = "Failed to add account."; + try { + // Axios-style error + const anyErr: any = error as any; + const resp = anyErr?.response; + if (resp?.data) { + const data = resp.data; + if (typeof data === "string") return data; + if (typeof data?.detail === "string") return data.detail; + if (typeof data?.message === "string") return data.message; + if (typeof data?.error === "string") return data.error; + } + if (typeof anyErr?.message === "string") return anyErr.message; + return fallback; + } catch { + return fallback; + } +} + // --- Component --- export function AccountsTab() { const queryClient = useQueryClient(); const [activeService, setActiveService] = useState("spotify"); const [isAdding, setIsAdding] = useState(false); + const [submitError, setSubmitError] = useState(null); const { data: credentials, isLoading } = useQuery({ queryKey: ["credentials", activeService], @@ -64,10 +86,13 @@ export function AccountsTab() { toast.success("Account added successfully!"); queryClient.invalidateQueries({ queryKey: ["credentials", activeService] }); setIsAdding(false); + setSubmitError(null); reset(); }, onError: (error) => { - toast.error(`Failed to add account: ${error.message}`); + const msg = extractApiErrorMessage(error); + setSubmitError(msg); + toast.error(msg); }, }); @@ -78,17 +103,26 @@ export function AccountsTab() { queryClient.invalidateQueries({ queryKey: ["credentials", activeService] }); }, onError: (error) => { - toast.error(`Failed to delete account: ${error.message}`); + const msg = extractApiErrorMessage(error); + toast.error(msg); }, }); const onSubmit: SubmitHandler = (data) => { + setSubmitError(null); addMutation.mutate({ service: activeService, data }); }; const renderAddForm = () => (

Add New {activeService === "spotify" ? "Spotify" : "Deezer"} Account

+ + {submitError && ( +
+ {submitError} +
+ )} +
=> { // Transform the keys before returning the data const camelData = convertKeysToCamelCase(combinedConfig) as FetchedCamelCaseSettings; - return camelData as unknown as FlatAppSettings; + const withDefaults: FlatAppSettings = { + ...(camelData as unknown as FlatAppSettings), + // Ensure required frontend-only fields exist + recursiveQuality: Boolean((camelData as any).recursiveQuality ?? false), + }; + + return withDefaults; } catch (error: any) { // If we get authentication errors, return default settings if (error.response?.status === 401 || error.response?.status === 403) { diff --git a/spotizerr-ui/src/contexts/settings-context.ts b/spotizerr-ui/src/contexts/settings-context.ts index 319e593..f09eda3 100644 --- a/spotizerr-ui/src/contexts/settings-context.ts +++ b/spotizerr-ui/src/contexts/settings-context.ts @@ -26,6 +26,7 @@ export interface AppSettings { skipExisting: boolean; m3u: boolean; hlsThreads: number; + recursiveQuality: boolean; // Properties from the old 'formatting' object track: string; album: string; diff --git a/spotizerr-ui/src/routes/config.tsx b/spotizerr-ui/src/routes/config.tsx index 3351ec6..9698b30 100644 --- a/spotizerr-ui/src/routes/config.tsx +++ b/spotizerr-ui/src/routes/config.tsx @@ -3,6 +3,7 @@ import { useSearch } from "@tanstack/react-router"; import { useSettings } from "../contexts/settings-context"; import { useAuth } from "../contexts/auth-context"; import { LoginScreen } from "../components/auth/LoginScreen"; +import pkgJson from "../../package.json"; // Lazy load config tab components for better code splitting const GeneralTab = lazy(() => import("../components/config/GeneralTab").then(m => ({ default: m.GeneralTab }))); @@ -28,6 +29,8 @@ const ConfigComponent = () => { // Get settings from the context instead of fetching here const { settings: config, isLoading } = useSettings(); + const appVersion = (pkgJson as any)?.version as string; + // Determine initial tab based on URL parameter, user role, and auth state const getInitialTab = () => { if (tab) { @@ -174,9 +177,12 @@ const ConfigComponent = () => { return (
-

- {authEnabled && !isAdmin ? "Profile Settings" : "Configuration"} -

+
+

+ {authEnabled && !isAdmin ? "Profile Settings" : "Configuration"} +

+ v{appVersion} +

{authEnabled && !isAdmin ? "Manage your profile and account settings." @@ -219,7 +225,7 @@ const ConfigComponent = () => {