feat: Add Multi-Gateway Configuration Module with Payment Settings Tab

This commit is contained in:
CycroftX
2026-02-10 11:35:23 +05:30
parent 514508df89
commit 0c8593ef22
13 changed files with 1979 additions and 25 deletions

View File

@@ -0,0 +1,158 @@
'use client';
import { useState } from 'react';
import { OrganizationConfig, SecurityConfig } from '@/lib/types/settings';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { updateSystemSetting } from '@/lib/actions/settings';
import { toast } from 'sonner';
import { Building2, ShieldCheck, Mail, Globe } from 'lucide-react';
interface OrganizationSettingsProps {
orgConfig: OrganizationConfig;
securityConfig: SecurityConfig;
onUpdate: (section: 'organization' | 'security', data: any) => void;
}
export function OrganizationSettings({ orgConfig, securityConfig, onUpdate }: OrganizationSettingsProps) {
const [loading, setLoading] = useState(false);
// Internal state for form usage
const [orgState, setOrgState] = useState(orgConfig);
const [secState, setSecState] = useState(securityConfig);
const handleSaveOrg = async () => {
setLoading(true);
try {
const res = await updateSystemSetting('organization', orgState);
if (res.success) {
toast.success('Organization profile updated');
onUpdate('organization', res.updatedSettings.organization);
}
} catch (error) {
toast.error('Failed to update organization settings');
} finally {
setLoading(false);
}
};
const handleSaveSecurity = async () => {
setLoading(true);
try {
const res = await updateSystemSetting('security', secState);
if (res.success) {
toast.success('Security policy updated');
onUpdate('security', res.updatedSettings.security);
}
} catch (error) {
toast.error('Failed to update security settings');
} finally {
setLoading(false);
}
};
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Building2 className="h-5 w-5 text-primary" />
Organization Profile
</CardTitle>
<CardDescription>
General branding and contact details for the control center and emails.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Brand Name</Label>
<Input
value={orgState.brandName}
onChange={(e) => setOrgState({ ...orgState, brandName: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label>Support Email</Label>
<div className="relative">
<Mail className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
className="pl-9"
value={orgState.supportEmail}
onChange={(e) => setOrgState({ ...orgState, supportEmail: e.target.value })}
/>
</div>
</div>
</div>
<div className="space-y-2">
<Label>Legal Address</Label>
<Input
value={orgState.legalAddress}
onChange={(e) => setOrgState({ ...orgState, legalAddress: e.target.value })}
/>
</div>
<div className="flex justify-end mt-4">
<Button onClick={handleSaveOrg} disabled={loading}>
{loading ? 'Saving...' : 'Save Profile'}
</Button>
</div>
</CardContent>
</Card>
<Card className="border-orange-200 bg-orange-50/10">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ShieldCheck className="h-5 w-5 text-orange-600" />
Security Policy
</CardTitle>
<CardDescription>
Enforce security rules for all Control Center staff members.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between p-4 border rounded-lg bg-card">
<div className="space-y-0.5">
<Label className="text-base">Enforce 2FA</Label>
<p className="text-sm text-muted-foreground">
Require Two-Factor Authentication for all admin accounts.
</p>
</div>
<Switch
checked={secState.enforce2FA}
onCheckedChange={(checked) => setSecState({ ...secState, enforce2FA: checked })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Session Timeout (Minutes)</Label>
<Input
type="number"
value={secState.sessionTimeoutMinutes}
onChange={(e) => setSecState({ ...secState, sessionTimeoutMinutes: parseInt(e.target.value) })}
/>
</div>
<div className="space-y-2">
<Label>Password Expiration (Days)</Label>
<Input
type="number"
value={secState.passwordExpirationDays}
onChange={(e) => setSecState({ ...secState, passwordExpirationDays: parseInt(e.target.value) })}
/>
</div>
</div>
<div className="flex justify-end">
<Button variant="outline" onClick={handleSaveSecurity} disabled={loading} className="border-orange-200 text-orange-700 hover:bg-orange-50 hover:text-orange-800">
{loading ? 'Saving...' : 'Update Security Policy'}
</Button>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,173 @@
'use client';
import { useState } from 'react';
import { PartnerConfig } from '@/lib/types/settings';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { updateSystemSetting } from '@/lib/actions/settings';
import { toast } from 'sonner';
import { Handshake, Banknote, FileCheck } from 'lucide-react';
interface PartnerGovernanceProps {
config: PartnerConfig;
onUpdate: (data: PartnerConfig) => void;
}
export function PartnerGovernanceTab({ config, onUpdate }: PartnerGovernanceProps) {
const [loading, setLoading] = useState(false);
const [state, setState] = useState(config);
const handleSave = async () => {
setLoading(true);
try {
const res = await updateSystemSetting('partner', state);
if (res.success && res.updatedSettings.partner) {
toast.success('Partner governance rules updated');
onUpdate(res.updatedSettings.partner);
}
} catch (error) {
toast.error('Failed to update partner settings');
} finally {
setLoading(false);
}
};
const toggleKycDoc = (doc: 'pan' | 'gst' | 'aadhaar' | 'cheque') => {
const current = state.allowedKycDocs;
const updated = current.includes(doc)
? current.filter(d => d !== doc)
: [...current, doc];
setState({ ...state, allowedKycDocs: updated });
};
return (
<div className="space-y-6">
<div className="grid md:grid-cols-2 gap-6">
{/* Onboarding Rules */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Handshake className="h-5 w-5 text-indigo-500" />
Onboarding & Approval
</CardTitle>
<CardDescription>
Set rules for new organizer accounts and events.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between py-2 border-b">
<div className="space-y-0.5">
<Label>Require KYC Approval</Label>
<p className="text-xs text-muted-foreground">Block payouts until verified.</p>
</div>
<Switch
checked={state.requireKyc}
onCheckedChange={(c) => setState({ ...state, requireKyc: c })}
/>
</div>
<div className="flex items-center justify-between py-2">
<div className="space-y-0.5">
<Label>Manual Event Approval</Label>
<p className="text-xs text-muted-foreground">Admins must approve live events.</p>
</div>
<Switch
checked={state.manualEventApproval}
onCheckedChange={(c) => setState({ ...state, manualEventApproval: c })}
/>
</div>
</CardContent>
</Card>
{/* Commission Model */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Banknote className="h-5 w-5 text-green-600" />
Commission & Payouts
</CardTitle>
<CardDescription>
Define the default revenue share model.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Default Commission (%)</Label>
<Input
type="number"
value={state.defaultCommissionPercent}
onChange={(e) => setState({ ...state, defaultCommissionPercent: Number(e.target.value) })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Payout Schedule</Label>
<Select
value={state.payoutSchedule}
onValueChange={(v: any) => setState({ ...state, payoutSchedule: v })}
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="daily">Daily (T+1)</SelectItem>
<SelectItem value="weekly">Weekly</SelectItem>
<SelectItem value="monthly">Monthly</SelectItem>
<SelectItem value="manual">Manual Request</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Min. Payout Amount ()</Label>
<Input
type="number"
value={state.minPayoutAmount}
onChange={(e) => setState({ ...state, minPayoutAmount: Number(e.target.value) })}
/>
</div>
</div>
</CardContent>
</Card>
</div>
{/* KYC Requirements */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileCheck className="h-5 w-5 text-slate-500" />
KYC Requirements
</CardTitle>
<CardDescription>
Select documents required for organizer verification.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{['pan', 'gst', 'aadhaar', 'cheque'].map((doc) => (
<div key={doc} className="flex items-center space-x-2 border p-3 rounded-md">
<Checkbox
id={`doc-${doc}`}
checked={state.allowedKycDocs.includes(doc as any)}
onCheckedChange={() => toggleKycDoc(doc as any)}
/>
<Label htmlFor={`doc-${doc}`} className="uppercase text-xs font-semibold">
{doc}
</Label>
</div>
))}
</div>
</CardContent>
</Card>
<div className="flex justify-end">
<Button onClick={handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Update Governance Rules'}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,196 @@
'use client';
import { useState } from 'react';
import { PaymentConfig, GatewayProvider, GatewayCredentials } from '@/lib/types/settings';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Label } from '@/components/ui/label';
import { GatewayConfigSheet } from '@/features/settings/components/GatewayConfigSheet';
import { updateRoutingRules } from '@/lib/actions/payment-settings';
import { toast } from 'sonner';
import { CreditCard, Globe, Settings2, ShieldCheck } from 'lucide-react';
interface PaymentConfigTabProps {
config: PaymentConfig;
onUpdate: (data: PaymentConfig) => void;
}
const PROVIDERS: { id: GatewayProvider; name: string }[] = [
{ id: 'razorpay', name: 'Razorpay' },
{ id: 'stripe', name: 'Stripe' },
{ id: 'payu', name: 'PayU' },
{ id: 'easebuzz', name: 'Easebuzz' },
{ id: 'worldline', name: 'Worldline' },
];
export function PaymentConfigTab({ config, onUpdate }: PaymentConfigTabProps) {
const [selectedProvider, setSelectedProvider] = useState<GatewayProvider | null>(null);
const [sheetOpen, setSheetOpen] = useState(false);
const [routingState, setRoutingState] = useState(config);
const handleOpenConfig = (provider: GatewayProvider) => {
setSelectedProvider(provider);
setSheetOpen(true);
};
const handleRoutingUpdate = async (key: keyof PaymentConfig, value: string) => {
const newState = { ...routingState, [key]: value };
setRoutingState(newState);
try {
const res = await updateRoutingRules({
defaultGateway: newState.defaultGateway,
fallbackGateway: newState.fallbackGateway,
internationalGateway: newState.internationalGateway
});
if (res.success) {
toast.success('Routing rules updated');
// Optimistic update handled by local state, but we should propagate up
onUpdate(newState);
}
} catch (error) {
toast.error('Failed to update routing');
}
};
return (
<div className="space-y-8">
{/* Gateway Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{PROVIDERS.map((provider) => {
const gwConfig = config.gateways[provider.id];
const isActive = gwConfig?.enabled;
const isLive = gwConfig?.mode === 'live';
return (
<Card key={provider.id} className={`group hover:shadow-md transition-shadow ${isActive ? 'border-primary/50 bg-primary/5' : 'opacity-75'}`}>
<CardHeader className="flex flex-row items-start justify-between pb-2">
<CardTitle className="text-lg font-bold">{provider.name}</CardTitle>
{isActive ? (
<Badge variant={isLive ? 'default' : 'secondary'} className={isLive ? 'bg-emerald-600' : ''}>
{isLive ? 'LIVE' : 'TEST'}
</Badge>
) : (
<Badge variant="outline">Disabled</Badge>
)}
</CardHeader>
<CardContent>
<div className="text-sm text-muted-foreground space-y-1">
{isActive ? (
<>
<div className="flex items-center gap-2">
<ShieldCheck className="h-4 w-4 text-emerald-500" />
Verified
</div>
<div className="flex gap-2 mt-2">
{gwConfig.features.upi && <Badge variant="outline" className="text-[10px]">UPI</Badge>}
{gwConfig.features.cards && <Badge variant="outline" className="text-[10px]">Cards</Badge>}
{gwConfig.features.emi && <Badge variant="outline" className="text-[10px]">EMI</Badge>}
</div>
</>
) : (
<div className="flex items-center gap-2 text-muted-foreground">
Not configured
</div>
)}
</div>
</CardContent>
<CardFooter>
<Button
variant={isActive ? "outline" : "secondary"}
className="w-full"
onClick={() => handleOpenConfig(provider.id)}
>
<Settings2 className="h-4 w-4 mr-2" />
Configure
</Button>
</CardFooter>
</Card>
);
})}
</div>
{/* Routing Logic */}
<Card className="border-indigo-200 bg-indigo-50/10">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-indigo-700">
<Globe className="h-5 w-5" />
Smart Routing Logic
</CardTitle>
<CardDescription>
Define how transactions are routed across active gateways.
</CardDescription>
</CardHeader>
<CardContent className="grid md:grid-cols-3 gap-6">
<div className="space-y-2">
<Label>Default Gateway</Label>
<Select
value={routingState.defaultGateway}
onValueChange={(v) => handleRoutingUpdate('defaultGateway', v)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{PROVIDERS.map(p => <SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Used for 90% of traffic.</p>
</div>
<div className="space-y-2">
<Label>Fallback Gateway</Label>
<Select
value={routingState.fallbackGateway}
onValueChange={(v) => handleRoutingUpdate('fallbackGateway', v)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{PROVIDERS.map(p => <SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Used if default fails.</p>
</div>
<div className="space-y-2">
<Label>International (Non-INR)</Label>
<Select
value={routingState.internationalGateway}
onValueChange={(v) => handleRoutingUpdate('internationalGateway', v)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{PROVIDERS.map(p => <SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>)}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Cards issued outside India.</p>
</div>
</CardContent>
</Card>
{/* Config Sheet Instance */}
{selectedProvider && config.gateways[selectedProvider] && (
<GatewayConfigSheet
open={sheetOpen}
onOpenChange={setSheetOpen}
provider={selectedProvider}
initialConfig={config.gateways[selectedProvider]}
onSave={() => {
// Refresh parent data? Handled via Actions and upper state update usually,
// but for now we rely on the parent's polling or manual refresh,
// effectively we might want to trigger a callback here.
// Ideally onUpdate would trigger a re-fetch.
window.location.reload(); // Quick dirty refresh to sync state in this demo
}}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,193 @@
'use client';
import { useState } from 'react';
import { PublicAppConfig } from '@/lib/types/settings';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { updateSystemSetting } from '@/lib/actions/settings';
import { toast } from 'sonner';
import { Smartphone, Zap, Percent, Link as LinkIcon } from 'lucide-react';
interface PublicAppConfigProps {
config: PublicAppConfig;
onUpdate: (data: PublicAppConfig) => void;
}
export function PublicAppConfigTab({ config, onUpdate }: PublicAppConfigProps) {
const [loading, setLoading] = useState(false);
const [state, setState] = useState(config);
const handleSave = async () => {
setLoading(true);
try {
const res = await updateSystemSetting('publicApp', state);
if (res.success && res.updatedSettings.publicApp) {
toast.success('Public App configuration updated');
onUpdate(res.updatedSettings.publicApp);
}
} catch (error) {
toast.error('Failed to update app config');
} finally {
setLoading(false);
}
};
// Helper to update deeply nested feature flags
const toggleFeature = (key: keyof PublicAppConfig['betaFeatures']) => {
setState(prev => ({
...prev,
betaFeatures: {
...prev.betaFeatures,
[key]: !prev.betaFeatures[key]
}
}));
};
return (
<div className="space-y-6">
<div className="grid md:grid-cols-2 gap-6">
{/* Feature Flags */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="h-5 w-5 text-yellow-500" />
Feature Flags
</CardTitle>
<CardDescription>
Toggle beta features for end-users instantly.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between py-2 border-b">
<div className="space-y-0.5">
<Label>Crypto Payments</Label>
<p className="text-xs text-muted-foreground">Accept ETH/SOL via Solana Pay</p>
</div>
<Switch
checked={state.betaFeatures.cryptoPayments}
onCheckedChange={() => toggleFeature('cryptoPayments')}
/>
</div>
<div className="flex items-center justify-between py-2 border-b">
<div className="space-y-0.5">
<Label>AI Recommendations</Label>
<p className="text-xs text-muted-foreground">Show "Events you might like"</p>
</div>
<Switch
checked={state.betaFeatures.aiRecommendations}
onCheckedChange={() => toggleFeature('aiRecommendations')}
/>
</div>
<div className="flex items-center justify-between py-2">
<div className="space-y-0.5">
<Label>Social Login</Label>
<p className="text-xs text-muted-foreground">Enable Google/Apple Auth</p>
</div>
<Switch
checked={state.betaFeatures.socialLogin}
onCheckedChange={() => toggleFeature('socialLogin')}
/>
</div>
</CardContent>
</Card>
{/* Commercials */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Percent className="h-5 w-5 text-emerald-600" />
Booking Fees & Tax
</CardTitle>
<CardDescription>
Set the global platform fee charged to users.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Platform Fee (Flat )</Label>
<Input
type="number"
value={state.fees.platformFeeFlat}
onChange={(e) => setState({
...state,
fees: { ...state.fees, platformFeeFlat: Number(e.target.value) }
})}
/>
<p className="text-xs text-muted-foreground">
Added to every ticket purchase.
</p>
</div>
<div className="space-y-2">
<Label>GST / Tax Rate (%)</Label>
<Input
type="number"
value={state.fees.taxRatePercent}
onChange={(e) => setState({
...state,
fees: { ...state.fees, taxRatePercent: Number(e.target.value) }
})}
/>
</div>
</CardContent>
</Card>
</div>
{/* Links */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<LinkIcon className="h-5 w-5 text-blue-500" />
Support Links
</CardTitle>
<CardDescription>
Update external URLs for help, terms, and privacy policies.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label>Help Center URL</Label>
<Input
value={state.links.helpCenterUrl}
onChange={(e) => setState({
...state,
links: { ...state.links, helpCenterUrl: e.target.value }
})}
/>
</div>
<div className="space-y-2">
<Label>Terms & Conditions URL</Label>
<Input
value={state.links.termsUrl}
onChange={(e) => setState({
...state,
links: { ...state.links, termsUrl: e.target.value }
})}
/>
</div>
<div className="space-y-2">
<Label>Privacy Policy URL</Label>
<Input
value={state.links.privacyUrl}
onChange={(e) => setState({
...state,
links: { ...state.links, privacyUrl: e.target.value }
})}
/>
</div>
</div>
</CardContent>
</Card>
<div className="flex justify-end sticky bottom-6 z-10">
<Button size="lg" onClick={handleSave} disabled={loading} className="shadow-lg">
{loading ? 'Publishing Updates...' : 'Publish Public App Config'}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,244 @@
'use client';
import { useState } from 'react';
import { SystemConfig } from '@/lib/types/settings';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { updateSystemSetting, purgeSystemCache } from '@/lib/actions/settings';
import { toast } from 'sonner';
import { Server, CreditCard, Radio, Trash2, AlertTriangle, Activity } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
interface SystemHealthProps {
config: SystemConfig;
onUpdate: (data: SystemConfig) => void;
}
export function SystemHealthTab({ config, onUpdate }: SystemHealthProps) {
const [loading, setLoading] = useState(false);
const [state, setState] = useState(config);
const [purgeLoading, setPurgeLoading] = useState(false);
const handleSave = async () => {
setLoading(true);
try {
const res = await updateSystemSetting('system', state);
if (res.success && res.updatedSettings.system) {
toast.success('System configuration updated');
onUpdate(res.updatedSettings.system);
}
} catch (error) {
toast.error('Failed to update system config');
} finally {
setLoading(false);
}
};
const handlePurgeCache = async () => {
setPurgeLoading(true);
try {
const res = await purgeSystemCache();
if (res.success) {
toast.success('Cache purged successfully', {
description: 'Changes are propagating to edge locations.'
});
}
} catch (error) {
toast.error('Failed to purge cache');
} finally {
setPurgeLoading(false);
}
};
return (
<div className="space-y-6">
{/* System Status Banner */}
<div className="grid md:grid-cols-3 gap-6">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">API Status</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-full bg-emerald-500 animate-pulse" />
<span className="text-2xl font-bold">Operational</span>
</div>
<p className="text-xs text-muted-foreground mt-1">Uptime: 99.99%</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">Cache Status</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<span className="text-2xl font-bold">Healthy</span>
<Badge variant="outline">TTL: {state.cache.ttl}s</Badge>
</div>
<p className="text-xs text-muted-foreground mt-1">
Last Purged: {state.cache.lastPurgedAt ? new Date(state.cache.lastPurgedAt).toLocaleTimeString() : 'Never'}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium text-muted-foreground">Database</CardTitle>
</CardHeader>
<CardContent>
<span className="text-2xl font-bold">Connected</span>
<p className="text-xs text-muted-foreground mt-1">Latency: 24ms</p>
</CardContent>
</Card>
</div>
{/* Payment Gateways */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CreditCard className="h-5 w-5 text-primary" />
Payment Gateways
</CardTitle>
<CardDescription>
Manage gateway keys and active modes (Test vs Live).
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Stripe */}
<div className="p-4 border rounded-lg bg-card/50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<h4 className="font-semibold">Stripe</h4>
{state.gateways.stripe.mode === 'live' ?
<Badge className="bg-emerald-500">Live</Badge> :
<Badge variant="secondary">Test Mode</Badge>
}
</div>
<Switch
checked={state.gateways.stripe.enabled}
onCheckedChange={(c) => setState({
...state,
gateways: { ...state.gateways, stripe: { ...state.gateways.stripe, enabled: c } }
})}
/>
</div>
<div className="grid gap-2">
<Label>Public Key</Label>
<Input
type="password"
value={state.gateways.stripe.publicKey}
readOnly // For demo safety
className="font-mono text-xs"
/>
</div>
<div className="flex items-center gap-2 mt-4">
<Label className="text-xs">Mode:</Label>
<div className="flex items-center space-x-2">
<Switch
checked={state.gateways.stripe.mode === 'live'}
onCheckedChange={(c) => setState({
...state,
gateways: { ...state.gateways, stripe: { ...state.gateways.stripe, mode: c ? 'live' : 'test' } }
})}
/>
<span className={state.gateways.stripe.mode === 'live' ? 'font-bold text-red-600' : 'text-muted-foreground'}>
{state.gateways.stripe.mode === 'live' ? 'LIVE TRAFFIC' : 'Test Mode'}
</span>
</div>
</div>
</div>
{/* Razorpay */}
<div className="p-4 border rounded-lg bg-card/50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<h4 className="font-semibold">Razorpay</h4>
{state.gateways.razorpay.mode === 'live' ?
<Badge className="bg-emerald-500">Live</Badge> :
<Badge variant="secondary">Test Mode</Badge>
}
</div>
<Switch
checked={state.gateways.razorpay.enabled}
onCheckedChange={(c) => setState({
...state,
gateways: { ...state.gateways, razorpay: { ...state.gateways.razorpay, enabled: c } }
})}
/>
</div>
<div className="grid gap-2">
<Label>Key ID</Label>
<Input
type="password"
value={state.gateways.razorpay.keyId}
readOnly
className="font-mono text-xs"
/>
</div>
</div>
</CardContent>
<CardFooter className="justify-end border-t pt-4">
<Button onClick={handleSave} disabled={loading}>Save Gateway Config</Button>
</CardFooter>
</Card>
{/* Danger Zone */}
<h3 className="text-lg font-semibold text-red-600 flex items-center gap-2 mt-8">
<AlertTriangle className="h-5 w-5" />
Danger Zone
</h3>
<div className="grid md:grid-cols-2 gap-6">
<Card className="border-red-200 bg-red-50/10">
<CardHeader>
<CardTitle className="text-base text-red-700">Maintenance Mode</CardTitle>
<CardDescription>
Disable the public facing app temporarily. Only admins can access.
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between items-center">
<div className="space-y-1">
<Label>Status</Label>
<p className="text-sm font-medium">
{false ? <span className="text-red-600">Active - App Offline</span> : <span className="text-emerald-600">Inactive - App Live</span>}
</p>
</div>
<Button variant="destructive">Enable Maintenance</Button>
</CardContent>
</Card>
<Card className="border-orange-200 bg-orange-50/10">
<CardHeader>
<CardTitle className="text-base text-orange-700">Cache Control</CardTitle>
<CardDescription>
Force purge the CDN cache for all static pages.
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between items-center">
<div className="space-y-1">
<Label>Metrics</Label>
<p className="text-xs text-muted-foreground">Items cached: ~14.2k</p>
</div>
<Button
variant="outline"
className="border-orange-300 text-orange-700 hover:bg-orange-100"
onClick={handlePurgeCache}
disabled={purgeLoading}
>
{purgeLoading ? (
<Activity className="h-4 w-4 mr-2 animate-spin" />
) : (
<Trash2 className="h-4 w-4 mr-2" />
)}
Purge Cache
</Button>
</CardContent>
</Card>
</div>
</div>
);
}