chore: Resolve linting warnings

This commit is contained in:
Dries Augustyns
2025-12-29 15:29:29 +01:00
parent 72dffe12e5
commit d56ba0cf9b
5 changed files with 26 additions and 152 deletions
-1
View File
@@ -5,7 +5,6 @@ import signale from 'signale';
import {prisma} from '../database/prisma.js';
import {redis} from '../database/redis.js';
import {ValidationError} from '../exceptions/index.js';
import {Keys} from './keys.js';
import {WorkflowExecutionService} from './WorkflowExecutionService.js';
@@ -1342,7 +1342,6 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
it('should match contacts who have triggered the event', async () => {
const prisma = getPrismaClient();
const match = await factories.createContact({projectId});
const noMatch = await factories.createContact({projectId});
// Create event for match contact
await prisma.event.create({
@@ -1365,7 +1364,6 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
it('should not match contacts who have not triggered the event', async () => {
const prisma = getPrismaClient();
const contact1 = await factories.createContact({projectId});
const contact2 = await factories.createContact({projectId});
// Create different event
await prisma.event.create({
@@ -1682,10 +1680,6 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
projectId,
data: {plan: 'basic'},
});
const noMatch2 = await factories.createContact({
projectId,
data: {plan: 'premium'},
});
// match and noMatch1 have the event, but only match has premium plan
await prisma.event.create({
@@ -1,109 +0,0 @@
import {motion} from 'framer-motion';
import React, {useState} from 'react';
interface PricingData {
name: string;
calculatePrice: (emails: number) => number;
color?: string;
}
interface PricingCalculatorProps {
competitors: PricingData[];
defaultVolume?: number;
}
const volumeOptions = [
{label: '10K emails/month', value: 10000},
{label: '100K emails/month', value: 100000},
{label: '500K emails/month', value: 500000},
{label: '1M emails/month', value: 1000000},
];
/**
* Interactive pricing calculator comparing Plunk with competitors
*/
export function PricingCalculator({competitors, defaultVolume = 100000}: PricingCalculatorProps) {
const [volume, setVolume] = useState(defaultVolume);
const plunkPrice = volume * 0.001;
return (
<motion.div
initial={{opacity: 0, y: 20}}
whileInView={{opacity: 1, y: 0}}
viewport={{once: true}}
transition={{duration: 0.7, ease: [0.22, 1, 0.36, 1]}}
className={'overflow-hidden rounded-xl border border-neutral-200 bg-white'}
>
{/* Header */}
<div className={'border-b border-neutral-200 bg-neutral-50 p-8'}>
<h3 className={'text-2xl font-bold text-neutral-900'}>Pricing Calculator</h3>
<p className={'mt-2 text-sm text-neutral-600'}>Compare costs across platforms at different email volumes</p>
</div>
{/* Volume Selector */}
<div className={'p-8'}>
<label className={'block text-sm font-semibold text-neutral-900'}>Monthly Email Volume</label>
<div className={'mt-4 grid grid-cols-2 gap-3 sm:grid-cols-4'}>
{volumeOptions.map(option => (
<button
key={option.value}
onClick={() => setVolume(option.value)}
className={`rounded-lg border px-4 py-3 text-sm font-medium transition ${
volume === option.value
? 'border-neutral-900 bg-neutral-900 text-white'
: 'border-neutral-300 bg-white text-neutral-900 hover:border-neutral-400'
}`}
>
{option.label}
</button>
))}
</div>
</div>
{/* Results */}
<div className={'space-y-4 p-8 pt-0'}>
{/* Plunk */}
<div className={'rounded-lg border border-neutral-900 bg-neutral-50 p-6'}>
<div className={'flex items-center justify-between'}>
<div>
<span className={'text-lg font-bold text-neutral-900'}>Plunk</span>
<span className={'ml-3 rounded-full bg-neutral-900 px-3 py-1 text-xs font-medium text-white'}>
Cheapest
</span>
</div>
<div className={'text-right'}>
<div className={'text-3xl font-bold text-neutral-900'}>${plunkPrice.toFixed(2)}</div>
<div className={'text-sm text-neutral-600'}>per month</div>
</div>
</div>
</div>
{/* Competitors */}
{competitors.map((competitor, index) => {
const price = competitor.calculatePrice(volume);
const savings = ((price - plunkPrice) / price) * 100;
return (
<div key={competitor.name} className={'rounded-lg border border-neutral-200 bg-white p-6'}>
<div className={'flex items-center justify-between'}>
<div>
<span className={'text-lg font-semibold text-neutral-900'}>{competitor.name}</span>
</div>
<div className={'text-right'}>
<div className={'text-3xl font-bold text-neutral-900'}>${price.toFixed(2)}</div>
<div className={'text-sm text-neutral-600'}>per month</div>
</div>
</div>
{savings > 0 && (
<div className={'mt-3 text-sm text-neutral-600'}>
Save <span className={'font-semibold text-neutral-900'}>{savings.toFixed(0)}%</span> with Plunk
</div>
)}
</div>
);
})}
</div>
</motion.div>
);
}
-1
View File
@@ -3,4 +3,3 @@ export * from './Footer';
export * from './ComparisonTable';
export * from './FAQSection';
export * from './CodeBlock';
export * from './PricingCalculator';
+26 -35
View File
@@ -14,7 +14,7 @@ import {
DialogTitle,
Input,
Label,
Switch
Switch,
} from '@plunk/ui';
import type {Contact} from '@plunk/db';
import {DashboardLayout} from '../../components/DashboardLayout';
@@ -34,7 +34,7 @@ import {
Search,
Trash2,
Upload,
XCircle
XCircle,
} from 'lucide-react';
import {NextSeo} from 'next-seo';
import Link from 'next/link';
@@ -237,27 +237,15 @@ export default function ContactsPage() {
{selectedContacts.size} contact{selectedContacts.size !== 1 ? 's' : ''} selected
</span>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleBulkAction('subscribe')}
>
<Button variant="outline" size="sm" onClick={() => handleBulkAction('subscribe')}>
<MailCheck className="h-4 w-4 mr-1.5" />
Subscribe
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleBulkAction('unsubscribe')}
>
<Button variant="outline" size="sm" onClick={() => handleBulkAction('unsubscribe')}>
<MailX className="h-4 w-4 mr-1.5" />
Unsubscribe
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleBulkAction('delete')}
>
<Button variant="outline" size="sm" onClick={() => handleBulkAction('delete')}>
<Trash2 className="h-4 w-4 mr-1.5" />
Delete
</Button>
@@ -625,8 +613,7 @@ interface ImportResult {
function ImportContactsDialog({open, onOpenChange, onSuccess}: ImportContactsDialogProps) {
const [file, setFile] = useState<File | null>(null);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobId, setJobId] = useState<string | null>(null);
const [, setJobId] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState<'idle' | 'uploading' | 'processing' | 'completed' | 'failed'>('idle');
@@ -978,7 +965,7 @@ interface BulkActionResult {
}
function BulkActionsDialog({open, onOpenChange, operation, contactIds, onSuccess}: BulkActionsDialogProps) {
const [jobId, setJobId] = useState<string | null>(null);
const [, setJobId] = useState<string | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const [progress, setProgress] = useState(0);
const [status, setStatus] = useState<'idle' | 'processing' | 'completed' | 'failed'>('idle');
@@ -1097,19 +1084,27 @@ function BulkActionsDialog({open, onOpenChange, operation, contactIds, onSuccess
const getOperationLabel = () => {
switch (operation) {
case 'subscribe': return 'Subscribe';
case 'unsubscribe': return 'Unsubscribe';
case 'delete': return 'Delete';
default: return 'Process';
case 'subscribe':
return 'Subscribe';
case 'unsubscribe':
return 'Unsubscribe';
case 'delete':
return 'Delete';
default:
return 'Process';
}
};
const getOperationColor = () => {
switch (operation) {
case 'subscribe': return 'green';
case 'unsubscribe': return 'yellow';
case 'delete': return 'red';
default: return 'blue';
case 'subscribe':
return 'green';
case 'unsubscribe':
return 'yellow';
case 'delete':
return 'red';
default:
return 'blue';
}
};
@@ -1129,14 +1124,12 @@ function BulkActionsDialog({open, onOpenChange, operation, contactIds, onSuccess
{contactIds.length !== 1 ? 's' : ''}?
</p>
{operation === 'delete' && (
<p className="text-sm text-red-600 mt-2 font-medium">
This action cannot be undone.
</p>
<p className="text-sm text-red-600 mt-2 font-medium">This action cannot be undone.</p>
)}
</div>
)}
{(status === 'processing') && (
{status === 'processing' && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-neutral-600">Processing contacts...</span>
@@ -1196,9 +1189,7 @@ function BulkActionsDialog({open, onOpenChange, operation, contactIds, onSuccess
<XCircle className="h-5 w-5" />
<span className="font-medium">Operation failed</span>
</div>
<p className="text-sm text-red-800 mt-1">
{errorMessage || 'Please try again.'}
</p>
<p className="text-sm text-red-800 mt-1">{errorMessage || 'Please try again.'}</p>
</div>
)}
</div>