CustomCrmSection.tsx4.5 KBView on GitHub
'use client';

import { useMutation, useQuery } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { useEffect, useState } from 'react';
import { Database } from 'lucide-react';
import { toast } from 'sonner';

type CustomCrmSectionProps = {
  userId?: string;
};

export function CustomCrmSection({ userId }: CustomCrmSectionProps) {
  const trpc = useTRPC();
  const [url, setUrl] = useState('');
  const [secret, setSecret] = useState('');
  const [secretHeader, setSecretHeader] = useState('X-Cedar-Secret');

  const { data, refetch, isLoading } = useQuery(
    trpc.connections.getWebhookExportConfig.queryOptions(userId ? { userId } : undefined),
  );
  const { mutateAsync: updateWebhookExportConfig, isPending } = useMutation(
    trpc.connections.updateWebhookExportConfig.mutationOptions(),
  );

  useEffect(() => {
    if (!data) return;
    setUrl(data.url ?? '');
    setSecret('');
    setSecretHeader(data.secretHeader || 'X-Cedar-Secret');
  }, [data]);

  const handleSave = async () => {
    if (!url.trim()) {
      toast.error('Enter a Custom CRM endpoint URL');
      return;
    }

    try {
      await updateWebhookExportConfig({
        url: url.trim(),
        secret=[redacted] || undefined,
        secretHeader: secretHeader.trim() || undefined,
        userId,
      });
      setSecret('');
      await refetch();
    } catch (error) {
      console.error('Failed to save Custom CRM config:', error);
      toast.error('Failed to save Custom CRM endpoint');
    }
  };

  const handleClear = async () => {
    try {
      await updateWebhookExportConfig({ url: null, userId });
      setUrl('');
      setSecret('');
      setSecretHeader('X-Cedar-Secret');
      await refetch();
    } catch (error) {
      console.error('Failed to clear Custom CRM config:', error);
      toast.error('Failed to clear Custom CRM endpoint');
    }
  };

  return (
    <div className="space-y-4">
      <div className="flex items-start justify-between gap-4">
        <div className="flex items-center gap-3">
          <div className="bg-primary/10 flex h-10 w-10 shrink-0 items-center justify-center rounded-lg">
            <Database className="text-primary h-5 w-5" />
          </div>
          <div>
            <h3 className="text-base font-semibold">Custom CRM</h3>
            <p className="text-muted-foreground text-sm">
              Send Cedar conversation field updates to a custom CRM webhook.
            </p>
          </div>
        </div>
        <Badge variant={data?.configured ? 'default' : 'secondary'}>
          {data?.configured ? 'Connected' : 'Not configured'}
        </Badge>
      </div>

      <div className="space-y-4">
        <div className="grid gap-2">
          <Label htmlFor="custom-crm-url">Endpoint URL</Label>
          <Input
            id="custom-crm-url"
            value={url}
            onChange={(event) => setUrl(event.target.value)}
            placeholder="https://example.com/webhooks/cedar-update-deal"
            disabled={isLoading || isPending}
          />
        </div>

        <div className="grid gap-2">
          <Label htmlFor="custom-crm-secret">Secret</Label>
          <Input
            id="custom-crm-secret"
            type="password"
            value={secret}
            onChange={(event) => setSecret(event.target.value)}
            placeholder={data?.hasSecret ? 'Leave blank to keep existing secret' : 'Secret value'}
            disabled={isLoading || isPending}
          />
        </div>

        <div className="grid gap-2">
          <Label htmlFor="custom-crm-secret-header">Secret header</Label>
          <Input
            id="custom-crm-secret-header"
            value={secretHeader}
            onChange={(event) => setSecretHeader(event.target.value)}
            placeholder="X-Cedar-Secret"
            disabled={isLoading || isPending}
          />
        </div>

        <div className="flex justify-end gap-2">
          <Button
            type="button"
            variant="outline"
            onClick={handleClear}
            disabled={isLoading || isPending || !data?.configured}
          >
            Clear
          </Button>
          <Button type="button" onClick={handleSave} disabled={isLoading || isPending}>
            Save
          </Button>
        </div>
      </div>
    </div>
  );
}