Mobile Improvements: 1. Modal footer buttons now stack vertically on mobile - Order: Save Settings (primary) -> View in WooCommerce -> Cancel - Full width buttons on mobile for easier tapping - Responsive padding: px-4 on mobile, px-6 on desktop 2. Refresh button moved inline with title - Added action prop to SettingsLayout - Refresh button now appears next to Payments title - Cleaner, more compact layout Payment Categories Simplified: 3. Removed Payment Providers section - PayPal, Stripe are also 3rd party, not different - Confusing to separate providers from other gateways - All non-manual gateways now in single category 4. Renamed to Online Payment Methods - Was: Manual + Payment Providers + 3rd Party - Now: Manual + Online Payment Methods - Clearer distinction: offline vs online payments 5. Unified styling for all online gateways - Same card style as manual methods - Status badges (Enabled/Disabled) - Requirements alerts - Manage button always visible Mobile UX: - Footer buttons: flex-col on mobile, flex-row on desktop - Proper button ordering with CSS order utilities - Responsive spacing and padding - Touch-friendly button sizes Files Modified: - Payments.tsx: Mobile footer + simplified categories - SettingsLayout.tsx: Added action prop for header actions Result: ✅ Better mobile experience ✅ Clearer payment method organization ✅ Consistent styling across all gateways
90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Loader2 } from 'lucide-react';
|
|
|
|
interface SettingsLayoutProps {
|
|
title: string;
|
|
description?: string;
|
|
children: React.ReactNode;
|
|
onSave?: () => Promise<void>;
|
|
saveLabel?: string;
|
|
isLoading?: boolean;
|
|
action?: React.ReactNode;
|
|
}
|
|
|
|
export function SettingsLayout({
|
|
title,
|
|
description,
|
|
children,
|
|
onSave,
|
|
saveLabel = 'Save changes',
|
|
isLoading = false,
|
|
action,
|
|
}: SettingsLayoutProps) {
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
const handleSave = async () => {
|
|
if (!onSave) return;
|
|
setIsSaving(true);
|
|
try {
|
|
await onSave();
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
{/* Sticky Header with Save Button */}
|
|
{onSave && (
|
|
<div className="sticky top-0 z-10 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
<div className="container max-w-5xl mx-auto px-4 py-3 flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-lg font-semibold">{title}</h1>
|
|
</div>
|
|
<Button
|
|
onClick={handleSave}
|
|
disabled={isSaving || isLoading}
|
|
size="sm"
|
|
>
|
|
{isSaving ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
saveLabel
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<div className="container max-w-5xl mx-auto px-4 py-8">
|
|
{!onSave && (
|
|
<div className="mb-8">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
|
|
{description && (
|
|
<p className="text-muted-foreground mt-2">{description}</p>
|
|
)}
|
|
</div>
|
|
{action && <div className="shrink-0">{action}</div>}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">{children}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|