فهرست منبع

refactor(dashboard): Simplify page layout architecture (#3447)

David Höck 10 ماه پیش
والد
کامیت
7704191675
23فایلهای تغییر یافته به همراه1609 افزوده شده و 1703 حذف شده
  1. 57 59
      packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx
  2. 133 140
      packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx
  3. 107 122
      packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx
  4. 39 42
      packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx
  5. 30 33
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx
  6. 129 132
      packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx
  7. 45 50
      packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx
  8. 71 75
      packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx
  9. 62 65
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx
  10. 84 80
      packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx
  11. 187 192
      packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx
  12. 96 99
      packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx
  13. 42 45
      packages/dashboard/src/app/routes/_authenticated/_profile/profile.tsx
  14. 104 110
      packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx
  15. 56 59
      packages/dashboard/src/app/routes/_authenticated/_roles/roles_.$id.tsx
  16. 41 53
      packages/dashboard/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx
  17. 59 67
      packages/dashboard/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx
  18. 30 33
      packages/dashboard/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx
  19. 34 35
      packages/dashboard/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx
  20. 64 69
      packages/dashboard/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx
  21. 34 40
      packages/dashboard/src/app/routes/_authenticated/_zones/zones_.$id.tsx
  22. 46 39
      packages/dashboard/src/lib/framework/layout-engine/page-layout.tsx
  23. 59 64
      packages/dashboard/src/lib/framework/page/detail-page.tsx

+ 57 - 59
packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx

@@ -11,7 +11,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -24,7 +23,7 @@ import {
     administratorDetailDocument,
     createAdministratorDocument,
     updateAdministratorDocument,
-} from './administrators.graphql.js'; 
+} from './administrators.graphql.js';
 import { RolePermissionsDisplay } from './components/role-permissions-display.js';
 
 export const Route = createFileRoute('/_authenticated/_administrators/administrators_/$id')({
@@ -91,67 +90,66 @@ function AdministratorDetailPage() {
     const roleIds = form.watch('roleIds');
 
     return (
-        <Page pageId="administrator-detail">
+        <Page pageId="administrator-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New administrator</Trans> : name}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateAdministrator']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <div className="md:grid md:grid-cols-2 gap-4">
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="firstName"
-                                label={<Trans>First name</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="lastName"
-                                label={<Trans>Last name</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="emailAddress"
-                                label={<Trans>Email Address or identifier</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="password"
-                                label={<Trans>Password</Trans>}
-                                render={({ field }) => <Input placeholder="" type="password" {...field} />}
-                            />
-                        </div>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Administrator" control={form.control} />
-                    <PageBlock column="main" blockId="roles" title={<Trans>Roles</Trans>}>
+
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateAdministrator']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <div className="md:grid md:grid-cols-2 gap-4">
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="firstName"
+                            label={<Trans>First name</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
+                        />
                         <FormFieldWrapper
                             control={form.control}
-                            name="roleIds"
-                            render={({ field }) => (
-                                <RoleSelector
-                                    value={field.value ?? []}
-                                    onChange={field.onChange}
-                                    multiple={true}
-                                />
-                            )}
+                            name="lastName"
+                            label={<Trans>Last name</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
                         />
-                        <RolePermissionsDisplay value={roleIds ?? []} />
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="emailAddress"
+                            label={<Trans>Email Address or identifier</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="password"
+                            label={<Trans>Password</Trans>}
+                            render={({ field }) => <Input placeholder="" type="password" {...field} />}
+                        />
+                    </div>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Administrator" control={form.control} />
+                <PageBlock column="main" blockId="roles" title={<Trans>Roles</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="roleIds"
+                        render={({ field }) => (
+                            <RoleSelector
+                                value={field.value ?? []}
+                                onChange={field.onChange}
+                                multiple={true}
+                            />
+                        )}
+                    />
+                    <RolePermissionsDisplay value={roleIds ?? []} />
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 133 - 140
packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx

@@ -1,3 +1,4 @@
+import { ChannelCodeLabel } from '@/components/shared/channel-code-label.js';
 import { CurrencySelector } from '@/components/shared/currency-selector.js';
 import { ErrorPage } from '@/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
@@ -16,7 +17,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -25,7 +25,6 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
 import { Trans, useLingui } from '@/lib/trans.js';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
-import { ChannelCodeLabel } from '@/components/shared/channel-code-label.js';
 import { channelDetailDocument, createChannelDocument, updateChannelDocument } from './channels.graphql.js';
 
 export const Route = createFileRoute('/_authenticated/_channels/channels_/$id')({
@@ -105,7 +104,7 @@ function ChannelDetailPage() {
     const codeIsDefault = entity?.code === DEFAULT_CHANNEL_CODE;
 
     return (
-        <Page pageId="channel-detail">
+        <Page pageId="channel-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? (
                     <Trans>New channel</Trans>
@@ -113,143 +112,137 @@ function ChannelDetailPage() {
                     <ChannelCodeLabel code={entity?.code ?? ''} />
                 )}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateChannel']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="code"
-                                label={<Trans>Code</Trans>}
-                                render={({ field }) => (
-                                    <Input placeholder="" {...field} disabled={codeIsDefault} />
-                                )}
-                            />
-                            <div></div>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="token"
-                                label={<Trans>Token</Trans>}
-                                description={
-                                    <Trans>
-                                        The token is used to specify the channel when making API requests.
-                                    </Trans>
-                                }
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="sellerId"
-                                label={<Trans>Seller</Trans>}
-                                render={({ field }) => (
-                                    <SellerSelector value={field.value ?? ''} onChange={field.onChange} />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="availableLanguageCodes"
-                                label={<Trans>Available languages</Trans>}
-                                render={({ field }) => (
-                                    <LanguageSelector
-                                        value={field.value ?? []}
-                                        onChange={field.onChange}
-                                        multiple={true}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="availableCurrencyCodes"
-                                label={<Trans>Available currencies</Trans>}
-                                render={({ field }) => (
-                                    <CurrencySelector
-                                        value={field.value ?? []}
-                                        onChange={field.onChange}
-                                        multiple={true}
-                                    />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <PageBlock
-                        column="main"
-                        blockId="channel-defaults"
-                        title={<Trans>Channel defaults</Trans>}
-                    >
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="defaultLanguageCode"
-                                label={<Trans>Default language</Trans>}
-                                render={({ field }) => (
-                                    <LanguageSelector
-                                        value={field.value ?? ''}
-                                        onChange={field.onChange}
-                                        multiple={false}
-                                        availableLanguageCodes={availableLanguageCodes ?? []}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="defaultCurrencyCode"
-                                label={<Trans>Default currency</Trans>}
-                                render={({ field }) => (
-                                    <CurrencySelector
-                                        value={field.value ?? ''}
-                                        onChange={field.onChange}
-                                        multiple={false}
-                                        availableCurrencyCodes={availableCurrencyCodes ?? []}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="defaultTaxZoneId"
-                                label={<Trans>Default tax zone</Trans>}
-                                render={({ field }) => (
-                                    <ZoneSelector value={field.value ?? ''} onChange={field.onChange} />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="defaultShippingZoneId"
-                                label={<Trans>Default shipping zone</Trans>}
-                                render={({ field }) => (
-                                    <ZoneSelector value={field.value ?? ''} onChange={field.onChange} />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="pricesIncludeTax"
-                                label={<Trans>Prices include tax for default tax zone</Trans>}
-                                description={
-                                    <Trans>
-                                        When this is enabled, the prices entered in the product catalog will
-                                        be included in the tax for the default tax zone.
-                                    </Trans>
-                                }
-                                render={({ field }) => (
-                                    <Switch checked={field.value ?? false} onCheckedChange={field.onChange} />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Channel" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateChannel']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => (
+                                <Input placeholder="" {...field} disabled={codeIsDefault} />
+                            )}
+                        />
+                        <div></div>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="token"
+                            label={<Trans>Token</Trans>}
+                            description={
+                                <Trans>
+                                    The token is used to specify the channel when making API requests.
+                                </Trans>
+                            }
+                            render={({ field }) => <Input placeholder="" {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="sellerId"
+                            label={<Trans>Seller</Trans>}
+                            render={({ field }) => (
+                                <SellerSelector value={field.value ?? ''} onChange={field.onChange} />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="availableLanguageCodes"
+                            label={<Trans>Available languages</Trans>}
+                            render={({ field }) => (
+                                <LanguageSelector
+                                    value={field.value ?? []}
+                                    onChange={field.onChange}
+                                    multiple={true}
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="availableCurrencyCodes"
+                            label={<Trans>Available currencies</Trans>}
+                            render={({ field }) => (
+                                <CurrencySelector
+                                    value={field.value ?? []}
+                                    onChange={field.onChange}
+                                    multiple={true}
+                                />
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <PageBlock column="main" blockId="channel-defaults" title={<Trans>Channel defaults</Trans>}>
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="defaultLanguageCode"
+                            label={<Trans>Default language</Trans>}
+                            render={({ field }) => (
+                                <LanguageSelector
+                                    value={field.value ?? ''}
+                                    onChange={field.onChange}
+                                    multiple={false}
+                                    availableLanguageCodes={availableLanguageCodes ?? []}
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="defaultCurrencyCode"
+                            label={<Trans>Default currency</Trans>}
+                            render={({ field }) => (
+                                <CurrencySelector
+                                    value={field.value ?? ''}
+                                    onChange={field.onChange}
+                                    multiple={false}
+                                    availableCurrencyCodes={availableCurrencyCodes ?? []}
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="defaultTaxZoneId"
+                            label={<Trans>Default tax zone</Trans>}
+                            render={({ field }) => (
+                                <ZoneSelector value={field.value ?? ''} onChange={field.onChange} />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="defaultShippingZoneId"
+                            label={<Trans>Default shipping zone</Trans>}
+                            render={({ field }) => (
+                                <ZoneSelector value={field.value ?? ''} onChange={field.onChange} />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="pricesIncludeTax"
+                            label={<Trans>Prices include tax for default tax zone</Trans>}
+                            description={
+                                <Trans>
+                                    When this is enabled, the prices entered in the product catalog will be
+                                    included in the tax for the default tax zone.
+                                </Trans>
+                            }
+                            render={({ field }) => (
+                                <Switch checked={field.value ?? false} onCheckedChange={field.onChange} />
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Channel" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 107 - 122
packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx

@@ -1,21 +1,13 @@
+import { RichTextInput } from '@/components/data-input/richt-text-input.js';
 import { EntityAssets } from '@/components/shared/entity-assets.js';
 import { ErrorPage } from '@/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
-import {
-    TranslatableFormFieldWrapper
-} from '@/components/shared/translatable-form-field.js';
+import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
 import { Button } from '@/components/ui/button.js';
-import {
-    FormControl,
-    FormDescription,
-    FormItem,
-    FormLabel,
-    FormMessage
-} from '@/components/ui/form.js';
+import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
 import { Input } from '@/components/ui/input.js';
 import { Switch } from '@/components/ui/switch.js';
-import { Textarea } from '@/components/ui/textarea.js';
 import { NEW_ENTITY_PATH } from '@/constants.js';
 import {
     CustomFieldsPageBlock,
@@ -24,7 +16,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -41,7 +32,6 @@ import {
 import { CollectionContentsPreviewTable } from './components/collection-contents-preview-table.js';
 import { CollectionContentsTable } from './components/collection-contents-table.js';
 import { CollectionFiltersSelector } from './components/collection-filters-selector.js';
-import { RichTextInput } from '@/components/data-input/richt-text-input.js';
 
 export const Route = createFileRoute('/_authenticated/_collections/collections_/$id')({
     component: CollectionDetailPage,
@@ -118,122 +108,117 @@ function CollectionDetailPage() {
     const currentInheritFiltersValue = form.watch('inheritFilters');
 
     return (
-        <Page pageId="collection-detail">
+        <Page pageId="collection-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New collection</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateCollection', 'UpdateCatalog']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="privacy">
-                        <FormFieldWrapper
-                            control={form.control}
-                            name="isPrivate"
-                            label={<Trans>Private</Trans>}
-                            description={<Trans>Private collections are not visible in the shop</Trans>}
-                            render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
-                            )}
-                        />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="slug"
-                                label={<Trans>Slug</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateCollection', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="privacy">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="isPrivate"
+                        label={<Trans>Private</Trans>}
+                        description={<Trans>Private collections are not visible in the shop</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
                         <TranslatableFormFieldWrapper
                             control={form.control}
-                            name="description"
-                            label={<Trans>Description</Trans>}
-                            render={({ field }) => <RichTextInput {...field} />}
-                        />
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Collection" control={form.control} />
-                    <PageBlock column="main" blockId="filters" title={<Trans>Filters</Trans>}>
-                        <FormFieldWrapper
-                            control={form.control}
-                            name="inheritFilters"
-                            label={<Trans>Inherit filters</Trans>}
-                            description={
-                                <Trans>
-                                    If enabled, the filters will be inherited from the parent collection and
-                                    combined with the filters set on this collection.
-                                </Trans>
-                            }
-                            render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
-                            )}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
-                        <FormFieldWrapper
+                        <TranslatableFormFieldWrapper
                             control={form.control}
-                            name="filters"
-                            render={({ field }) => (
-                                <CollectionFiltersSelector
-                                    value={field.value ?? []}
-                                    onChange={field.onChange}
-                                />
-                            )}
+                            name="slug"
+                            label={<Trans>Slug</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
-                    </PageBlock>
-                    <PageBlock column="side" blockId="assets">
-                        <FormItem>
-                            <FormLabel>
-                                <Trans>Assets</Trans>
-                            </FormLabel>
-                            <FormControl>
-                                <EntityAssets
-                                    assets={entity?.assets}
-                                    featuredAsset={entity?.featuredAsset}
-                                    compact={true}
-                                    value={form.getValues()}
-                                    onChange={value => {
-                                        form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                        form.setValue('assetIds', value.assetIds ?? [], {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                    }}
-                                />
-                            </FormControl>
-                            <FormDescription></FormDescription>
-                            <FormMessage />
-                        </FormItem>
-                    </PageBlock>
-                    <PageBlock column="main" blockId="contents" title={<Trans>Facet values</Trans>}>
-                        {shouldPreviewContents || creatingNewEntity ? (
-                            <CollectionContentsPreviewTable
-                                parentId={entity?.parent?.id}
-                                filters={currentFiltersValue ?? []}
-                                inheritFilters={currentInheritFiltersValue ?? false}
-                            />
-                        ) : (
-                            <CollectionContentsTable collectionId={entity?.id} />
+                    </DetailFormGrid>
+                    <TranslatableFormFieldWrapper
+                        control={form.control}
+                        name="description"
+                        label={<Trans>Description</Trans>}
+                        render={({ field }) => <RichTextInput {...field} />}
+                    />
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Collection" control={form.control} />
+                <PageBlock column="main" blockId="filters" title={<Trans>Filters</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="inheritFilters"
+                        label={<Trans>Inherit filters</Trans>}
+                        description={
+                            <Trans>
+                                If enabled, the filters will be inherited from the parent collection and
+                                combined with the filters set on this collection.
+                            </Trans>
+                        }
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="filters"
+                        render={({ field }) => (
+                            <CollectionFiltersSelector value={field.value ?? []} onChange={field.onChange} />
                         )}
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                    />
+                </PageBlock>
+                <PageBlock column="side" blockId="assets">
+                    <FormItem>
+                        <FormLabel>
+                            <Trans>Assets</Trans>
+                        </FormLabel>
+                        <FormControl>
+                            <EntityAssets
+                                assets={entity?.assets}
+                                featuredAsset={entity?.featuredAsset}
+                                compact={true}
+                                value={form.getValues()}
+                                onChange={value => {
+                                    form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
+                                    form.setValue('assetIds', value.assetIds ?? [], {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
+                                }}
+                            />
+                        </FormControl>
+                        <FormDescription></FormDescription>
+                        <FormMessage />
+                    </FormItem>
+                </PageBlock>
+                <PageBlock column="main" blockId="contents" title={<Trans>Facet values</Trans>}>
+                    {shouldPreviewContents || creatingNewEntity ? (
+                        <CollectionContentsPreviewTable
+                            parentId={entity?.parent?.id}
+                            filters={currentFiltersValue ?? []}
+                            inheritFilters={currentInheritFiltersValue ?? false}
+                        />
+                    ) : (
+                        <CollectionContentsTable collectionId={entity?.id} />
+                    )}
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 39 - 42
packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx

@@ -13,7 +13,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -76,51 +75,49 @@ function CountryDetailPage() {
     });
 
     return (
-        <Page pageId="country-detail">
+        <Page pageId="country-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New country</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateCountry']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateCountry']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled">
+                    <FormFieldWrapper
+                        control={form.control}
+                        label={<Trans>Enabled</Trans>}
+                        name="enabled"
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
+                        />
                         <FormFieldWrapper
                             control={form.control}
-                            label={<Trans>Enabled</Trans>}
-                            name="enabled"
-                            render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
-                            )}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="code"
-                                label={<Trans>Code</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Country" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Country" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 30 - 33
packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx

@@ -12,7 +12,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -73,42 +72,40 @@ function CustomerGroupDetailPage() {
     });
 
     return (
-        <Page pageId="customer-group-detail">
+        <Page pageId="customer-group-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New customer group</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateCustomerGroup']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input placeholder="" {...field} />}
-                            />
-                        </DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateCustomerGroup']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input placeholder="" {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="CustomerGroup" control={form.control} />
+                {entity && (
+                    <PageBlock column="main" blockId="customers" title={<Trans>Customers</Trans>}>
+                        <CustomerGroupMembersTable customerGroupId={entity?.id} />
                     </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="CustomerGroup" control={form.control} />
-                    {entity && (
-                        <PageBlock column="main" blockId="customers" title={<Trans>Customers</Trans>}>
-                            <CustomerGroupMembersTable customerGroupId={entity?.id} />
-                        </PageBlock>
-                    )}
-                </PageLayout>
-            </PageDetailForm>
+                )}
+            </PageLayout>
         </Page>
     );
 }

+ 129 - 132
packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx

@@ -1,6 +1,7 @@
 import { CustomerGroupChip } from '@/components/shared/customer-group-chip.js';
 import { CustomerGroupSelector } from '@/components/shared/customer-group-selector.js';
 import { ErrorPage } from '@/components/shared/error-page.js';
+import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
 import { Button } from '@/components/ui/button.js';
 import {
@@ -20,7 +21,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -46,7 +46,6 @@ import {
     removeCustomerFromGroupDocument,
     updateCustomerDocument,
 } from './customers.graphql.js';
-import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 
 export const Route = createFileRoute('/_authenticated/_customers/customers_/$id')({
     component: CustomerDetailPage,
@@ -139,140 +138,138 @@ function CustomerDetailPage() {
     console.log(entity);
 
     return (
-        <Page pageId="customer-detail">
+        <Page pageId="customer-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New customer</Trans> : customerName}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateCustomer']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="title"
-                                label={<Trans>Title</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <div></div>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="firstName"
-                                label={<Trans>First name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="lastName"
-                                label={<Trans>Last name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="emailAddress"
-                                label={<Trans>Email address</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="phoneNumber"
-                                label={<Trans>Phone number</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Customer" control={form.control} />
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateCustomer']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="title"
+                            label={<Trans>Title</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <div></div>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="firstName"
+                            label={<Trans>First name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="lastName"
+                            label={<Trans>Last name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="emailAddress"
+                            label={<Trans>Email address</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="phoneNumber"
+                            label={<Trans>Phone number</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Customer" control={form.control} />
 
-                    {entity && (
-                        <>
-                            <PageBlock column="main" blockId="addresses" title={<Trans>Addresses</Trans>}>
-                                <DetailFormGrid>
-                                    {entity?.addresses?.map(address => (
-                                        <CustomerAddressCard
-                                            key={address.id}
-                                            address={address}
-                                            editable
-                                            deletable
-                                            onUpdate={() => {
-                                                refreshEntity();
-                                            }}
-                                            onDelete={() => {
-                                                refreshEntity();
-                                            }}
-                                        />
-                                    ))}
-                                </DetailFormGrid>
+                {entity && (
+                    <>
+                        <PageBlock column="main" blockId="addresses" title={<Trans>Addresses</Trans>}>
+                            <DetailFormGrid>
+                                {entity?.addresses?.map(address => (
+                                    <CustomerAddressCard
+                                        key={address.id}
+                                        address={address}
+                                        editable
+                                        deletable
+                                        onUpdate={() => {
+                                            refreshEntity();
+                                        }}
+                                        onDelete={() => {
+                                            refreshEntity();
+                                        }}
+                                    />
+                                ))}
+                            </DetailFormGrid>
 
-                                <Dialog open={newAddressOpen} onOpenChange={setNewAddressOpen}>
-                                    <DialogTrigger asChild>
-                                        <Button variant="outline">
-                                            <Plus className="w-4 h-4" /> <Trans>Add new address</Trans>
-                                        </Button>
-                                    </DialogTrigger>
-                                    <DialogContent>
-                                        <DialogHeader>
-                                            <DialogTitle>
-                                                <Trans>Add new address</Trans>
-                                            </DialogTitle>
-                                            <DialogDescription>
-                                                <Trans>Add a new address to the customer.</Trans>
-                                            </DialogDescription>
-                                        </DialogHeader>
-                                        <CustomerAddressForm
-                                            onSubmit={values => {
-                                                const { id, ...input } = values;
-                                                createAddress({
-                                                    customerId: entity.id,
-                                                    input,
-                                                });
-                                            }}
-                                        />
-                                    </DialogContent>
-                                </Dialog>
-                            </PageBlock>
+                            <Dialog open={newAddressOpen} onOpenChange={setNewAddressOpen}>
+                                <DialogTrigger asChild>
+                                    <Button variant="outline">
+                                        <Plus className="w-4 h-4" /> <Trans>Add new address</Trans>
+                                    </Button>
+                                </DialogTrigger>
+                                <DialogContent>
+                                    <DialogHeader>
+                                        <DialogTitle>
+                                            <Trans>Add new address</Trans>
+                                        </DialogTitle>
+                                        <DialogDescription>
+                                            <Trans>Add a new address to the customer.</Trans>
+                                        </DialogDescription>
+                                    </DialogHeader>
+                                    <CustomerAddressForm
+                                        onSubmit={values => {
+                                            const { id, ...input } = values;
+                                            createAddress({
+                                                customerId: entity.id,
+                                                input,
+                                            });
+                                        }}
+                                    />
+                                </DialogContent>
+                            </Dialog>
+                        </PageBlock>
 
-                            <PageBlock column="main" blockId="orders" title={<Trans>Orders</Trans>}>
-                                <CustomerOrderTable customerId={entity.id} />
-                            </PageBlock>
-                            <PageBlock column="main" blockId="history" title={<Trans>Customer history</Trans>}>
-                                <CustomerHistoryContainer customerId={entity.id} />
-                            </PageBlock>
-                            <PageBlock column="side" blockId="status" title={<Trans>Status</Trans>}>
-                                <CustomerStatusBadge user={entity.user} />
-                            </PageBlock>
-                            <PageBlock column="side" blockId="groups" title={<Trans>Customer groups</Trans>}>
-                                <div
-                                    className={`flex flex-col gap-2 ${entity?.groups?.length > 0 ? 'mb-2' : ''}`}
-                                >
-                                    {entity?.groups?.map(group => (
-                                        <CustomerGroupChip
-                                            key={group.id}
-                                            group={group}
-                                            onRemove={groupId =>
-                                                removeCustomerFromGroup({ customerId: entity.id, groupId })
-                                            }
-                                        />
-                                    ))}
-                                </div>
-                                <CustomerGroupSelector
-                                    onSelect={group =>
-                                        addCustomerToGroup({ customerId: entity.id, groupId: group.id })
-                                    }
-                                />
-                            </PageBlock>
-                        </>
-                    )}
-                </PageLayout>
-            </PageDetailForm>
+                        <PageBlock column="main" blockId="orders" title={<Trans>Orders</Trans>}>
+                            <CustomerOrderTable customerId={entity.id} />
+                        </PageBlock>
+                        <PageBlock column="main" blockId="history" title={<Trans>Customer history</Trans>}>
+                            <CustomerHistoryContainer customerId={entity.id} />
+                        </PageBlock>
+                        <PageBlock column="side" blockId="status" title={<Trans>Status</Trans>}>
+                            <CustomerStatusBadge user={entity.user} />
+                        </PageBlock>
+                        <PageBlock column="side" blockId="groups" title={<Trans>Customer groups</Trans>}>
+                            <div
+                                className={`flex flex-col gap-2 ${entity?.groups?.length > 0 ? 'mb-2' : ''}`}
+                            >
+                                {entity?.groups?.map(group => (
+                                    <CustomerGroupChip
+                                        key={group.id}
+                                        group={group}
+                                        onRemove={groupId =>
+                                            removeCustomerFromGroup({ customerId: entity.id, groupId })
+                                        }
+                                    />
+                                ))}
+                            </div>
+                            <CustomerGroupSelector
+                                onSelect={group =>
+                                    addCustomerToGroup({ customerId: entity.id, groupId: group.id })
+                                }
+                            />
+                        </PageBlock>
+                    </>
+                )}
+            </PageLayout>
         </Page>
     );
 }

+ 45 - 50
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx

@@ -1,9 +1,7 @@
 import { ErrorPage } from '@/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
-import {
-    TranslatableFormFieldWrapper
-} from '@/components/shared/translatable-form-field.js';
+import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
 import { Button } from '@/components/ui/button.js';
 import { Input } from '@/components/ui/input.js';
 import { Switch } from '@/components/ui/switch.js';
@@ -15,7 +13,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -88,57 +85,55 @@ function FacetDetailPage() {
     });
 
     return (
-        <Page pageId="facet-detail">
+        <Page pageId="facet-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New facet</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="privacy">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="privacy">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="isPrivate"
+                        label={<Trans>Private</Trans>}
+                        description={<Trans>Private facets are not visible in the shop</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
                         <FormFieldWrapper
                             control={form.control}
-                            name="isPrivate"
-                            label={<Trans>Private</Trans>}
-                            description={<Trans>Private facets are not visible in the shop</Trans>}
-                            render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
-                            )}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Facet" control={form.control} />
+                {entity && (
+                    <PageBlock column="main" blockId="facet-values" title={<Trans>Facet values</Trans>}>
+                        <FacetValuesTable facetId={entity?.id} />
                     </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="code"
-                                label={<Trans>Code</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Facet" control={form.control} />
-                    {entity && (
-                        <PageBlock column="main" blockId="facet-values" title={<Trans>Facet values</Trans>}>
-                            <FacetValuesTable facetId={entity?.id} />
-                        </PageBlock>
-                    )}
-                </PageLayout>
-            </PageDetailForm>
+                )}
+            </PageLayout>
         </Page>
     );
 }

+ 71 - 75
packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx

@@ -13,7 +13,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -83,83 +82,80 @@ function GlobalSettingsPage() {
     });
 
     return (
-        <Page pageId="global-settings">
+        <Page pageId="global-settings" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 <Trans>Global settings</Trans>
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateSettings', 'UpdateGlobalSettings']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="availableLanguages"
-                                label={<Trans>Available languages</Trans>}
-                                description={
-                                    <Trans>
-                                        Sets the languages that are available for all channels. Individual
-                                        channels can then support a subset of these languages.
-                                    </Trans>
-                                }
-                                render={({ field }) => (
-                                    <LanguageSelector
-                                        value={field.value ?? []}
-                                        onChange={field.onChange}
-                                        multiple={true}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="outOfStockThreshold"
-                                label={<Trans>Global out of stock threshold</Trans>}
-                                description={
-                                    <Trans>
-                                        Sets the stock level at which this a variant is considered to be out
-                                        of stock. Using a negative value enables backorder support. Can be
-                                        overridden by product variants.
-                                    </Trans>
-                                }
-                                render={({ field }) => (
-                                    <Input
-                                        value={field.value ?? []}
-                                        onChange={e => field.onChange(Number(e.target.valueAsNumber))}
-                                        type="number"
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="trackInventory"
-                                label={<Trans>Track inventory by default</Trans>}
-                                description={
-                                    <Trans>
-                                        When tracked, product variant stock levels will be automatically
-                                        adjusted when sold. This setting can be overridden by individual
-                                        product variants.
-                                    </Trans>
-                                }
-                                render={({ field }) => (
-                                    <Switch checked={field.value} onCheckedChange={field.onChange} />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="GlobalSettings" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateSettings', 'UpdateGlobalSettings']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="availableLanguages"
+                            label={<Trans>Available languages</Trans>}
+                            description={
+                                <Trans>
+                                    Sets the languages that are available for all channels. Individual
+                                    channels can then support a subset of these languages.
+                                </Trans>
+                            }
+                            render={({ field }) => (
+                                <LanguageSelector
+                                    value={field.value ?? []}
+                                    onChange={field.onChange}
+                                    multiple={true}
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="outOfStockThreshold"
+                            label={<Trans>Global out of stock threshold</Trans>}
+                            description={
+                                <Trans>
+                                    Sets the stock level at which this a variant is considered to be out of
+                                    stock. Using a negative value enables backorder support. Can be overridden
+                                    by product variants.
+                                </Trans>
+                            }
+                            render={({ field }) => (
+                                <Input
+                                    value={field.value ?? []}
+                                    onChange={e => field.onChange(Number(e.target.valueAsNumber))}
+                                    type="number"
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="trackInventory"
+                            label={<Trans>Track inventory by default</Trans>}
+                            description={
+                                <Trans>
+                                    When tracked, product variant stock levels will be automatically adjusted
+                                    when sold. This setting can be overridden by individual product variants.
+                                </Trans>
+                            }
+                            render={({ field }) => (
+                                <Switch checked={field.value} onCheckedChange={field.onChange} />
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="GlobalSettings" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 62 - 65
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx

@@ -8,14 +8,13 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
 import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/framework/page/use-detail-page.js';
 import { Trans, useLingui } from '@/lib/trans.js';
-import { createFileRoute, Link } from '@tanstack/react-router';
+import { Link, createFileRoute } from '@tanstack/react-router';
 import { User } from 'lucide-react';
 import { toast } from 'sonner';
 import { OrderAddress } from './components/order-address.js';
@@ -59,78 +58,76 @@ function OrderDetailPage() {
             });
         },
     });
-    
+
     if (!entity) {
         return null;
     }
 
     return (
-        <Page pageId="order-detail">
+        <Page pageId="order-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{entity?.code ?? ''}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="order-table">
-                        <OrderTable order={entity} />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="tax-summary" title={<Trans>Tax summary</Trans>}>
-                        <OrderTaxSummary order={entity} />
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Order" control={form.control} />
-                    <PageBlock column="main" blockId="order-history" title={<Trans>Order history</Trans>}>
-                        <OrderHistoryContainer orderId={entity.id} />
-                    </PageBlock>
-                    <PageBlock column="side" blockId="state" title={<Trans>State</Trans>}>
-                        <Badge variant="outline">{entity?.state}</Badge>
-                    </PageBlock>
-                    <PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
-                        <Button variant="ghost" asChild>
-                            <Link to={`/customers/${entity?.customer?.id}`}>
-                                <User className="w-4 h-4" />
-                                {entity?.customer?.firstName} {entity?.customer?.lastName}
-                            </Link>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
                         </Button>
-                        <div className="mt-4 divide-y">
-                            {entity?.shippingAddress && (
-                                <div className="pb-6">
-                                    <div className="font-medium">
-                                        <Trans>Shipping address</Trans>
-                                    </div>
-                                    <OrderAddress address={entity.shippingAddress} />
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="order-table">
+                    <OrderTable order={entity} />
+                </PageBlock>
+                <PageBlock column="main" blockId="tax-summary" title={<Trans>Tax summary</Trans>}>
+                    <OrderTaxSummary order={entity} />
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Order" control={form.control} />
+                <PageBlock column="main" blockId="order-history" title={<Trans>Order history</Trans>}>
+                    <OrderHistoryContainer orderId={entity.id} />
+                </PageBlock>
+                <PageBlock column="side" blockId="state" title={<Trans>State</Trans>}>
+                    <Badge variant="outline">{entity?.state}</Badge>
+                </PageBlock>
+                <PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
+                    <Button variant="ghost" asChild>
+                        <Link to={`/customers/${entity?.customer?.id}`}>
+                            <User className="w-4 h-4" />
+                            {entity?.customer?.firstName} {entity?.customer?.lastName}
+                        </Link>
+                    </Button>
+                    <div className="mt-4 divide-y">
+                        {entity?.shippingAddress && (
+                            <div className="pb-6">
+                                <div className="font-medium">
+                                    <Trans>Shipping address</Trans>
                                 </div>
-                            )}
-                            {entity?.billingAddress && (
-                                <div className="pt-4">
-                                    <div className="font-medium">
-                                        <Trans>Billing address</Trans>
-                                    </div>
-                                    <OrderAddress address={entity.billingAddress} />
+                                <OrderAddress address={entity.shippingAddress} />
+                            </div>
+                        )}
+                        {entity?.billingAddress && (
+                            <div className="pt-4">
+                                <div className="font-medium">
+                                    <Trans>Billing address</Trans>
                                 </div>
-                            )}
-                        </div>
-                    </PageBlock>
-                    <PageBlock column="side" blockId="payment-details" title={<Trans>Payment details</Trans>}>
-                        {entity?.payments?.map(payment => (
-                            <PaymentDetails
-                                key={payment.id}
-                                payment={payment}
-                                currencyCode={entity.currencyCode}
-                            />
-                        ))}
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                                <OrderAddress address={entity.billingAddress} />
+                            </div>
+                        )}
+                    </div>
+                </PageBlock>
+                <PageBlock column="side" blockId="payment-details" title={<Trans>Payment details</Trans>}>
+                    {entity?.payments?.map(payment => (
+                        <PaymentDetails
+                            key={payment.id}
+                            payment={payment}
+                            currencyCode={entity.currencyCode}
+                        />
+                    ))}
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 84 - 80
packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx

@@ -14,7 +14,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -59,14 +58,18 @@ function PaymentMethodDetailPage() {
                 name: entity.name,
                 code: entity.code,
                 description: entity.description,
-                    checker: entity.checker?.code ? {
-                    code: entity.checker?.code,
-                    arguments: entity.checker?.args,
-                } : null,
-                handler: entity.handler?.code ? {
-                    code: entity.handler?.code,
-                    arguments: entity.handler?.args,
-                } : null,
+                checker: entity.checker?.code
+                    ? {
+                          code: entity.checker?.code,
+                          arguments: entity.checker?.args,
+                      }
+                    : null,
+                handler: entity.handler?.code
+                    ? {
+                          code: entity.handler?.code,
+                          arguments: entity.handler?.args,
+                      }
+                    : null,
                 translations: entity.translations.map(translation => ({
                     id: translation.id,
                     languageCode: translation.languageCode,
@@ -99,81 +102,82 @@ function PaymentMethodDetailPage() {
     });
 
     return (
-        <Page pageId="payment-method-detail">
+        <Page pageId="payment-method-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New payment method</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdatePaymentMethod']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdatePaymentMethod']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="enabled"
+                        label={<Trans>Enabled</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value ?? false} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
                         <FormFieldWrapper
-                                control={form.control}
-                                name="enabled"
-                                label={<Trans>Enabled</Trans>}
-                                render={({ field }) => <Switch checked={field.value ?? false} onCheckedChange={field.onChange} />}
+                            control={form.control}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                    <TranslatableFormFieldWrapper
+                        control={form.control}
+                        name="description"
+                        label={<Trans>Description</Trans>}
+                        render={({ field }) => <RichTextInput {...field} />}
+                    />
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="PaymentMethod" control={form.control} />
+                <PageBlock
+                    column="main"
+                    blockId="payment-eligibility-checker"
+                    title={<Trans>Payment eligibility checker</Trans>}
+                >
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="checker"
+                        render={({ field }) => (
+                            <PaymentEligibilityCheckerSelector
+                                value={field.value}
+                                onChange={field.onChange}
                             />
-                        </PageBlock>
-                        <PageBlock column="main" blockId="main-form">
-                            <DetailFormGrid>
-                                <TranslatableFormFieldWrapper
-                                    control={form.control}
-                                    name="name"
-                                    label={<Trans>Name</Trans>}
-                                    render={({ field }) => <Input {...field} />}
-                                />
-                                <FormFieldWrapper
-                                    control={form.control}
-                                    name="code"
-                                    label={<Trans>Code</Trans>}
-                                    render={({ field }) => <Input {...field} />}
-                                />
-                            </DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="description"
-                                label={<Trans>Description</Trans>}
-                                render={({ field }) => <RichTextInput {...field} />}
-                            />
-                        </PageBlock>
-                        <CustomFieldsPageBlock column="main" entityType="PaymentMethod" control={form.control} />
-                        <PageBlock column="main" blockId="payment-eligibility-checker" title={<Trans>Payment eligibility checker</Trans>}>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="checker"
-                                render={({ field }) => (
-                                    <PaymentEligibilityCheckerSelector
-                                        value={field.value}
-                                        onChange={field.onChange}
-                                    />
-                                )}
-                            />
-                        </PageBlock>
-                        <PageBlock column="main" blockId="payment-handler" title={<Trans>Calculator</Trans>}>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="handler"
-                                render={({ field }) => (
-                                    <PaymentHandlerSelector
-                                        value={field.value}
-                                        onChange={field.onChange}
-                                    />
-                                )}
-                            />
-                        </PageBlock>
-                    </PageLayout>
-                </PageDetailForm>
-            </Page>
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="payment-handler" title={<Trans>Calculator</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="handler"
+                        render={({ field }) => (
+                            <PaymentHandlerSelector value={field.value} onChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+            </PageLayout>
+        </Page>
     );
 }

+ 187 - 192
packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx

@@ -5,9 +5,7 @@ import { ErrorPage } from '@/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
 import { TaxCategorySelector } from '@/components/shared/tax-category-selector.js';
-import {
-    TranslatableFormFieldWrapper
-} from '@/components/shared/translatable-form-field.js';
+import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
 import { Button } from '@/components/ui/button.js';
 import {
     FormControl,
@@ -15,7 +13,7 @@ import {
     FormField,
     FormItem,
     FormLabel,
-    FormMessage
+    FormMessage,
 } from '@/components/ui/form.js';
 import { Input } from '@/components/ui/input.js';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select.js';
@@ -28,7 +26,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -112,225 +109,223 @@ function ProductVariantDetailPage() {
     const [price, taxCategoryId] = form.watch(['price', 'taxCategoryId']);
 
     return (
-        <Page pageId="product-variant-detail">
+        <Page pageId="product-variant-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New product variant</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="enabled"
+                        label={<Trans>Enabled</Trans>}
+                        description={<Trans>When enabled, a product is available in the shop</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Product name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="sku"
+                            label={<Trans>SKU</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="ProductVariant" control={form.control} />
+
+                <PageBlock column="main" blockId="price-and-tax" title={<Trans>Price and tax</Trans>}>
+                    <div className="grid grid-cols-2 gap-4 items-start">
                         <FormFieldWrapper
                             control={form.control}
-                            name="enabled"
-                            label={<Trans>Enabled</Trans>}
-                            description={<Trans>When enabled, a product is available in the shop</Trans>}
+                            name="taxCategoryId"
+                            label={<Trans>Tax category</Trans>}
                             render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
+                                <TaxCategorySelector value={field.value} onChange={field.onChange} />
                             )}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Product name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="sku"
-                                label={<Trans>SKU</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="ProductVariant" control={form.control} />
 
-                    <PageBlock column="main" blockId="price-and-tax" title={<Trans>Price and tax</Trans>}>
-                        <div className="grid grid-cols-2 gap-4 items-start">
+                        <div>
                             <FormFieldWrapper
                                 control={form.control}
-                                name="taxCategoryId"
-                                label={<Trans>Tax category</Trans>}
+                                name="price"
+                                label={<Trans>Price</Trans>}
                                 render={({ field }) => (
-                                    <TaxCategorySelector value={field.value} onChange={field.onChange} />
+                                    <MoneyInput {...field} currency={entity?.currencyCode} />
                                 )}
                             />
-
-                            <div>
+                            <VariantPriceDetail
+                                priceIncludesTax={activeChannel?.pricesIncludeTax ?? false}
+                                price={price}
+                                currencyCode={
+                                    entity?.currencyCode ?? activeChannel?.defaultCurrencyCode ?? ''
+                                }
+                                taxCategoryId={taxCategoryId}
+                            />
+                        </div>
+                    </div>
+                </PageBlock>
+                <PageBlock column="main" blockId="stock" title={<Trans>Stock</Trans>}>
+                    <DetailFormGrid>
+                        {entity?.stockLevels.map((stockLevel, index) => (
+                            <Fragment key={stockLevel.id}>
                                 <FormFieldWrapper
                                     control={form.control}
-                                    name="price"
-                                    label={<Trans>Price</Trans>}
+                                    name={`stockLevels.${index}.stockOnHand`}
                                     render={({ field }) => (
-                                        <MoneyInput {...field} currency={entity?.currencyCode} />
-                                    )}
-                                />
-                                <VariantPriceDetail
-                                    priceIncludesTax={activeChannel?.pricesIncludeTax ?? false}
-                                    price={price}
-                                    currencyCode={entity?.currencyCode ?? activeChannel?.defaultCurrencyCode ?? ''}
-                                    taxCategoryId={taxCategoryId}
-                                />
-                            </div>
-                        </div>
-                    </PageBlock>
-                    <PageBlock column="main" blockId="stock" title={<Trans>Stock</Trans>}>
-                        <DetailFormGrid>
-                            {entity?.stockLevels.map((stockLevel, index) => (
-                                <Fragment key={stockLevel.id}>
-                                    <FormFieldWrapper
-                                        control={form.control}
-                                        name={`stockLevels.${index}.stockOnHand`}
-                                        render={({ field }) => (
-                                            <FormItem>
-                                                <FormLabel>
-                                                    <Trans>Stock level</Trans>
-                                                </FormLabel>
-                                                <FormControl>
-                                                    <Input type="number" {...field} />
-                                                </FormControl>
-                                            </FormItem>
-                                        )}
-                                    />
-                                    <div>
                                         <FormItem>
                                             <FormLabel>
-                                                <Trans>Allocated</Trans>
+                                                <Trans>Stock level</Trans>
                                             </FormLabel>
-                                            <div className="text-sm pt-1.5">{stockLevel.stockAllocated}</div>
-                                        </FormItem>
-                                    </div>
-                                </Fragment>
-                            ))}
-
-                            <FormField
-                                control={form.control}
-                                name="trackInventory"
-                                render={({ field }) => (
-                                    <FormItem>
-                                        <FormLabel>
-                                            <Trans>Track inventory</Trans>
-                                        </FormLabel>
-                                        <Select onValueChange={field.onChange} value={field.value}>
                                             <FormControl>
-                                                <SelectTrigger className="">
-                                                    <SelectValue placeholder="Track inventory" />
-                                                </SelectTrigger>
+                                                <Input type="number" {...field} />
                                             </FormControl>
-                                            <SelectContent>
-                                                <SelectItem value="INHERIT">
-                                                    <Trans>Inherit from global settings</Trans>
-                                                </SelectItem>
-                                                <SelectItem value="TRUE">
-                                                    <Trans>Track</Trans>
-                                                </SelectItem>
-                                                <SelectItem value="FALSE">
-                                                    <Trans>Do not track</Trans>
-                                                </SelectItem>
-                                            </SelectContent>
-                                        </Select>
-                                    </FormItem>
-                                )}
-                            />
-                            <FormField
-                                control={form.control}
-                                name="outOfStockThreshold"
-                                render={({ field }) => (
+                                        </FormItem>
+                                    )}
+                                />
+                                <div>
                                     <FormItem>
                                         <FormLabel>
-                                            <Trans>Out-of-stock threshold</Trans>
+                                            <Trans>Allocated</Trans>
                                         </FormLabel>
-                                        <FormControl>
-                                            <Input type="number" {...field} />
-                                        </FormControl>
-                                        <FormDescription>
-                                            <Trans>
-                                                Sets the stock level at which this variant is considered to be
-                                                out of stock. Using a negative value enables backorder
-                                                support.
-                                            </Trans>
-                                        </FormDescription>
+                                        <div className="text-sm pt-1.5">{stockLevel.stockAllocated}</div>
                                     </FormItem>
-                                )}
-                            />
-                            <FormField
-                                control={form.control}
-                                name="useGlobalOutOfStockThreshold"
-                                render={({ field }) => (
-                                    <FormItem>
-                                        <FormLabel>
-                                            <Trans>Use global out-of-stock threshold</Trans>
-                                        </FormLabel>
+                                </div>
+                            </Fragment>
+                        ))}
+
+                        <FormField
+                            control={form.control}
+                            name="trackInventory"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>
+                                        <Trans>Track inventory</Trans>
+                                    </FormLabel>
+                                    <Select onValueChange={field.onChange} value={field.value}>
                                         <FormControl>
-                                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                                            <SelectTrigger className="">
+                                                <SelectValue placeholder="Track inventory" />
+                                            </SelectTrigger>
                                         </FormControl>
-                                        <FormDescription>
-                                            <Trans>
-                                                Sets the stock level at which this variant is considered to be
-                                                out of stock. Using a negative value enables backorder
-                                                support.
-                                            </Trans>
-                                        </FormDescription>
-                                    </FormItem>
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-
-                    <PageBlock column="side" blockId="facet-values">
-                        <FormFieldWrapper
+                                        <SelectContent>
+                                            <SelectItem value="INHERIT">
+                                                <Trans>Inherit from global settings</Trans>
+                                            </SelectItem>
+                                            <SelectItem value="TRUE">
+                                                <Trans>Track</Trans>
+                                            </SelectItem>
+                                            <SelectItem value="FALSE">
+                                                <Trans>Do not track</Trans>
+                                            </SelectItem>
+                                        </SelectContent>
+                                    </Select>
+                                </FormItem>
+                            )}
+                        />
+                        <FormField
                             control={form.control}
-                            name="facetValueIds"
-                            label={<Trans>Facet values</Trans>}
+                            name="outOfStockThreshold"
                             render={({ field }) => (
-                                <AssignedFacetValues facetValues={entity?.facetValues ?? []} {...field} />
+                                <FormItem>
+                                    <FormLabel>
+                                        <Trans>Out-of-stock threshold</Trans>
+                                    </FormLabel>
+                                    <FormControl>
+                                        <Input type="number" {...field} />
+                                    </FormControl>
+                                    <FormDescription>
+                                        <Trans>
+                                            Sets the stock level at which this variant is considered to be out
+                                            of stock. Using a negative value enables backorder support.
+                                        </Trans>
+                                    </FormDescription>
+                                </FormItem>
                             )}
                         />
-                    </PageBlock>
-                    <PageBlock column="side" blockId="assets">
-                        <FormItem>
-                            <FormLabel>
-                                <Trans>Assets</Trans>
-                            </FormLabel>
-                            <FormControl>
-                                <EntityAssets
-                                    assets={entity?.assets}
-                                    featuredAsset={entity?.featuredAsset}
-                                    compact={true}
-                                    value={form.getValues()}
-                                    onChange={value => {
-                                        form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                        form.setValue('assetIds', value.assetIds ?? undefined, {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                    }}
-                                />
-                            </FormControl>
-                            <FormDescription></FormDescription>
-                            <FormMessage />
-                        </FormItem>
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                        <FormField
+                            control={form.control}
+                            name="useGlobalOutOfStockThreshold"
+                            render={({ field }) => (
+                                <FormItem>
+                                    <FormLabel>
+                                        <Trans>Use global out-of-stock threshold</Trans>
+                                    </FormLabel>
+                                    <FormControl>
+                                        <Switch checked={field.value} onCheckedChange={field.onChange} />
+                                    </FormControl>
+                                    <FormDescription>
+                                        <Trans>
+                                            Sets the stock level at which this variant is considered to be out
+                                            of stock. Using a negative value enables backorder support.
+                                        </Trans>
+                                    </FormDescription>
+                                </FormItem>
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+
+                <PageBlock column="side" blockId="facet-values">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="facetValueIds"
+                        label={<Trans>Facet values</Trans>}
+                        render={({ field }) => (
+                            <AssignedFacetValues facetValues={entity?.facetValues ?? []} {...field} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="side" blockId="assets">
+                    <FormItem>
+                        <FormLabel>
+                            <Trans>Assets</Trans>
+                        </FormLabel>
+                        <FormControl>
+                            <EntityAssets
+                                assets={entity?.assets}
+                                featuredAsset={entity?.featuredAsset}
+                                compact={true}
+                                value={form.getValues()}
+                                onChange={value => {
+                                    form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
+                                    form.setValue('assetIds', value.assetIds ?? undefined, {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
+                                }}
+                            />
+                        </FormControl>
+                        <FormDescription></FormDescription>
+                        <FormMessage />
+                    </FormItem>
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 96 - 99
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx

@@ -17,7 +17,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -64,7 +63,7 @@ function ProductDetailPage() {
                 translations: entity.translations.map(translation => ({
                     id: translation.id,
                     languageCode: translation.languageCode,
-                    name: translation.name, 
+                    name: translation.name,
                     slug: translation.slug,
                     description: translation.description,
                     customFields: (translation as any).customFields,
@@ -88,112 +87,110 @@ function ProductDetailPage() {
     });
 
     return (
-        <Page pageId="product-detail" entity={entity}>
+        <Page pageId="product-detail" entity={entity} form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New product</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled-toggle">
-                        <FormFieldWrapper
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled-toggle">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="enabled"
+                        label={<Trans>Enabled</Trans>}
+                        description={<Trans>When enabled, a product is available in the shop</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
                             control={form.control}
-                            name="enabled"
-                            label={<Trans>Enabled</Trans>}
-                            description={<Trans>When enabled, a product is available in the shop</Trans>}
-                            render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
-                            )}
+                            name="name"
+                            label={<Trans>Product name</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Product name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="slug"
-                                label={<Trans>Slug</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
-
                         <TranslatableFormFieldWrapper
                             control={form.control}
-                            name="description"
-                            label={<Trans>Description</Trans>}
-                            render={({ field }) => <RichTextInput {...field} />}
+                            name="slug"
+                            label={<Trans>Slug</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+
+                    <TranslatableFormFieldWrapper
+                        control={form.control}
+                        name="description"
+                        label={<Trans>Description</Trans>}
+                        render={({ field }) => <RichTextInput {...field} />}
+                    />
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Product" control={form.control} />
+                {entity && entity.variantList.totalItems > 0 && (
+                    <PageBlock column="main" blockId="product-variants-table">
+                        <ProductVariantsTable productId={params.id} />
+                    </PageBlock>
+                )}
+                {entity && entity.variantList.totalItems === 0 && (
+                    <PageBlock column="main" blockId="create-product-variants-dialog">
+                        <CreateProductVariantsDialog
+                            productId={entity.id}
+                            productName={entity.name}
+                            onSuccess={() => {
+                                refreshEntity();
+                            }}
                         />
                     </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Product" control={form.control} />
-                    {entity && entity.variantList.totalItems > 0 && (
-                        <PageBlock column="main" blockId="product-variants-table">
-                            <ProductVariantsTable productId={params.id} />
-                        </PageBlock>
-                    )}
-                    {entity && entity.variantList.totalItems === 0 && (
-                        <PageBlock column="main" blockId="create-product-variants-dialog">
-                            <CreateProductVariantsDialog
-                                productId={entity.id}
-                                productName={entity.name}
-                                onSuccess={() => {
-                                    refreshEntity();
+                )}
+                <PageBlock column="side" blockId="facet-values">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="facetValueIds"
+                        label={<Trans>Facet values</Trans>}
+                        render={({ field }) => (
+                            <AssignedFacetValues facetValues={entity?.facetValues ?? []} {...field} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="side" blockId="assets">
+                    <FormItem>
+                        <FormLabel>
+                            <Trans>Assets</Trans>
+                        </FormLabel>
+                        <FormControl>
+                            <EntityAssets
+                                assets={entity?.assets}
+                                featuredAsset={entity?.featuredAsset}
+                                compact={true}
+                                value={form.getValues()}
+                                onChange={value => {
+                                    form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
+                                    form.setValue('assetIds', value.assetIds ?? [], {
+                                        shouldDirty: true,
+                                        shouldValidate: true,
+                                    });
                                 }}
                             />
-                        </PageBlock>
-                    )}
-                    <PageBlock column="side" blockId="facet-values">
-                        <FormFieldWrapper
-                            control={form.control}
-                            name="facetValueIds"
-                            label={<Trans>Facet values</Trans>}
-                            render={({ field }) => (
-                                <AssignedFacetValues facetValues={entity?.facetValues ?? []} {...field} />
-                            )}
-                        />
-                    </PageBlock>
-                    <PageBlock column="side" blockId="assets">
-                        <FormItem>
-                            <FormLabel>
-                                <Trans>Assets</Trans>
-                            </FormLabel>
-                            <FormControl>
-                                <EntityAssets
-                                    assets={entity?.assets}
-                                    featuredAsset={entity?.featuredAsset}
-                                    compact={true}
-                                    value={form.getValues()}
-                                    onChange={value => {
-                                        form.setValue('featuredAssetId', value.featuredAssetId ?? undefined, {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                        form.setValue('assetIds', value.assetIds ?? [], {
-                                            shouldDirty: true,
-                                            shouldValidate: true,
-                                        });
-                                    }}
-                                />
-                            </FormControl>
-                            <FormDescription></FormDescription>
-                            <FormMessage />
-                        </FormItem>
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                        </FormControl>
+                        <FormDescription></FormDescription>
+                        <FormMessage />
+                    </FormItem>
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 42 - 45
packages/dashboard/src/app/routes/_authenticated/_profile/profile.tsx

@@ -9,7 +9,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -73,53 +72,51 @@ function ProfilePage() {
     });
 
     return (
-        <Page pageId="profile">
+        <Page pageId="profile" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 <Trans>Profile</Trans>
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <Button
-                            type="submit"
-                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                        >
-                            <Trans>Update</Trans>
-                        </Button>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="firstName"
-                                label={<Trans>First name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="lastName"
-                                label={<Trans>Last name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="emailAddress"
-                                label={<Trans>Email Address or identifier</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="password"
-                                label={<Trans>Password</Trans>}
-                                render={({ field }) => <Input type="password" {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Administrator" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <Button
+                        type="submit"
+                        disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                    >
+                        <Trans>Update</Trans>
+                    </Button>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="firstName"
+                            label={<Trans>First name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="lastName"
+                            label={<Trans>Last name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="emailAddress"
+                            label={<Trans>Email Address or identifier</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="password"
+                            label={<Trans>Password</Trans>}
+                            render={({ field }) => <Input type="password" {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Administrator" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 104 - 110
packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx

@@ -15,7 +15,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -109,133 +108,128 @@ function PromotionDetailPage() {
     });
 
     return (
-        <Page pageId="promotion-detail">
+        <Page pageId="promotion-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New promotion</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdatePromotion']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdatePromotion']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="enabled"
+                        label={<Trans>Enabled</Trans>}
+                        description={<Trans>When enabled, a promotion is available in the shop</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <div></div>
+                    </DetailFormGrid>
+                    <div className="mb-4">
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="description"
+                            label={<Trans>Description</Trans>}
+                            render={({ field }) => <RichTextInput {...field} />}
+                        />
+                    </div>
+                    <DetailFormGrid>
                         <FormFieldWrapper
                             control={form.control}
-                            name="enabled"
-                            label={<Trans>Enabled</Trans>}
-                            description={<Trans>When enabled, a promotion is available in the shop</Trans>}
+                            name="startsAt"
+                            label={<Trans>Starts at</Trans>}
                             render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
+                                <DateTimeInput
+                                    value={field.value}
+                                    onChange={value => field.onChange(value.toISOString())}
+                                />
                             )}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <div></div>
-                        </DetailFormGrid>
-                        <div className="mb-4">
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="description"
-                                label={<Trans>Description</Trans>}
-                                render={({ field }) => <RichTextInput {...field} />}
-                            />
-                        </div>
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="startsAt"
-                                label={<Trans>Starts at</Trans>}
-                                render={({ field }) => (
-                                    <DateTimeInput
-                                        value={field.value}
-                                        onChange={value => field.onChange(value.toISOString())}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="endsAt"
-                                label={<Trans>Ends at</Trans>}
-                                render={({ field }) => (
-                                    <DateTimeInput
-                                        value={field.value}
-                                        onChange={value => field.onChange(value.toISOString())}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="couponCode"
-                                label={<Trans>Coupon code</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="perCustomerUsageLimit"
-                                label={<Trans>Per customer usage limit</Trans>}
-                                render={({ field }) => (
-                                    <Input
-                                        type="number"
-                                        value={field.value ?? ''}
-                                        onChange={e => field.onChange(e.target.valueAsNumber)}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="usageLimit"
-                                label={<Trans>Usage limit</Trans>}
-                                render={({ field }) => (
-                                    <Input
-                                        type="number"
-                                        value={field.value ?? ''}
-                                        onChange={e => field.onChange(e.target.valueAsNumber)}
-                                    />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Promotion" control={form.control} />
-                    <PageBlock column="main" blockId="conditions" title={<Trans>Conditions</Trans>}>
                         <FormFieldWrapper
                             control={form.control}
-                            name="conditions"
+                            name="endsAt"
+                            label={<Trans>Ends at</Trans>}
                             render={({ field }) => (
-                                <PromotionConditionsSelector
-                                    value={field.value ?? []}
-                                    onChange={field.onChange}
+                                <DateTimeInput
+                                    value={field.value}
+                                    onChange={value => field.onChange(value.toISOString())}
                                 />
                             )}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="actions" title={<Trans>Actions</Trans>}>
                         <FormFieldWrapper
                             control={form.control}
-                            name="actions"
+                            name="couponCode"
+                            label={<Trans>Coupon code</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="perCustomerUsageLimit"
+                            label={<Trans>Per customer usage limit</Trans>}
                             render={({ field }) => (
-                                <PromotionActionsSelector
-                                    value={field.value ?? []}
-                                    onChange={field.onChange}
+                                <Input
+                                    type="number"
+                                    value={field.value ?? ''}
+                                    onChange={e => field.onChange(e.target.valueAsNumber)}
                                 />
                             )}
                         />
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="usageLimit"
+                            label={<Trans>Usage limit</Trans>}
+                            render={({ field }) => (
+                                <Input
+                                    type="number"
+                                    value={field.value ?? ''}
+                                    onChange={e => field.onChange(e.target.valueAsNumber)}
+                                />
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Promotion" control={form.control} />
+                <PageBlock column="main" blockId="conditions" title={<Trans>Conditions</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="conditions"
+                        render={({ field }) => (
+                            <PromotionConditionsSelector
+                                value={field.value ?? []}
+                                onChange={field.onChange}
+                            />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="actions" title={<Trans>Actions</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="actions"
+                        render={({ field }) => (
+                            <PromotionActionsSelector value={field.value ?? []} onChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 56 - 59
packages/dashboard/src/app/routes/_authenticated/_roles/roles_.$id.tsx

@@ -11,7 +11,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -72,74 +71,72 @@ function RoleDetailPage() {
     });
 
     return (
-        <Page pageId="role-detail">
+        <Page pageId="role-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New role</Trans> : (entity?.description ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateAdministrator']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateAdministrator']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="description"
+                            label={<Trans>Description</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <PageBlock column="main" blockId="channels">
+                    <div className="space-y-8">
+                        <div className="md:grid md:grid-cols-2 gap-4">
                             <FormFieldWrapper
                                 control={form.control}
-                                name="description"
-                                label={<Trans>Description</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="code"
-                                label={<Trans>Code</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <PageBlock column="main" blockId="channels">
-                        <div className="space-y-8">
-                            <div className="md:grid md:grid-cols-2 gap-4">
-                                <FormFieldWrapper
-                                    control={form.control}
-                                    name="channelIds"
-                                    label={<Trans>Channels</Trans>}
-                                    description={
-                                        <Trans>
-                                            The selected permissions will be applied to the these channels.
-                                        </Trans>
-                                    }
-                                    render={({ field }) => (
-                                        <ChannelSelector
-                                            multiple={true}
-                                            value={field.value ?? []}
-                                            onChange={value => field.onChange(value)}
-                                        />
-                                    )}
-                                />
-                            </div>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="permissions"
-                                label={<Trans>Permissions</Trans>}
+                                name="channelIds"
+                                label={<Trans>Channels</Trans>}
+                                description={
+                                    <Trans>
+                                        The selected permissions will be applied to the these channels.
+                                    </Trans>
+                                }
                                 render={({ field }) => (
-                                    <PermissionsGrid
+                                    <ChannelSelector
+                                        multiple={true}
                                         value={field.value ?? []}
                                         onChange={value => field.onChange(value)}
                                     />
                                 )}
                             />
                         </div>
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="permissions"
+                            label={<Trans>Permissions</Trans>}
+                            render={({ field }) => (
+                                <PermissionsGrid
+                                    value={field.value ?? []}
+                                    onChange={value => field.onChange(value)}
+                                />
+                            )}
+                        />
+                    </div>
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 41 - 53
packages/dashboard/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx

@@ -1,13 +1,14 @@
 import { ErrorPage } from '@/components/shared/error-page.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
 import { Button } from '@/components/ui/button.js';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
+import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
 import { Input } from '@/components/ui/input.js';
 import { NEW_ENTITY_PATH } from '@/constants.js';
 import {
     CustomFieldsPageBlock,
     Page,
     PageActionBar,
+    PageActionBarRight,
     PageBlock,
     PageLayout,
     PageTitle,
@@ -17,11 +18,7 @@ import { useDetailPage } from '@/framework/page/use-detail-page.js';
 import { Trans, useLingui } from '@/lib/trans.js';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
-import {
-    createSellerDocument,
-    sellerDetailDocument,
-    updateSellerDocument,
-} from './sellers.graphql.js';
+import { createSellerDocument, sellerDetailDocument, updateSellerDocument } from './sellers.graphql.js';
 
 export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
     component: SellerDetailPage,
@@ -71,53 +68,44 @@ function SellerDetailPage() {
     });
 
     return (
-        <Page pageId="seller-detail">
-            <PageTitle>
-                {creatingNewEntity ? <Trans>New seller</Trans> : (entity?.name ?? '')}
-            </PageTitle>
-            <Form {...form}>
-                <form onSubmit={submitHandler} className="space-y-8">
-                    <PageActionBar>
-                        <div></div>
-                        <PermissionGuard requires={['UpdateSeller']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBar>
-                    <PageLayout>
-                        <PageBlock column="main" blockId="main-form">
-                            <div className="md:flex w-full gap-4">
-                                <div className="w-1/2">
-                                    <FormField
-                                        control={form.control}
-                                        name="name"
-                                        render={({ field }) => (
-                                            <FormItem>
-                                                <FormLabel>
-                                                    <Trans>Name</Trans>
-                                                </FormLabel>
-                                                <FormControl>
-                                                    <Input placeholder="" {...field} />
-                                                </FormControl>
-                                                <FormMessage />
-                                            </FormItem>
-                                        )}
-                                    />
-                                </div>
-                            </div>
-                        </PageBlock>
-                        <CustomFieldsPageBlock
-                            column="main"
-                            entityType="Seller"
-                            control={form.control}
-                        />
-                    </PageLayout>
-                </form>
-            </Form>
+        <Page pageId="seller-detail" form={form} submitHandler={submitHandler}>
+            <PageTitle>{creatingNewEntity ? <Trans>New seller</Trans> : (entity?.name ?? '')}</PageTitle>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateSeller']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <div className="md:flex w-full gap-4">
+                        <div className="w-1/2">
+                            <FormField
+                                control={form.control}
+                                name="name"
+                                render={({ field }) => (
+                                    <FormItem>
+                                        <FormLabel>
+                                            <Trans>Name</Trans>
+                                        </FormLabel>
+                                        <FormControl>
+                                            <Input placeholder="" {...field} />
+                                        </FormControl>
+                                        <FormMessage />
+                                    </FormItem>
+                                )}
+                            />
+                        </div>
+                    </div>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Seller" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 59 - 67
packages/dashboard/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx

@@ -1,9 +1,7 @@
 import { ErrorPage } from '@/components/shared/error-page.js';
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
-import {
-    TranslatableFormFieldWrapper
-} from '@/components/shared/translatable-form-field.js';
+import { TranslatableFormFieldWrapper } from '@/components/shared/translatable-form-field.js';
 import { Button } from '@/components/ui/button.js';
 import { Input } from '@/components/ui/input.js';
 import { Textarea } from '@/components/ui/textarea.js';
@@ -15,7 +13,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -97,83 +94,78 @@ function ShippingMethodDetailPage() {
     });
 
     return (
-        <Page pageId="shipping-method-detail">
+        <Page pageId="shipping-method-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New shipping method</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateShippingMethod']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <TranslatableFormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="code"
-                                label={<Trans>Code</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateShippingMethod']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
                         <TranslatableFormFieldWrapper
                             control={form.control}
-                            name="description"
-                            label={<Trans>Description</Trans>}
-                            render={({ field }) => <Textarea {...field} />}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="fulfillmentHandler"
-                                label={<Trans>Fulfillment handler</Trans>}
-                                render={({ field }) => (
-                                    <FulfillmentHandlerSelector
-                                        value={field.value}
-                                        onChange={field.onChange}
-                                    />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Promotion" control={form.control} />
-                    <PageBlock column="main" blockId="conditions" title={<Trans>Conditions</Trans>}>
                         <FormFieldWrapper
                             control={form.control}
-                            name="checker"
-                            render={({ field }) => (
-                                <ShippingEligibilityCheckerSelector
-                                    value={field.value}
-                                    onChange={field.onChange}
-                                />
-                            )}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input {...field} />}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="calculator" title={<Trans>Calculator</Trans>}>
+                    </DetailFormGrid>
+                    <TranslatableFormFieldWrapper
+                        control={form.control}
+                        name="description"
+                        label={<Trans>Description</Trans>}
+                        render={({ field }) => <Textarea {...field} />}
+                    />
+                    <DetailFormGrid>
                         <FormFieldWrapper
                             control={form.control}
-                            name="calculator"
+                            name="fulfillmentHandler"
+                            label={<Trans>Fulfillment handler</Trans>}
                             render={({ field }) => (
-                                <ShippingCalculatorSelector value={field.value} onChange={field.onChange} />
+                                <FulfillmentHandlerSelector value={field.value} onChange={field.onChange} />
                             )}
                         />
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Promotion" control={form.control} />
+                <PageBlock column="main" blockId="conditions" title={<Trans>Conditions</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="checker"
+                        render={({ field }) => (
+                            <ShippingEligibilityCheckerSelector
+                                value={field.value}
+                                onChange={field.onChange}
+                            />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="calculator" title={<Trans>Calculator</Trans>}>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="calculator"
+                        render={({ field }) => (
+                            <ShippingCalculatorSelector value={field.value} onChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }

+ 30 - 33
packages/dashboard/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx

@@ -12,7 +12,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -75,44 +74,42 @@ function StockLocationDetailPage() {
     });
 
     return (
-        <Page pageId="stock-location-detail">
+        <Page pageId="stock-location-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New stock location</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateStockLocation']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                label={<Trans>Name</Trans>}
-                                name="name"
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <div></div>
-                        </DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateStockLocation']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
                         <FormFieldWrapper
                             control={form.control}
-                            name="description"
-                            label={<Trans>Description</Trans>}
-                            render={({ field }) => <Textarea {...field} />}
+                            label={<Trans>Name</Trans>}
+                            name="name"
+                            render={({ field }) => <Input {...field} />}
                         />
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="StockLocation" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+                        <div></div>
+                    </DetailFormGrid>
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="description"
+                        label={<Trans>Description</Trans>}
+                        render={({ field }) => <Textarea {...field} />}
+                    />
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="StockLocation" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 34 - 35
packages/dashboard/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx

@@ -12,7 +12,6 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
@@ -74,43 +73,43 @@ function TaxCategoryDetailPage() {
     });
 
     return (
-        <Page pageId="tax-category-detail">
+        <Page pageId="tax-category-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>
                 {creatingNewEntity ? <Trans>New tax category</Trans> : (entity?.name ?? '')}
             </PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateTaxCategory']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="isDefault"
-                                label={<Trans>Is default tax category</Trans>}
-                                render={({ field }) => <Switch checked={field.value} onCheckedChange={field.onChange} />}
-                            />
-                       </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="TaxCategory" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateTaxCategory']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="isDefault"
+                            label={<Trans>Is default tax category</Trans>}
+                            render={({ field }) => (
+                                <Switch checked={field.value} onCheckedChange={field.onChange} />
+                            )}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="TaxCategory" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 64 - 69
packages/dashboard/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx

@@ -1,12 +1,13 @@
 import { AffixedInput } from '@/components/data-input/affixed-input.js';
 import { ErrorPage } from '@/components/shared/error-page.js';
+import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
 import { TaxCategorySelector } from '@/components/shared/tax-category-selector.js';
+import { ZoneSelector } from '@/components/shared/zone-selector.js';
 import { Button } from '@/components/ui/button.js';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
 import { Input } from '@/components/ui/input.js';
+import { Switch } from '@/components/ui/switch.js';
 import { NEW_ENTITY_PATH } from '@/constants.js';
-import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
 import {
     CustomFieldsPageBlock,
     DetailFormGrid,
@@ -14,19 +15,15 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
+import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
 import { useDetailPage } from '@/framework/page/use-detail-page.js';
 import { Trans, useLingui } from '@/lib/trans.js';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
 import { createTaxRateDocument, taxRateDetailQuery, updateTaxRateDocument } from './tax-rates.graphql.js';
-import { ZoneSelector } from '@/components/shared/zone-selector.js';
-import { Switch } from '@/components/ui/switch.js';
-import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
-import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 
 export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id')({
     component: TaxRateDetailPage,
@@ -80,74 +77,72 @@ function TaxRateDetailPage() {
     });
 
     return (
-        <Page pageId="tax-rate-detail">
+        <Page pageId="tax-rate-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New tax rate</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateTaxRate']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="side" blockId="enabled">
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateTaxRate']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="enabled">
+                    <FormFieldWrapper
+                        control={form.control}
+                        name="enabled"
+                        label={<Trans>Enabled</Trans>}
+                        render={({ field }) => (
+                            <Switch checked={field.value} onCheckedChange={field.onChange} />
+                        )}
+                    />
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="value"
+                            label={<Trans>Rate</Trans>}
+                            render={({ field }) => (
+                                <AffixedInput
+                                    type="number"
+                                    suffix="%"
+                                    value={field.value}
+                                    onChange={e => field.onChange(e.target.valueAsNumber)}
+                                />
+                            )}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="categoryId"
+                            label={<Trans>Tax category</Trans>}
+                            render={({ field }) => (
+                                <TaxCategorySelector value={field.value} onChange={field.onChange} />
+                            )}
+                        />
                         <FormFieldWrapper
                             control={form.control}
-                            name="enabled"
-                            label={<Trans>Enabled</Trans>}
+                            name="zoneId"
+                            label={<Trans>Zone</Trans>}
                             render={({ field }) => (
-                                <Switch checked={field.value} onCheckedChange={field.onChange} />
+                                <ZoneSelector value={field.value} onChange={field.onChange} />
                             )}
                         />
-                    </PageBlock>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="value"
-                                label={<Trans>Rate</Trans>}
-                                render={({ field }) => (
-                                    <AffixedInput
-                                        type="number"
-                                        suffix="%"
-                                        value={field.value}
-                                        onChange={e => field.onChange(e.target.valueAsNumber)}
-                                    />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="categoryId"
-                                label={<Trans>Tax category</Trans>}
-                                render={({ field }) => (
-                                    <TaxCategorySelector value={field.value} onChange={field.onChange} />
-                                )}
-                            />
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="zoneId"
-                                label={<Trans>Zone</Trans>}
-                                render={({ field }) => (
-                                    <ZoneSelector value={field.value} onChange={field.onChange} />
-                                )}
-                            />
-                        </DetailFormGrid>
-                    </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="TaxRate" control={form.control} />
-                </PageLayout>
-            </PageDetailForm>
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="TaxRate" control={form.control} />
+            </PageLayout>
         </Page>
     );
 }

+ 34 - 40
packages/dashboard/src/app/routes/_authenticated/_zones/zones_.$id.tsx

@@ -1,11 +1,9 @@
 import { ErrorPage } from '@/components/shared/error-page.js';
+import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { PermissionGuard } from '@/components/shared/permission-guard.js';
 import { Button } from '@/components/ui/button.js';
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.js';
 import { Input } from '@/components/ui/input.js';
-import { Switch } from '@/components/ui/switch.js';
 import { NEW_ENTITY_PATH } from '@/constants.js';
-import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
 import {
     CustomFieldsPageBlock,
     DetailFormGrid,
@@ -13,18 +11,16 @@ import {
     PageActionBar,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '@/framework/layout-engine/page-layout.js';
-import { getDetailQueryOptions, useDetailPage } from '@/framework/page/use-detail-page.js';
+import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
+import { useDetailPage } from '@/framework/page/use-detail-page.js';
 import { Trans, useLingui } from '@/lib/trans.js';
 import { createFileRoute, useNavigate } from '@tanstack/react-router';
 import { toast } from 'sonner';
-import { createZoneDocument, zoneDetailQuery, updateZoneDocument } from './zones.graphql.js';
 import { ZoneCountriesTable } from './components/zone-countries-table.js';
-import { detailPageRouteLoader } from '@/framework/page/detail-page-route-loader.js';
-import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
+import { createZoneDocument, updateZoneDocument, zoneDetailQuery } from './zones.graphql.js';
 
 export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
     component: ZoneDetailPage,
@@ -70,40 +66,38 @@ function ZoneDetailPage() {
     });
 
     return (
-        <Page pageId="zone-detail">
+        <Page pageId="zone-detail" form={form} submitHandler={submitHandler}>
             <PageTitle>{creatingNewEntity ? <Trans>New zone</Trans> : (entity?.name ?? '')}</PageTitle>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarRight>
-                        <PermissionGuard requires={['UpdateZone']}>
-                            <Button
-                                type="submit"
-                                disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                            >
-                                <Trans>Update</Trans>
-                            </Button>
-                        </PermissionGuard>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            <FormFieldWrapper
-                                control={form.control}
-                                name="name"
-                                label={<Trans>Name</Trans>}
-                                render={({ field }) => <Input {...field} />}
-                            />
-                        </DetailFormGrid>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateZone']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="Zone" control={form.control} />
+                {entity && (
+                    <PageBlock column="main" blockId="countries" title={<Trans>Countries</Trans>}>
+                        <ZoneCountriesTable zoneId={entity.id} canAddCountries={true} />
                     </PageBlock>
-                    <CustomFieldsPageBlock column="main" entityType="Zone" control={form.control} />
-                    {entity && (
-                        <PageBlock column="main" blockId="countries" title={<Trans>Countries</Trans>}>
-                            <ZoneCountriesTable zoneId={entity.id} canAddCountries={true} />
-                        </PageBlock>
-                    )}
-                </PageLayout>
-            </PageDetailForm>
+                )}
+            </PageLayout>
         </Page>
     );
 }

+ 46 - 39
packages/dashboard/src/lib/framework/layout-engine/page-layout.tsx

@@ -6,7 +6,7 @@ import { useCustomFieldConfig } from '@/hooks/use-custom-field-config.js';
 import { usePage } from '@/hooks/use-page.js';
 import { cn } from '@/lib/utils.js';
 import { useMediaQuery } from '@uidotdev/usehooks';
-import React, { ComponentProps, createContext, useState } from 'react';
+import React, { ComponentProps, createContext } from 'react';
 import { Control, UseFormReturn } from 'react-hook-form';
 import { DashboardActionBarItem } from '../extension-api/extension-api-types.js';
 import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
@@ -15,17 +15,50 @@ import { LocationWrapper } from './location-wrapper.js';
 export interface PageProps extends ComponentProps<'div'> {
     pageId?: string;
     entity?: any;
+    form?: UseFormReturn<any>;
+    submitHandler?: any;
 }
 
 export const PageProvider = createContext<PageContext | undefined>(undefined);
 
-export function Page({ children, pageId, entity, ...props }: PageProps) {
-    const [form, setForm] = useState<UseFormReturn<any> | undefined>(undefined);
+export function Page({ children, pageId, entity, form, submitHandler, ...props }: PageProps) {
+    const childArray = React.Children.toArray(children);
+
+    const pageTitle = childArray.find(child => React.isValidElement(child) && child.type === PageTitle);
+    const pageActionBar = childArray.find(
+        child => React.isValidElement(child) && child.type === PageActionBar,
+    );
+
+    const pageContent = childArray.filter(
+        child => React.isValidElement(child) && child.type !== PageTitle && child.type !== PageActionBar,
+    );
+
+    const pageHeader = (
+        <div className="flex items-center justify-between">
+            {pageTitle}
+            {pageActionBar}
+        </div>
+    );
+
+    const pageContentWithOptionalForm = form ? (
+        <Form {...form}>
+            <form onSubmit={submitHandler} className="space-y-4">
+                {pageHeader}
+                {pageContent}
+            </form>
+        </Form>
+    ) : (
+        <div className="space-y-4">
+            {pageHeader}
+            {pageContent}
+        </div>
+    );
+
     return (
-        <PageProvider value={{ pageId, form, setForm, entity }}>
+        <PageProvider value={{ pageId, form, entity }}>
             <LocationWrapper>
                 <div className={cn('m-4', props.className)} {...props}>
-                    {children}
+                    {pageContentWithOptionalForm}
                 </div>
             </LocationWrapper>
         </PageProvider>
@@ -123,28 +156,6 @@ export function PageLayout({ children, className }: PageLayoutProps) {
     );
 }
 
-export function PageDetailForm({
-    children,
-    form,
-    submitHandler,
-}: {
-    children: React.ReactNode;
-    form: UseFormReturn<any>;
-    submitHandler: any;
-}) {
-    const page = usePage();
-    if (!page.form && form) {
-        page.setForm(form);
-    }
-    return (
-        <Form {...form}>
-            <form onSubmit={submitHandler} className="space-y-8">
-                {children}
-            </form>
-        </Form>
-    );
-}
-
 export function DetailFormGrid({ children }: { children: React.ReactNode }) {
     return <div className="md:grid md:grid-cols-2 gap-4 items-start mb-4">{children}</div>;
 }
@@ -153,30 +164,26 @@ export interface PageContext {
     pageId?: string;
     entity?: any;
     form?: UseFormReturn<any>;
-    setForm: (form: UseFormReturn<any>) => void;
 }
 
 export function PageTitle({ children }: { children: React.ReactNode }) {
-    return <h1 className="text-2xl font-semibold mb-4">{children}</h1>;
+    return <h1 className="text-2xl font-semibold">{children}</h1>;
 }
 
 export function PageActionBar({ children }: { children: React.ReactNode }) {
     let childArray = React.Children.toArray(children);
-    // For some reason, sometimes the `children` prop is passed as this component itself, so we need to unwrap it
-    // This is a bit of a hack, but it works
-    if (childArray.length === 1 && (childArray[0] as React.ReactElement).type === PageActionBar) {
-        childArray = React.Children.toArray((childArray[0] as any)?.props?.children);
-    }
+
     const leftContent = childArray.filter(
-        child => React.isValidElement(child) && (child.type as any)?.name === 'PageActionBarLeft',
+        child => React.isValidElement(child) && child.type === PageActionBarLeft,
     );
     const rightContent = childArray.filter(
-        child => React.isValidElement(child) && (child.type as any)?.name === 'PageActionBarRight',
+        child => React.isValidElement(child) && child.type === PageActionBarRight,
     );
+
     return (
-        <div className="flex justify-between gap-2">
-            <div className="flex justify-start gap-2">{leftContent}</div>
-            <div className="flex justify-end gap-2">{rightContent}</div>
+        <div className={cn('flex gap-2', leftContent.length > 0 ? 'justify-between' : 'justify-end')}>
+            {leftContent.length > 0 && <div className="flex justify-start gap-2">{leftContent}</div>}
+            {rightContent.length > 0 && <div className="flex justify-end gap-2">{rightContent}</div>}
         </div>
     );
 }

+ 59 - 64
packages/dashboard/src/lib/framework/page/detail-page.tsx

@@ -1,11 +1,15 @@
 import { FormFieldWrapper } from '@/components/shared/form-field-wrapper.js';
 import { Input } from '@/components/ui/input.js';
 import { useDetailPage } from '@/framework/page/use-detail-page.js';
-import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { Trans } from '@/lib/trans.js';
+import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { AnyRoute } from '@tanstack/react-router';
 import { ResultOf, VariablesOf } from 'gql.tada';
 
+import { DateTimeInput } from '@/components/data-input/datetime-input.js';
+import { Button } from '@/components/ui/button.js';
+import { Checkbox } from '@/components/ui/checkbox.js';
+import { toast } from 'sonner';
 import { getOperationVariablesFields } from '../document-introspection/get-document-structure.js';
 import {
     DetailFormGrid,
@@ -14,15 +18,10 @@ import {
     PageActionBarLeft,
     PageActionBarRight,
     PageBlock,
-    PageDetailForm,
     PageLayout,
     PageTitle,
 } from '../layout-engine/page-layout.js';
 import { DetailEntityPath } from './page-types.js';
-import { toast } from 'sonner';
-import { Button } from '@/components/ui/button.js';
-import { Checkbox } from '@/components/ui/checkbox.js';
-import { DateTimeInput } from '@/components/data-input/datetime-input.js';
 
 export interface DetailPageProps<
     T extends TypedDocumentNode<any, any>,
@@ -74,64 +73,60 @@ export function DetailPage<
     const updateFields = getOperationVariablesFields(updateDocument);
 
     return (
-        <Page pageId={pageId}>
-            <PageDetailForm form={form} submitHandler={submitHandler}>
-                <PageActionBar>
-                    <PageActionBarLeft>
-                        <PageTitle>{title(entity)}</PageTitle>
-                    </PageActionBarLeft>
-                    <PageActionBarRight>
-                        <Button
-                            type="submit"
-                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
-                        >
-                            <Trans>Update</Trans>
-                        </Button>
-                    </PageActionBarRight>
-                </PageActionBar>
-                <PageLayout>
-                    <PageBlock column="main" blockId="main-form">
-                        <DetailFormGrid>
-                            {updateFields.map(fieldInfo => {
-                                console.log(fieldInfo);
-                                if (fieldInfo.name === 'id' && fieldInfo.type === 'ID') {
-                                    return null;
-                                }
-                                return (
-                                    <FormFieldWrapper
-                                        key={fieldInfo.name}
-                                        control={form.control}
-                                        name={fieldInfo.name as never}
-                                        label={fieldInfo.name}
-                                        render={({ field }) => {
-                                            switch (fieldInfo.type) {
-                                                case 'Int':
-                                                case 'Float':
-                                                    return (
-                                                        <Input
-                                                            type="number"
-                                                            value={field.value}
-                                                            onChange={e =>
-                                                                field.onChange(e.target.valueAsNumber)
-                                                            }
-                                                        />
-                                                    );
-                                                case 'DateTime':
-                                                    return <DateTimeInput {...field} />;
-                                                case 'Boolean':
-                                                    return <Checkbox {...field} />;
-                                                case 'String':
-                                                default:
-                                                    return <Input {...field} />;
-                                            }
-                                        }}
-                                    />
-                                );
-                            })}
-                        </DetailFormGrid>
-                    </PageBlock>
-                </PageLayout>
-            </PageDetailForm>
+        <Page pageId={pageId} form={form} submitHandler={submitHandler}>
+            <PageActionBar>
+                <PageActionBarLeft>
+                    <PageTitle>{title(entity)}</PageTitle>
+                </PageActionBarLeft>
+                <PageActionBarRight>
+                    <Button
+                        type="submit"
+                        disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                    >
+                        <Trans>Update</Trans>
+                    </Button>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        {updateFields.map(fieldInfo => {
+                            console.log(fieldInfo);
+                            if (fieldInfo.name === 'id' && fieldInfo.type === 'ID') {
+                                return null;
+                            }
+                            return (
+                                <FormFieldWrapper
+                                    key={fieldInfo.name}
+                                    control={form.control}
+                                    name={fieldInfo.name as never}
+                                    label={fieldInfo.name}
+                                    render={({ field }) => {
+                                        switch (fieldInfo.type) {
+                                            case 'Int':
+                                            case 'Float':
+                                                return (
+                                                    <Input
+                                                        type="number"
+                                                        value={field.value}
+                                                        onChange={e => field.onChange(e.target.valueAsNumber)}
+                                                    />
+                                                );
+                                            case 'DateTime':
+                                                return <DateTimeInput {...field} />;
+                                            case 'Boolean':
+                                                return <Checkbox {...field} />;
+                                            case 'String':
+                                            default:
+                                                return <Input {...field} />;
+                                        }
+                                    }}
+                                />
+                            );
+                        })}
+                    </DetailFormGrid>
+                </PageBlock>
+            </PageLayout>
         </Page>
     );
 }