From 21b34e763fba6ae1713722c7fcb2b7a0b90d99cf Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Sat, 4 Jul 2026 12:09:27 +0100 Subject: [PATCH] fix: handle missing integrations in launch editor --- .../src/api/routes/integrations.controller.ts | 72 ++++++++++--------- .../src/components/launches/calendar.tsx | 17 ++++- .../launches/launches.component.tsx | 20 +++--- .../src/components/new-launch/store.ts | 31 ++++++-- 4 files changed, 91 insertions(+), 49 deletions(-) diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index 024a890432..3177c2f059 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -87,39 +87,47 @@ export class IntegrationsController { @Get('/list') async getIntegrationList(@GetOrgFromRequest() org: Organization) { - return { - integrations: await Promise.all( - ( - await this._integrationService.getIntegrationsList(org.id) - ).map(async (p) => { - const findIntegration = this._integrationManager.getSocialIntegration( - p.providerIdentifier + const integrations = await Promise.all( + ( + await this._integrationService.getIntegrationsList(org.id) + ).map(async (p) => { + const findIntegration = this._integrationManager.getSocialIntegration( + p.providerIdentifier + ); + if (!findIntegration) { + console.warn( + `Skipping integration ${p.id}: provider "${p.providerIdentifier}" is not registered` ); - return { - name: p.name, - id: p.id, - internalId: p.internalId, - disabled: p.disabled, - editor: findIntegration.editor, - stripLinks: !!findIntegration?.stripLinks?.(), - picture: p.picture || '/no-picture.jpg', - identifier: p.providerIdentifier, - inBetweenSteps: p.inBetweenSteps, - refreshNeeded: p.refreshNeeded, - isCustomFields: !!findIntegration.customFields, - ...(findIntegration.customFields - ? { customFields: await findIntegration.customFields() } - : {}), - display: p.profile, - type: p.type, - time: JSON.parse(p.postingTimes), - changeProfilePicture: !!findIntegration?.changeProfilePicture, - changeNickName: !!findIntegration?.changeNickname, - customer: p.customer, - additionalSettings: p.additionalSettings || '[]', - }; - }) - ), + return undefined; + } + return { + name: p.name, + id: p.id, + internalId: p.internalId, + disabled: p.disabled, + editor: findIntegration.editor, + stripLinks: !!findIntegration?.stripLinks?.(), + picture: p.picture || '/no-picture.jpg', + identifier: p.providerIdentifier, + inBetweenSteps: p.inBetweenSteps, + refreshNeeded: p.refreshNeeded, + isCustomFields: !!findIntegration.customFields, + ...(findIntegration.customFields + ? { customFields: await findIntegration.customFields() } + : {}), + display: p.profile, + type: p.type, + time: JSON.parse(p.postingTimes), + changeProfilePicture: !!findIntegration?.changeProfilePicture, + changeNickName: !!findIntegration?.changeNickname, + customer: p.customer, + additionalSettings: p.additionalSettings || '[]', + }; + }) + ); + + return { + integrations: integrations.filter(Boolean), }; } diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index 8bb1c633af..1a03f4b82d 100644 --- a/apps/frontend/src/components/launches/calendar.tsx +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -114,6 +114,21 @@ const usePostActions = (onMutate?: () => void) => { }; const data = await (await fetch(`/posts/group/${post.group}`)).json(); + if ( + !isDuplicate && + data?.integration && + !integrations.some((integration) => integration.id === data.integration) + ) { + toaster.show( + t( + 'post_channel_no_longer_available', + 'This post belongs to a channel that is no longer available and cannot be edited.' + ), + 'warning' + ); + return; + } + const date = !isDuplicate ? null : (await (await fetch('/posts/find-slot')).json()).date; @@ -170,7 +185,7 @@ const usePostActions = (onMutate?: () => void) => { title: ``, }); }, - [integrations, fetch, modal, mutate] + [integrations, fetch, modal, mutate, toaster, t] ); const copyDebugJson = useCallback( diff --git a/apps/frontend/src/components/launches/launches.component.tsx b/apps/frontend/src/components/launches/launches.component.tsx index 8544a3e77f..a06808c207 100644 --- a/apps/frontend/src/components/launches/launches.component.tsx +++ b/apps/frontend/src/components/launches/launches.component.tsx @@ -363,16 +363,18 @@ export const LaunchesComponent = () => { const [mode] = useCookie('mode', 'dark'); const { isLoading, data: integrations, mutate } = useIntegrationList(); - const totalNonDisabledChannels = useMemo(() => { - return ( - integrations?.filter((integration: any) => !integration.disabled) - ?.length || 0 - ); + const validIntegrations = useMemo(() => { + return integrations?.filter(Boolean) || []; }, [integrations]); + + const totalNonDisabledChannels = useMemo(() => { + return validIntegrations.filter((integration: any) => !integration.disabled) + .length; + }, [validIntegrations]); const changeItemGroup = useCallback( async (id: string, group: string) => { mutate( - integrations.map((integration: any) => { + validIntegrations.map((integration: any) => { if (integration.id === id) { return { ...integration, @@ -393,15 +395,15 @@ export const LaunchesComponent = () => { }); mutate(); }, - [integrations] + [validIntegrations] ); const sortedIntegrations = useMemo(() => { return orderBy( - integrations, + validIntegrations, ['type', 'disabled', 'identifier'], ['desc', 'asc', 'asc'] ); - }, [integrations]); + }, [validIntegrations]); const menuIntegrations = useMemo(() => { return orderBy( Object.values( diff --git a/apps/frontend/src/components/new-launch/store.ts b/apps/frontend/src/components/new-launch/store.ts index 2ea620b826..3944859669 100644 --- a/apps/frontend/src/components/new-launch/store.ts +++ b/apps/frontend/src/components/new-launch/store.ts @@ -102,7 +102,7 @@ interface StoreState { setAllIntegrations: (integrations: Integrations[]) => void; setCurrent: (current: string) => void; addOrRemoveSelectedIntegration: ( - integration: Integrations, + integration: Integrations | undefined, settings: any ) => void; reset: () => void; @@ -164,9 +164,13 @@ export const useLaunchStore = create()((set) => ({ current: current, })), addOrRemoveSelectedIntegration: ( - integration: Integrations, + integration: Integrations | undefined, settings: any ) => { + if (!integration) { + return; + } + set((state) => { const existing = state.selectedIntegrations.find( (i) => i.integration.id === integration.id @@ -224,13 +228,19 @@ export const useLaunchStore = create()((set) => ({ ); if (integrationIndex === -1) { + const selectedIntegration = state.selectedIntegrations.find( + (i) => i.integration?.id === integrationId + ); + + if (!selectedIntegration) { + return {}; + } + return { internal: [ ...state.internal, { - integration: state.selectedIntegrations.find( - (i) => i.integration.id === integrationId - )!.integration, + integration: selectedIntegration.integration, integrationValue: value, }, ], @@ -301,7 +311,7 @@ export const useLaunchStore = create()((set) => ({ addRemoveInternal: (integrationId: string) => set((state) => { const integration = state.selectedIntegrations.find( - (i) => i.integration.id === integrationId + (i) => i.integration?.id === integrationId ); const findIntegrationIndex = state.internal.findIndex( (i) => i.integration.id === integrationId @@ -315,6 +325,10 @@ export const useLaunchStore = create()((set) => ({ }; } + if (!integration) { + return {}; + } + return { internal: [ ...state.internal, @@ -369,7 +383,10 @@ export const useLaunchStore = create()((set) => ({ if (item.integration.id === integrationId) { const targetIndex = direction === 'up' ? index - 1 : index + 1; - if (targetIndex < 0 || targetIndex >= item.integrationValue.length) { + if ( + targetIndex < 0 || + targetIndex >= item.integrationValue.length + ) { return item; }