Add Loading component and use across views

Introduce a reusable Loading component (src/components/ui/loading.tsx) that shows a spinning Loader2 icon and an optional message. Replace ad-hoc loading UIs by importing and using Loading in BrowseView and CastView. In App.tsx, add mediaLoading state (set around fetchAllMedia) and pass it to BrowseView; also add local loading states to MediaDetailRoute and CastDetailRoute to show Loading while fetching details. These changes centralize loading UX and remove duplicated spinner markup.
This commit is contained in:
Lars Behrends
2026-04-11 01:26:41 +02:00
parent 555209ed4b
commit 0d530ea99c
4 changed files with 38 additions and 6 deletions
+16
View File
@@ -14,6 +14,7 @@ import CastDetailView from './components/CastDetailView';
import AddMediaView from './components/AddMediaView';
import ImporterView from './components/ImporterView';
import SettingsView from './components/SettingsView';
import Loading from './components/ui/loading';
import { MOCK_MEDIA, DETAIL_MEDIA } from './data';
import { Media, Staff, MediaCategory, UserSettings } from './types';
import { fetchAllMedia, fetchMediaById, fetchCastById, convertApiCastToStaff, fetchSettings, updateSettings } from './api';
@@ -37,6 +38,7 @@ function AppContent() {
// Load media from API on component mount (only when not on cast routes)
const [apiMedia, setApiMedia] = useState<Media[]>([]);
const [mediaLoading, setMediaLoading] = useState(true);
useEffect(() => {
const loadSettingsFromApi = async () => {
@@ -72,11 +74,14 @@ function AppContent() {
useEffect(() => {
const loadMediaFromApi = async () => {
setMediaLoading(true);
try {
const media = await fetchAllMedia();
setApiMedia(media);
} catch (error) {
console.error('Failed to load media from API:', error);
} finally {
setMediaLoading(false);
}
};
@@ -320,6 +325,7 @@ function AppContent() {
itemsPerPage={settings?.itemsPerPage}
gridItemSize={settings?.gridItemSize}
onGridItemSizeChange={handleGridItemSizeChange}
loading={mediaLoading}
/>
} />
<Route path="/media/:id" element={
@@ -385,10 +391,12 @@ function AppContent() {
function MediaDetailRoute({ selectedMedia, setSelectedMedia, allMedia, onPersonClick }: any) {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadMedia = async () => {
if (id) {
setLoading(true);
try {
const fetchedMedia = await fetchMediaById(id);
if (fetchedMedia) {
@@ -399,12 +407,15 @@ function MediaDetailRoute({ selectedMedia, setSelectedMedia, allMedia, onPersonC
} catch (error) {
console.error('Failed to fetch media:', error);
navigate('/');
} finally {
setLoading(false);
}
}
};
loadMedia();
}, [id]);
if (loading) return <Loading message="Loading media details..." />;
if (!selectedMedia) return null;
return (
@@ -419,10 +430,12 @@ function MediaDetailRoute({ selectedMedia, setSelectedMedia, allMedia, onPersonC
function CastDetailRoute({ selectedPerson, setSelectedPerson }: any) {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadCast = async () => {
if (id) {
setLoading(true);
try {
const castData = await fetchCastById(id);
if (castData) {
@@ -434,12 +447,15 @@ function CastDetailRoute({ selectedPerson, setSelectedPerson }: any) {
} catch (error) {
console.error('Failed to load cast:', error);
navigate('/cast');
} finally {
setLoading(false);
}
}
};
loadCast();
}, [id]);
if (loading) return <Loading message="Loading cast details..." />;
if (!selectedPerson) return null;
return (