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>
This commit is contained in:
222
frontend/src/pages/TrialExpired.tsx
Normal file
222
frontend/src/pages/TrialExpired.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user