link-slack-channel-dialog.tsx6.1 KBView on GitHub /**
* Link Slack Channel Dialog Component
* Allows linking a Slack channel to an existing conversation
*/
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { Button } from '@/components/ui/button';
import { useState, useEffect } from 'react';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
interface LinkSlackChannelDialogProps {
conversationId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function LinkSlackChannelDialog({
conversationId,
open,
onOpenChange,
}: LinkSlackChannelDialogProps) {
const trpc = useTRPC();
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>('');
const [selectedChannelId, setSelectedChannelId] = useState<string>('');
const [isLinking, setIsLinking] = useState(false);
// Fetch Slack connections to get workspace IDs
const { data: connectionsData, isLoading: isLoadingConnections } = useQuery(
trpc.connections.list.queryOptions(),
);
// Fetch channels for selected workspace
const { data: channelsData, isLoading: isLoadingChannels } = useQuery({
...trpc.integrations.slack.listChannels.queryOptions({ workspaceId: selectedWorkspaceId }),
enabled: !!selectedWorkspaceId,
});
// Link channel mutation
const linkChannelMutation = useMutation(trpc.crm.linkSlackChannel.mutationOptions());
// Reset selections when dialog closes
useEffect(() => {
if (!open) {
setSelectedWorkspaceId('');
setSelectedChannelId('');
setIsLinking(false);
}
}, [open]);
const handleLink = async () => {
if (!selectedWorkspaceId || !selectedChannelId) {
toast.error('Please select both a workspace and a channel');
return;
}
setIsLinking(true);
try {
await linkChannelMutation.mutateAsync({
conversationId,
channelId: selectedChannelId,
workspaceId: selectedWorkspaceId,
});
onOpenChange(false);
// Reset selections
setSelectedWorkspaceId('');
setSelectedChannelId('');
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
toast.error(`Failed to link channel: ${errorMessage}`);
} finally {
setIsLinking(false);
}
};
const slackConnections = connectionsData?.slackConnections || [];
const channels = channelsData?.channels || [];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Link Slack Channel</DialogTitle>
<DialogDescription>
Select a Slack workspace and channel to link to this conversation.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Workspace Selection */}
<div className="space-y-2">
<label className="text-sm font-medium">Workspace</label>
<Select
value={selectedWorkspaceId}
onValueChange={(value) => {
setSelectedWorkspaceId(value);
setSelectedChannelId(''); // Reset channel selection when workspace changes
}}
disabled={isLoadingConnections || isLinking}
>
<SelectTrigger>
<SelectValue placeholder="Select a workspace" />
</SelectTrigger>
<SelectContent>
{slackConnections.map(
(conn: { id: string; name: string | null; externalId: string | null }) => (
<SelectItem key=[redacted] value={conn.externalId || ''}>
{conn.name || conn.externalId || 'Unknown Workspace'}
</SelectItem>
),
)}
</SelectContent>
</Select>
{slackConnections.length === 0 && !isLoadingConnections && (
<p className="text-muted-foreground text-sm">
No Slack workspaces connected. Please connect a Slack workspace first.
</p>
)}
</div>
{/* Channel Selection */}
{selectedWorkspaceId && (
<div className="space-y-2">
<label className="text-sm font-medium">Channel</label>
<Select
value={selectedChannelId}
onValueChange={setSelectedChannelId}
disabled={isLoadingChannels || isLinking}
>
<SelectTrigger>
<SelectValue placeholder="Select a channel" />
</SelectTrigger>
<SelectContent>
{isLoadingChannels ? (
<SelectItem value="loading" disabled>
Loading channels...
</SelectItem>
) : channels.length === 0 ? (
<SelectItem value="no-channels" disabled>
No channels available
</SelectItem>
) : (
channels.map((channel: { id: string; name: string; isPrivate: boolean }) => (
<SelectItem key=[redacted] value={channel.id}>
#{channel.name}
{channel.isPrivate && ' (Private)'}
</SelectItem>
))
)}
</SelectContent>
</Select>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isLinking}>
Cancel
</Button>
<Button
onClick={handleLink}
disabled={!selectedWorkspaceId || !selectedChannelId || isLinking}
>
{isLinking && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Link Channel
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}