Files
smoothschedule/frontend/src/pages/TrialExpired.tsx
poduck 2e111364a2 Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes:
- Django backend with multi-tenancy (django-tenants)
- React + TypeScript frontend with Vite
- Platform administration API with role-based access control
- Authentication system with token-based auth
- Quick login dev tools for testing different user roles
- CORS and CSRF configuration for local development
- Docker development environment setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 01:43:20 -05:00

223 lines
9.7 KiB
TypeScript

import React from 'react';
import { useNavigate, useOutletContext } from 'react-router-dom';
import { Clock, ArrowRight, Check, X, CreditCard, TrendingDown, AlertTriangle } from 'lucide-react';
import { User, Business } from '../types';
/**
* TrialExpired Page
* Shown when a business trial has expired and they need to either upgrade or downgrade to free tier
*/
const TrialExpired: React.FC = () => {
const navigate = useNavigate();
const { user, business } = useOutletContext<{ user: User; business: Business }>();
const isOwner = user.role === 'owner';
// Feature comparison based on tier
const getTierFeatures = (tier: string | undefined) => {
switch (tier) {
case 'Professional':
return [
{ name: 'Unlimited appointments', included: true },
{ name: 'Online booking portal', included: true },
{ name: 'Email notifications', included: true },
{ name: 'SMS reminders', included: true },
{ name: 'Custom branding', included: true },
{ name: 'Advanced analytics', included: true },
{ name: 'Payment processing', included: true },
{ name: 'Priority support', included: true },
];
case 'Business':
return [
{ name: 'Everything in Professional', included: true },
{ name: 'Multiple locations', included: true },
{ name: 'Team management', included: true },
{ name: 'API access', included: true },
{ name: 'Custom domain', included: true },
{ name: 'White-label options', included: true },
{ name: 'Dedicated account manager', included: true },
];
case 'Enterprise':
return [
{ name: 'Everything in Business', included: true },
{ name: 'Unlimited users', included: true },
{ name: 'Custom integrations', included: true },
{ name: 'SLA guarantee', included: true },
{ name: 'Custom contract terms', included: true },
{ name: '24/7 phone support', included: true },
{ name: 'On-premise deployment option', included: true },
];
default:
return [];
}
};
const freeTierFeatures = [
{ name: 'Up to 50 appointments/month', included: true },
{ name: 'Basic online booking', included: true },
{ name: 'Email notifications', included: true },
{ name: 'SMS reminders', included: false },
{ name: 'Custom branding', included: false },
{ name: 'Advanced analytics', included: false },
{ name: 'Payment processing', included: false },
{ name: 'Priority support', included: false },
];
const paidTierFeatures = getTierFeatures(business.plan);
const handleUpgrade = () => {
navigate('/payments');
};
const handleDowngrade = () => {
if (window.confirm('Are you sure you want to downgrade to the Free plan? You will lose access to premium features immediately.')) {
// TODO: Implement downgrade to free tier API call
console.log('Downgrading to free tier...');
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center p-4">
<div className="max-w-4xl w-full">
{/* Main Card */}
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-red-500 to-orange-500 text-white p-8 text-center">
<div className="flex justify-center mb-4">
<div className="bg-white/20 backdrop-blur-sm rounded-full p-4">
<Clock size={48} />
</div>
</div>
<h1 className="text-3xl font-bold mb-2">Your 14-Day Trial Has Expired</h1>
<p className="text-white/90 text-lg">
Your trial of the {business.plan} plan ended on{' '}
{business.trialEnd ? new Date(business.trialEnd).toLocaleDateString() : 'N/A'}
</p>
</div>
{/* Content */}
<div className="p-8">
<div className="mb-8">
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-4">
What happens now?
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
You have two options to continue using SmoothSchedule:
</p>
</div>
{/* Feature Comparison */}
<div className="grid md:grid-cols-2 gap-6 mb-8">
{/* Free Tier Card */}
<div className="border-2 border-gray-200 dark:border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">Free Plan</h3>
<span className="px-3 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full text-sm font-medium">
$0/month
</span>
</div>
<ul className="space-y-3 mb-6">
{freeTierFeatures.map((feature, idx) => (
<li key={idx} className="flex items-start gap-2">
{feature.included ? (
<Check size={20} className="text-green-500 flex-shrink-0 mt-0.5" />
) : (
<X size={20} className="text-gray-400 flex-shrink-0 mt-0.5" />
)}
<span className={feature.included ? 'text-gray-700 dark:text-gray-300' : 'text-gray-400 dark:text-gray-500 line-through'}>
{feature.name}
</span>
</li>
))}
</ul>
{isOwner && (
<button
onClick={handleDowngrade}
className="w-full px-4 py-3 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors font-medium flex items-center justify-center gap-2"
>
<TrendingDown size={20} />
Downgrade to Free
</button>
)}
</div>
{/* Paid Tier Card */}
<div className="border-2 border-blue-500 dark:border-blue-400 rounded-xl p-6 bg-blue-50/50 dark:bg-blue-900/20 relative">
<div className="absolute top-4 right-4">
<span className="px-3 py-1 bg-blue-500 text-white rounded-full text-xs font-bold uppercase tracking-wide">
Recommended
</span>
</div>
<div className="mb-4">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-1">
{business.plan} Plan
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">Continue where you left off</p>
</div>
<ul className="space-y-3 mb-6">
{paidTierFeatures.slice(0, 8).map((feature, idx) => (
<li key={idx} className="flex items-start gap-2">
<Check size={20} className="text-blue-500 flex-shrink-0 mt-0.5" />
<span className="text-gray-700 dark:text-gray-300">{feature.name}</span>
</li>
))}
{paidTierFeatures.length > 8 && (
<li className="text-sm text-gray-500 dark:text-gray-400 italic">
+ {paidTierFeatures.length - 8} more features
</li>
)}
</ul>
{isOwner && (
<button
onClick={handleUpgrade}
className="w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-500 text-white rounded-lg hover:from-blue-700 hover:to-blue-600 transition-all font-medium flex items-center justify-center gap-2 shadow-lg shadow-blue-500/30"
>
<CreditCard size={20} />
Upgrade Now
<ArrowRight size={20} />
</button>
)}
</div>
</div>
{/* Important Notice */}
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<div className="flex gap-3">
<div className="flex-shrink-0">
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-500" />
</div>
<div className="flex-1">
<p className="text-sm text-yellow-800 dark:text-yellow-200 font-medium">
{isOwner
? 'Your account has limited functionality until you choose an option.'
: 'Please contact your business owner to upgrade or downgrade the account.'}
</p>
</div>
</div>
</div>
{!isOwner && (
<div className="mt-6 text-center">
<p className="text-gray-600 dark:text-gray-400">
Business Owner: <span className="font-semibold text-gray-900 dark:text-white">{business.name}</span>
</p>
</div>
)}
</div>
</div>
{/* Footer */}
<div className="text-center mt-6">
<p className="text-gray-600 dark:text-gray-400 text-sm">
Questions? Contact our support team at{' '}
<a href="mailto:support@smoothschedule.com" className="text-blue-600 dark:text-blue-400 hover:underline">
support@smoothschedule.com
</a>
</p>
</div>
</div>
</div>
);
};
export default TrialExpired;