Add user settings UI and API integration

Introduce a full user settings feature: add a SettingsView component and UserSettings type, plus API helpers to fetch, create, and update settings (convertors between API and app shapes). App now loads settings on mount, persists category toggles to the API, exposes a /settings route, and passes itemsPerPage into BrowseView and CastView. Header gains a settings icon/link and BrowseView/CastView update pagination option defaults. This enables centralized library/display/content preferences and syncs them with the backend.
This commit is contained in:
Lars Behrends
2026-04-10 14:14:27 +02:00
parent f5c3e96823
commit 04156486e2
7 changed files with 555 additions and 16 deletions

View File

@@ -13,9 +13,10 @@ import CastView from './components/CastView';
import CastDetailView from './components/CastDetailView';
import AddMediaView from './components/AddMediaView';
import ImporterView from './components/ImporterView';
import SettingsView from './components/SettingsView';
import { MOCK_MEDIA, DETAIL_MEDIA } from './data';
import { Media, Staff, MediaCategory } from './types';
import { fetchAllMedia, fetchMediaById, fetchCastById, convertApiCastToStaff } from './api';
import { Media, Staff, MediaCategory, UserSettings } from './types';
import { fetchAllMedia, fetchMediaById, fetchCastById, convertApiCastToStaff, fetchSettings, updateSettings } from './api';
function AppContent() {
const navigate = useNavigate();
@@ -28,12 +29,41 @@ function AppContent() {
const [selectedPerson, setSelectedPerson] = useState<Staff | null>(null);
const [searchQuery, setSearchQuery] = useState(searchParams.get('search') || '');
const [enabledCategories, setEnabledCategories] = useState<MediaCategory[]>(['Anime', 'Movies', 'TV Series', 'Music', 'Books', 'Consoles', 'Games', 'Adult']);
const [settings, setSettings] = useState<UserSettings | null>(null);
const [customMedia, setCustomMedia] = useState<Media[]>([]);
const [adultMedia, setAdultMedia] = useState<Media[]>([]);
// Load media from API on component mount (only when not on cast routes)
const [apiMedia, setApiMedia] = useState<Media[]>([]);
useEffect(() => {
const loadSettingsFromApi = async () => {
try {
const loadedSettings = await fetchSettings();
if (loadedSettings) {
setSettings(loadedSettings);
setEnabledCategories(loadedSettings.enabledCategories);
}
} catch (error) {
console.error('Failed to load settings from API:', error);
}
};
loadSettingsFromApi();
}, []);
const reloadSettings = async () => {
try {
const loadedSettings = await fetchSettings();
if (loadedSettings) {
setSettings(loadedSettings);
setEnabledCategories(loadedSettings.enabledCategories);
}
} catch (error) {
console.error('Failed to reload settings from API:', error);
}
};
useEffect(() => {
const loadMediaFromApi = async () => {
try {
@@ -50,7 +80,7 @@ function AppContent() {
}
}, [location.pathname]);
const toggleCategory = (category: MediaCategory) => {
const toggleCategory = async (category: MediaCategory) => {
setEnabledCategories(prev => {
const isEnabling = !prev.includes(category);
const newList = isEnabling
@@ -62,6 +92,27 @@ function AppContent() {
const nextCategory = newList.find(c => c !== category) || 'Anime';
setActiveCategory(nextCategory as MediaCategory);
}
// Save to API
const baseSettings = settings || {
enabledCategories: prev,
itemsPerPage: 20,
defaultView: 'grid',
showAdultContent: false,
autoPlayTrailers: false,
language: 'en',
theme: 'system',
};
const updatedSettings: UserSettings = {
...baseSettings,
enabledCategories: newList,
};
updateSettings(updatedSettings).then(saved => {
if (saved) {
setSettings(saved);
}
});
return newList;
});
};
@@ -237,6 +288,7 @@ function AppContent() {
mediaList={filteredMedia}
onMediaClick={handleMediaClick}
activeCategory={activeCategory}
itemsPerPage={settings?.itemsPerPage}
/>
} />
<Route path="/media/:id" element={
@@ -251,6 +303,7 @@ function AppContent() {
<CastView
onPersonClick={handlePersonClick}
enabledCategories={enabledCategories}
itemsPerPage={settings?.itemsPerPage}
/>
} />
<Route path="/cast/:id" element={
@@ -268,6 +321,9 @@ function AppContent() {
<Route path="/import" element={
<ImporterView />
} />
<Route path="/settings" element={
<SettingsView onSettingsSaved={reloadSettings} />
} />
</Routes>
</LayoutGroup>
</main>