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>
223 lines
9.7 KiB
TypeScript
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;
|