Make slack cards better

This commit is contained in:
Nate Kelley 2025-07-09 15:26:55 -06:00
parent 35a160301a
commit 6f6e70c54c
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 64 additions and 49 deletions

View File

@ -1,7 +1,4 @@
import type { import type { GetIntegrationResponse, GetChannelsResponse } from '@buster/server-shared/slack';
GetIntegrationResponse,
GetChannelsResponse
} from '@buster/server-shared/slack';
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
export const slackGetIntegration = queryOptions<GetIntegrationResponse>({ export const slackGetIntegration = queryOptions<GetIntegrationResponse>({
@ -9,7 +6,7 @@ export const slackGetIntegration = queryOptions<GetIntegrationResponse>({
}); });
export const slackGetChannels = queryOptions<GetChannelsResponse>({ export const slackGetChannels = queryOptions<GetChannelsResponse>({
queryKey: ['slack', 'channels'] queryKey: ['slack', 'channels', 'list']
}); });
export const slackQueryKeys = { export const slackQueryKeys = {

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import React from 'react'; import React, { useMemo } from 'react';
import { SettingsCards } from '../settings/SettingsCard'; import { SettingsCards } from '../settings/SettingsCard';
import { SlackIcon } from '@/components/ui/icons/customIcons/SlackIcon'; import { SlackIcon } from '@/components/ui/icons/customIcons/SlackIcon';
import { Text } from '@/components/ui/typography'; import { Text } from '@/components/ui/typography';
@ -15,25 +15,34 @@ import {
import { Dropdown, type DropdownItems } from '@/components/ui/dropdown'; import { Dropdown, type DropdownItems } from '@/components/ui/dropdown';
import { LinkSlash, Refresh2 } from '@/components/ui/icons'; import { LinkSlash, Refresh2 } from '@/components/ui/icons';
import { Select } from '@/components/ui/select'; import { Select } from '@/components/ui/select';
import { StatusCard } from '@/components/ui/card/StatusCard';
import { useMemoizedFn } from '@/hooks';
export const SlackIntegrations = React.memo(() => { export const SlackIntegrations = React.memo(() => {
const { data: slackIntegration } = useGetSlackIntegration(); const {
data: slackIntegration,
isFetched: isFetchedSlackIntegration,
error: slackIntegrationError
} = useGetSlackIntegration();
const isConnected = slackIntegration?.connected ?? false; const isConnected = slackIntegration?.connected ?? false;
return ( const cards = useMemo(() => {
<SettingsCards const sections = [
title="Slack" <ConnectSlackCard key="connect-slack-card" />,
description="Connect Buster with Slack" isConnected && <ConnectedSlackChannels key="connected-slack-channels" />
cards={[ ].filter(Boolean);
{ return [{ sections }];
sections: [ }, [isConnected]);
<ConnectSlackCard key="connect-slack-card" />,
isConnected && <ConnectedSlackChannels key="connected-slack-channels" /> if (slackIntegrationError) {
].filter(Boolean) return <StatusCard message="Error fetching slack integration." variant={'danger'} />;
} }
]}
/> if (!isFetchedSlackIntegration) {
); return <div className="bg-gray-light/50 h-24 w-full animate-pulse rounded"></div>;
}
return <SettingsCards title="Slack" description="Connect Buster with Slack" cards={cards} />;
}); });
SlackIntegrations.displayName = 'SlackIntegrations'; SlackIntegrations.displayName = 'SlackIntegrations';
@ -112,6 +121,21 @@ const ConnectedSlackChannels = React.memo(() => {
const channels = slackChannelsData?.channels || []; const channels = slackChannelsData?.channels || [];
const selectedChannelId = slackIntegration?.integration?.default_channel?.id; const selectedChannelId = slackIntegration?.integration?.default_channel?.id;
const items = useMemo(() => {
return channels.map((channel) => ({
label: channel.name,
value: channel.id
}));
}, [channels]);
const onChange = useMemoizedFn((channelId: string) => {
const channel = channels.find((channel) => channel.id === channelId);
if (!channel) return;
updateSlackIntegration({
default_channel: channel
});
});
return ( return (
<div className="flex items-center justify-between space-x-4"> <div className="flex items-center justify-between space-x-4">
<div className="flex flex-col space-y-0.5"> <div className="flex flex-col space-y-0.5">
@ -125,7 +149,7 @@ const ConnectedSlackChannels = React.memo(() => {
<> <>
{isFetchedSlackChannels && ( {isFetchedSlackChannels && (
<Button <Button
size={'small'} size={'tall'}
variant="ghost" variant="ghost"
suffix={<Refresh2 />} suffix={<Refresh2 />}
onClick={() => refetchSlackChannels()}> onClick={() => refetchSlackChannels()}>
@ -135,19 +159,10 @@ const ConnectedSlackChannels = React.memo(() => {
<Select <Select
className="w-fit min-w-40" className="w-fit min-w-40"
items={channels.map((channel) => ({ items={items}
label: channel.name,
value: channel.id
}))}
placeholder="Select a channel" placeholder="Select a channel"
value={selectedChannelId} value={selectedChannelId}
onChange={(channelId) => { onChange={onChange}
const channel = channels.find((channel) => channel.id === channelId);
if (!channel) return;
updateSlackIntegration({
default_channel: channel
});
}}
loading={isLoadingSlackChannels || isLoadingSlackIntegration} loading={isLoadingSlackChannels || isLoadingSlackIntegration}
/> />
</> </>

View File

@ -10,21 +10,24 @@ interface SettingsCardsProps {
}[]; }[];
} }
export const SettingsCards: React.FC<SettingsCardsProps> = ({ title, description, cards }) => { export const SettingsCards: React.FC<SettingsCardsProps> = React.memo(
return ( ({ title, description, cards }) => {
<div className="flex flex-col space-y-3.5"> return (
<div className="flex flex-col space-y-1.5"> <div className="flex flex-col space-y-3.5">
<Title as="h3" className="text-lg"> <div className="flex flex-col space-y-1.5">
{title} <Title as="h3" className="text-lg">
</Title> {title}
<Paragraph variant="secondary">{description}</Paragraph> </Title>
<Paragraph variant="secondary">{description}</Paragraph>
</div>
{cards.map((card, index) => (
<SettingsCard key={index} sections={card.sections} />
))}
</div> </div>
{cards.map((card, index) => ( );
<SettingsCard key={index} sections={card.sections} /> }
))} );
</div> SettingsCards.displayName = 'SettingsCards';
);
};
const SettingsCard = ({ sections }: { sections: React.ReactNode[] }) => { const SettingsCard = ({ sections }: { sections: React.ReactNode[] }) => {
return ( return (

View File

@ -8,7 +8,7 @@ const buster = packageJson.version;
export const PERSIST_TIME = 1000 * 60 * 60 * 24 * 7; // 7 days export const PERSIST_TIME = 1000 * 60 * 60 * 24 * 7; // 7 days
export const PERSISTED_QUERIES = [].map(hashKey); export const PERSISTED_QUERIES = [queryKeys.slackGetChannels.queryKey].map(hashKey);
export const PERMANENT_QUERIES = [queryKeys.getCurrencies.queryKey].map(hashKey); export const PERMANENT_QUERIES = [queryKeys.getCurrencies.queryKey].map(hashKey);