import {useState} from 'react'; import { Alert, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@plunk/ui'; import {AlertCircle, Loader2, Trash2} from 'lucide-react'; import {toast} from 'sonner'; import useSWR from 'swr'; import {useActiveProject} from '../lib/contexts/ActiveProjectProvider'; import {network} from '../lib/network'; interface FieldData { field: string; type: 'string' | 'number' | 'boolean' | 'date'; coverage: number; } interface FieldUsage { usedInSegments: Array<{id: string; name: string}>; usedInCampaigns: Array<{id: string; name: string}>; contactCount: number; canDelete: boolean; } interface EventUsage { usedInSegments: Array<{id: string; name: string}>; usedInWorkflows: Array<{id: string; name: string}>; totalCount: number; uniqueContacts: number; canDelete: boolean; } export function DataManagementSettings() { const {activeProject} = useActiveProject(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedField, setSelectedField] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null); const [isDeleting, setIsDeleting] = useState(false); // Fetch contact fields const {data: fieldsData, mutate: mutateFields} = useSWR<{fields: FieldData[]; count: number}>( activeProject?.id ? `/contacts/fields` : null, ); // Fetch event names const {data: eventsData, mutate: mutateEvents} = useSWR<{eventNames: string[]}>( activeProject?.id ? `/events/names` : null, ); // Fetch field usage for selected field const {data: fieldUsage} = useSWR( selectedField ? `/contacts/fields/${encodeURIComponent(selectedField)}/usage` : null, ); // Fetch event usage for selected event const {data: eventUsage} = useSWR( selectedEvent ? `/events/${encodeURIComponent(selectedEvent)}/usage` : null, ); // Filter out standard fields and only show custom data fields const customFields = fieldsData?.fields.filter(f => f.field.startsWith('data.')) || []; // Filter out system events const customEvents = eventsData?.eventNames.filter( name => !name.startsWith('email.') && !name.startsWith('segment.') && !name.startsWith('contact.'), ) || []; const handleDeleteField = async () => { if (!selectedField || !fieldUsage?.canDelete) return; setIsDeleting(true); try { const result = await network.fetch<{deletedFrom: number}>( 'DELETE', `/contacts/fields/${encodeURIComponent(selectedField)}`, ); toast.success(`Field deleted from ${result.deletedFrom} contact(s)`); setDeleteDialogOpen(false); setSelectedField(null); mutateFields(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to delete field'); } finally { setIsDeleting(false); } }; const handleDeleteEvent = async () => { if (!selectedEvent || !eventUsage?.canDelete) return; setIsDeleting(true); try { const result = await network.fetch<{deletedCount: number}>( 'DELETE', `/events/${encodeURIComponent(selectedEvent)}`, ); toast.success(`Deleted ${result.deletedCount} event(s)`); setDeleteDialogOpen(false); setSelectedEvent(null); mutateEvents(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to delete event'); } finally { setIsDeleting(false); } }; const openFieldDeleteDialog = (field: string) => { setSelectedField(field); setSelectedEvent(null); setDeleteDialogOpen(true); }; const openEventDeleteDialog = (eventName: string) => { setSelectedEvent(eventName); setSelectedField(null); setDeleteDialogOpen(true); }; return (
{/* Custom Contact Fields */} Custom Contact Fields Manage custom fields stored in your contact data. You can only delete fields that are not used in any segments or campaigns. {customFields.length === 0 ? (

No custom fields found

) : ( Field Name Type Coverage Actions {customFields.map(field => ( {field.field.replace('data.', '')} {field.type} {field.coverage}% ))}
)}
{/* Custom Events */} Custom Events Manage custom events tracked in your project. You can only delete events that are not used in any segments or workflows. System events (email.*, segment.*) cannot be deleted. {customEvents.length === 0 ? (

No custom events found

) : ( Event Name Actions {customEvents.map(eventName => ( {eventName} ))}
)}
{/* Delete Confirmation Dialog */} {selectedField ? 'Delete Field' : 'Delete Event'} {selectedField ? `Are you sure you want to delete the field "${selectedField.replace('data.', '')}"?` : `Are you sure you want to delete the event "${selectedEvent}"?`} {selectedField && fieldUsage && (

Contacts with this field: {fieldUsage.contactCount}

Used in segments: {fieldUsage.usedInSegments.length}

Used in campaigns: {fieldUsage.usedInCampaigns.length}

{!fieldUsage.canDelete && (

Cannot delete this field

This field is currently used in:

{fieldUsage.usedInSegments.length > 0 && (
    {fieldUsage.usedInSegments.map(segment => (
  • Segment: {segment.name}
  • ))}
)} {fieldUsage.usedInCampaigns.length > 0 && (
    {fieldUsage.usedInCampaigns.map(campaign => (
  • Campaign: {campaign.name}
  • ))}
)}
)}
)} {selectedEvent && eventUsage && (

Total events: {eventUsage.totalCount}

Unique contacts: {eventUsage.uniqueContacts}

Used in segments: {eventUsage.usedInSegments.length}

Used in workflows: {eventUsage.usedInWorkflows.length}

{!eventUsage.canDelete && (

Cannot delete this event

This event is currently used in:

{eventUsage.usedInSegments.length > 0 && (
    {eventUsage.usedInSegments.map(segment => (
  • Segment: {segment.name}
  • ))}
)} {eventUsage.usedInWorkflows.length > 0 && (
    {eventUsage.usedInWorkflows.map(workflow => (
  • Workflow: {workflow.name}
  • ))}
)}
)}
)}
); }