fix: Modal footer outside scroll + checkbox yes/no conversion

This commit is contained in:
dwindown
2025-11-06 00:05:22 +07:00
parent 96f0482cfb
commit 91449bec60
2 changed files with 95 additions and 43 deletions

View File

@@ -47,12 +47,13 @@ interface GenericGatewayFormProps {
};
onSave: (settings: Record<string, unknown>) => Promise<void>;
onCancel: () => void;
hideFooter?: boolean;
}
// Supported field types (outside component to avoid re-renders)
const SUPPORTED_FIELD_TYPES = ['text', 'password', 'checkbox', 'select', 'textarea', 'number', 'email', 'url'];
export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGatewayFormProps) {
export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = false }: GenericGatewayFormProps) {
const [formData, setFormData] = useState<Record<string, unknown>>({});
const [isSaving, setIsSaving] = useState(false);
const [unsupportedFields, setUnsupportedFields] = useState<string[]>([]);
@@ -126,12 +127,14 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
switch (field.type) {
case 'checkbox':
// WooCommerce uses "yes"/"no" strings, convert to boolean
const isChecked = value === 'yes' || value === true;
return (
<div key={field.id} className="flex items-center space-x-2">
<Checkbox
id={field.id}
checked={value as boolean}
onCheckedChange={(checked) => handleFieldChange(field.id, checked)}
checked={isChecked}
onCheckedChange={(checked) => handleFieldChange(field.id, checked ? 'yes' : 'no')}
/>
<div className="grid gap-1.5 leading-none">
<Label
@@ -159,7 +162,10 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
{field.required && <span className="text-destructive ml-1">*</span>}
</Label>
{field.description && (
<p className="text-sm text-muted-foreground">{field.description}</p>
<p
className="text-sm text-muted-foreground"
dangerouslySetInnerHTML={{ __html: field.description }}
/>
)}
<Select
value={value as string}
@@ -188,7 +194,10 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
{field.required && <span className="text-destructive ml-1">*</span>}
</Label>
{field.description && (
<p className="text-sm text-muted-foreground">{field.description}</p>
<p
className="text-sm text-muted-foreground"
dangerouslySetInnerHTML={{ __html: field.description }}
/>
)}
<Textarea
id={field.id}
@@ -210,7 +219,10 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
{field.required && <span className="text-destructive ml-1">*</span>}
</Label>
{field.description && (
<p className="text-sm text-muted-foreground">{field.description}</p>
<p
className="text-sm text-muted-foreground"
dangerouslySetInnerHTML={{ __html: field.description }}
/>
)}
<Input
id={field.id}
@@ -322,8 +334,9 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
)}
</form>
{/* Sticky Footer */}
<div className="sticky bottom-0 bg-background border-t px-6 py-4 flex items-center justify-between mt-6">
{/* Footer - only render if not hidden */}
{!hideFooter && (
<div className="sticky bottom-0 bg-background border-t py-4 -mx-6 px-6 flex items-center justify-between mt-6">
<Button
type="button"
variant="outline"
@@ -362,6 +375,7 @@ export function GenericGatewayForm({ gateway, onSave, onCancel }: GenericGateway
</Button>
</div>
</div>
)}
</>
);
}

View File

@@ -328,17 +328,55 @@ export default function PaymentsPage() {
{/* Gateway Settings Modal */}
{selectedGateway && (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="max-w-2xl max-h-[80vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-4 border-b">
<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>
<div className="flex-1 overflow-y-auto px-6 py-4">
<div className="flex-1 overflow-y-auto px-6 py-4 min-h-0">
<GenericGatewayForm
gateway={selectedGateway}
onSave={handleSaveGateway}
onCancel={() => setIsModalOpen(false)}
hideFooter
/>
</div>
{/* Footer outside scrollable area */}
<div className="border-t px-6 py-4 flex items-center justify-between shrink-0 bg-background">
<Button
type="button"
variant="outline"
onClick={() => setIsModalOpen(false)}
disabled={saveMutation.isPending}
>
Cancel
</Button>
<div className="flex items-center gap-2">
<Button
type="button"
variant="ghost"
asChild
>
<a
href={selectedGateway.wc_settings_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1"
>
View in WooCommerce
<ExternalLink className="h-4 w-4" />
</a>
</Button>
<Button
onClick={() => {
const form = document.querySelector('form');
if (form) form.requestSubmit();
}}
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)}