crm-step.tsx10.0 KBView on GitHub
'use client';

import type { CrmProviderId } from '../../../../server/src/services/integrations/crm';
import { CrmIntegrationCard } from '@/modules/integrations/crm-integration-card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { Session } from '@/modules/auth/utils/auth-client';
import { useTRPC } from '@/providers/query-provider';
import { Button } from '@/components/ui/button';
import { useMemo, useState } from 'react';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';

interface CrmStepProps {
  integrationsData?: {
    integrations: Array<{
      id: string;
      name: string;
      connected: boolean;
      type: string;
      capabilities?: {
        connectionType?: 'oauth' | 'api_key' | 'manual' | 'none';
      };
      orgConfig?: {
        type: 'api_key' | 'oauth' | 'manual' | 'service_account';
        userMustAuthenticate: boolean;
        hasOrgCredentials: boolean;
      };
      isPersonalConnection?: boolean;
    }>;
  };
  session?: Session | null;
  selectedProviderId?: string;
}

export function CrmStep({ integrationsData, session, selectedProviderId }: CrmStepProps) {
  const trpc = useTRPC();
  const queryClient = useQueryClient();
  const [isActivating, setIsActivating] = useState<Record<string, boolean>>({});

  const { mutateAsync: activateOrgIntegration } = useMutation(
    trpc.integrations.activateOrgIntegration.mutationOptions(),
  );

  const crmProviders = useMemo(() => {
    const all = integrationsData?.integrations.filter((i) => i.type === 'crm') || [];
    return selectedProviderId ? all.filter((i) => i.id === selectedProviderId) : all;
  }, [integrationsData?.integrations, selectedProviderId]);

  // Handle activation of org-provisioned integrations
  const handleActivateOrgIntegration = async (providerId: string) => {
    const providerName = providerId.charAt(0).toUpperCase() + providerId.slice(1);

    try {
      setIsActivating((prev) => ({ ...prev, [providerId]: true }));
      const result = await activateOrgIntegration({ providerId });

      if (result.alreadyConnected) {
        toast.info(`${providerName} is already connected`);
      }

      queryClient.invalidateQueries({ queryKey=[redacted] });
    } catch (error) {
      console.error(`[CrmStep] Error activating ${providerId}:`, error);
      toast.error(
        `Failed to activate ${providerName}: ${error instanceof Error ? error.message : String(error)}`,
      );
    } finally {
      setIsActivating((prev) => ({ ...prev, [providerId]: false }));
    }
  };

  if (crmProviders.length === 0) {
    return (
      <div className="flex flex-col items-center space-y-4 text-center">
        <h3 className="text-lg font-medium">No CRM providers available</h3>
        <p className="text-muted-foreground text-sm">
          Please contact support if you believe this is an error.
        </p>
      </div>
    );
  }

  // Use the first provider as the default tab
  const defaultTab = crmProviders[0]?.id || '';

  if (selectedProviderId) {
    const provider = crmProviders[0];
    if (!provider) return null;
    const isConnected = provider.connected;
    const isOrgProvisioned =
      provider.orgConfig?.userMustAuthenticate === false &&
      provider.orgConfig?.hasOrgCredentials &&
      provider.capabilities?.connectionType === 'api_key';
    return (
      <div className="space-y-4">
        <div className="space-y-2">
          <div className="flex items-center justify-between">
            <h3 className="text-lg font-medium">Connect {provider.name}</h3>
            <div className="flex items-center gap-2">
              {provider.isPersonalConnection && isConnected && (
                <span className="rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-700 dark:bg-amber-900/30 dark:text-amber-300">Personal</span>
              )}
              {isConnected && <span className="flex items-center gap-2 text-sm font-medium text-green-600">āœ“ Connected</span>}
            </div>
          </div>
          <p className="text-muted-foreground text-sm">Sync your contacts, deals, and pipeline data with {provider.name}.</p>
          {provider.id === 'salesforce' && (
            <p className="text-muted-foreground text-xs">If you face any difficulty logging in, open a new tab and login to Salesforce first, then try again.</p>
          )}
        </div>
        {!isConnected && isOrgProvisioned && (
          <div className="space-y-4">
            <div className="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-800 dark:bg-blue-950/30">
              <span className="rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700 dark:bg-blue-900 dark:text-blue-300">Provided by your organization</span>
              <p className="mt-2 text-sm text-blue-700 dark:text-blue-300">Your organization has configured {provider.name}. Click below to activate your account.</p>
            </div>
            <Button size="lg" className="w-full" onClick={() => handleActivateOrgIntegration(provider.id)} disabled={isActivating[provider.id]}>
              {isActivating[provider.id] ? <><Loader2 className="mr-2 h-5 w-5 animate-spin" /><span>Activating...</span></> : `Activate ${provider.name}`}
            </Button>
          </div>
        )}
        {!isOrgProvisioned && <CrmIntegrationCard providerId={provider.id as CrmProviderId} userId={session?.user?.id} />}
        {isConnected && isOrgProvisioned && <CrmIntegrationCard providerId={provider.id as CrmProviderId} userId={session?.user?.id} />}
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <Tabs defaultValue={defaultTab} className="w-full">
        <div className="overflow-x-auto">
          <TabsList>
            {crmProviders.map((provider) => (
              <TabsTrigger key=[redacted] value={provider.id}>
                {provider.name}
              </TabsTrigger>
            ))}
          </TabsList>
        </div>

        {crmProviders.map((provider) => {
          const isConnected = provider.connected;
          const isOrgProvisioned =
            provider.orgConfig?.userMustAuthenticate === false &&
            provider.orgConfig?.hasOrgCredentials &&
            provider.capabilities?.connectionType === 'api_key';

          return (
            <TabsContent key=[redacted] value={provider.id} className="space-y-4 pt-4">
              <div className="space-y-2">
                <div className="flex items-center justify-between">
                  <h3 className="text-lg font-medium">Connect {provider.name}</h3>
                  <div className="flex items-center gap-2">
                    {provider.isPersonalConnection && isConnected && (
                      <span className="rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-700 dark:bg-amber-900/30 dark:text-amber-300">
                        Personal
                      </span>
                    )}
                    {isConnected && (
                      <span className="flex items-center gap-2 text-sm font-medium text-green-600">
                        āœ“ Connected
                      </span>
                    )}
                  </div>
                </div>
                <p className="text-muted-foreground text-sm">
                  Sync your contacts, deals, and pipeline data with {provider.name}.
                </p>
                {/* Show Salesforce-specific note if needed */}
                {provider.id === 'salesforce' && (
                  <p className="text-muted-foreground text-xs">
                    If you face any difficulty logging in by clicking the button below, open a new
                    tab and login to Salesforce first, then try clicking the button again.
                  </p>
                )}
              </div>

              {/* Org-provisioned API key providers - show Activate button */}
              {!isConnected && isOrgProvisioned && (
                <div className="space-y-4">
                  <div className="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-800 dark:bg-blue-950/30">
                    <div className="flex items-center gap-2">
                      <span className="rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700 dark:bg-blue-900 dark:text-blue-300">
                        Provided by your organization
                      </span>
                    </div>
                    <p className="mt-2 text-sm text-blue-700 dark:text-blue-300">
                      Your organization has configured {provider.name}. Click below to activate your
                      account.
                    </p>
                  </div>
                  <Button
                    size="lg"
                    className="w-full"
                    onClick={() => handleActivateOrgIntegration(provider.id)}
                    disabled={isActivating[provider.id]}
                  >
                    {isActivating[provider.id] ? (
                      <>
                        <Loader2 className="mr-2 h-5 w-5 animate-spin" />
                        <span>Activating...</span>
                      </>
                    ) : (
                      `Activate ${provider.name}`
                    )}
                  </Button>
                </div>
              )}

              {/* Regular connection flow for non-org-provisioned providers or OAuth */}
              {!isOrgProvisioned && (
                <CrmIntegrationCard
                  providerId={provider.id as CrmProviderId}
                  userId={session?.user?.id}
                />
              )}

              {/* Show card after activation for org-provisioned providers */}
              {isConnected && isOrgProvisioned && (
                <CrmIntegrationCard
                  providerId={provider.id as CrmProviderId}
                  userId={session?.user?.id}
                />
              )}
            </TabsContent>
          );
        })}
      </Tabs>
    </div>
  );
}