Files
WooNooW/admin-spa/src/routes/Settings/Payments.tsx
dwindown 3bd2c07308 feat: Improve settings layout and add addon integration design
🎨 Layout Changes:
- Changed settings from boxed (max-w-5xl) to full-width
- Consistent with Orders/Dashboard pages
- Better use of space for complex forms

📝 Payments Page Reorder:
- Manual payment methods first (Bank Transfer, COD)
- Payment providers second (Stripe, PayPal)
- Payment settings third (test mode, capture)
- Test mode banner moved inside Payment Settings card

📚 Documentation:
- Created SETUP_WIZARD_DESIGN.md
- 5-step wizard flow (Store, Payments, Shipping, Taxes, Product)
- Smart defaults and skip logic
- Complete addon integration architecture

🔌 Addon Integration Design:
- PaymentProviderRegistry with filter hooks
- ShippingMethodRegistry with filter hooks
- REST API endpoints for dynamic loading
- Example addon implementations
- Support for custom React components

 Key Features:
- woonoow_payment_providers filter hook
- woonoow_shipping_zones filter hook
- Dynamic component loading from addons
- OAuth flow support for payment gateways
- Backward compatible with WooCommerce
2025-11-05 19:47:25 +07:00

238 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import { SettingsLayout } from './components/SettingsLayout';
import { SettingsCard } from './components/SettingsCard';
import { ToggleField } from './components/ToggleField';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { CreditCard, DollarSign, Banknote, Settings } from 'lucide-react';
import { toast } from 'sonner';
interface PaymentProvider {
id: string;
name: string;
description: string;
icon: React.ReactNode;
enabled: boolean;
connected: boolean;
fees?: string;
testMode?: boolean;
}
export default function PaymentsPage() {
const [testMode, setTestMode] = useState(false);
const [providers] = useState<PaymentProvider[]>([
{
id: 'stripe',
name: 'Stripe Payments',
description: 'Accept Visa, Mastercard, Amex, and more',
icon: <CreditCard className="h-6 w-6" />,
enabled: false,
connected: false,
fees: '2.9% + $0.30 per transaction',
},
{
id: 'paypal',
name: 'PayPal',
description: 'Accept PayPal payments worldwide',
icon: <DollarSign className="h-6 w-6" />,
enabled: true,
connected: true,
fees: '3.49% + fixed fee per transaction',
},
]);
const [manualMethods, setManualMethods] = useState([
{ id: 'bacs', name: 'Bank Transfer (BACS)', enabled: true },
{ id: 'cod', name: 'Cash on Delivery', enabled: true },
{ id: 'cheque', name: 'Check Payments', enabled: false },
]);
const handleSave = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
toast.success('Payment settings have been updated successfully.');
};
const toggleManualMethod = (id: string) => {
setManualMethods((prev) =>
prev.map((m) => (m.id === id ? { ...m, enabled: !m.enabled } : m))
);
};
return (
<SettingsLayout
title="Payments"
description="Manage how you get paid"
onSave={handleSave}
>
{/* Manual Payment Methods - First priority */}
<SettingsCard
title="Manual Payment Methods"
description="Accept payments outside your online store"
>
<div className="space-y-4">
{manualMethods.map((method) => (
<div
key={method.id}
className="border rounded-lg p-4"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="p-2 bg-muted rounded-lg">
<Banknote className="h-5 w-5 text-muted-foreground" />
</div>
<div>
<h3 className="font-medium">{method.name}</h3>
{method.enabled && (
<p className="text-sm text-muted-foreground mt-1">
Customers can choose this at checkout
</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
{method.enabled && (
<Button variant="ghost" size="sm">
<Settings className="h-4 w-4" />
</Button>
)}
<ToggleField
id={method.id}
label=""
checked={method.enabled}
onCheckedChange={() => toggleManualMethod(method.id)}
/>
</div>
</div>
</div>
))}
</div>
</SettingsCard>
{/* Payment Providers - Second priority */}
<SettingsCard
title="Payment Providers"
description="Accept credit cards and digital wallets"
>
<div className="space-y-4">
{providers.map((provider) => (
<div
key={provider.id}
className="border rounded-lg p-4 hover:border-primary/50 transition-colors"
>
<div className="flex items-start justify-between">
<div className="flex items-start gap-4 flex-1">
<div className="p-2 bg-primary/10 rounded-lg text-primary">
{provider.icon}
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold">{provider.name}</h3>
{provider.connected ? (
<Badge variant="default" className="bg-green-500">
Connected
</Badge>
) : (
<Badge variant="secondary"> Not connected</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mb-2">
{provider.description}
</p>
{provider.fees && (
<p className="text-xs text-muted-foreground">
{provider.fees}
</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
{provider.connected ? (
<>
<Button variant="outline" size="sm">
<Settings className="h-4 w-4 mr-2" />
Manage
</Button>
<Button variant="ghost" size="sm">
Disconnect
</Button>
</>
) : (
<Button size="sm">Set up {provider.name}</Button>
)}
</div>
</div>
</div>
))}
<Button variant="outline" className="w-full">
+ Add payment provider
</Button>
</div>
</SettingsCard>
{/* Payment Settings - Third priority (test mode, capture, etc) */}
<SettingsCard
title="Payment Settings"
description="General payment options"
>
{/* Test Mode Banner */}
{testMode && (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-4">
<div className="flex items-center gap-2">
<span className="text-yellow-600 dark:text-yellow-400 font-semibold">
Test Mode Active
</span>
<span className="text-sm text-yellow-700 dark:text-yellow-300">
No real charges will be processed
</span>
</div>
</div>
)}
<ToggleField
id="testMode"
label="Test mode"
description="Process test transactions without real charges"
checked={testMode}
onCheckedChange={setTestMode}
/>
<div className="pt-4 border-t">
<div className="space-y-2">
<label className="text-sm font-medium">Payment capture</label>
<p className="text-sm text-muted-foreground">
Choose when to capture payment from customers
</p>
<div className="space-y-2 mt-2">
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="capture" value="automatic" defaultChecked />
<span className="text-sm">
<strong>Authorize and capture</strong> - Charge immediately when order is placed
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="capture" value="manual" />
<span className="text-sm">
<strong>Authorize only</strong> - Manually capture payment later
</span>
</label>
</div>
</div>
</div>
</SettingsCard>
{/* Help Card */}
<div className="bg-muted/50 border rounded-lg p-4">
<p className="text-sm font-medium mb-2">💡 Need help setting up payments?</p>
<p className="text-sm text-muted-foreground">
Our setup wizard can help you connect Stripe or PayPal in minutes.
</p>
<Button variant="link" className="px-0 mt-2">
Start setup wizard
</Button>
</div>
</SettingsLayout>
);
}