From 3bce0c019134c9ce814a6155cbdd005986531b76 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Thu, 28 May 2026 18:02:48 +0200 Subject: [PATCH] feat(navigator): empty-state + skeleton views, fix double search fetch (P4 wave 1a) Visual polish, first wave: - NavigatorEmptyStateView: replaces the bare "No rooms found" text with a centered icon + message + a Create-room CTA. Reuses existing i18n keys (navigator.search.returned.no.results / .roomsettings.moderation.none / .createroom.create) so no new localization entries are needed. - NavigatorSearchSkeletonView: animate-pulse placeholder rows shown while a search is in flight and no result is cached yet (matches the HK dashboard skeleton pattern). Replaces the NitroCard.Content spinner overlay for the result list. Bug fix bundled in: NavigatorSearchView called useNavigatorSearch() a second time purely to read searchResult for its input-sync effect. Since the hook is not a useBetween singleton, that registered a duplicate NavigatorSearchEvent listener AND fired a duplicate NavigatorSearchComposer on every search. NavigatorView now owns the single useNavigatorSearch() call and passes searchResult to NavigatorSearchView via prop. Test maintenance: useNavigatorSearch.test.tsx was written for the original useNitroQuery implementation, which upstream reverted (05d71dd1) to useMessageEvent + useState. Removed the dead QueryClient scaffolding, fixed case 1 (assert no fetch starts with empty tab), dropped case 7 (the query invalidator no longer exists). 6 cases, all green. Full suite 471/471. Typecheck: only the environmental renderer-mismatch errors (soundboard / rare-values / floorplan APIs absent from the linked renderer), none in navigator files. --- src/components/navigator/NavigatorView.tsx | 12 +- .../views/search/NavigatorEmptyStateView.tsx | 33 +++++ .../search/NavigatorSearchSkeletonView.tsx | 25 ++++ .../views/search/NavigatorSearchView.tsx | 12 +- .../navigator/useNavigatorSearch.test.tsx | 127 +++--------------- 5 files changed, 93 insertions(+), 116 deletions(-) create mode 100644 src/components/navigator/views/search/NavigatorEmptyStateView.tsx create mode 100644 src/components/navigator/views/search/NavigatorSearchSkeletonView.tsx diff --git a/src/components/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx index b93b8d5..727d06b 100644 --- a/src/components/navigator/NavigatorView.tsx +++ b/src/components/navigator/NavigatorView.tsx @@ -14,8 +14,10 @@ import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView'; import { NavigatorRoomInfoView } from './views/NavigatorRoomInfoView'; import { NavigatorRoomLinkView } from './views/NavigatorRoomLinkView'; import { NavigatorRoomSettingsView } from './views/room-settings/NavigatorRoomSettingsView'; +import { NavigatorEmptyStateView } from './views/search/NavigatorEmptyStateView'; import { NavigatorSearchResultView } from './views/search/NavigatorSearchResultView'; import { NavigatorSearchSavesResultView } from './views/search/NavigatorSearchSavesResultView'; +import { NavigatorSearchSkeletonView } from './views/search/NavigatorSearchSkeletonView'; import { NavigatorSearchView } from './views/search/NavigatorSearchView'; export const NavigatorView: FC<{}> = props => @@ -132,7 +134,7 @@ export const NavigatorView: FC<{}> = props => - + { !isCreatorOpen &&
{ isOpenSavesSearches && @@ -140,13 +142,13 @@ export const NavigatorView: FC<{}> = props =>
}
- +
+ { (isFetching && !searchResult) && + } { searchResult && searchResult.results.map((result, index) => ) } { searchResult && (!searchResult.results || searchResult.results.length === 0) && -
- { LocalizeText(searchResult.code === 'myworld_view' ? 'navigator.roomsettings.moderation.none' : 'navigator.search.returned.no.results') } -
} + useNavigatorUiStore.getState().openCreator() } /> }
void; +} + +export const NavigatorEmptyStateView: FC = props => +{ + const { code, onCreateRoom } = props; + + const isMyWorld = (code === 'myworld_view'); + const messageKey = isMyWorld ? 'navigator.roomsettings.moderation.none' : 'navigator.search.returned.no.results'; + + return ( +
+
+ +
+
+ { LocalizeText(messageKey) } +
+ +
+ ); +}; diff --git a/src/components/navigator/views/search/NavigatorSearchSkeletonView.tsx b/src/components/navigator/views/search/NavigatorSearchSkeletonView.tsx new file mode 100644 index 0000000..95666d8 --- /dev/null +++ b/src/components/navigator/views/search/NavigatorSearchSkeletonView.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; + +interface NavigatorSearchSkeletonViewProps +{ + rows?: number; +} + +export const NavigatorSearchSkeletonView: FC = props => +{ + const { rows = 5 } = props; + + return ( +