fix: Consistency across buttons and labels
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import {Button, IconSpinner} from '@plunk/ui';
|
||||
import {Button, EmptyState, IconSpinner} from '@plunk/ui';
|
||||
import type {Activity, CursorPaginatedResponse} from '@plunk/types';
|
||||
import {network} from '../lib/network';
|
||||
import {ActivityItem} from './ActivityItem';
|
||||
import {EmptyState} from './EmptyState';
|
||||
import {Activity as ActivityIcon} from 'lucide-react';
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
|
||||
|
||||
@@ -133,8 +133,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'event.triggered':
|
||||
return {
|
||||
icon: Zap,
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-100',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.eventName === 'string' ? metadata.eventName : undefined) || 'Event triggered',
|
||||
description: undefined,
|
||||
badge: {
|
||||
@@ -150,8 +150,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.sent':
|
||||
return {
|
||||
icon: Send,
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-50',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email sent',
|
||||
description: metadata.campaignName
|
||||
? `Campaign: ${String(metadata.campaignName)}`
|
||||
@@ -169,8 +169,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.delivered':
|
||||
return {
|
||||
icon: CheckCircle,
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-50',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email delivered',
|
||||
description: metadata.campaignName
|
||||
? `Campaign: ${String(metadata.campaignName)}`
|
||||
@@ -186,8 +186,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.received':
|
||||
return {
|
||||
icon: Inbox,
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-100',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email received',
|
||||
description: typeof metadata.from === 'string' ? `From: ${metadata.from}` : 'Inbound email',
|
||||
badge: {
|
||||
@@ -199,8 +199,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.opened':
|
||||
return {
|
||||
icon: Eye,
|
||||
color: 'text-purple-600',
|
||||
bgColor: 'bg-purple-100',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email opened',
|
||||
description:
|
||||
typeof metadata.totalOpens === 'number' && metadata.totalOpens > 1
|
||||
@@ -219,8 +219,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.clicked':
|
||||
return {
|
||||
icon: MousePointerClick,
|
||||
color: 'text-orange-600',
|
||||
bgColor: 'bg-orange-100',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email clicked',
|
||||
description:
|
||||
typeof metadata.totalClicks === 'number' && metadata.totalClicks > 1
|
||||
@@ -239,8 +239,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.bounced':
|
||||
return {
|
||||
icon: XCircle,
|
||||
color: 'text-red-600',
|
||||
bgColor: 'bg-red-100',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-50',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Email bounced',
|
||||
description: (typeof metadata.error === 'string' ? metadata.error : undefined) || 'Email failed to deliver',
|
||||
badge: {
|
||||
@@ -252,8 +252,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'email.complaint':
|
||||
return {
|
||||
icon: ShieldAlert,
|
||||
color: 'text-red-600',
|
||||
bgColor: 'bg-red-100',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-50',
|
||||
title: (typeof metadata.subject === 'string' ? metadata.subject : undefined) || 'Spam complaint',
|
||||
description: metadata.campaignName
|
||||
? `Campaign: ${String(metadata.campaignName)}`
|
||||
@@ -269,8 +269,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'workflow.started':
|
||||
return {
|
||||
icon: Workflow,
|
||||
color: 'text-indigo-600',
|
||||
bgColor: 'bg-indigo-100',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.workflowName === 'string' ? metadata.workflowName : undefined) || 'Workflow started',
|
||||
description: `Status: ${String(metadata.status || 'unknown')}`,
|
||||
badge: {
|
||||
@@ -282,8 +282,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'workflow.completed':
|
||||
return {
|
||||
icon: CheckCheck,
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-50',
|
||||
title: (typeof metadata.workflowName === 'string' ? metadata.workflowName : undefined) || 'Workflow completed',
|
||||
description: metadata.exitReason
|
||||
? `Exit: ${String(metadata.exitReason)}`
|
||||
@@ -297,8 +297,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'campaign.scheduled':
|
||||
return {
|
||||
icon: Calendar,
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-50',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.campaignName === 'string' ? metadata.campaignName : undefined) || 'Campaign scheduled',
|
||||
description: metadata.subject
|
||||
? `${String(metadata.subject)}${metadata.totalRecipients ? ` • ${metadata.totalRecipients} recipients` : ''}`
|
||||
@@ -314,8 +314,8 @@ function getActivityConfig(activity: Activity): ActivityConfig {
|
||||
case 'workflow.email.scheduled':
|
||||
return {
|
||||
icon: Calendar,
|
||||
color: 'text-indigo-600',
|
||||
bgColor: 'bg-indigo-50',
|
||||
color: 'text-neutral-600',
|
||||
bgColor: 'bg-neutral-100',
|
||||
title: (typeof metadata.stepName === 'string' ? metadata.stepName : undefined) || 'Workflow email scheduled',
|
||||
description: metadata.workflowName
|
||||
? `Workflow: ${String(metadata.workflowName)}${metadata.subject ? ` • ${String(metadata.subject)}` : ''}`
|
||||
@@ -353,7 +353,7 @@ export const ActivityItem = memo(function ActivityItem({activity, isUpcoming = f
|
||||
<div className={`flex items-start gap-4 ${isUpcoming ? 'opacity-80' : ''}`}>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`h-10 w-10 rounded-lg ${config.bgColor} flex items-center justify-center flex-shrink-0 ${isUpcoming ? 'ring-2 ring-offset-2 ring-blue-200' : ''}`}
|
||||
className={`h-10 w-10 rounded-lg ${config.bgColor} flex items-center justify-center flex-shrink-0 ${isUpcoming ? 'ring-2 ring-offset-2 ring-neutral-300' : ''}`}
|
||||
>
|
||||
<Icon className={`h-5 w-5 ${config.color}`} />
|
||||
</div>
|
||||
@@ -410,7 +410,7 @@ export const ActivityItem = memo(function ActivityItem({activity, isUpcoming = f
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={`text-xs flex-shrink-0 whitespace-nowrap ${isUpcoming ? 'text-blue-600 font-medium' : 'text-neutral-400'}`}
|
||||
className={`text-xs flex-shrink-0 whitespace-nowrap ${isUpcoming ? 'text-neutral-700 font-medium' : 'text-neutral-400'}`}
|
||||
title={timestamp.toLocaleString()}
|
||||
>
|
||||
{relativeTime}
|
||||
|
||||
@@ -111,8 +111,8 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt
|
||||
{/* Total Usage */}
|
||||
<div className="border border-neutral-200 rounded-lg p-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 bg-blue-50 rounded-lg">
|
||||
<TrendingUp className="h-5 w-5 text-blue-600" />
|
||||
<div className="p-2 bg-neutral-100 rounded-lg">
|
||||
<TrendingUp className="h-5 w-5 text-neutral-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-500">Total Emails Sent</p>
|
||||
@@ -123,19 +123,19 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt
|
||||
|
||||
{/* Account Credits */}
|
||||
{consumptionData.credits && consumptionData.credits.hasCredits && (
|
||||
<div className="border border-green-200 rounded-lg p-6 bg-green-50">
|
||||
<div className="border border-neutral-200 rounded-lg p-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 bg-white rounded-lg shadow-sm border border-green-200">
|
||||
<Coins className="h-5 w-5 text-green-600" />
|
||||
<div className="p-2 bg-neutral-100 rounded-lg">
|
||||
<Coins className="h-5 w-5 text-neutral-600" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-green-700">Account Credits</p>
|
||||
<p className="text-2xl font-bold text-green-900">
|
||||
<p className="text-sm text-neutral-500">Account Credits</p>
|
||||
<p className="text-2xl font-bold text-neutral-900">
|
||||
{formatCurrency(consumptionData.credits.creditAmount, consumptionData.credits.currency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-green-700 mt-2">
|
||||
<p className="text-xs text-neutral-500 mt-2">
|
||||
Credits will be automatically applied to your upcoming invoices
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -90,16 +90,16 @@ export function BillingInvoices({projectId, hasSubscription, onManageBilling}: B
|
||||
const getStatusBadge = (status: string, paid: boolean) => {
|
||||
// Stripe invoices have status 'paid' when paid, or paid boolean is true
|
||||
if (paid || status === 'paid') {
|
||||
return <Badge variant="green">Paid</Badge>;
|
||||
return <Badge variant="success">Paid</Badge>;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'open':
|
||||
return <Badge variant="orange">Unpaid</Badge>;
|
||||
return <Badge variant="warning">Unpaid</Badge>;
|
||||
case 'draft':
|
||||
return <Badge variant="neutral">Draft</Badge>;
|
||||
case 'uncollectible':
|
||||
return <Badge variant="red">Uncollectible</Badge>;
|
||||
return <Badge variant="destructive">Uncollectible</Badge>;
|
||||
case 'void':
|
||||
return <Badge variant="neutral">Void</Badge>;
|
||||
default:
|
||||
@@ -169,7 +169,7 @@ export function BillingInvoices({projectId, hasSubscription, onManageBilling}: B
|
||||
<span className="font-medium">Total:</span>{' '}
|
||||
{formatCurrency(invoice.total, invoice.currency)}
|
||||
</p>
|
||||
<p className="text-orange-600 font-medium">
|
||||
<p className="text-amber-700 font-medium">
|
||||
<span className="font-medium">Amount Due:</span>{' '}
|
||||
{formatCurrency(invoice.amountDue, invoice.currency)}
|
||||
</p>
|
||||
|
||||
@@ -384,15 +384,15 @@ interface UsageDisplayProps {
|
||||
|
||||
const UsageDisplay = memo(function UsageDisplay({category, usage, currency}: UsageDisplayProps) {
|
||||
const statusColor = useMemo(() => {
|
||||
if (usage.isBlocked) return 'text-red-600';
|
||||
if (usage.isWarning) return 'text-orange-600';
|
||||
return 'text-green-600';
|
||||
if (usage.isBlocked) return 'text-red-700';
|
||||
if (usage.isWarning) return 'text-amber-700';
|
||||
return 'text-neutral-600';
|
||||
}, [usage.isBlocked, usage.isWarning]);
|
||||
|
||||
const progressColor = useMemo(() => {
|
||||
if (usage.isBlocked) return 'bg-red-600';
|
||||
if (usage.isWarning) return 'bg-orange-500';
|
||||
return 'bg-green-600';
|
||||
if (usage.isWarning) return 'bg-amber-500';
|
||||
return 'bg-neutral-900';
|
||||
}, [usage.isBlocked, usage.isWarning]);
|
||||
|
||||
const statusIcon = useMemo(() => {
|
||||
@@ -440,10 +440,10 @@ const UsageDisplay = memo(function UsageDisplay({category, usage, currency}: Usa
|
||||
<Progress value={Math.min(usage.percentage, 100)} className="h-2" indicatorClassName={progressColor} />
|
||||
|
||||
{usage.isBlocked && (
|
||||
<Alert className="mt-3 bg-red-50 border-red-200 text-red-900">
|
||||
<Alert variant="destructive" className="mt-3">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<div className="ml-2">
|
||||
<p className={'text-sm'}>
|
||||
<p className="text-sm">
|
||||
<strong>Limit reached:</strong> No more {category.toLowerCase()} emails can be sent this month.
|
||||
</p>
|
||||
</div>
|
||||
@@ -451,7 +451,7 @@ const UsageDisplay = memo(function UsageDisplay({category, usage, currency}: Usa
|
||||
)}
|
||||
|
||||
{usage.isWarning && !usage.isBlocked && (
|
||||
<Alert className="mt-3 bg-orange-50 border-orange-200 text-orange-900">
|
||||
<Alert variant="warning" className="mt-3">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<div className="ml-2">
|
||||
<p className="text-sm">
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
EmptyState,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -26,7 +27,6 @@ import type {PaginatedResponse} from '@plunk/types';
|
||||
import {ArrowLeft, Calendar, Mail, Users} from 'lucide-react';
|
||||
import {useState} from 'react';
|
||||
import useSWR from 'swr';
|
||||
import {EmptyState} from './EmptyState';
|
||||
|
||||
interface CampaignSelectionDialogProps {
|
||||
open: boolean;
|
||||
@@ -97,23 +97,16 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: CampaignStatus) => {
|
||||
const variants: Record<
|
||||
CampaignStatus,
|
||||
{variant: 'default' | 'secondary' | 'destructive' | 'outline'; label: string; className?: string}
|
||||
> = {
|
||||
DRAFT: {variant: 'secondary', label: 'Draft', className: 'bg-neutral-100 text-neutral-700'},
|
||||
SCHEDULED: {variant: 'default', label: 'Scheduled', className: 'bg-blue-100 text-blue-700'},
|
||||
SENDING: {variant: 'default', label: 'Sending', className: 'bg-purple-100 text-purple-700'},
|
||||
SENT: {variant: 'default', label: 'Sent', className: 'bg-green-100 text-green-700'},
|
||||
CANCELLED: {variant: 'destructive', label: 'Cancelled', className: 'bg-red-100 text-red-700'},
|
||||
const variants: Record<CampaignStatus, {variant: 'neutral' | 'default' | 'success'; label: string}> = {
|
||||
DRAFT: {variant: 'neutral', label: 'Draft'},
|
||||
SCHEDULED: {variant: 'default', label: 'Scheduled'},
|
||||
SENDING: {variant: 'default', label: 'Sending'},
|
||||
SENT: {variant: 'success', label: 'Sent'},
|
||||
CANCELLED: {variant: 'neutral', label: 'Cancelled'},
|
||||
};
|
||||
|
||||
const config = variants[status];
|
||||
return (
|
||||
<Badge variant={config.variant} className={config.className}>
|
||||
{config.label}
|
||||
</Badge>
|
||||
);
|
||||
return <Badge variant={config.variant}>{config.label}</Badge>;
|
||||
};
|
||||
|
||||
const getAudienceLabel = (campaign: Campaign) => {
|
||||
@@ -132,7 +125,7 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{step === 'configure' && (
|
||||
<Button variant="ghost" size="sm" onClick={handleBack} className="h-8 w-8 p-0">
|
||||
<Button variant="ghost" size="icon" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
EmptyState,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
IconSpinner,
|
||||
} from '@plunk/ui';
|
||||
import {AlertCircle, Database, Trash2, Zap} from 'lucide-react';
|
||||
import {EmptyState} from './EmptyState';
|
||||
import {toast} from 'sonner';
|
||||
import useSWR from 'swr';
|
||||
import {useActiveProject} from '../lib/contexts/ActiveProjectProvider';
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
ConfirmDialog,
|
||||
EmptyState,
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
} from '@plunk/ui';
|
||||
import {AnimatePresence, motion} from 'framer-motion';
|
||||
import {Check, CheckCircle2, ChevronDown, Copy, Globe, RefreshCw, Trash2, XCircle} from 'lucide-react';
|
||||
import {EmptyState} from './EmptyState';
|
||||
import {useConfig} from '../lib/hooks/useConfig';
|
||||
import {useAddDomain, useCheckDomainVerification, useDomains, useRemoveDomain} from '../lib/hooks/useDomains';
|
||||
|
||||
@@ -369,13 +369,12 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant="destructiveGhost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setDomainToRemove({id: domain.id, name: domain.domain});
|
||||
setShowRemoveDialog(true);
|
||||
}}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import type {LucideIcon} from 'lucide-react';
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
action?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function EmptyState({icon: Icon, title, description, action, className}: EmptyStateProps) {
|
||||
return (
|
||||
<div className={`text-center py-14 ${className ?? ''}`}>
|
||||
<div className="inline-flex items-center justify-center w-10 h-10 rounded-md border border-neutral-200 bg-neutral-50 mb-4">
|
||||
<Icon className="h-5 w-5 text-neutral-400" />
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-neutral-900 mb-1">{title}</h3>
|
||||
<p className="text-sm text-neutral-500 max-w-xs mx-auto leading-relaxed mb-5">{description}</p>
|
||||
{action}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -114,13 +114,7 @@ export function KeyValueEditor({initialData, onChange}: KeyValueEditorProps) {
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removePair(pair.id)}
|
||||
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Button type="button" variant="destructiveGhost" size="icon" onClick={() => removePair(pair.id)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ interface SecuritySettingsProps {
|
||||
|
||||
const STATUS_CONFIG: Record<SecurityLevel, {color: string; icon: typeof CheckCircle; bg: string; label: string}> = {
|
||||
healthy: {color: 'text-green-600', icon: CheckCircle, bg: 'bg-green-100', label: 'Healthy'},
|
||||
warning: {color: 'text-orange-600', icon: AlertTriangle, bg: 'bg-orange-100', label: 'Warning'},
|
||||
warning: {color: 'text-amber-700', icon: AlertTriangle, bg: 'bg-amber-100', label: 'Warning'},
|
||||
critical: {color: 'text-red-600', icon: AlertCircle, bg: 'bg-red-100', label: 'Critical'},
|
||||
};
|
||||
|
||||
|
||||
@@ -528,13 +528,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onRemove}
|
||||
className="mt-6 text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Button type="button" variant="destructiveGhost" size="sm" onClick={onRemove} className="mt-6">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -603,12 +597,12 @@ function FilterGroupComponent({group, onChange, onRemove, depth = 0, availableFi
|
||||
onChange(rest);
|
||||
}, [group, onChange]);
|
||||
|
||||
const bgColors = ['bg-white', 'bg-blue-50/50', 'bg-purple-50/50', 'bg-green-50/50'];
|
||||
const borderColors = ['border-neutral-300', 'border-blue-300', 'border-purple-300', 'border-green-300'];
|
||||
const bgColors = ['bg-white', 'bg-neutral-50', 'bg-white', 'bg-neutral-50'];
|
||||
const borderColors = ['border-neutral-200', 'border-neutral-200', 'border-neutral-300', 'border-neutral-300'];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`p-4 rounded-lg border-2 ${borderColors[depth % borderColors.length]} ${bgColors[depth % bgColors.length]}`}
|
||||
className={`p-4 rounded-lg border ${borderColors[depth % borderColors.length]} ${bgColors[depth % bgColors.length]}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -616,13 +610,7 @@ function FilterGroupComponent({group, onChange, onRemove, depth = 0, availableFi
|
||||
<span className="text-sm font-medium text-neutral-700">Filter Group {depth > 0 && `(Nested)`}</span>
|
||||
</div>
|
||||
{onRemove && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onRemove}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Button type="button" variant="destructiveGhost" size="sm" onClick={onRemove}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
@@ -645,10 +633,10 @@ function FilterGroupComponent({group, onChange, onRemove, depth = 0, availableFi
|
||||
<span className="text-xs font-medium text-neutral-600 uppercase tracking-wide">Nested Conditions</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
variant="destructiveGhost"
|
||||
size="sm"
|
||||
onClick={removeNestedCondition}
|
||||
className="h-6 text-xs text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
className="h-6 text-xs"
|
||||
>
|
||||
Remove nested
|
||||
</Button>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
EmptyState,
|
||||
IconSpinner,
|
||||
Input,
|
||||
Label,
|
||||
@@ -21,7 +22,6 @@ import type {PaginatedResponse} from '@plunk/types';
|
||||
import {ArrowLeft, FileText, Search} from 'lucide-react';
|
||||
import {useState} from 'react';
|
||||
import useSWR from 'swr';
|
||||
import {EmptyState} from './EmptyState';
|
||||
|
||||
interface TemplateSelectionDialogProps {
|
||||
open: boolean;
|
||||
@@ -106,7 +106,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{step === 'configure' && (
|
||||
<Button variant="ghost" size="sm" onClick={handleBack} className="h-8 w-8 p-0">
|
||||
<Button variant="ghost" size="icon" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
@@ -228,7 +228,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<CardTitle className="text-base truncate">{template.name}</CardTitle>
|
||||
<Badge className="capitalize" variant={template.type === 'MARKETING' ? 'info' : template.type === 'HEADLESS' ? 'warning' : 'success'}>
|
||||
<Badge className="capitalize" variant="neutral">
|
||||
{template.type.toLowerCase()}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -285,10 +285,7 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
|
||||
<div className="pb-4 mb-1 border-b border-neutral-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-neutral-900">{selectedTemplate.name}</span>
|
||||
<Badge
|
||||
className="capitalize"
|
||||
variant={selectedTemplate.type === 'MARKETING' ? 'info' : 'success'}
|
||||
>
|
||||
<Badge className="capitalize" variant="neutral">
|
||||
{selectedTemplate.type.toLowerCase()}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -73,12 +73,7 @@ export function UnpaidInvoiceBanner({projectId, hasSubscription}: UnpaidInvoiceB
|
||||
Please pay your outstanding invoices to avoid service interruption.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handlePayNow}
|
||||
className="ml-4 bg-red-600 hover:bg-red-700 text-white flex items-center gap-2"
|
||||
>
|
||||
<Button variant="destructive" size="sm" onClick={handlePayNow} className="ml-4">
|
||||
Pay Now
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user