From 895725447166bb15d129404988f2dc1d3a1b953f Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 15 Jun 2026 01:16:18 -0400 Subject: [PATCH] feat(SearchCommoServe): add CORS handling and loading state for search errors --- src/app/components/SearchAssembly64.tsx | 1 + src/app/components/SearchCommoServe.tsx | 28 ++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/app/components/SearchAssembly64.tsx b/src/app/components/SearchAssembly64.tsx index c5e2ab9..7bc132d 100644 --- a/src/app/components/SearchAssembly64.tsx +++ b/src/app/components/SearchAssembly64.tsx @@ -231,6 +231,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA }; const handleSearch = () => doSearch(query, categoryFilter, 0); + const handleLoadMore = () => doSearch(query, categoryFilter, offset, true); // Build an AQL token for a preset value and append/replace it in the query. // Tokens that already contain a colon (e.g. 'subcat:c64comdemos') are diff --git a/src/app/components/SearchCommoServe.tsx b/src/app/components/SearchCommoServe.tsx index ac9532a..caae960 100644 --- a/src/app/components/SearchCommoServe.tsx +++ b/src/app/components/SearchCommoServe.tsx @@ -158,6 +158,7 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC const [isSearching, setIsSearching] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); const [searchError, setSearchError] = useState(null); + const [corsBlocked, setCorsBlocked] = useState(false); const [categoryFilter, setCategoryFilter] = useState(() => _store.categoryFilter); const [categories, setCategories] = useState([]); @@ -186,8 +187,9 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC }, []); useEffect(() => { - leetJson('/search/categories').then(setCategories).catch(() => {}); - leetJson('/search/aql/presets').then(setPresets).catch(() => {}); + const isCors = (e: any) => /failed to fetch|networkerror/i.test(e?.message ?? ''); + leetJson('/search/categories').then(setCategories).catch(e => { if (isCors(e)) setCorsBlocked(true); }); + leetJson('/search/aql/presets').then(setPresets).catch(e => { if (isCors(e)) setCorsBlocked(true); }); }, []); const categoryName = useMemo(() => { @@ -215,7 +217,8 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC setHasMore(data.length === PAGE_SIZE); setHasSearched(true); } catch (e: any) { - setSearchError(e?.message ?? 'Search failed'); + if (/failed to fetch|networkerror/i.test(e?.message ?? '')) setCorsBlocked(true); + else setSearchError(e?.message ?? 'Search failed'); } finally { setIsSearching(false); setIsLoadingMore(false); @@ -397,14 +400,25 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC className="flex-1 overflow-y-auto" onScroll={e => { _store.scrollTop = (e.currentTarget as HTMLDivElement).scrollTop; }} > - {isSearching && !hasSearched && ( + {corsBlocked && ( +
+ 🚫 +

CommoServe is not accessible

+

+ The CommoServe server does not allow requests from this browser origin (CORS policy). + This service may only be reachable directly from your Meatloaf device. +

+
+ )} + + {!corsBlocked && isSearching && !hasSearched && (

Searching…

)} - {searchError && ( + {!corsBlocked && searchError && (

{searchError}

)} - {!searchError && hasSearched && ( + {!corsBlocked && !searchError && hasSearched && ( <> {results.length > 0 ? ( <> @@ -487,7 +501,7 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC )} - {!hasSearched && !isSearching && ( + {!corsBlocked && !hasSearched && !isSearching && (

Search the CommoServe database