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>
  );
}