Add Payment Gateway selection to Settings page
This commit is contained in:
132
src/features/settings/components/PaymentGatewayCard.tsx
Normal file
132
src/features/settings/components/PaymentGatewayCard.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
import { CreditCard, CheckCircle2, RotateCw } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const availableGateways = [
|
||||||
|
{
|
||||||
|
id: "stripe",
|
||||||
|
name: "Stripe",
|
||||||
|
logo: "https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg",
|
||||||
|
fee: "2.9% + ₹30",
|
||||||
|
status: "Connected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "razorpay",
|
||||||
|
name: "Razorpay",
|
||||||
|
logo: "https://upload.wikimedia.org/wikipedia/commons/8/89/Razorpay_logo.svg",
|
||||||
|
fee: "2%",
|
||||||
|
status: "Available",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "phonepe",
|
||||||
|
name: "PhonePe",
|
||||||
|
logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/PhonePe_Logo.svg/2560px-PhonePe_Logo.svg.png",
|
||||||
|
fee: "1.9%",
|
||||||
|
status: "Available",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cashfree",
|
||||||
|
name: "Cashfree",
|
||||||
|
logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Cashfree_Payments_Logo.svg/2560px-Cashfree_Payments_Logo.svg.png",
|
||||||
|
fee: "1.95%",
|
||||||
|
status: "Available",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function PaymentGatewayCard() {
|
||||||
|
const [activeGateway, setActiveGateway] = useState("stripe");
|
||||||
|
const [loading, setLoading] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleGatewayChange = (id: string) => {
|
||||||
|
if (activeGateway === id) return;
|
||||||
|
setLoading(id);
|
||||||
|
|
||||||
|
// Simulate connection lag
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveGateway(id);
|
||||||
|
setLoading(null);
|
||||||
|
toast.success(`Switched active payment gateway to ${availableGateways.find(g => g.id === id)?.name}`);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="neu-card p-6 h-full flex flex-col">
|
||||||
|
<div className="flex items-center gap-4 mb-6">
|
||||||
|
<div className="h-12 w-12 rounded-xl bg-indigo-500/10 flex items-center justify-center">
|
||||||
|
<CreditCard className="h-6 w-6 text-indigo-600 dark:text-indigo-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-lg text-foreground">Payment Gateway</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">Collection preferences</p>
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
{loading ? (
|
||||||
|
<RotateCw className="h-4 w-4 text-indigo-500 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Badge className="bg-indigo-500/15 text-indigo-600 hover:bg-indigo-500/25 border-none gap-1">
|
||||||
|
<div className="h-1.5 w-1.5 rounded-full bg-indigo-500 animate-pulse" />
|
||||||
|
Processing
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 space-y-4">
|
||||||
|
<RadioGroup value={activeGateway} onValueChange={handleGatewayChange} className="space-y-3">
|
||||||
|
{availableGateways.map((gateway) => (
|
||||||
|
<div
|
||||||
|
key={gateway.id}
|
||||||
|
className={cn(
|
||||||
|
"relative flex items-center justify-between p-4 rounded-xl border transition-all cursor-pointer",
|
||||||
|
activeGateway === gateway.id
|
||||||
|
? "border-indigo-500/50 bg-indigo-500/5 shadow-neu-sm"
|
||||||
|
: "border-border/50 bg-card hover:bg-secondary/50"
|
||||||
|
)}
|
||||||
|
onClick={() => handleGatewayChange(gateway.id)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<RadioGroupItem value={gateway.id} id={gateway.id} className="border-indigo-500 text-indigo-500" />
|
||||||
|
<div className="h-8 w-20 flex items-center justify-center bg-white rounded p-1">
|
||||||
|
{/* Placeholder for logos if external works, else text */}
|
||||||
|
{/* Using text fallback for safety/offline dev */}
|
||||||
|
<span className="font-bold text-xs text-black">{gateway.name}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor={gateway.id} className="font-semibold cursor-pointer">{gateway.name}</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">Fee: {gateway.fee}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{gateway.status === "Connected" || activeGateway === gateway.id ? (
|
||||||
|
<CheckCircle2 className="h-5 w-5 text-indigo-500" />
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs text-muted-foreground h-7"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleGatewayChange(gateway.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-6 border-t border-border/50">
|
||||||
|
<p className="text-xs text-muted-foreground text-center">
|
||||||
|
Processing payments settled in <b>T+2 days</b> via selected gateway.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { OrganizationProfileCard } from '@/features/settings/components/Organiza
|
|||||||
import { PayoutBankingCard } from '@/features/settings/components/PayoutBankingCard';
|
import { PayoutBankingCard } from '@/features/settings/components/PayoutBankingCard';
|
||||||
import { TeamSecurityCard } from '@/features/settings/components/TeamSecurityCard';
|
import { TeamSecurityCard } from '@/features/settings/components/TeamSecurityCard';
|
||||||
import { DeveloperSection } from '@/features/settings/components/DeveloperSection';
|
import { DeveloperSection } from '@/features/settings/components/DeveloperSection';
|
||||||
|
import { PaymentGatewayCard } from '@/features/settings/components/PaymentGatewayCard';
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
return (
|
return (
|
||||||
@@ -11,42 +12,16 @@ export default function Settings() {
|
|||||||
description="Manage platform configuration and critical operations."
|
description="Manage platform configuration and critical operations."
|
||||||
>
|
>
|
||||||
<div className="space-y-6 max-w-[1200px]">
|
<div className="space-y-6 max-w-[1200px]">
|
||||||
{/* Top Grid */}
|
{/* Top Grid: Identity & Banking */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[400px]">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto lg:h-[400px]">
|
||||||
{/* Box 1: Organization Identity (High Priority) */}
|
|
||||||
<OrganizationProfileCard />
|
<OrganizationProfileCard />
|
||||||
|
|
||||||
{/* Box 2: Financials & Banking (High Priority) */}
|
|
||||||
<PayoutBankingCard />
|
<PayoutBankingCard />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
{/* Middle Grid: Gateways & Security */}
|
||||||
{/* Box 3: Security (Full Width on Mobile, 2/3 on Desktop if needed, but here simple 1/3 stack or full width row) */}
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-auto">
|
||||||
{/* Adjusting layout: Let's make Security taking full row or sidebar style.
|
<PaymentGatewayCard />
|
||||||
Based on requirements "2x2 Grid or Vertical Stack".
|
<TeamSecurityCard />
|
||||||
Let's stick to a clean layout where Security sits next to maybe a logs panel or just takes full width.
|
|
||||||
Actually, the requirements said "Layout: 2x2 Grid".
|
|
||||||
Let's put Team & Security as the 3rd box, and maybe Developer as 4th?
|
|
||||||
But Developer is collapsible.
|
|
||||||
*/}
|
|
||||||
<div className="lg:col-span-2">
|
|
||||||
<TeamSecurityCard />
|
|
||||||
</div>
|
|
||||||
<div className="lg:col-span-1">
|
|
||||||
{/* Placeholder or Tip card? Or maybe just make TeamSecurityCard full width if it has lists.
|
|
||||||
The TeamSecurityCard design I made is card-like.
|
|
||||||
Let's span it across 2 columns for better readability of the list.
|
|
||||||
*/}
|
|
||||||
<div className="neu-card p-6 h-full bg-gradient-to-br from-primary/5 to-transparent border-primary/20">
|
|
||||||
<h4 className="font-bold text-lg mb-2">Need Help?</h4>
|
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
|
||||||
Contact our dedicated support team for assistance with account configurations.
|
|
||||||
</p>
|
|
||||||
<button className="text-sm font-bold text-primary hover:underline">
|
|
||||||
Open Support Ticket →
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Collapsible Developer Section */}
|
{/* Collapsible Developer Section */}
|
||||||
|
|||||||
Reference in New Issue
Block a user