fix: Implement responsive Drawer for payment gateway settings on mobile
Problem: Payment gateway settings modal was using Dialog on all screen sizes
Solution: Split into responsive Dialog (desktop) and Drawer (mobile)
Changes:
1. Added Drawer and useMediaQuery imports
2. Added isDesktop hook: useMediaQuery("(min-width: 768px)")
3. Split modal into two conditional renders:
- Desktop (≥768px): Dialog with horizontal footer layout
- Mobile (<768px): Drawer with vertical footer layout
Desktop Layout (Dialog):
- Center modal overlay
- Horizontal footer: Cancel | View in WC | Save
- max-h-[80vh] for scrolling
Mobile Layout (Drawer):
- Bottom sheet (slides up from bottom)
- Vertical footer (full width buttons):
1. Save Settings (primary)
2. View in WooCommerce (ghost)
3. Cancel (outline)
- max-h-[90vh] for more screen space
- Swipe down to dismiss
Benefits:
✅ Native mobile experience with bottom sheet
✅ Easier to reach buttons on mobile (bottom of screen)
✅ Better one-handed use
✅ Swipe gesture to dismiss
✅ Desktop keeps familiar modal experience
User Changes Applied:
- AlertDialog z-index: z-50 → z-[999] (higher than other modals)
- Dialog max-height: max-h-[100vh] → max-h-[80vh] (better desktop UX)
Files Modified:
- Payments.tsx: Responsive Dialog/Drawer implementation
- alert-dialog.tsx: Increased z-index for proper layering
This commit is contained in:
@@ -8,9 +8,11 @@ import { GenericGatewayForm } from '@/components/settings/GenericGatewayForm';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/ui/drawer';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { CreditCard, Banknote, Settings, RefreshCw, ExternalLink, Loader2, AlertTriangle } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||
|
||||
interface GatewayField {
|
||||
id: string;
|
||||
@@ -55,6 +57,7 @@ export default function PaymentsPage() {
|
||||
const [selectedGateway, setSelectedGateway] = useState<PaymentGateway | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [togglingGateway, setTogglingGateway] = useState<string | null>(null);
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
// Fetch all payment gateways
|
||||
const { data: gateways = [], isLoading, refetch } = useQuery({
|
||||
@@ -261,10 +264,10 @@ export default function PaymentsPage() {
|
||||
)}
|
||||
</SettingsLayout>
|
||||
|
||||
{/* Gateway Settings Modal */}
|
||||
{selectedGateway && (
|
||||
{/* Gateway Settings Modal - Responsive: Dialog on desktop, Drawer on mobile */}
|
||||
{selectedGateway && isDesktop && (
|
||||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[100vh] flex flex-col p-0 gap-0">
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col p-0 gap-0">
|
||||
<DialogHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
||||
<DialogTitle>{selectedGateway.title} Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -277,22 +280,20 @@ export default function PaymentsPage() {
|
||||
/>
|
||||
</div>
|
||||
{/* Footer outside scrollable area */}
|
||||
<div className="border-t px-4 sm:px-6 py-3 sm:py-4 flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-0 sm:justify-between shrink-0 bg-background sm:rounded-b-lg">
|
||||
<div className="border-t px-6 py-4 flex items-center justify-between shrink-0 bg-background rounded-b-lg">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
disabled={saveMutation.isPending}
|
||||
className="order-3 sm:order-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 order-1 sm:order-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
asChild
|
||||
className="justify-center"
|
||||
>
|
||||
<a
|
||||
href={selectedGateway.wc_settings_url}
|
||||
@@ -310,7 +311,6 @@ export default function PaymentsPage() {
|
||||
if (form) form.requestSubmit();
|
||||
}}
|
||||
disabled={saveMutation.isPending}
|
||||
className="order-1 sm:order-2"
|
||||
>
|
||||
{saveMutation.isPending ? 'Saving...' : 'Save Settings'}
|
||||
</Button>
|
||||
@@ -319,6 +319,62 @@ export default function PaymentsPage() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{selectedGateway && !isDesktop && (
|
||||
<Drawer open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||
<DrawerContent className="max-h-[90vh] flex flex-col">
|
||||
<DrawerHeader className="border-b shrink-0">
|
||||
<DrawerTitle>{selectedGateway.title} Settings</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="flex-1 overflow-y-auto px-4 py-6 min-h-0">
|
||||
<GenericGatewayForm
|
||||
gateway={selectedGateway}
|
||||
onSave={handleSaveGateway}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
hideFooter
|
||||
/>
|
||||
</div>
|
||||
{/* Footer outside scrollable area */}
|
||||
<div className="border-t px-4 py-3 flex flex-col gap-2 shrink-0 bg-background">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const form = document.querySelector('form');
|
||||
if (form) form.requestSubmit();
|
||||
}}
|
||||
disabled={saveMutation.isPending}
|
||||
className="w-full"
|
||||
>
|
||||
{saveMutation.isPending ? 'Saving...' : 'Save Settings'}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
asChild
|
||||
className="w-full"
|
||||
>
|
||||
<a
|
||||
href={selectedGateway.wc_settings_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center gap-1"
|
||||
>
|
||||
View in WooCommerce
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
disabled={saveMutation.isPending}
|
||||
className="w-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user