Only propose configured apps during onboarding install step (#22712)

## What

- Onboarding "Install your first apps" now proposes only apps that are
actually installable: it intersects the onboarding list with
`findManyMarketplaceApps`, which the backend already filters to listed +
configured apps (all required server variables set).
- If none are available, the step auto-skips. If the marketplace query
fails, it shows an intentional fallback (heading + Skip) instead of
silently skipping or rendering an empty install card.
- `findManyMarketplaceApps` now accepts `universalIdentifiers`, so
onboarding fetches and configuration-checks only its own apps instead of
the entire catalog.

## Why

Previously the step rendered all hardcoded apps regardless of
configuration, only borrowing logos from the marketplace, so a user
could be offered an app the admin never configured. This centralizes
onboarding availability on the marketplace's existing logic and keeps
the query bounded as the marketplace grows.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22712?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-09 18:12:11 +02:00
committed by GitHub
parent b9cd62f396
commit 9c405384f3
13 changed files with 295 additions and 100 deletions
@@ -4,8 +4,8 @@ import { MARKETPLACE_APP_FRAGMENT } from '@/marketplace/graphql/fragments/market
export const FIND_MANY_MARKETPLACE_APPS = gql`
${MARKETPLACE_APP_FRAGMENT}
query FindManyMarketplaceApps {
findManyMarketplaceApps {
query FindManyMarketplaceApps($universalIdentifiers: [String!]) {
findManyMarketplaceApps(universalIdentifiers: $universalIdentifiers) {
...MarketplaceAppFields
}
}
@@ -1,8 +1,12 @@
import { useQuery } from '@apollo/client/react';
import { FindManyMarketplaceAppsDocument } from '~/generated-metadata/graphql';
export const useMarketplaceApps = () => {
const { data, loading, error } = useQuery(FindManyMarketplaceAppsDocument);
export const useMarketplaceApps = ({
universalIdentifiers,
}: { universalIdentifiers?: string[] } = {}) => {
const { data, loading, error } = useQuery(FindManyMarketplaceAppsDocument, {
variables: { universalIdentifiers },
});
return {
data: data?.findManyMarketplaceApps ?? [],
@@ -0,0 +1,35 @@
import { useTriggerInstallAppsOnboardingStep } from '@/onboarding/hooks/useTriggerInstallAppsOnboardingStep';
import { useEffect, useRef } from 'react';
type InstallAppsAutoSkipEffectProps = {
onError: () => void;
};
export const InstallAppsAutoSkipEffect = ({
onError,
}: InstallAppsAutoSkipEffectProps) => {
const triggerInstallAppsOnboardingStep =
useTriggerInstallAppsOnboardingStep();
// oxlint-disable-next-line twenty/no-state-useref
const hasSkippedRef = useRef(false);
useEffect(() => {
if (hasSkippedRef.current) {
return;
}
hasSkippedRef.current = true;
const skip = async () => {
try {
await triggerInstallAppsOnboardingStep([]);
} catch {
onError();
}
};
void skip();
}, [triggerInstallAppsOnboardingStep, onError]);
return null;
};