diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx index 8f0a2c93..b0383468 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -49,6 +49,15 @@ export default function StreamystatsPage() { ); const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true; + const searchLocked = pluginSettings?.searchEngine?.locked === true; + const movieRecsLocked = + pluginSettings?.streamyStatsMovieRecommendations?.locked === true; + const seriesRecsLocked = + pluginSettings?.streamyStatsSeriesRecommendations?.locked === true; + const promotedWatchlistsLocked = + pluginSettings?.streamyStatsPromotedWatchlists?.locked === true; + const hideWatchlistsTabLocked = + pluginSettings?.hideWatchlistsTab?.locked === true; const isStreamystatsEnabled = !!url; const onSave = useCallback(() => { @@ -146,7 +155,9 @@ export default function StreamystatsPage() { placeholder={t( "home.settings.plugins.streamystats.server_url_placeholder", )} - value={url} + value={ + isUrlLocked ? (settings?.streamyStatsServerUrl ?? "") : url + } keyboardType='url' returnKeyType='done' autoCapitalize='none' @@ -171,11 +182,18 @@ export default function StreamystatsPage() { > + {/* Locked controls show the live admin value and can't be toggled — + local form state would let the switch flip while the write guard + drops the change. */} @@ -183,52 +201,62 @@ export default function StreamystatsPage() { title={t( "home.settings.plugins.streamystats.enable_movie_recommendations", )} - disabledByAdmin={ - pluginSettings?.streamyStatsMovieRecommendations?.locked === true - } + disabledByAdmin={movieRecsLocked} > diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index f4c6c7dd..2b8c7aca 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -504,7 +504,17 @@ export const useSettings = () => { if (!_settings) { return; } - const hasChanges = Object.entries(update).some( + // Admin-locked settings are enforced at write time too: a control that + // isn't disabled in the UI must not persist a value the admin pinned. + // The read memo already overrides locked keys, but without this guard the + // write would silently land in user storage and resurface once unlocked. + const sanitizedUpdate = Object.fromEntries( + Object.entries(update).filter( + ([key]) => pluginSettings?.[key as keyof Settings]?.locked !== true, + ), + ) as Partial; + + const hasChanges = Object.entries(sanitizedUpdate).some( ([key, value]) => _settings[key as keyof Settings] !== value, ); @@ -513,7 +523,7 @@ export const useSettings = () => { const newSettings = { ...defaultValues, ..._settings, - ...update, + ...sanitizedUpdate, } as Settings; setSettings(newSettings); saveSettings(newSettings);