Files
TransferTribe/src/app/pricing/page.tsx
T
twotalesanimation 2e688c8e52 Add TransferTribe app and fix critical transfer security flaws
The application source was untracked, so this commit brings it under
version control together with fixes for the issues found while auditing
it. Notable fixes:

Authorization
- Require a session and scope to senderEmail on /api/transfers/[id]
  (GET, DELETE) and .../resend. These were unauthenticated over an
  autoincrement id, so the ids could be walked to soft-delete any
  transfer, read sender/recipient metadata, or make the app mail
  arbitrary recipients.
- Require a session on the legacy /api/send and /api/chunk-upload
  endpoints, and take the sender from the session rather than a request
  field so transfers cannot be posted as another user.

Encryption
- Replace the chunk encryption scheme. Every chunk was encrypted under
  one shared session IV with its auth tag discarded, which reuses the
  AES-GCM keystream (XORing two ciphertexts recovers plaintext without
  the key) and left the stored file undecryptable, surfacing to users as
  a wrong-password error. Chunks are now self-contained frames carrying
  their own random IV and auth tag, behind a magic+salt header.
- Files written by the previous format now report UNSUPPORTED_FORMAT
  instead of a misleading password error.

Download
- Verify the password against the stored bcrypt hash before serving a
  file, and enforce expiresAt and DELETED/EXPIRED status.
- Move the password from the query string into a POST body so it stays
  out of access logs and Referer headers.
- Record a download only after successful authentication.
- Decrypt frame by frame through a stream instead of buffering the whole
  file, and encode the Content-Disposition filename per RFC 5987.

Data exposure
- /api/download ran before the password prompt and returned the full
  transfer row, including absolute server file paths. It now returns
  only what the pre-password screen renders; filenames, message and
  recipient are withheld until /api/verify succeeds.

Correctness
- Fix BigInt handling that made /api/transfers and /api/transfers/[id]
  fail unconditionally (JSON.stringify cannot serialize BigInt, and
  seeding a BigInt reduce with 0 throws).
- Fail loudly on a missing chunk during reassembly rather than silently
  writing a corrupt file.
- Meter plan usage in plaintext bytes rather than on-disk encrypted size.

Ignore /uploads: it holds runtime transfer payloads, not source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 08:48:53 +02:00

