fix(jellyseerr): address PR review feedback on TV connect flow

- validateJellyseerrSession: hit the authenticated /auth/me endpoint instead
  of the public /status, and only report "expired" on 401/403. Transient
  failures (offline, 5xx) are treated as valid so a flaky network no longer
  logs the user out. The helper no longer mutates persisted state.
- SearchPage: clear the stale "connected" state via clearAllJellyseerData on
  an expired session, gate the Discover validation effect behind isFocused,
  cancel in-flight validation on unmount, and reset the connect prompt when
  the tab loses focus so it can show again.
- settings.tv (seerr): sync the local server-URL field with settings so a
  late-arriving stored/plugin-locked URL isn't shown stale or overwritten.
- useJellyseerr: add setJellyseerrUser/updateSettings to clearAllJellyseerData
  deps to avoid a stale closure.
- TVJellyseerrSearchResults: scale poster/avatar dimensions and use the
  scaled vertical padding (per review).
- translations: drop manual sv.json keys (Crowdin syncs from en.json).

Claude-Session: https://claude.ai/code/session_016Hhu5DruGLPhdP4LAoy1Xd
This commit is contained in:
Fredrik Burmester
2026-06-30 12:07:42 +02:00
parent e1ac98b597
commit 6a1b34ee17
5 changed files with 68 additions and 34 deletions

View File

@@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Directory, Paths } from "expo-file-system";
import { Image } from "expo-image";
import { useAtom } from "jotai";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -70,6 +70,13 @@ export default function SettingsTV() {
);
const [jellyseerrPassword, setJellyseerrPassword] = useState("");
// Settings load storage + plugin overrides after mount, and a plugin can lock
// the URL at runtime. Keep the local field in sync with the effective value
// so it never shows stale/empty text or overwrites a locked URL on blur.
useEffect(() => {
setJellyseerrServerUrl(settings.jellyseerrServerUrl || "");
}, [settings.jellyseerrServerUrl]);
const isJellyseerrLocked =
pluginSettings?.jellyseerrServerUrl?.locked === true;
const isJellyseerrConnected = !!jellyseerrApi;

View File

@@ -122,13 +122,19 @@ export default function SearchPage() {
const isFocused = useIsFocused();
const { settings } = useSettings();
const { jellyseerrApi } = useJellyseerr();
const { jellyseerrApi, clearAllJellyseerData } = useJellyseerr();
// Prompt the user to connect when a Jellyseerr server is configured but no
// session exists yet (only once per focus, and only while the tab is focused).
// session exists yet (once per focus, and only while the tab is focused).
const jellyseerrAlertedRef = useRef(false);
useEffect(() => {
if (!isFocused || !settings?.jellyseerrServerUrl || jellyseerrApi) return;
// Reset when the tab loses focus so the prompt can show again next time
// (e.g. after the user disconnects and returns).
if (!isFocused) {
jellyseerrAlertedRef.current = false;
return;
}
if (!settings?.jellyseerrServerUrl || jellyseerrApi) return;
if (jellyseerrAlertedRef.current) return;
jellyseerrAlertedRef.current = true;
Alert.alert(
@@ -137,22 +143,36 @@ export default function SearchPage() {
);
}, [isFocused, settings?.jellyseerrServerUrl, jellyseerrApi, t]);
// Validate the Jellyseerr session when switching to Discover; warn if expired.
// Validate the Jellyseerr session when switching to Discover; if the session
// has expired, clear the stale "connected" state and prompt to reconnect.
useEffect(() => {
if (
!isFocused ||
searchType !== "Discover" ||
!jellyseerrApi ||
!settings?.jellyseerrServerUrl
)
return;
let cancelled = false;
validateJellyseerrSession(settings.jellyseerrServerUrl).then((status) => {
if (status.valid) return;
if (cancelled || status.valid || status.reason !== "expired") return;
clearAllJellyseerData();
Alert.alert(
t("jellyseerr.session_expired"),
t("jellyseerr.session_expired_connect_again"),
);
});
}, [searchType, jellyseerrApi, settings?.jellyseerrServerUrl, t]);
return () => {
cancelled = true;
};
}, [
isFocused,
searchType,
jellyseerrApi,
settings?.jellyseerrServerUrl,
clearAllJellyseerData,
t,
]);
const [jellyseerrOrderBy, setJellyseerrOrderBy] =
useState<JellyseerrSearchSort>(