Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): integrations update and create flow #7281

Merged
merged 124 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
3c289cd
feat: wip
scopsy Dec 3, 2024
b016b5d
feat: wip
scopsy Dec 3, 2024
313cdad
Merge branch 'next' into feat-integrations-page
scopsy Dec 10, 2024
d36b41c
Update .source
scopsy Dec 10, 2024
3dbeaae
fix: styles
scopsy Dec 10, 2024
23b6e4a
Merge branch 'next' into feat-integrations-page
scopsy Dec 10, 2024
ce40aca
fix: refactor cards
scopsy Dec 10, 2024
75fffc6
fix: paths
scopsy Dec 10, 2024
1503299
fix: add items
scopsy Dec 10, 2024
07ce663
feat: refactored cards
scopsy Dec 10, 2024
824f36d
fix: buttons
scopsy Dec 10, 2024
362578f
fix: added fields
scopsy Dec 10, 2024
e78b513
fix: integration styling
scopsy Dec 10, 2024
9f61bbc
fix: scrolling
scopsy Dec 11, 2024
1aa3886
Merge branch 'next' into feat-integrations-page
scopsy Dec 11, 2024
fb5fcbc
fix: reusabe update sidebar
scopsy Dec 11, 2024
48f3173
fix: error
scopsy Dec 11, 2024
481822d
fix: remove unused
scopsy Dec 11, 2024
99b7eb8
fix: headears
scopsy Dec 11, 2024
5f69c65
fix: provider config
scopsy Dec 11, 2024
5dea17a
fix: order
scopsy Dec 11, 2024
606660b
fix: minor adjustments
scopsy Dec 11, 2024
f9c9989
fix: minor period
scopsy Dec 11, 2024
eb606bc
Merge branch 'next' into feat-integrations-page
scopsy Dec 11, 2024
7640653
Update integrations-list-page.tsx
scopsy Dec 11, 2024
6ccb7d2
fix: inbox connected state
scopsy Dec 11, 2024
b00f235
demo provider
scopsy Dec 11, 2024
a11a03a
Merge branch 'next' into feat-integrations-page
scopsy Dec 11, 2024
bd9f69e
Merge branch 'next' into feat-integrations-page
scopsy Dec 11, 2024
1e02633
fix: align wokrlfows list
scopsy Dec 11, 2024
241cfd7
fix: minor fixes
scopsy Dec 11, 2024
9b662be
fix: minor changes
scopsy Dec 11, 2024
ae1f6ed
fix: changes
scopsy Dec 11, 2024
704c32f
fix: renaming
scopsy Dec 11, 2024
844a547
fix: refactors
scopsy Dec 11, 2024
a76d20a
fix: items
scopsy Dec 11, 2024
d68a59b
fix: add base
scopsy Dec 11, 2024
3d2591c
fix: locaitons
scopsy Dec 11, 2024
95ecd6a
remove logos
scopsy Dec 11, 2024
31f7e58
Delete
scopsy Dec 11, 2024
bf4bf94
fix: revert
scopsy Dec 11, 2024
c49519f
Merge branch 'feat-integrations-page' of https://github.com/novuhq/no…
scopsy Dec 11, 2024
46dd4ac
Merge branch 'next' into feat-integrations-page
scopsy Dec 12, 2024
acc38fd
fix: minor fixes
scopsy Dec 12, 2024
0328579
fix: items
scopsy Dec 12, 2024
926915a
Merge branch 'next' into feat-integrations-page
scopsy Dec 15, 2024
086d8d5
Merge branch 'next' into feat-integrations-page
scopsy Dec 16, 2024
9a27ea1
feat: add segmented control
scopsy Dec 16, 2024
a109bfc
fix: env selection
scopsy Dec 16, 2024
ddb921c
fix: connect button
scopsy Dec 16, 2024
7951524
fix: navigation
scopsy Dec 16, 2024
025cc81
fix: primary check
scopsy Dec 16, 2024
a620d39
fix: feature flag
scopsy Dec 16, 2024
36d9bf8
fix: novu providers fetch
scopsy Dec 16, 2024
2175dba
fix: pr review
scopsy Dec 16, 2024
dccf725
fix: reload doc
scopsy Dec 16, 2024
3e60fe8
fix: integration step import
scopsy Dec 16, 2024
85f1d56
fix: integrations list page
scopsy Dec 16, 2024
425a095
refactor: remove pr
scopsy Dec 16, 2024
66c4cdf
Revert "fix: integrations list page"
scopsy Dec 16, 2024
1e445c7
Revert "refactor: remove pr"
scopsy Dec 16, 2024
9b7e1c2
fix: update src
scopsy Dec 16, 2024
6030684
fix: types
scopsy Dec 16, 2024
a026fd6
fix: types
scopsy Dec 16, 2024
4e41c9c
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 16, 2024
b842f4f
Revert "fix: types"
scopsy Dec 16, 2024
3b5f586
fix: imports
scopsy Dec 16, 2024
89d2ec4
fix: import
scopsy Dec 16, 2024
ee8bb1e
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 16, 2024
b072e00
fix: revert file
scopsy Dec 16, 2024
788dba3
Merge branch 'feat-new-integrations-page-table' of https://github.com…
scopsy Dec 16, 2024
ad7b420
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 16, 2024
1a0eff0
fix: query keys
scopsy Dec 16, 2024
c7ca662
fix: types
scopsy Dec 16, 2024
c7315ca
fix: types
scopsy Dec 16, 2024
7fe2f01
fix: refactor
scopsy Dec 16, 2024
8843fce
fix: review
scopsy Dec 16, 2024
cfc4b78
fix: bug
scopsy Dec 16, 2024
b17543a
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 16, 2024
97f9f29
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 16, 2024
baa0df3
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 18, 2024
316efad
fix: pr comments
scopsy Dec 18, 2024
ca70188
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 18, 2024
8fece59
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 18, 2024
1d0a181
fix: pr comments
scopsy Dec 18, 2024
01a9189
fix: pr comments
scopsy Dec 18, 2024
aede118
fix: primary modal
scopsy Dec 18, 2024
871a00e
fix: interim
scopsy Dec 18, 2024
a808a62
fix: refactor
scopsy Dec 18, 2024
1d862f8
fix: items
scopsy Dec 18, 2024
a94b5ee
fix: flow
scopsy Dec 18, 2024
4ecc577
fix: pr comments
scopsy Dec 18, 2024
2e272cb
fix: pr comments
scopsy Dec 18, 2024
4be39c4
fix: items
scopsy Dec 18, 2024
0c4079b
fix: secret
scopsy Dec 18, 2024
4465165
fix: pr comments
scopsy Dec 18, 2024
6bb7678
fix: header title
scopsy Dec 18, 2024
06ce8c4
fix: clicking on the inline toast
scopsy Dec 18, 2024
189d0b2
fix: show error
scopsy Dec 18, 2024
61cdacc
fix: connected passing
scopsy Dec 18, 2024
7349523
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 18, 2024
b67bd97
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 18, 2024
79b68fb
fix: autocomplete
scopsy Dec 18, 2024
1b9281e
fix: imports
scopsy Dec 18, 2024
3300ec3
fix: paths
scopsy Dec 18, 2024
dedea45
fix: export
scopsy Dec 18, 2024
7030627
fix: current env
scopsy Dec 18, 2024
491d6c2
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 18, 2024
aae62cc
fix: rename
scopsy Dec 18, 2024
6e1980d
Merge branch 'feat-integrations-page' of https://github.com/novuhq/no…
scopsy Dec 18, 2024
8b4e997
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 18, 2024
2604b05
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 18, 2024
596ca8a
Merge branch 'next' into feat-new-integrations-page-table
scopsy Dec 20, 2024
7adc69b
fix: lock file
scopsy Dec 20, 2024
0f15abb
Delete pnpm-lock.yaml
scopsy Dec 20, 2024
b5c056e
fix: pnpm file
scopsy Dec 20, 2024
413ae89
bull mq pro
scopsy Dec 20, 2024
1f3c693
fix: pnpm
scopsy Dec 20, 2024
f59147c
fix:
scopsy Dec 20, 2024
fb494b4
Merge branch 'feat-new-integrations-page-table' into feat-integration…
scopsy Dec 20, 2024
91aa132
Merge branch 'next' into feat-integrations-page
scopsy Dec 20, 2024
cbbf0fb
fix:
scopsy Dec 20, 2024
31094fc
fix: add space validation
scopsy Dec 20, 2024
b97f518
fix: primaryselecion
scopsy Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .source
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export class CreateIntegration {
if (command.identifier) {
const existingIntegrationWithIdentifier = await this.integrationRepository.findOne({
_organizationId: command.organizationId,
_environmentId: command.environmentId,
identifier: command.identifier,
});

Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/api/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function deleteIntegration({ id, environment }: { id: string; envir
}

export async function createIntegration(data: CreateIntegrationData, environment: IEnvironment) {
return await post('/integrations', {
return await post<{ data: IIntegration }>('/integrations', {
body: data,
environment: environment,
});
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/src/components/billing/contact-sales-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ApiServiceLevelEnum } from '@novu/shared';
import { HubspotForm } from '../hubspot-form';
import { HUBSPOT_FORM_IDS } from './utils/hubspot.constants';
import { useAuth } from '@/context/auth/hooks';
import { toast } from 'sonner';
import { showSuccessToast } from '../primitives/sonner-helpers';

interface ContactSalesModalProps {
isOpen: boolean;
Expand Down Expand Up @@ -37,7 +37,7 @@ export function ContactSalesModal({ isOpen, onClose, intendedApiServiceLevel }:
readonlyProperties={['email']}
focussedProperty="TICKET.content"
onFormSubmitted={() => {
toast.success('Thank you for contacting us! We will be in touch soon.');
showSuccessToast('Thank you for contacting us! We will be in touch soon.');
onClose();
}}
/>
Expand Down
6 changes: 3 additions & 3 deletions apps/dashboard/src/components/billing/plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { PlansRow } from './plans-row';
import { HighlightsRow } from './highlights-row';
import { Features } from './features';
import { cn } from '../../utils/ui';
import { toast } from 'sonner';
import { useTelemetry } from '../../hooks/use-telemetry';
import { TelemetryEvent } from '../../utils/telemetry';
import { useFetchSubscription } from '../../hooks/use-fetch-subscription';
import { showErrorToast, showSuccessToast } from '../primitives/sonner-helpers';

export function Plan() {
const track = useTelemetry();
Expand All @@ -21,15 +21,15 @@ export function Plan() {
const checkoutResult = new URLSearchParams(window.location.search).get('result');

if (checkoutResult === 'success') {
toast.success('Payment was successful.');
showSuccessToast('Payment was successful.');
track(TelemetryEvent.BILLING_PAYMENT_SUCCESS, {
billingInterval: selectedBillingInterval,
plan: data?.apiServiceLevel,
});
}

if (checkoutResult === 'canceled') {
toast.error('Payment was canceled.');
showErrorToast('Payment was canceled.');
track(TelemetryEvent.BILLING_PAYMENT_CANCELED, {
billingInterval: selectedBillingInterval,
plan: data?.apiServiceLevel,
Expand Down
11 changes: 10 additions & 1 deletion apps/dashboard/src/components/confirmation-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ConfirmationModalProps = {
description: ReactNode;
confirmButtonText: string;
isLoading?: boolean;
isConfirmDisabled?: boolean;
};

export const ConfirmationModal = ({
Expand All @@ -31,6 +32,7 @@ export const ConfirmationModal = ({
description,
confirmButtonText,
isLoading,
isConfirmDisabled,
}: ConfirmationModalProps) => {
return (
<Dialog modal open={open} onOpenChange={onOpenChange}>
Expand All @@ -53,7 +55,14 @@ export const ConfirmationModal = ({
</Button>
</DialogClose>

<Button type="button" size="sm" variant="primary" onClick={onConfirm} isLoading={isLoading}>
<Button
type="button"
size="sm"
variant="primary"
onClick={onConfirm}
isLoading={isLoading}
disabled={isConfirmDisabled}
>
{confirmButtonText}
</Button>
</DialogFooter>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/primitives/tabs';
import { CHANNEL_TYPE_TO_STRING } from '@/utils/channels';
import { IProviderConfig } from '@novu/shared';
import { IntegrationListItem } from './integration-list-item';
import { INTEGRATION_CHANNELS } from '../utils/channels';

type ChannelTabsProps = {
integrationsByChannel: Record<string, IProviderConfig[]>;
searchQuery: string;
onIntegrationSelect: (integrationId: string) => void;
};

export function ChannelTabs({ integrationsByChannel, searchQuery, onIntegrationSelect }: ChannelTabsProps) {
return (
<Tabs defaultValue={INTEGRATION_CHANNELS[0]} className="flex h-full flex-col">
<TabsList variant="regular" className="bg-background sticky top-0 z-10 gap-6 border-t-0 !px-3">
{INTEGRATION_CHANNELS.map((channel) => (
<TabsTrigger key={channel} value={channel} variant="regular" className="!px-0 !py-3">
{CHANNEL_TYPE_TO_STRING[channel]}
</TabsTrigger>
))}
</TabsList>

{INTEGRATION_CHANNELS.map((channel) => (
<TabsContent key={channel} value={channel} className="flex-1">
{integrationsByChannel[channel]?.length > 0 ? (
<div className="flex flex-col gap-4 p-3">
{integrationsByChannel[channel].map((integration) => (
<IntegrationListItem
key={integration.id}
integration={integration}
onClick={() => onIntegrationSelect(integration.id)}
/>
))}
</div>
) : (
<EmptyState channel={channel} searchQuery={searchQuery} />
)}
</TabsContent>
))}
</Tabs>
);
}

function EmptyState({ channel, searchQuery }: { channel: string; searchQuery: string }) {
return (
<div className="text-muted-foreground flex min-h-[200px] items-center justify-center text-center">
{searchQuery ? (
<p>No {channel.toLowerCase()} integrations match your search</p>
) : (
<p>No {channel.toLowerCase()} integrations available</p>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useNavigate, useParams } from 'react-router-dom';
import { providers as novuProviders } from '@novu/shared';
import { useCreateIntegration } from '@/hooks/use-create-integration';
import { useIntegrationList } from './hooks/use-integration-list';
import { useSidebarNavigationManager } from './hooks/use-sidebar-navigation-manager';
import { IntegrationSheet } from './integration-sheet';
import { ChannelTabs } from './channel-tabs';
import { IntegrationConfiguration } from './integration-configuration';
import { Button } from '../../../components/primitives/button';
import { handleIntegrationError } from './utils/handle-integration-error';
import { useSetPrimaryIntegration } from '../../../hooks/use-set-primary-integration';
import { SelectPrimaryIntegrationModal } from './modals/select-primary-integration-modal';
import { IntegrationFormData } from '../types';
import { useIntegrationPrimaryModal } from './hooks/use-integration-primary-modal';
import { useFetchIntegrations } from '@/hooks/use-fetch-integrations';
import { buildRoute, ROUTES } from '../../../utils/routes';
import { showSuccessToast } from '../../../components/primitives/sonner-helpers';

export type CreateIntegrationSidebarProps = {
isOpened: boolean;
};

export function CreateIntegrationSidebar({ isOpened }: CreateIntegrationSidebarProps) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something weird is happening when clicking on connect integration

Screen.Recording.2024-12-20.at.09.45.53.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not able to reproduce, might be some local thing going on. I will deploy this to staging and will see if it's still noticable

const navigate = useNavigate();
const { providerId } = useParams();

const providers = novuProviders;
const { mutateAsync: createIntegration, isPending } = useCreateIntegration();
const { mutateAsync: setPrimaryIntegration, isPending: isSettingPrimary } = useSetPrimaryIntegration();
const { integrations } = useFetchIntegrations();

const handleIntegrationSelect = (integrationId: string) => {
navigate(buildRoute(ROUTES.INTEGRATIONS_CONNECT_PROVIDER, { providerId: integrationId }), { replace: true });
};

const handleBack = () => {
navigate(ROUTES.INTEGRATIONS_CONNECT, { replace: true });
};

const { selectedIntegration, step, searchQuery, onIntegrationSelect, onBack } = useSidebarNavigationManager({
isOpened,
initialProviderId: providerId,
onIntegrationSelect: handleIntegrationSelect,
onBack: handleBack,
});

const { integrationsByChannel } = useIntegrationList(searchQuery);
const provider = providers?.find((p) => p.id === (selectedIntegration || providerId));
const {
isPrimaryModalOpen,
setIsPrimaryModalOpen,
pendingData,
handleSubmitWithPrimaryCheck,
handlePrimaryConfirm,
existingPrimaryIntegration,
isChannelSupportPrimary,
} = useIntegrationPrimaryModal({
onSubmit: handleCreateIntegration,
integrations,
channel: provider?.channel,
mode: 'create',
});

async function handleCreateIntegration(data: IntegrationFormData) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the integration is created using the existing identifier we just show the error toast but should additionally mark the field in the form and show the validation error there
Screenshot 2024-12-20 at 09 52 20

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find a quick one for this, I don't think it's adds a lot value for the amount of work. Will skip it for now

if (!provider) return;

try {
const integration = await createIntegration({
providerId: provider.id,
channel: provider.channel,
credentials: data.credentials,
name: data.name,
identifier: data.identifier,
active: data.active,
_environmentId: data.environmentId,
});

if (data.primary && isChannelSupportPrimary && data.active) {
await setPrimaryIntegration({ integrationId: integration.data._id });
}

showSuccessToast('Integration created successfully');

navigate(ROUTES.INTEGRATIONS);
} catch (error: unknown) {
handleIntegrationError(error, 'create');
}
}

const handleClose = () => {
navigate(ROUTES.INTEGRATIONS);
};

return (
<>
<IntegrationSheet
isOpened={isOpened}
onClose={handleClose}
provider={provider}
mode="create"
step={step}
onBack={onBack}
>
{step === 'select' ? (
<div className="scrollbar-custom flex-1 overflow-y-auto">
<ChannelTabs
integrationsByChannel={integrationsByChannel}
searchQuery={searchQuery}
onIntegrationSelect={onIntegrationSelect}
/>
</div>
) : provider ? (
<>
<div className="scrollbar-custom flex-1 overflow-y-auto">
<IntegrationConfiguration
isChannelSupportPrimary={isChannelSupportPrimary}
provider={provider}
onSubmit={handleSubmitWithPrimaryCheck}
mode="create"
/>
</div>
<div className="bg-background flex justify-end gap-2 border-t p-3">
<Button
type="submit"
form="integration-configuration-form"
isLoading={isPending || isSettingPrimary}
size="sm"
>
Create Integration
</Button>
</div>
</>
) : null}
</IntegrationSheet>

<SelectPrimaryIntegrationModal
isOpen={isPrimaryModalOpen}
onOpenChange={setIsPrimaryModalOpen}
onConfirm={handlePrimaryConfirm}
currentPrimaryName={existingPrimaryIntegration?.name}
newPrimaryName={pendingData?.name ?? ''}
isLoading={isPending || isSettingPrimary}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useMemo } from 'react';
import { ChannelTypeEnum, ChatProviderIdEnum, IProviderConfig, PushProviderIdEnum } from '@novu/shared';
import { providers, EmailProviderIdEnum, SmsProviderIdEnum } from '@novu/shared';
import { ProvidersIdEnum } from '@novu/shared';

export function useIntegrationList(searchQuery: string = '') {
const filteredIntegrations = useMemo(() => {
if (!providers) return [];

const filtered = providers.filter(
(provider: IProviderConfig) =>
provider.displayName.toLowerCase().includes(searchQuery.toLowerCase()) &&
provider.id !== EmailProviderIdEnum.Novu &&
provider.id !== SmsProviderIdEnum.Novu
);

const popularityOrder: Record<ChannelTypeEnum, ProvidersIdEnum[]> = {
[ChannelTypeEnum.EMAIL]: [
EmailProviderIdEnum.SendGrid,
EmailProviderIdEnum.Mailgun,
EmailProviderIdEnum.Postmark,
EmailProviderIdEnum.Mailjet,
EmailProviderIdEnum.Mandrill,
EmailProviderIdEnum.SES,
EmailProviderIdEnum.Outlook365,
EmailProviderIdEnum.CustomSMTP,
],
[ChannelTypeEnum.SMS]: [
SmsProviderIdEnum.Twilio,
SmsProviderIdEnum.Plivo,
SmsProviderIdEnum.SNS,
SmsProviderIdEnum.Nexmo,
SmsProviderIdEnum.Telnyx,
SmsProviderIdEnum.Sms77,
SmsProviderIdEnum.Infobip,
SmsProviderIdEnum.Gupshup,
],
[ChannelTypeEnum.PUSH]: [
PushProviderIdEnum.FCM,
PushProviderIdEnum.EXPO,
PushProviderIdEnum.APNS,
PushProviderIdEnum.OneSignal,
],
[ChannelTypeEnum.CHAT]: [
ChatProviderIdEnum.Slack,
ChatProviderIdEnum.Discord,
ChatProviderIdEnum.MsTeams,
ChatProviderIdEnum.Mattermost,
],
[ChannelTypeEnum.IN_APP]: [],
};

return filtered.sort((a, b) => {
const channelOrder = popularityOrder[a.channel] || [];
const indexA = channelOrder.indexOf(a.id);
const indexB = channelOrder.indexOf(b.id);

if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
}

if (indexA !== -1) return -1;
if (indexB !== -1) return 1;

return 0;
});
}, [providers, searchQuery]);

const integrationsByChannel = useMemo(() => {
return Object.values(ChannelTypeEnum).reduce(
(acc, channel) => {
acc[channel] = filteredIntegrations.filter((provider: IProviderConfig) => provider.channel === channel);

return acc;
},
{} as Record<ChannelTypeEnum, IProviderConfig[]>
);
}, [filteredIntegrations]);

return {
filteredIntegrations,
integrationsByChannel,
};
}
Loading
Loading