- Update App.tsx routes to use /dashboard/ prefix for all business user routes - Add redirect from / to /dashboard for authenticated business users - Update Sidebar.tsx navigation links with /dashboard/ prefix - Update SettingsLayout.tsx settings navigation paths - Update all help pages with /dashboard/help/ routes - Update navigate() calls in components: - TrialBanner, PaymentSettingsSection, NotificationDropdown - BusinessLayout, UpgradePrompt, QuotaWarningBanner - QuotaOverageModal, OpenTicketsWidget, CreatePlugin - MyPlugins, PluginMarketplace, HelpTicketing - HelpGuide, Upgrade, TrialExpired - CustomDomainsSettings, QuotaSettings - Fix hardcoded lvh.me URL in BusinessEditModal to use buildSubdomainUrl 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
290 lines
11 KiB
TypeScript
290 lines
11 KiB
TypeScript
/**
|
|
* Payment Settings Section Component
|
|
* Unified payment configuration UI that shows the appropriate setup
|
|
* based on the business tier (API keys for Free, Connect for Paid)
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import {
|
|
CreditCard,
|
|
CheckCircle,
|
|
AlertCircle,
|
|
Loader2,
|
|
FlaskConical,
|
|
Zap,
|
|
ArrowUpRight,
|
|
Sparkles,
|
|
} from 'lucide-react';
|
|
import { Business } from '../types';
|
|
import { usePaymentConfig } from '../hooks/usePayments';
|
|
import StripeApiKeysForm from './StripeApiKeysForm';
|
|
import ConnectOnboardingEmbed from './ConnectOnboardingEmbed';
|
|
|
|
interface PaymentSettingsSectionProps {
|
|
business: Business;
|
|
}
|
|
|
|
type PaymentModeType = 'direct_api' | 'connect' | 'none';
|
|
|
|
const PaymentSettingsSection: React.FC<PaymentSettingsSectionProps> = ({ business }) => {
|
|
const navigate = useNavigate();
|
|
const { data: config, isLoading, error, refetch } = usePaymentConfig();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center gap-3">
|
|
<Loader2 className="animate-spin text-gray-400" size={24} />
|
|
<span className="text-gray-600">Loading payment configuration...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center gap-3 text-red-600">
|
|
<AlertCircle size={24} />
|
|
<span>Failed to load payment configuration</span>
|
|
</div>
|
|
<button
|
|
onClick={() => refetch()}
|
|
className="mt-3 px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200"
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const paymentMode = (config?.payment_mode || 'none') as PaymentModeType;
|
|
const tierAllowsPayments = config?.tier_allows_payments || false;
|
|
const stripeConfigured = config?.stripe_configured || false;
|
|
const canAcceptPayments = config?.can_accept_payments || false;
|
|
const tier = config?.tier || business.plan || 'Free';
|
|
const isFreeTier = tier === 'Free';
|
|
|
|
// Determine Stripe environment (test vs live) from API keys
|
|
const getStripeEnvironment = (): 'test' | 'live' | null => {
|
|
const maskedKey = config?.api_keys?.publishable_key_masked;
|
|
if (!maskedKey) return null;
|
|
if (maskedKey.startsWith('pk_test_')) return 'test';
|
|
if (maskedKey.startsWith('pk_live_')) return 'live';
|
|
return null;
|
|
};
|
|
const stripeEnvironment = getStripeEnvironment();
|
|
|
|
// Status badge component
|
|
const StatusBadge = () => {
|
|
if (!tierAllowsPayments) {
|
|
return (
|
|
<span className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-full">
|
|
<AlertCircle size={12} />
|
|
Upgrade Required
|
|
</span>
|
|
);
|
|
}
|
|
if (canAcceptPayments) {
|
|
return (
|
|
<span className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded-full">
|
|
<CheckCircle size={12} />
|
|
Ready
|
|
</span>
|
|
);
|
|
}
|
|
return (
|
|
<span className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300 rounded-full">
|
|
<AlertCircle size={12} />
|
|
Setup Required
|
|
</span>
|
|
);
|
|
};
|
|
|
|
// Mode description
|
|
const getModeDescription = () => {
|
|
if (isFreeTier) {
|
|
return 'Free tier businesses use their own Stripe API keys for payment processing. No platform fees apply.';
|
|
}
|
|
return `${tier} tier businesses use Stripe Connect for payment processing with platform-managed payments.`;
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
|
|
{/* Header */}
|
|
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
|
<CreditCard className="text-purple-600 dark:text-purple-400" size={24} />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Payment Configuration</h2>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">{getModeDescription()}</p>
|
|
</div>
|
|
</div>
|
|
<StatusBadge />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Test/Live Mode Banner */}
|
|
{stripeEnvironment && config?.api_keys?.status === 'active' && (
|
|
<div
|
|
className={`px-6 py-3 flex items-center gap-3 ${
|
|
stripeEnvironment === 'test'
|
|
? 'bg-amber-50 border-b border-amber-200'
|
|
: 'bg-green-50 border-b border-green-200'
|
|
}`}
|
|
>
|
|
{stripeEnvironment === 'test' ? (
|
|
<>
|
|
<div className="p-2 bg-amber-100 rounded-full">
|
|
<FlaskConical className="text-amber-600" size={20} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-semibold text-amber-800">Test Mode</p>
|
|
<p className="text-sm text-amber-700">
|
|
Payments are simulated. No real money will be charged.
|
|
</p>
|
|
</div>
|
|
<a
|
|
href="https://dashboard.stripe.com/test/apikeys"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-sm font-medium text-amber-700 hover:text-amber-800 underline"
|
|
>
|
|
Get Live Keys
|
|
</a>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="p-2 bg-green-100 rounded-full">
|
|
<Zap className="text-green-600" size={20} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-semibold text-green-800">Live Mode</p>
|
|
<p className="text-sm text-green-700">
|
|
Payments are real. Customers will be charged.
|
|
</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<div className="p-6">
|
|
{/* Tier info banner */}
|
|
<div className="mb-6 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<span className="text-sm text-gray-600 dark:text-gray-400">Current Plan:</span>
|
|
<span className={`ml-2 px-2 py-0.5 text-xs font-semibold rounded-full ${
|
|
tier === 'Enterprise' ? 'bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300' :
|
|
tier === 'Business' ? 'bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300' :
|
|
tier === 'Professional' ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300' :
|
|
'bg-gray-100 dark:bg-gray-600 text-gray-800 dark:text-gray-200'
|
|
}`}>
|
|
{tier}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
Payment Mode:{' '}
|
|
<span className="font-medium text-gray-900 dark:text-white">
|
|
{paymentMode === 'direct_api' ? 'Direct API Keys' :
|
|
paymentMode === 'connect' ? 'Stripe Connect' :
|
|
'Not Configured'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Upgrade prompt when tier doesn't allow payments */}
|
|
{!tierAllowsPayments ? (
|
|
<div className="bg-gradient-to-br from-purple-50 to-indigo-50 border border-purple-200 rounded-xl p-6">
|
|
<div className="flex items-start gap-4">
|
|
<div className="p-3 bg-purple-100 rounded-xl">
|
|
<Sparkles className="text-purple-600" size={28} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
|
Unlock Online Payments
|
|
</h3>
|
|
<p className="text-gray-600 mb-4">
|
|
Your current plan doesn't include online payment processing. Upgrade your subscription
|
|
or add the Online Payments add-on to start accepting payments from your customers.
|
|
</p>
|
|
<ul className="space-y-2 mb-6 text-sm text-gray-600">
|
|
<li className="flex items-center gap-2">
|
|
<CheckCircle size={16} className="text-green-500" />
|
|
Accept credit cards, debit cards, and digital wallets
|
|
</li>
|
|
<li className="flex items-center gap-2">
|
|
<CheckCircle size={16} className="text-green-500" />
|
|
Automatic invoicing and payment reminders
|
|
</li>
|
|
<li className="flex items-center gap-2">
|
|
<CheckCircle size={16} className="text-green-500" />
|
|
Secure PCI-compliant payment processing
|
|
</li>
|
|
<li className="flex items-center gap-2">
|
|
<CheckCircle size={16} className="text-green-500" />
|
|
Detailed transaction history and analytics
|
|
</li>
|
|
</ul>
|
|
<div className="flex flex-wrap gap-3">
|
|
<button
|
|
onClick={() => navigate('/dashboard/settings/billing')}
|
|
className="flex items-center gap-2 px-5 py-2.5 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors"
|
|
>
|
|
<ArrowUpRight size={16} />
|
|
View Upgrade Options
|
|
</button>
|
|
<a
|
|
href="mailto:support@smoothschedule.com?subject=Online Payments Add-on"
|
|
className="flex items-center gap-2 px-5 py-2.5 text-sm font-medium text-purple-700 bg-purple-100 rounded-lg hover:bg-purple-200 transition-colors"
|
|
>
|
|
Contact Sales
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{/* Tier-specific content */}
|
|
{isFreeTier ? (
|
|
<StripeApiKeysForm
|
|
apiKeys={config?.api_keys || null}
|
|
onSuccess={() => refetch()}
|
|
/>
|
|
) : (
|
|
<ConnectOnboardingEmbed
|
|
connectAccount={config?.connect_account || null}
|
|
tier={tier}
|
|
onComplete={() => refetch()}
|
|
/>
|
|
)}
|
|
|
|
{/* Upgrade notice for free tier with deprecated keys */}
|
|
{isFreeTier && config?.api_keys?.status === 'deprecated' && (
|
|
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
<h4 className="font-medium text-blue-800 mb-1">
|
|
Upgraded to a Paid Plan?
|
|
</h4>
|
|
<p className="text-sm text-blue-700">
|
|
If you've recently upgraded, your API keys have been deprecated.
|
|
Please contact support to complete your Stripe Connect setup.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PaymentSettingsSection;
|