fix: Modal footer outside scroll + checkbox yes/no conversion
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user