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>
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Clock, X, ArrowRight, Sparkles } from 'lucide-react';
|
|
import { Business } from '../types';
|
|
|
|
interface TrialBannerProps {
|
|
business: Business;
|
|
}
|
|
|
|
/**
|
|
* TrialBanner Component
|
|
* Shows at the top of the business layout when trial is active
|
|
* Displays days remaining and upgrade CTA
|
|
* Dismissible but reappears on page reload
|
|
*/
|
|
const TrialBanner: React.FC<TrialBannerProps> = ({ business }) => {
|
|
const { t } = useTranslation();
|
|
const [isDismissed, setIsDismissed] = useState(false);
|
|
const navigate = useNavigate();
|
|
|
|
if (isDismissed || !business.isTrialActive || !business.daysLeftInTrial) {
|
|
return null;
|
|
}
|
|
|
|
const daysLeft = business.daysLeftInTrial;
|
|
const isUrgent = daysLeft <= 3;
|
|
const trialEndDate = business.trialEnd ? new Date(business.trialEnd).toLocaleDateString() : '';
|
|
|
|
const handleUpgrade = () => {
|
|
navigate('/upgrade');
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
setIsDismissed(true);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`relative ${
|
|
isUrgent
|
|
? 'bg-gradient-to-r from-red-500 to-orange-500'
|
|
: 'bg-gradient-to-r from-blue-600 to-blue-500'
|
|
} text-white shadow-md`}
|
|
>
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
|
<div className="flex items-center justify-between gap-4">
|
|
{/* Left: Trial Info */}
|
|
<div className="flex items-center gap-3 flex-1">
|
|
<div className={`p-2 rounded-full ${isUrgent ? 'bg-white/20' : 'bg-white/20'} backdrop-blur-sm`}>
|
|
{isUrgent ? (
|
|
<Clock size={20} className="animate-pulse" />
|
|
) : (
|
|
<Sparkles size={20} />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-semibold text-sm sm:text-base">
|
|
{t('trial.banner.title')} - {t('trial.banner.daysLeft', { days: daysLeft })}
|
|
</p>
|
|
<p className="text-xs sm:text-sm text-white/90 hidden sm:block">
|
|
{t('trial.banner.expiresOn', { date: trialEndDate })}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right: CTA Button */}
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={handleUpgrade}
|
|
className="group px-4 py-2 bg-white text-blue-600 hover:bg-blue-50 rounded-lg font-semibold text-sm transition-all shadow-lg hover:shadow-xl flex items-center gap-2"
|
|
>
|
|
{t('trial.banner.upgradeNow')}
|
|
<ArrowRight size={16} className="group-hover:translate-x-1 transition-transform" />
|
|
</button>
|
|
|
|
{/* Dismiss Button */}
|
|
<button
|
|
onClick={handleDismiss}
|
|
className="p-2 hover:bg-white/20 rounded-lg transition-colors"
|
|
aria-label={t('trial.banner.dismiss')}
|
|
>
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TrialBanner;
|