types: Abstract inline interfaces to @plunk/types

This commit is contained in:
Dries Augustyns
2026-01-01 10:52:43 +01:00
parent 38da58e5e9
commit 85c992a9f9
95 changed files with 1370 additions and 1350 deletions
+6 -34
View File
@@ -1,39 +1,11 @@
import {Button} from '@plunk/ui';
import type {Activity, CursorPaginatedResponse} from '@plunk/types';
import {network} from '../lib/network';
import {ActivityItem} from './ActivityItem';
import {Loader2} from 'lucide-react';
import {useCallback, useEffect, useMemo, useState} from 'react';
export enum ActivityType {
EVENT_TRIGGERED = 'event.triggered',
EMAIL_SENT = 'email.sent',
EMAIL_DELIVERED = 'email.delivered',
EMAIL_OPENED = 'email.opened',
EMAIL_CLICKED = 'email.clicked',
EMAIL_BOUNCED = 'email.bounced',
CAMPAIGN_SENT = 'campaign.sent',
CAMPAIGN_SCHEDULED = 'campaign.scheduled',
WORKFLOW_STARTED = 'workflow.started',
WORKFLOW_COMPLETED = 'workflow.completed',
WORKFLOW_EMAIL_SCHEDULED = 'workflow.email.scheduled',
}
export interface Activity {
id: string;
type: ActivityType;
timestamp: string;
contactEmail?: string;
contactId?: string;
metadata: Record<string, unknown>;
}
interface PaginatedActivities {
activities: Activity[];
nextCursor?: string;
hasMore: boolean;
}
interface ActivityFeedProps {
export interface ActivityFeedProps {
typeFilter?: string;
dateRangeDays?: number;
contactId?: string;
@@ -84,17 +56,17 @@ export function ActivityFeed({typeFilter, dateRangeDays = 30, contactId}: Activi
params.set('contactId', contactId);
}
const result = await network.fetch<PaginatedActivities>('GET', `/activity?${params.toString()}`);
const result = await network.fetch<CursorPaginatedResponse<Activity>>('GET', `/activity?${params.toString()}`);
if (cursor) {
// Append to existing activities
setActivities(prev => [...prev, ...result.activities]);
setActivities(prev => [...prev, ...result.data]);
} else {
// Replace activities
setActivities(result.activities);
setActivities(result.data);
}
setNextCursor(result.nextCursor);
setNextCursor(result.cursor);
setHasMore(result.hasMore);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load activities');
+1 -1
View File
@@ -1,5 +1,5 @@
import {Badge, Button, Collapsible, CollapsibleContent, CollapsibleTrigger} from '@plunk/ui';
import type {Activity} from './ActivityFeed';
import type {Activity} from '@plunk/types';
import {memo, useState} from 'react';
import {EmailPreviewModal} from './EmailPreviewModal';
import {
@@ -1,5 +1,5 @@
import {Card, CardContent, CardDescription, CardHeader, CardTitle, Alert} from '@plunk/ui';
import {AlertCircle, TrendingUp, Coins} from 'lucide-react';
import {Alert, Card, CardContent, CardDescription, CardHeader, CardTitle} from '@plunk/ui';
import {AlertCircle, Coins, TrendingUp} from 'lucide-react';
import {useBillingConsumption} from '../lib/hooks/useBillingConsumption';
import {useConfig} from '../lib/hooks/useConfig';
import {useCallback} from 'react';
+1 -1
View File
@@ -1,4 +1,4 @@
import {Card, CardContent, CardDescription, CardHeader, CardTitle, Alert, Badge, Button} from '@plunk/ui';
import {Alert, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle} from '@plunk/ui';
import {AlertCircle, Download, ExternalLink, FileText} from 'lucide-react';
import {useBillingInvoices} from '../lib/hooks/useBillingInvoices';
@@ -63,9 +63,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
});
const {data, isLoading} = useSWR<PaginatedCampaigns>(
open
? `/campaigns?page=${page}&pageSize=10${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}`
: null,
open ? `/campaigns?page=${page}&pageSize=10${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}` : null,
{revalidateOnFocus: false},
);
@@ -408,11 +406,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
<Button variant="outline" onClick={handleBack} className="flex-1">
Back
</Button>
<Button
onClick={handleConfirm}
className="flex-1"
disabled={!Object.values(selectedFields).some(v => v)}
>
<Button onClick={handleConfirm} className="flex-1" disabled={!Object.values(selectedFields).some(v => v)}>
Create Campaign
</Button>
</div>
@@ -27,7 +27,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
SelectValue,
} from '@plunk/ui';
import {Code2, Eye, Monitor, Smartphone, Tablet, Upload, X} from 'lucide-react';
import {network} from '../../lib/network';
@@ -298,7 +298,7 @@ export function EmailEditor({value, onChange, placeholder, subject, from, replyT
subscribeUrl: `${window.location.origin}/subscribe/${contact.id}`,
manageUrl: `${window.location.origin}/manage/${contact.id}`,
data: contact.data || {},
...(contact.data as Record<string, unknown> | null || {}),
...((contact.data as Record<string, unknown> | null) || {}),
};
return replaceVariables(currentHtml, contactData);
@@ -317,7 +317,7 @@ export function EmailEditor({value, onChange, placeholder, subject, from, replyT
subscribeUrl: `${window.location.origin}/subscribe/${contact.id}`,
manageUrl: `${window.location.origin}/manage/${contact.id}`,
data: contact.data || {},
...(contact.data as Record<string, unknown> | null || {}),
...((contact.data as Record<string, unknown> | null) || {}),
};
return replaceVariables(subject, contactData);
@@ -64,7 +64,7 @@ export function HtmlEditor({value, onChange, placeholder}: HtmlEditorProps) {
theme,
EditorView.lineWrapping, // Enable line wrapping for long lines
],
[theme]
[theme],
);
return (
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {Node, mergeAttributes} from '@tiptap/core';
import {ReactNodeViewRenderer, NodeViewWrapper, type ReactNodeViewProps} from '@tiptap/react';
import {mergeAttributes, Node} from '@tiptap/core';
import {NodeViewWrapper, type ReactNodeViewProps, ReactNodeViewRenderer} from '@tiptap/react';
import {useEffect, useRef, useState} from 'react';
interface ImageAttrs {
@@ -78,11 +78,14 @@ export function Toolbar({editor, onInsertVariable, onInsertImage, canUploadImage
setShowLinkInput(false);
}, [editor]);
const setColor = useCallback((color: string) => {
if (!editor) return;
editor.chain().focus().setColor(color).run();
setSelectedColor(color);
}, [editor]);
const setColor = useCallback(
(color: string) => {
if (!editor) return;
editor.chain().focus().setColor(color).run();
setSelectedColor(color);
},
[editor],
);
const applyCustomColor = useCallback(() => {
if (customColor && /^#[0-9A-F]{6}$/i.test(customColor)) {
@@ -31,7 +31,7 @@ const detectCustomHtmlPatterns = (html: string): boolean => {
const classValue = match[1];
if (!classValue) continue;
// Split by whitespace to get individual classes
const classes = classValue.split(/\s+/).filter((c) => c.length > 0);
const classes = classValue.split(/\s+/).filter(c => c.length > 0);
// Check if any class is NOT in the allowed list
const allowedPrefixes = [
'prose',
@@ -42,7 +42,7 @@ const detectCustomHtmlPatterns = (html: string): boolean => {
'selected',
'resize-handle',
];
const hasDisallowedClass = classes.some((cls) => !allowedPrefixes.some((prefix) => cls.startsWith(prefix)));
const hasDisallowedClass = classes.some(cls => !allowedPrefixes.some(prefix => cls.startsWith(prefix)));
if (hasDisallowedClass) {
hasCustomClasses = true;
break;
+1 -4
View File
@@ -234,10 +234,7 @@ export function QuickStart({setupState, isLoading}: QuickStartProps) {
<p className="text-xs text-neutral-600 leading-relaxed">{step.description}</p>
</div>
<Link href={step.link} className="flex-shrink-0">
<Button
size="sm"
variant={step.isCompleted ? 'outline' : 'default'}
>
<Button size="sm" variant={step.isCompleted ? 'outline' : 'default'}>
{step.linkText}
</Button>
</Link>
@@ -30,9 +30,7 @@ export function SecurityWarningBanner({status}: SecurityWarningBannerProps) {
<AlertTitle>{title}</AlertTitle>
<AlertDescription className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
<div className="space-y-2 flex-1">
<p className="text-sm font-medium">
Your project has exceeded the following security thresholds:
</p>
<p className="text-sm font-medium">Your project has exceeded the following security thresholds:</p>
<ul className={`list-disc list-inside space-y-1 text-sm ${messageColor}`}>
{issues.map((issue, idx) => (
<li key={idx}>{issue}</li>
@@ -13,7 +13,7 @@ import {
DialogHeader,
DialogTitle,
Input,
Label
Label,
} from '@plunk/ui';
import type {Template} from '@plunk/db';
import {ArrowLeft, FileText, Search} from 'lucide-react';
+2 -2
View File
@@ -12,7 +12,7 @@ import {
ReactFlow,
useEdgesState,
useNodesState,
useReactFlow
useReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import type {WorkflowStep} from '@plunk/db';
@@ -29,7 +29,7 @@ import {
Timer,
Trash2,
UserCog,
Webhook
Webhook,
} from 'lucide-react';
import {useCallback, useEffect, useMemo, useState} from 'react';
import dagre from 'dagre';
@@ -10,7 +10,7 @@ import {
Position,
ReactFlow,
useEdgesState,
useNodesState
useNodesState,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import type {WorkflowStep} from '@plunk/db';