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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ const buster = packageJson.version;
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);