diff --git a/frontend/src/api.js b/frontend/src/api.js index d050e4a7..97d20d65 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1471,6 +1471,22 @@ export default class API { } } + static async deleteSetting({ id, key }) { + if (!id) return; + try { + await request(`${host}/api/core/settings/${id}/`, { + method: 'DELETE', + }); + if (key) { + useSettingsStore.getState().removeSetting(key); + } + return true; + } catch (e) { + errorNotification('Failed to delete setting', e); + throw e; + } + } + static async getChannelStats(uuid = null) { try { const response = await request(`${host}/proxy/ts/status`); diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index ec449074..4fdd1561 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -615,61 +615,73 @@ const SettingsPage = () => { }); }, [tmdbSetting?.value, validateTmdbKeyValue]); - const handleSaveTmdbKey = async () => { - const trimmedKey = tmdbKey.trim(); + const persistTmdbKey = async (rawValue) => { + const trimmedKey = (rawValue || '').trim(); + setTmdbKey(trimmedKey); setSavingTmdbKey(true); try { - let validationResult = null; - if (trimmedKey) { - validationResult = await validateTmdbKeyValue(trimmedKey); - if (!validationResult?.overall_valid) { - notifications.show({ - title: 'Invalid TMDB key', - message: - validationResult?.message || - 'Metadata providers are unavailable. TMDB rejected the API key and Movie-DB is unreachable.', - color: 'red', - }); - return; - } - } else { - validationResult = await validateTmdbKeyValue(''); + const validationResult = await validateTmdbKeyValue(trimmedKey); + + if (trimmedKey && !validationResult?.overall_valid) { + notifications.show({ + title: 'Invalid TMDB key', + message: + validationResult?.message || + 'Metadata providers are unavailable. TMDB rejected the API key and Movie-DB is unreachable.', + color: 'red', + }); + return; } - if (tmdbSetting && tmdbSetting.id) { - await API.updateSetting({ - ...tmdbSetting, - value: trimmedKey, - }); - } else { - await API.createSetting({ - key: 'tmdb-api-key', - name: 'TMDB API Key', - value: trimmedKey, - }); + if (trimmedKey) { + if (tmdbSetting && tmdbSetting.id) { + await API.updateSetting({ + ...tmdbSetting, + value: trimmedKey, + }); + } else { + await API.createSetting({ + key: 'tmdb-api-key', + name: 'TMDB API Key', + value: trimmedKey, + }); + } + } else if (tmdbSetting && tmdbSetting.id) { + await API.deleteSetting(tmdbSetting); } const provider = validationResult?.provider || activeMetadataSource; const usingFallback = provider === 'movie-db'; + const title = trimmedKey + ? provider === 'tmdb' + ? 'TMDB key saved' + : 'Saved with fallback' + : usingFallback + ? 'TMDB key removed' + : 'Metadata unavailable'; + const message = trimmedKey + ? provider === 'tmdb' + ? 'TMDB API key saved and verified.' + : usingFallback + ? 'Movie-DB fallback will be used for metadata until TMDB becomes available.' + : 'Metadata providers are currently unavailable. Libraries may fail to scan.' + : usingFallback + ? 'TMDB API key removed. Movie-DB fallback will be used for metadata.' + : 'TMDB API key removed, but no metadata providers are currently available.'; + const color = trimmedKey + ? provider === 'tmdb' + ? 'green' + : usingFallback + ? 'blue' + : 'red' + : usingFallback + ? 'blue' + : 'red'; + notifications.show({ - title: - provider === 'tmdb' - ? 'TMDB key saved' - : trimmedKey - ? 'Saved with fallback' - : 'Using fallback metadata', - message: - provider === 'tmdb' - ? 'TMDB API key saved and verified.' - : usingFallback - ? 'Movie-DB fallback will be used for metadata until TMDB becomes available.' - : 'Metadata providers are currently unavailable. Libraries may fail to scan.', - color: - provider === 'tmdb' - ? 'green' - : usingFallback - ? 'blue' - : 'red', + title, + message, + color, }); } catch (error) { console.error('Failed to save TMDB key', error); @@ -683,6 +695,24 @@ const SettingsPage = () => { } }; + const handleSaveTmdbKey = () => persistTmdbKey(tmdbKey); + + const handleDeleteTmdbKey = async () => { + if (!tmdbKey) return; + const confirmed = + typeof window === 'undefined' + ? true + : window.confirm( + 'Remove the TMDB API key? Metadata will fall back to Movie-DB when possible.' + ); + if (!confirmed) return; + try { + await persistTmdbKey(''); + } catch (error) { + console.error('Failed to remove TMDB key', error); + } + }; + const libraryActionsDisabled = tmdbValidating || !metadataSourcesAvailable; const tmdbMessageColor = tmdbValidationState === 'valid' @@ -990,6 +1020,23 @@ const SettingsPage = () => { placeholder="Enter TMDB API key" value={tmdbKey} onChange={(event) => setTmdbKey(event.currentTarget.value)} + rightSection={ + tmdbKey && tmdbKey.trim().length > 0 ? ( + + + + + + ) : null + } + rightSectionPointerEvents="auto" description="Used for metadata and artwork lookups." /> diff --git a/frontend/src/store/settings.jsx b/frontend/src/store/settings.jsx index 99390320..61f03f2e 100644 --- a/frontend/src/store/settings.jsx +++ b/frontend/src/store/settings.jsx @@ -40,6 +40,13 @@ const useSettingsStore = create((set) => ({ set((state) => ({ settings: { ...state.settings, [setting.key]: setting }, })), + removeSetting: (key) => + set((state) => { + if (!key) return state; + const next = { ...state.settings }; + delete next[key]; + return { ...state, settings: next }; + }), })); export default useSettingsStore;