types: Abstract inline interfaces to @plunk/types
This commit is contained in:
@@ -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,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,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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user