310 lines
13 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Header } from '@/components/header';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import {
Check,
X,
Zap,
Crown,
Gift,
Upload,
Clock,
Shield,
Users,
Mail,
Settings,
Infinity
} from 'lucide-react';
interface PricingTier {
name: string;
price: string;
period: string;
description: string;
icon: React.ComponentType<any>;
popular?: boolean;
features: {
name: string;
included: boolean;
value?: string;
}[];
cta: string;
color: string;
}
export default function PricingPage() {
const [isAnnual, setIsAnnual] = useState(false);
const tiers: PricingTier[] = [
{
name: 'Free',
price: '$0',
period: 'forever',
description: 'Perfect for personal use and trying out Transfer Tribe',
icon: Gift,
color: 'from-slate-500 to-slate-600',
cta: 'Get Started Free',
features: [
{ name: 'File size limit', included: true, value: '2GB per transfer' },
{ name: 'Monthly transfers', included: true, value: '10 transfers' },
{ name: 'Transfer expiration', included: true, value: '48 hours' },
{ name: 'Password protection', included: true },
{ name: 'Email notifications', included: true },
{ name: 'Basic support', included: true },
{ name: 'Custom branding', included: false },
{ name: 'Extended expiration', included: false },
{ name: 'Priority support', included: false },
{ name: 'Advanced analytics', included: false },
],
},
{
name: 'Rookie',
price: isAnnual ? '$8' : '$10',
period: isAnnual ? 'per month (billed annually)' : 'per month',
description: 'Ideal for professionals and small teams who need more flexibility',
icon: Zap,
popular: true,
color: 'from-blue-500 to-indigo-600',
cta: 'Start Free Trial',
features: [
{ name: 'File size limit', included: true, value: '10GB per transfer' },
{ name: 'Monthly transfers', included: true, value: '100 transfers' },
{ name: 'Transfer expiration', included: true, value: 'Up to 7 days' },
{ name: 'Password protection', included: true },
{ name: 'Email notifications', included: true },
{ name: 'Priority support', included: true },
{ name: 'Download tracking', included: true },
{ name: 'Transfer history', included: true, value: '6 months' },
{ name: 'Custom branding', included: false },
{ name: 'Advanced analytics', included: false },
],
},
{
name: 'Pro',
price: isAnnual ? '$20' : '$25',
period: isAnnual ? 'per month (billed annually)' : 'per month',
description: 'For businesses and power users who need maximum control',
icon: Crown,
color: 'from-purple-500 to-pink-600',
cta: 'Start Free Trial',
features: [
{ name: 'File size limit', included: true, value: 'Unlimited' },
{ name: 'Monthly transfers', included: true, value: 'Unlimited' },
{ name: 'Transfer expiration', included: true, value: 'Custom (1-30 days)' },
{ name: 'Password protection', included: true },
{ name: 'Email notifications', included: true },
{ name: 'Priority support', included: true },
{ name: 'Download tracking', included: true },
{ name: 'Transfer history', included: true, value: 'Unlimited' },
{ name: 'Custom branding', included: true },
{ name: 'Advanced analytics', included: true },
],
},
];
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<Header />
<main className="container mx-auto px-4 py-12">
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-slate-900 via-blue-600 to-indigo-600 dark:from-slate-100 dark:via-blue-400 dark:to-indigo-400 bg-clip-text text-transparent mb-4">
Choose Your Plan
</h1>
<p className="text-xl text-slate-600 dark:text-slate-400 max-w-2xl mx-auto mb-8">
Start free and upgrade as you grow. All plans include our core security features and 99.9% uptime guarantee.
</p>
<div className="flex items-center justify-center gap-4 mb-8">
<span className={`text-sm font-medium ${!isAnnual ? 'text-slate-900 dark:text-slate-100' : 'text-slate-500 dark:text-slate-400'}`}>
Monthly
</span>
<button
onClick={() => setIsAnnual(!isAnnual)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
isAnnual ? 'bg-blue-600' : 'bg-slate-200 dark:bg-slate-700'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
isAnnual ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
<span className={`text-sm font-medium ${isAnnual ? 'text-slate-900 dark:text-slate-100' : 'text-slate-500 dark:text-slate-400'}`}>
Annual
</span>
{isAnnual && (
<Badge className="bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400">
Save 20%
</Badge>
)}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-7xl mx-auto">
{tiers.map((tier, index) => {
const IconComponent = tier.icon;
return (
<Card
key={tier.name}
className={`relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm border-white/20 dark:border-slate-700/20 hover:bg-white/90 dark:hover:bg-slate-800/90 transition-all ${
tier.popular ? 'ring-2 ring-blue-500 scale-105' : ''
}`}
>
{tier.popular && (
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2">
<Badge className="bg-gradient-to-r from-blue-500 to-indigo-600 text-white px-4 py-1">
Most Popular
</Badge>
</div>
)}
<CardHeader className="text-center pb-8">
<div className={`w-16 h-16 bg-gradient-to-br ${tier.color} rounded-full flex items-center justify-center mx-auto mb-4`}>
<IconComponent className="w-8 h-8 text-white" />
</div>
<CardTitle className="text-2xl font-bold text-slate-900 dark:text-slate-100">
{tier.name}
</CardTitle>
<div className="mt-4">
<span className="text-4xl font-bold text-slate-900 dark:text-slate-100">
{tier.price}
</span>
<span className="text-slate-500 dark:text-slate-400 ml-2">
{tier.period}
</span>
</div>
<p className="text-slate-600 dark:text-slate-400 mt-4">
{tier.description}
</p>
</CardHeader>
<CardContent className="space-y-6">
<Button
className={`w-full ${
tier.popular
? 'bg-gradient-to-r from-blue-500 to-indigo-600 hover:from-blue-600 hover:to-indigo-700'
: 'bg-gradient-to-r from-slate-600 to-slate-700 hover:from-slate-700 hover:to-slate-800'
}`}
size="lg"
>
{tier.cta}
</Button>
<div className="space-y-4">
<h4 className="font-semibold text-slate-900 dark:text-slate-100">
What's included:
</h4>
<ul className="space-y-3">
{tier.features.map((feature, featureIndex) => (
<li key={featureIndex} className="flex items-start gap-3">
{feature.included ? (
<Check className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
) : (
<X className="w-5 h-5 text-slate-400 mt-0.5 flex-shrink-0" />
)}
<div className="flex-1">
<span className={`text-sm ${
feature.included
? 'text-slate-700 dark:text-slate-300'
: 'text-slate-400 dark:text-slate-500'
}`}>
{feature.name}
</span>
{feature.value && (
<span className={`block text-xs ${
feature.included
? 'text-slate-500 dark:text-slate-400'
: 'text-slate-400 dark:text-slate-500'
}`}>
{feature.value}
</span>
)}
</div>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
);
})}
</div>
<div className="mt-16 text-center">
<h2 className="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-8">
Why Choose Transfer Tribe?
</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 max-w-4xl mx-auto">
<div className="flex flex-col items-center space-y-3">
<div className="w-12 h-12 bg-gradient-to-br from-green-400 to-emerald-600 rounded-full flex items-center justify-center">
<Shield className="w-6 h-6 text-white" />
</div>
<h3 className="font-semibold text-slate-900 dark:text-slate-100">Enterprise Security</h3>
<p className="text-sm text-slate-600 dark:text-slate-400 text-center">
Bank-level encryption and security protocols
</p>
</div>
<div className="flex flex-col items-center space-y-3">
<div className="w-12 h-12 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full flex items-center justify-center">
<Upload className="w-6 h-6 text-white" />
</div>
<h3 className="font-semibold text-slate-900 dark:text-slate-100">Lightning Fast</h3>
<p className="text-sm text-slate-600 dark:text-slate-400 text-center">
Powered by Google Cloud for maximum speed
</p>
</div>
<div className="flex flex-col items-center space-y-3">
<div className="w-12 h-12 bg-gradient-to-br from-purple-400 to-purple-600 rounded-full flex items-center justify-center">
<Users className="w-6 h-6 text-white" />
</div>
<h3 className="font-semibold text-slate-900 dark:text-slate-100">Team Friendly</h3>
<p className="text-sm text-slate-600 dark:text-slate-400 text-center">
Perfect for individuals and teams of any size
</p>
</div>
<div className="flex flex-col items-center space-y-3">
<div className="w-12 h-12 bg-gradient-to-br from-orange-400 to-red-600 rounded-full flex items-center justify-center">
<Mail className="w-6 h-6 text-white" />
</div>
<h3 className="font-semibold text-slate-900 dark:text-slate-100">Email Integration</h3>
<p className="text-sm text-slate-600 dark:text-slate-400 text-center">
Seamless email delivery and notifications
</p>
</div>
</div>
</div>
<div className="mt-16 bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-8 text-center border border-white/20 dark:border-slate-700/20">
<h2 className="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-4">
Need a Custom Solution?
</h2>
<p className="text-slate-600 dark:text-slate-400 mb-6 max-w-2xl mx-auto">
For enterprise customers with specific requirements, we offer custom plans with dedicated support,
advanced integrations, and tailored features.
</p>
<Button variant="outline" size="lg" className="mr-4">
Contact Sales
</Button>
<Button variant="outline" size="lg">
View Enterprise Features
</Button>
</div>
<div className="mt-12 text-center">
<p className="text-sm text-slate-500 dark:text-slate-400">
All plans include a 14-day free trial. No credit card required. Cancel anytime.
</p>
</div>
</main>
</div>
);
}