Skip to content

Commit

Permalink
fix: Loading state for Marketplace related lists (RocketChat#31299)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagoevanp authored Dec 22, 2023
1 parent b6b7198 commit caa7707
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-pots-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": minor
---

fix: Loading state for `Marketplace` related lists
9 changes: 3 additions & 6 deletions apps/meteor/client/contexts/AppsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@ import { AsyncStatePhase } from '../lib/asyncState';
import type { App } from '../views/marketplace/types';

export type AppsContextValue = {
installedApps: AsyncState<{ apps: App[] }>;
marketplaceApps: AsyncState<{ apps: App[] }>;
privateApps: AsyncState<{ apps: App[] }>;
installedApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
marketplaceApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
privateApps: Omit<AsyncState<{ apps: App[] }>, 'error'>;
reload: () => Promise<void>;
};

export const AppsContext = createContext<AppsContextValue>({
installedApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
marketplaceApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
privateApps: {
phase: AsyncStatePhase.LOADING,
value: undefined,
error: undefined,
},
reload: () => Promise.resolve(),
});
23 changes: 20 additions & 3 deletions apps/meteor/client/providers/AppsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ import { AppClientOrchestratorInstance } from '../../ee/client/apps/orchestrator
import { AppsContext } from '../contexts/AppsContext';
import { useIsEnterprise } from '../hooks/useIsEnterprise';
import { useInvalidateLicense } from '../hooks/useLicense';
import type { AsyncState } from '../lib/asyncState';
import { AsyncStatePhase } from '../lib/asyncState';
import { useInvalidateAppsCountQueryCallback } from '../views/marketplace/hooks/useAppsCountQuery';
import type { App } from '../views/marketplace/types';

const sortByName = (apps: App[]): App[] => apps.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1));

const getAppState = (
loading: boolean,
apps: App[] | undefined,
): Omit<
AsyncState<{
apps: App[];
}>,
'error'
> => ({
phase: loading ? AsyncStatePhase.LOADING : AsyncStatePhase.RESOLVED,
value: { apps: apps || [] },
});

const AppsProvider: FC = ({ children }) => {
const isAdminUser = usePermission('manage-apps');

Expand Down Expand Up @@ -129,13 +143,16 @@ const AppsProvider: FC = ({ children }) => {
},
);

const [marketplaceAppsData, installedAppsData, privateAppsData] = store.data || [];
const { isLoading } = store;

return (
<AppsContext.Provider
children={children}
value={{
installedApps: { phase: AsyncStatePhase.RESOLVED, value: { apps: store.data?.[1] || [] } },
marketplaceApps: { phase: AsyncStatePhase.RESOLVED, value: { apps: store.data?.[0] || [] } },
privateApps: { phase: AsyncStatePhase.RESOLVED, value: { apps: store.data?.[2] || [] } },
installedApps: getAppState(isLoading, installedAppsData),
marketplaceApps: getAppState(isLoading, marketplaceAppsData),
privateApps: getAppState(isLoading, privateAppsData),
reload: async () => {
await Promise.all([queryClient.invalidateQueries(['marketplace'])]);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,16 @@ const AppsPageContent = (): ReactElement => {
context,
});

const noInstalledApps = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value.totalAppsLength === 0;
const noInstalledApps = appsResult.phase === AsyncStatePhase.RESOLVED && !isMarketplace && appsResult.value?.totalAppsLength === 0;

const noMarketplaceOrInstalledAppMatches =
appsResult.phase === AsyncStatePhase.RESOLVED && (isMarketplace || isPremium) && appsResult.value.count === 0;
appsResult.phase === AsyncStatePhase.RESOLVED && (isMarketplace || isPremium) && appsResult.value?.count === 0;

const noInstalledAppMatches =
appsResult.phase === AsyncStatePhase.RESOLVED &&
context === 'installed' &&
appsResult.value.totalAppsLength !== 0 &&
appsResult.value.count === 0;
appsResult.value?.totalAppsLength !== 0 &&
appsResult.value?.count === 0;

const noAppRequests = context === 'requested' && appsResult?.value?.count === 0;

Expand Down Expand Up @@ -194,13 +194,13 @@ const AppsPageContent = (): ReactElement => {
}

if (noMarketplaceOrInstalledAppMatches) {
return <NoMarketplaceOrInstalledAppMatchesEmptyState shouldShowSearchText={appsResult.value.shouldShowSearchText} text={text} />;
return <NoMarketplaceOrInstalledAppMatchesEmptyState shouldShowSearchText={!!appsResult.value?.shouldShowSearchText} text={text} />;
}

if (noInstalledAppMatches) {
return (
<NoInstalledAppMatchesEmptyState
shouldShowSearchText={appsResult.value.shouldShowSearchText}
shouldShowSearchText={!!appsResult.value?.shouldShowSearchText}
text={text}
onButtonClick={handleReturn}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import FeaturedAppsSections from './FeaturedAppsSections';
type AppsPageContentBodyProps = {
isMarketplace: boolean;
isFiltered: boolean;
appsResult: { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number };
appsResult?: { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number };
itemsPerPage: 25 | 50 | 100;
current: number;
onSetItemsPerPage: React.Dispatch<React.SetStateAction<25 | 50 | 100>>;
Expand Down Expand Up @@ -43,8 +43,8 @@ const AppsPageContentBody = ({
<Box display='flex' flexDirection='column' overflow='hidden' height='100%' pi={24}>
{noErrorsOcurred && (
<Box overflowY='scroll' height='100%' ref={scrollableRef}>
{isMarketplace && !isFiltered && <FeaturedAppsSections appsListId={appsListId} appsResult={appsResult.allApps || []} />}
<AppsList appsListId={appsListId} apps={appsResult.items || []} title={isMarketplace ? t('All_Apps') : undefined} />
{isMarketplace && !isFiltered && <FeaturedAppsSections appsListId={appsListId} appsResult={appsResult?.allApps || []} />}
<AppsList appsListId={appsListId} apps={appsResult?.items || []} title={isMarketplace ? t('All_Apps') : undefined} />
</Box>
)}
</Box>
Expand Down
5 changes: 3 additions & 2 deletions apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ export const useFilteredApps = ({
sortingMethod: string;
status: string;
context?: string;
}): AsyncState<
{ items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }
}): Omit<
AsyncState<{ items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }>,
'error'
> => {
const value = useMemo(() => {
if (appsData.value === undefined) {
Expand Down

0 comments on commit caa7707

Please sign in to comment.