feat: Move action buttons to contextual headers for CRUD pages
Implemented proper contextual header pattern for all Order CRUD pages. Problem: - New/Edit pages had action buttons at bottom of form - Detail page showed duplicate headers (contextual + inline) - Not following mobile-first best practices Solution: [Back] Page Title [Action] 1. Edit Order Page Header: [Back] Edit Order #337 [Save] Implementation: - Added formRef to trigger form submit from header - Save button in contextual header - Removed submit button from form bottom - Button shows loading state during save Changes: - Edit.tsx: Added formRef, updated header with Save button - OrderForm.tsx: Added formRef and hideSubmitButton props - Form submit triggered via formRef.current.requestSubmit() 2. New Order Page Header: [Back] New Order [Create] Implementation: - Added formRef to trigger form submit from header - Create button in contextual header - Removed submit button from form bottom - Button shows loading state during creation Changes: - New.tsx: Added formRef, updated header with Create button - Same OrderForm props as Edit page 3. Order Detail Page Header: (hidden) Implementation: - Cleared contextual header completely - Detail page has its own inline header with actions - Inline header: [Back] Order #337 [Print] [Invoice] [Label] [Edit] Changes: - Detail.tsx: clearPageHeader() in useEffect - No duplicate headers OrderForm Component Updates: - Added formRef prop (React.RefObject<HTMLFormElement>) - Added hideSubmitButton prop (boolean) - Form element accepts ref: <form ref={formRef}> - Submit button conditionally rendered: {!hideSubmitButton && <Button...>} - Backward compatible (both props optional) Benefits: ✅ Consistent header pattern across all CRUD pages ✅ Action buttons always visible (sticky header) ✅ Better mobile UX (no scrolling to find buttons) ✅ Loading states in header buttons ✅ Clean, modern interface ✅ Follows industry standards (Gmail, Notion, Linear) Files Modified: - routes/Orders/New.tsx - routes/Orders/Edit.tsx - routes/Orders/Detail.tsx - routes/Orders/partials/OrderForm.tsx Result: ✅ New/Edit: Action buttons in contextual header ✅ Detail: No contextual header (has inline header) ✅ Professional, mobile-first UX! 🎯
This commit is contained in:
@@ -92,6 +92,8 @@ type Props = {
|
||||
rightTop?: React.ReactNode;
|
||||
itemsEditable?: boolean;
|
||||
showCoupons?: boolean;
|
||||
formRef?: React.RefObject<HTMLFormElement>;
|
||||
hideSubmitButton?: boolean;
|
||||
};
|
||||
|
||||
const STATUS_LIST = ['pending','processing','on-hold','completed','cancelled','refunded','failed'];
|
||||
@@ -113,6 +115,8 @@ export default function OrderForm({
|
||||
showCoupons = true,
|
||||
currency,
|
||||
currencySymbol,
|
||||
formRef,
|
||||
hideSubmitButton = false,
|
||||
}: Props) {
|
||||
const oneCountryOnly = countries.length === 1;
|
||||
const firstCountry = countries[0]?.code || 'US';
|
||||
@@ -346,7 +350,7 @@ export default function OrderForm({
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={cn('grid grid-cols-1 lg:grid-cols-3 gap-6', className)}>
|
||||
<form ref={formRef} onSubmit={handleSubmit} className={cn('grid grid-cols-1 lg:grid-cols-3 gap-6', className)}>
|
||||
{/* Left: Order details */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Items and Coupons */}
|
||||
@@ -933,9 +937,11 @@ export default function OrderForm({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button type="submit" disabled={submitting} className="w-full">
|
||||
{submitting ? (mode === 'edit' ? __('Saving…') : __('Creating…')) : (mode === 'edit' ? __('Save changes') : __('Create order'))}
|
||||
</Button>
|
||||
{!hideSubmitButton && (
|
||||
<Button type="submit" disabled={submitting} className="w-full">
|
||||
{submitting ? (mode === 'edit' ? __('Saving…') : __('Creating…')) : (mode === 'edit' ? __('Save changes') : __('Create order'))}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user