@mohasinac/appkit 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
- package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
- package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
- package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
- package/dist/_internal/server/features/audit-log/actions.js +38 -0
- package/dist/_internal/server/features/brands/actions.js +0 -2
- package/dist/_internal/server/features/brands/og.d.ts +4 -1
- package/dist/_internal/server/features/brands/og.js +1 -1
- package/dist/_internal/server/features/grouped/data.d.ts +4 -0
- package/dist/_internal/server/features/grouped/data.js +24 -0
- package/dist/_internal/server/features/grouped/index.d.ts +1 -1
- package/dist/_internal/server/features/grouped/index.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +9 -0
- package/dist/_internal/server/features/orders/adapters.js +25 -0
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +17 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
- package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +3 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
- package/dist/_internal/server/notifications/channel-health.js +101 -0
- package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
- package/dist/_internal/shared/features/brands/schema.js +0 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/client.d.ts +21 -6
- package/dist/client.js +13 -3
- package/dist/constants/api-endpoints.d.ts +6 -9
- package/dist/constants/api-endpoints.js +2 -3
- package/dist/contracts/client-auth.d.ts +3 -7
- package/dist/contracts/email.d.ts +1 -0
- package/dist/features/account/components/AddressForm.js +24 -2
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserBidsView.d.ts +1 -0
- package/dist/features/account/components/UserBidsView.js +47 -0
- package/dist/features/account/components/UserOrdersView.d.ts +4 -11
- package/dist/features/account/components/UserOrdersView.js +54 -4
- package/dist/features/account/components/UserReturnsView.d.ts +4 -9
- package/dist/features/account/components/UserReturnsView.js +37 -3
- package/dist/features/account/components/index.d.ts +3 -2
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/admin/actions/admin-actions.js +22 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
- package/dist/features/admin/actions/notification-actions.js +4 -3
- package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
- package/dist/features/admin/components/AdminAdsView.js +86 -89
- package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
- package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
- package/dist/features/admin/components/AdminAuditLogView.js +95 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
- package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
- package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
- package/dist/features/admin/components/AdminCartsView.js +34 -4
- package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminHistoryView.js +11 -1
- package/dist/features/admin/components/AdminNotificationsView.js +27 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +92 -24
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
- package/dist/features/admin/components/AdminPayoutsView.js +15 -31
- package/dist/features/admin/components/AdminProductEditorView.js +26 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
- package/dist/features/admin/components/AdminStoresView.js +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
- package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
- package/dist/features/admin/components/AdminUserEditorView.js +4 -2
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +14 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
- package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
- package/dist/features/admin/components/ViewNotificationModal.js +15 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
- package/dist/features/admin/components/index.d.ts +8 -1
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
- package/dist/features/admin/constants/filter-tabs.js +27 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
- package/dist/features/admin/hooks/useAdminListing.js +3 -1
- package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
- package/dist/features/admin/hooks/useAdminListingData.js +9 -2
- package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.d.ts +12 -0
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +8 -0
- package/dist/features/auctions/schemas/index.d.ts +5 -0
- package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
- package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
- package/dist/features/audit-log/schemas/firestore.js +35 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +8 -35
- package/dist/features/auth/hooks/useAuth.js +21 -37
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +2 -0
- package/dist/features/blog/schemas/index.d.ts +32 -0
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/categories/components/BrandDetailPageView.js +14 -12
- package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
- package/dist/features/categories/schemas/firestore.d.ts +19 -2
- package/dist/features/categories/schemas/index.d.ts +24 -3
- package/dist/features/categories/schemas/index.js +3 -1
- package/dist/features/categories/types/index.d.ts +7 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
- package/dist/features/contact/components/ContactForm.js +17 -4
- package/dist/features/contact/email.d.ts +1 -0
- package/dist/features/contact/email.js +3 -0
- package/dist/features/events/components/AdminEventEditorView.js +54 -2
- package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
- package/dist/features/events/components/AdminEventEntriesView.js +56 -89
- package/dist/features/events/components/EventFormDrawer.js +5 -1
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
- package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
- package/dist/features/events/schemas/index.d.ts +22 -0
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +4 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +18 -0
- package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/layout/TitleBarLayout.d.ts +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/media/MediaImage.js +39 -3
- package/dist/features/media/types/index.d.ts +10 -0
- package/dist/features/media/types/index.js +1 -0
- package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
- package/dist/features/media/upload/MediaUploadField.js +11 -7
- package/dist/features/media/upload/video-poster.d.ts +2 -0
- package/dist/features/media/upload/video-poster.js +16 -1
- package/dist/features/media/upload/video-poster.test.js +1 -1
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
- package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/constants/payment-window.d.ts +25 -0
- package/dist/features/orders/constants/payment-window.js +25 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +56 -0
- package/dist/features/orders/schemas/index.d.ts +24 -24
- package/dist/features/orders/types/index.d.ts +33 -0
- package/dist/features/pre-orders/schemas/index.d.ts +5 -0
- package/dist/features/products/components/BidHistory.js +9 -1
- package/dist/features/products/components/GroupSettingsPanel.js +25 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductGrid.js +15 -1
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/index.d.ts +5 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/search/components/SearchView.d.ts +1 -3
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/QuickProductForm.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +66 -107
- package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
- package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
- package/dist/features/seller/components/SellerOrdersView.js +134 -163
- package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
- package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
- package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
- package/dist/features/seller/components/SellerPayoutsView.js +25 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
- package/dist/features/seller/components/SellerProductShell.js +89 -36
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
- package/dist/features/seller/components/SellerProductsView.js +131 -189
- package/dist/features/seller/components/SellerReviewsView.js +95 -133
- package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
- package/dist/features/seller/components/SellerShippingView.js +41 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
- package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
- package/dist/features/seller/components/SellerStorefrontView.js +42 -5
- package/dist/features/seller/components/index.d.ts +2 -2
- package/dist/features/seller/components/index.js +2 -2
- package/dist/features/shell/FormShell.d.ts +24 -4
- package/dist/features/shell/FormShell.js +12 -6
- package/dist/features/shell/QuickFormDrawer.js +67 -12
- package/dist/features/shell/StepForm.d.ts +30 -1
- package/dist/features/shell/StepForm.js +47 -4
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +1 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
- package/dist/features/shipments/schemas/validation.d.ts +14 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
- package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
- package/dist/features/tester/components/TesterHubView.js +3 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
- package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +404 -15
- package/dist/index.d.ts +15 -3
- package/dist/index.js +22 -5
- package/dist/next/routing/route-map.d.ts +11 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/providers/email-resend/provider.js +1 -0
- package/dist/providers/firebase-client/auth.d.ts +1 -0
- package/dist/providers/firebase-client/auth.js +7 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +61 -25
- package/dist/security/pii-mask.js +1 -1
- package/dist/seed/categories-seed-data.js +69 -0
- package/dist/seed/products-auctions-seed-data.js +11 -0
- package/dist/seed/products-standard-seed-data.js +93 -4
- package/dist/seed/site-settings-seed-data.js +13 -1
- package/dist/seed/store-extensions-seed-data.js +1 -1
- package/dist/server-entry.d.ts +2 -1
- package/dist/server-entry.js +20 -2
- package/dist/server.d.ts +7 -2
- package/dist/server.js +8 -3
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +11 -2
- package/dist/ui/components/Form.js +4 -3
- package/dist/ui/components/FormField.d.ts +3 -1
- package/dist/ui/components/FormField.js +22 -4
- package/dist/ui/components/ListingLayout.d.ts +1 -2
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +68 -0
- package/dist/ui/components/RecordDetailModal.js +8 -0
- package/dist/ui/components/SlottedListingView.js +9 -1
- package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
- package/dist/ui/forms/FormErrorSummary.js +37 -0
- package/dist/ui/forms/FormShell.d.ts +37 -2
- package/dist/ui/forms/FormShell.js +37 -7
- package/dist/ui/forms/FormShell.style.css +17 -0
- package/dist/ui/forms/index.d.ts +2 -0
- package/dist/ui/forms/index.js +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/media-url.d.ts +3 -1
- package/dist/utils/media-url.js +20 -1
- package/dist/validation/schemas.d.ts +4 -4
- package/package.json +1 -1
- package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
- package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
- package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
- package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
- package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
- package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
- package/dist/features/auth/consent-otp.d.ts +0 -57
- package/dist/features/auth/consent-otp.js +0 -127
- package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
- package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
- package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
- package/dist/features/checkout/actions/checkout-actions.js +0 -124
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
- package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
- package/dist/features/classified/components/ClassifiedListView.js +0 -44
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
- package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
- package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
- package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
- package/dist/features/live/components/LiveItemsListView.js +0 -44
- package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
- package/dist/features/seller/components/SellerTemplatesView.js +0 -276
- package/dist/react/hooks/useVisibleItems.d.ts +0 -37
- package/dist/react/hooks/useVisibleItems.js +0 -59
- package/dist/ui/components/TabStrip.d.ts +0 -34
- package/dist/ui/components/TabStrip.js +0 -56
- package/dist/ui/components/TabStrip.style.css +0 -97
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ZodTypeAny } from "zod";
|
|
3
3
|
import { type SurfaceProps } from "./surface-tokens";
|
|
4
|
-
import { type UseFormShellStateResult } from "../forms/FormShell";
|
|
4
|
+
import { type FormShellContextValue, type UseFormShellStateResult } from "../forms/FormShell";
|
|
5
5
|
export type FormSpacing = "none" | "xs" | "sm" | "md" | "lg" | "xl";
|
|
6
6
|
export interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "children">, SurfaceProps {
|
|
7
7
|
/**
|
|
@@ -19,13 +19,22 @@ export interface FormProps extends Omit<React.FormHTMLAttributes<HTMLFormElement
|
|
|
19
19
|
* boilerplate per callsite.
|
|
20
20
|
*/
|
|
21
21
|
schema?: ZodTypeAny;
|
|
22
|
+
/**
|
|
23
|
+
* Use an externally-computed `FormShellContextValue` (e.g. from a parent's
|
|
24
|
+
* own `useFormShellState(schema)` call) instead of `<Form>` creating its
|
|
25
|
+
* own. Needed when other elements outside this `<form>` — a submit button
|
|
26
|
+
* in a separate sidebar, associated via the HTML `form` attribute — must
|
|
27
|
+
* share the exact same validation state; otherwise `<Form>`'s own internal
|
|
28
|
+
* provider would shadow the parent's for everything rendered as `children`.
|
|
29
|
+
*/
|
|
30
|
+
shellCtx?: FormShellContextValue;
|
|
22
31
|
/** Cross-axis alignment of flex/grid children. Replaces raw `items-*` className. */
|
|
23
32
|
align?: "start" | "center" | "end" | "stretch";
|
|
24
33
|
/** Gap between children — use instead of raw `gap-*` className. */
|
|
25
34
|
gap?: GapToken;
|
|
26
35
|
}
|
|
27
36
|
export type GapToken = "none" | "xs" | "sm" | "md" | "lg" | "xl";
|
|
28
|
-
export declare function Form({ children, spacing, schema, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className, ...props }: FormProps): React.JSX.Element;
|
|
37
|
+
export declare function Form({ children, spacing, schema, shellCtx: shellCtxProp, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className, ...props }: FormProps): React.JSX.Element;
|
|
29
38
|
export interface FormGroupProps {
|
|
30
39
|
children: React.ReactNode;
|
|
31
40
|
columns?: 1 | 2 | 3 | 4;
|
|
@@ -43,12 +43,13 @@ const FORM_ALIGN_MAP = {
|
|
|
43
43
|
end: "items-end",
|
|
44
44
|
stretch: "items-stretch",
|
|
45
45
|
};
|
|
46
|
-
export function Form({ children, spacing, schema, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className = "", ...props }) {
|
|
46
|
+
export function Form({ children, spacing, schema, shellCtx: shellCtxProp, align, gap, surface, padding, paddingX, paddingY, rounded, roundedTop, roundedBottom, border, shadow, overflow, className = "", ...props }) {
|
|
47
47
|
const helpers = useFormShellState(schema);
|
|
48
|
+
const effectiveCtx = shellCtxProp ?? helpers.shellCtx;
|
|
48
49
|
const content = typeof children === "function"
|
|
49
|
-
? children(helpers)
|
|
50
|
+
? children({ ...helpers, shellCtx: effectiveCtx })
|
|
50
51
|
: children;
|
|
51
|
-
return (_jsx(FormShellContext.Provider, { value:
|
|
52
|
+
return (_jsx(FormShellContext.Provider, { value: effectiveCtx, children: _jsx("form", { className: [
|
|
52
53
|
"appkit-form",
|
|
53
54
|
spacing ? FORM_SPACING_MAP[spacing] : "",
|
|
54
55
|
align ? FORM_ALIGN_MAP[align] : "",
|
|
@@ -8,7 +8,9 @@ export interface FormFieldProps {
|
|
|
8
8
|
value?: string;
|
|
9
9
|
onChange?: (value: string) => void;
|
|
10
10
|
onBlur?: () => void;
|
|
11
|
+
/** Override error — falls back to FormShell context error for this field */
|
|
11
12
|
error?: string;
|
|
13
|
+
/** Override touched — falls back to FormShell context touched state for this field */
|
|
12
14
|
touched?: boolean;
|
|
13
15
|
placeholder?: string;
|
|
14
16
|
required?: boolean;
|
|
@@ -24,5 +26,5 @@ export interface FormFieldProps {
|
|
|
24
26
|
accept?: string;
|
|
25
27
|
maxSizeMB?: number;
|
|
26
28
|
}
|
|
27
|
-
export declare function FormField({ label, name, card, type, value, onChange, onBlur, error, touched, placeholder, required, disabled, autoComplete, rows, hint, helpText, options, onUpload, captureSource, captureMode, accept, maxSizeMB, }: FormFieldProps): React.JSX.Element;
|
|
29
|
+
export declare function FormField({ label, name, card, type, value, onChange, onBlur, error: errorProp, touched: touchedProp, placeholder, required, disabled, autoComplete, rows, hint, helpText, options, onUpload, captureSource, captureMode, accept, maxSizeMB, }: FormFieldProps): React.JSX.Element;
|
|
28
30
|
export default FormField;
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useContext } from "react";
|
|
3
4
|
import { Input } from "./Input";
|
|
4
5
|
import { Select } from "./Select";
|
|
5
6
|
import { Textarea } from "./Textarea";
|
|
6
7
|
import { Label, Text, Span } from "./Typography";
|
|
7
8
|
import { ImageUpload, MediaUploadField } from "../../features/media";
|
|
9
|
+
import { FormShellContext } from "../forms/FormShell";
|
|
8
10
|
const CARD_CLASS = "appkit-form-field appkit-form-field--card";
|
|
9
11
|
const BASE_CLASS = "appkit-form-field";
|
|
10
|
-
export function FormField({ label, name, card = false, type = "text", value = "", onChange, onBlur, error, touched, placeholder, required = false, disabled = false, autoComplete, rows, hint, helpText, options = [], onUpload, captureSource, captureMode, accept, maxSizeMB, }) {
|
|
12
|
+
export function FormField({ label, name, card = false, type = "text", value = "", onChange, onBlur, error: errorProp, touched: touchedProp, placeholder, required = false, disabled = false, autoComplete, rows, hint, helpText, options = [], onUpload, captureSource, captureMode, accept, maxSizeMB, }) {
|
|
13
|
+
// Falls back to FormShellContext (the same context FieldInput/FieldSelect
|
|
14
|
+
// read) whenever the caller doesn't pass an explicit error/touched prop —
|
|
15
|
+
// an explicit prop always wins, preserving back-compat for the handful of
|
|
16
|
+
// call sites that already manage their own error state manually.
|
|
17
|
+
const ctx = useContext(FormShellContext);
|
|
18
|
+
const error = errorProp ?? ctx?.errors[name];
|
|
19
|
+
const touched = touchedProp ?? ctx?.touched[name];
|
|
11
20
|
const showError = error
|
|
12
21
|
? touched != null
|
|
13
22
|
? touched && !!error
|
|
@@ -16,12 +25,21 @@ export function FormField({ label, name, card = false, type = "text", value = ""
|
|
|
16
25
|
const inputId = `field-${name}`;
|
|
17
26
|
const errorId = `${inputId}-error`;
|
|
18
27
|
const describedBy = showError ? errorId : undefined;
|
|
28
|
+
function handleFieldChange(newValue) {
|
|
29
|
+
onChange?.(newValue);
|
|
30
|
+
if (showError)
|
|
31
|
+
ctx?.clearFieldError(name);
|
|
32
|
+
}
|
|
33
|
+
function handleFieldBlur() {
|
|
34
|
+
ctx?.setFieldTouched(name);
|
|
35
|
+
onBlur?.();
|
|
36
|
+
}
|
|
19
37
|
if (type === "image" && onUpload) {
|
|
20
|
-
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-505", children: [_jsx("div", { "aria-disabled": disabled || undefined, className: disabled ? "pointer-events-none opacity-60" : undefined, children: _jsx(ImageUpload, { currentImage: value || undefined, onUpload: onUpload, onChange: (url) =>
|
|
38
|
+
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-505", children: [_jsx("div", { "aria-disabled": disabled || undefined, className: disabled ? "pointer-events-none opacity-60" : undefined, children: _jsx(ImageUpload, { currentImage: value || undefined, onUpload: onUpload, onChange: (url) => handleFieldChange(url), label: label ? `${label}${required ? " *" : ""}` : undefined, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", accept: accept, maxSizeMB: maxSizeMB }) }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
|
|
21
39
|
}
|
|
22
40
|
if (type === "media" && onUpload) {
|
|
23
|
-
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-506", children: [_jsx(MediaUploadField, { label: `${label || name}${required ? " *" : ""}`, value: value, onChange: (url) =>
|
|
41
|
+
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-506", children: [_jsx(MediaUploadField, { label: `${label || name}${required ? " *" : ""}`, value: value, onChange: (url) => handleFieldChange(url), onUpload: onUpload, disabled: disabled, helperText: hint ?? helpText, captureSource: captureSource ?? "file-only", captureMode: captureMode ?? "both", accept: accept, maxSizeMB: maxSizeMB }), showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
|
|
24
42
|
}
|
|
25
|
-
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-507", children: [label ? (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required ? (_jsx(Span, { className: "appkit-form-field__required", children: "*" })) : null] })) : null, type === "select" ? (_jsx(Select, { id: inputId, name: name, value: value, onChange: (event) =>
|
|
43
|
+
return (_jsxs("div", { className: card ? CARD_CLASS : BASE_CLASS, "data-section": "formfield-div-507", children: [label ? (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required ? (_jsx(Span, { className: "appkit-form-field__required", children: "*" })) : null] })) : null, type === "select" ? (_jsx(Select, { id: inputId, name: name, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, disabled: disabled, options: options, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : type === "textarea" ? (_jsx(Textarea, { id: inputId, name: name, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, placeholder: placeholder, disabled: disabled, rows: rows, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })) : (_jsx(Input, { id: inputId, name: name, type: type, value: value, onChange: (event) => handleFieldChange(event.target.value), onBlur: handleFieldBlur, placeholder: placeholder, disabled: disabled, autoComplete: autoComplete, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": describedBy })), !showError && (hint || helpText) ? (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint ?? helpText })) : null, showError ? (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error })) : null] }));
|
|
26
44
|
}
|
|
27
45
|
export default FormField;
|
|
@@ -59,7 +59,6 @@ export interface ListingLayoutProps {
|
|
|
59
59
|
/** Count of filter changes staged but not yet committed. Shows in Apply button. */
|
|
60
60
|
filterPendingCount?: number;
|
|
61
61
|
className?: string;
|
|
62
|
-
loading?: boolean;
|
|
63
62
|
errorSlot?: ReactNode;
|
|
64
63
|
labels?: ListingLayoutLabels;
|
|
65
64
|
/** Portal context — sets `isDashboard` default (admin/seller=true, user/public=false). */
|
|
@@ -69,4 +68,4 @@ export interface ListingLayoutProps {
|
|
|
69
68
|
/** When provided, the entire layout is replaced with this view (detail/edit mode). */
|
|
70
69
|
detailView?: ReactNode;
|
|
71
70
|
}
|
|
72
|
-
export declare function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen, filterPendingCount, className,
|
|
71
|
+
export declare function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen, filterPendingCount, className, errorSlot, labels, portal, overlays, detailView, }: ListingLayoutProps): import("react").JSX.Element;
|
|
@@ -35,7 +35,7 @@ const DEFAULT_LABELS = {
|
|
|
35
35
|
hideFilters: "Hide filters",
|
|
36
36
|
filterActiveCount: (count) => `${count} active`,
|
|
37
37
|
};
|
|
38
|
-
export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount = 0, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount = 0, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen = false, filterPendingCount, className = "",
|
|
38
|
+
export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filterActiveCount = 0, onFilterApply, onFilterClear, filterTitle, activeFiltersSlot, resultCountSlot, searchSlot, sortSlot, viewToggleSlot, actionsSlot, selectedCount = 0, onClearSelection, bulkActionItems, toolbarPaginationSlot, paginationSlot, children, isDashboard, defaultSidebarOpen = false, filterPendingCount, className = "", errorSlot, labels, portal, overlays, detailView, }) {
|
|
39
39
|
// useState must run unconditionally on every render — these were
|
|
40
40
|
// previously declared after the detail-view early return below, which
|
|
41
41
|
// skipped both hook calls whenever `detailView` was passed, violating the
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecordDetailModal — the shared "open one listing row and actually read it"
|
|
3
|
+
* surface.
|
|
4
|
+
*
|
|
5
|
+
* Built 2026-08-21 when `audit-listing-detail-affordance` found 16 of 70
|
|
6
|
+
* dashboard listing views were dead ends: rows the user could see but never
|
|
7
|
+
* open. Two distinct failure shapes, both fixed by this one primitive:
|
|
8
|
+
*
|
|
9
|
+
* 1. Data fetched then discarded — AdminCartsView/AdminWishlistsView/
|
|
10
|
+
* AdminHistoryView all compute `items.length` for the row subtitle and
|
|
11
|
+
* throw the `items[]` array itself away, with no surface to show it
|
|
12
|
+
* (CLAUDE.md Recurrent Root Cause #52).
|
|
13
|
+
* 2. Acting blind — AdminCatalogueApprovalsView offers Approve/Reject, and
|
|
14
|
+
* AdminTesterFeedbackListView offers "Confirm bug", on records whose
|
|
15
|
+
* content (photos, description, the tester's comment/screenshot) was
|
|
16
|
+
* never rendered anywhere.
|
|
17
|
+
*
|
|
18
|
+
* Sixteen bespoke modals would have tripped the Duplication Decision
|
|
19
|
+
* Framework's Rule of Three immediately, so this is deliberately one
|
|
20
|
+
* primitive with three optional content slots — `fields` (definition rows),
|
|
21
|
+
* `items` (a thumbnail list, the Root Cause #52 fix), and `metadata` (a raw
|
|
22
|
+
* JSON payload dump) — rather than a per-feature component each time.
|
|
23
|
+
*
|
|
24
|
+
* Consumers pass plain data, not JSX, so a listing view's detail wiring stays
|
|
25
|
+
* the ~20-line "map the raw row into fields/items" shape at every call site.
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from "react";
|
|
28
|
+
import type { JsonValue } from "../../schemas/types";
|
|
29
|
+
export interface RecordDetailField {
|
|
30
|
+
label: string;
|
|
31
|
+
/** Rendered as-is when a node; plain strings/numbers get default styling. */
|
|
32
|
+
value: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
export interface RecordDetailItem {
|
|
35
|
+
/** Thumbnail URL — falls back to MediaImage's placeholder when absent. */
|
|
36
|
+
image?: string;
|
|
37
|
+
title: string;
|
|
38
|
+
subtitle?: string;
|
|
39
|
+
/** Right-aligned trailing text (price, quantity, timestamp). */
|
|
40
|
+
trailing?: string;
|
|
41
|
+
/** When set, the title becomes a link to this record's own detail page. */
|
|
42
|
+
href?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RecordDetailBadge {
|
|
45
|
+
label: string;
|
|
46
|
+
variant?: "default" | "success" | "warning" | "danger" | "info" | "secondary";
|
|
47
|
+
}
|
|
48
|
+
export interface RecordDetailModalProps {
|
|
49
|
+
isOpen: boolean;
|
|
50
|
+
onClose: () => void;
|
|
51
|
+
title: string;
|
|
52
|
+
/** Small status chips under the title (status, read-state, role, etc). */
|
|
53
|
+
badges?: RecordDetailBadge[];
|
|
54
|
+
/** Free-text lead paragraph — a comment body, description, or message. */
|
|
55
|
+
description?: string;
|
|
56
|
+
/** Definition rows: the record's scalar fields. */
|
|
57
|
+
fields?: RecordDetailField[];
|
|
58
|
+
/** A nested collection on the record (cart items, wishlist entries, …). */
|
|
59
|
+
items?: {
|
|
60
|
+
heading: string;
|
|
61
|
+
entries: RecordDetailItem[];
|
|
62
|
+
};
|
|
63
|
+
/** Raw payload dump for records that carry an open-ended object. */
|
|
64
|
+
metadata?: Record<string, JsonValue>;
|
|
65
|
+
/** Action buttons rendered at the bottom (approve/reject/etc). */
|
|
66
|
+
footer?: ReactNode;
|
|
67
|
+
}
|
|
68
|
+
export declare function RecordDetailModal({ isOpen, onClose, title, badges, description, fields, items, metadata, footer, }: RecordDetailModalProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Anchor, Badge, Code, Div, Modal, Row, Stack, Text } from "../index";
|
|
4
|
+
import { MediaImage } from "../../features/media/MediaImage";
|
|
5
|
+
export function RecordDetailModal({ isOpen, onClose, title, badges, description, fields, items, metadata, footer, }) {
|
|
6
|
+
const hasMetadata = Boolean(metadata && Object.keys(metadata).length > 0);
|
|
7
|
+
return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: title, children: _jsxs(Stack, { gap: "md", children: [badges && badges.length > 0 && (_jsx(Row, { gap: "sm", align: "center", wrap: true, children: badges.map((b) => (_jsx(Badge, { variant: b.variant ?? "default", children: b.label }, b.label))) })), description && (_jsx(Div, { rounded: "lg", padding: "sm", surface: "muted", border: "default", children: _jsx(Text, { size: "sm", children: description }) })), fields && fields.length > 0 && (_jsx(Stack, { gap: "xs", children: fields.map((f) => (_jsxs(Row, { justify: "between", gap: "md", children: [_jsx(Text, { size: "sm", color: "muted", children: f.label }), _jsx(Text, { size: "sm", weight: "medium", className: "text-right", children: f.value })] }, f.label))) })), items && items.entries.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", weight: "semibold", color: "muted", children: [items.heading, " (", items.entries.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.entries.map((entry, i) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", paddingY: "y-xs-tall", padding: "x-sm", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: entry.image, alt: entry.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [entry.href ? (_jsx(Anchor, { href: entry.href, tone: "brand", size: "sm", weight: "medium", children: entry.title })) : (_jsx(Text, { size: "sm", weight: "medium", className: "truncate", children: entry.title })), entry.subtitle && (_jsx(Text, { size: "xs", color: "muted", children: entry.subtitle }))] })] }), entry.trailing && (_jsx(Text, { size: "sm", weight: "medium", className: "shrink-0", children: entry.trailing }))] }, `${entry.title}-${i}`))) })] })), hasMetadata && (_jsxs(Div, { rounded: "lg", padding: "sm", surface: "muted", border: "default", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", className: "mb-1", children: "Details" }), _jsx(Code, { size: "xs", className: "block whitespace-pre-wrap font-mono", children: JSON.stringify(metadata, null, 2) })] })), footer && _jsx(Row, { justify: "end", gap: "sm", children: footer })] }) }));
|
|
8
|
+
}
|
|
@@ -25,7 +25,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
25
25
|
import { useState } from "react";
|
|
26
26
|
import { Div } from "./Div";
|
|
27
27
|
import { Heading } from "./Typography";
|
|
28
|
-
export function SlottedListingView({ title, labels = {}, portal, renderHeader, renderTabs, renderSearch, renderSort, renderFilters, renderActiveFilters, renderBulkActions, renderTable, renderPagination, overlays,
|
|
28
|
+
export function SlottedListingView({ title, labels = {}, portal, renderHeader, renderTabs, renderSearch, renderSort, renderFilters, renderActiveFilters, renderBulkActions, renderTable, renderPagination, overlays,
|
|
29
|
+
// audit-dead-underscore-prop-ok: deliberately unused — see the doc comment
|
|
30
|
+
// on `manageSearch` above; wiring this to gate renderSearch would break
|
|
31
|
+
// real consumers with self-contained closures.
|
|
32
|
+
manageSearch: _manageSearch, manageSelection,
|
|
33
|
+
// audit-dead-underscore-prop-ok: deliberately unused — see the doc comment
|
|
34
|
+
// on `manageSort` above; wiring this to gate renderSort would break real
|
|
35
|
+
// consumers with self-contained closures.
|
|
36
|
+
manageSort: _manageSort = false, inlineToolbar = false, total = 0, isLoading = false, className = "", }) {
|
|
29
37
|
const effectiveManageSelection = manageSelection ?? (portal === "admin" || portal === "seller");
|
|
30
38
|
const [search, setSearch] = useState("");
|
|
31
39
|
const [selectedIds, setSelectedIds] = useState([]);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface FormErrorSummaryProps {
|
|
2
|
+
/** Hide the whole block when there are currently no errors. Default true. */
|
|
3
|
+
hideWhenEmpty?: boolean;
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Heading shown above the error list. */
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Shared, live validation-error summary — supplements (does not replace) the
|
|
10
|
+
* per-field inline errors `FieldInput`/`FieldSelect`/`FormField` already
|
|
11
|
+
* render. Reads `FormShellContext` directly (no required props), so it's
|
|
12
|
+
* placed once beside a form's Submit/Save button and reflects every current
|
|
13
|
+
* error immediately as fields change — not gated by `touched`, unlike the
|
|
14
|
+
* inline per-field errors.
|
|
15
|
+
*
|
|
16
|
+
* For multi-step forms whose step engine populates
|
|
17
|
+
* `FormShellContextValue.fieldToStepIndex`, each entry names its owning step
|
|
18
|
+
* and is clickable to jump there. Falls back to a flat list when no step
|
|
19
|
+
* context exists (single-step `<Form>` usage).
|
|
20
|
+
*
|
|
21
|
+
* Uses the `.appkit-formshell__error-summary` class family already defined
|
|
22
|
+
* in `FormShell.style.css` for this exact purpose.
|
|
23
|
+
*/
|
|
24
|
+
export declare function FormErrorSummary({ hideWhenEmpty, className, title, }: FormErrorSummaryProps): import("react").JSX.Element | null;
|
|
25
|
+
export default FormErrorSummary;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import { FormShellContext } from "./FormShell";
|
|
5
|
+
import { Span } from "../components/Typography";
|
|
6
|
+
/**
|
|
7
|
+
* Shared, live validation-error summary — supplements (does not replace) the
|
|
8
|
+
* per-field inline errors `FieldInput`/`FieldSelect`/`FormField` already
|
|
9
|
+
* render. Reads `FormShellContext` directly (no required props), so it's
|
|
10
|
+
* placed once beside a form's Submit/Save button and reflects every current
|
|
11
|
+
* error immediately as fields change — not gated by `touched`, unlike the
|
|
12
|
+
* inline per-field errors.
|
|
13
|
+
*
|
|
14
|
+
* For multi-step forms whose step engine populates
|
|
15
|
+
* `FormShellContextValue.fieldToStepIndex`, each entry names its owning step
|
|
16
|
+
* and is clickable to jump there. Falls back to a flat list when no step
|
|
17
|
+
* context exists (single-step `<Form>` usage).
|
|
18
|
+
*
|
|
19
|
+
* Uses the `.appkit-formshell__error-summary` class family already defined
|
|
20
|
+
* in `FormShell.style.css` for this exact purpose.
|
|
21
|
+
*/
|
|
22
|
+
export function FormErrorSummary({ hideWhenEmpty = true, className, title = "Please fix the following:", }) {
|
|
23
|
+
const ctx = useContext(FormShellContext);
|
|
24
|
+
if (!ctx)
|
|
25
|
+
return null;
|
|
26
|
+
const entries = Object.entries(ctx.errors);
|
|
27
|
+
if (entries.length === 0 && hideWhenEmpty)
|
|
28
|
+
return null;
|
|
29
|
+
if (entries.length === 0)
|
|
30
|
+
return null;
|
|
31
|
+
return (_jsxs("div", { role: "alert", "aria-live": "polite", className: ["appkit-formshell__error-summary", className].filter(Boolean).join(" "), children: [_jsx("div", { className: "appkit-formshell__error-summary__title", children: title }), _jsx("ul", { className: "appkit-formshell__error-summary__list", children: entries.map(([key, message]) => {
|
|
32
|
+
const stepIndex = ctx.fieldToStepIndex?.[key];
|
|
33
|
+
const stepLabel = stepIndex != null ? ctx.steps[stepIndex]?.label : undefined;
|
|
34
|
+
return (_jsx("li", { children: stepLabel ? (_jsxs("button", { type: "button", className: "appkit-formshell__error-summary__step-link", onClick: () => ctx.goToStep(stepIndex), children: [_jsxs(Span, { weight: "semibold", children: [stepLabel, ":"] }), " ", message] })) : (_jsx(Span, { children: message })) }, key));
|
|
35
|
+
}) })] }));
|
|
36
|
+
}
|
|
37
|
+
export default FormErrorSummary;
|
|
@@ -30,6 +30,14 @@ export interface FormShellContextValue {
|
|
|
30
30
|
isDirty: boolean;
|
|
31
31
|
isSubmitting: boolean;
|
|
32
32
|
stepErrorCounts: number[];
|
|
33
|
+
/**
|
|
34
|
+
* Maps an error key (same key shape `errors` uses) to the index into
|
|
35
|
+
* `steps` that owns it. Populated only by step-aware producers (the
|
|
36
|
+
* `features/shell` wizard chrome/step engine) — undefined for single-step
|
|
37
|
+
* forms (`<Form>`, `useFormShellState` with no steps). `<FormErrorSummary>`
|
|
38
|
+
* treats undefined as "no step context" and renders a flat list.
|
|
39
|
+
*/
|
|
40
|
+
fieldToStepIndex?: Record<string, number>;
|
|
33
41
|
}
|
|
34
42
|
export declare const FormShellContext: React.Context<FormShellContextValue | null>;
|
|
35
43
|
export declare function useFormShell(): FormShellContextValue;
|
|
@@ -43,12 +51,22 @@ export interface UseFormShellStateResult {
|
|
|
43
51
|
* supplied values, write inline errors via `setFieldError`, and return the
|
|
44
52
|
* parsed value on success or null on failure.
|
|
45
53
|
*/
|
|
46
|
-
validate: <T = unknown>(values:
|
|
54
|
+
validate: <T = unknown>(values: unknown) => T | null;
|
|
47
55
|
}
|
|
48
56
|
/**
|
|
49
57
|
* `applyZodIssues` — pipe every issue from a Zod safeParse failure into the
|
|
50
58
|
* FormShellContext error map. Use after `schema.safeParse(values)` returns
|
|
51
59
|
* `success: false` to surface inline errors on `<FieldInput>` etc.
|
|
60
|
+
*
|
|
61
|
+
* Keys are the full dotted/indexed path (`"video.duration"`, `"images.2"`),
|
|
62
|
+
* not just the top-level segment — a plain `issue.path[0]` key would collapse
|
|
63
|
+
* every nested issue under one object onto the same map entry, silently
|
|
64
|
+
* dropping all but the last. Top-level-only fields (the common case) are
|
|
65
|
+
* unaffected: a single-segment path joins to the same string as before.
|
|
66
|
+
*
|
|
67
|
+
* Root-level `.refine()` issues (empty `path: []`, no owning field) are still
|
|
68
|
+
* skipped — there is no field to attach them to, and no schema in this
|
|
69
|
+
* codebase currently relies on one being surfaced this way.
|
|
52
70
|
*/
|
|
53
71
|
export declare function applyZodIssues(issues: ReadonlyArray<{
|
|
54
72
|
path: ReadonlyArray<PropertyKey>;
|
|
@@ -70,5 +88,22 @@ export interface FormShellProviderProps {
|
|
|
70
88
|
autoSaveDelayMs?: number;
|
|
71
89
|
/** Current form values — passed into onSaveDraft on auto-save */
|
|
72
90
|
values?: FormValues;
|
|
91
|
+
/**
|
|
92
|
+
* Optional Zod schema — when supplied, the provider runs `.safeParse(values)`
|
|
93
|
+
* live (every time `values` changes, not just on submit) and populates its
|
|
94
|
+
* own `errors` map from the issues, so `<FieldInput>`/`<FormField>`/
|
|
95
|
+
* `<FormErrorSummary>` descendants get real validation without the parent
|
|
96
|
+
* component having to reach into the provider's internal state itself.
|
|
97
|
+
*/
|
|
98
|
+
schema?: import("zod").ZodTypeAny;
|
|
99
|
+
/**
|
|
100
|
+
* Maps an error key to the index of the step that owns it — forwarded
|
|
101
|
+
* as-is into the context value for `<FormErrorSummary>` to consume. The
|
|
102
|
+
* provider itself has no concept of "steps"; the caller (typically a
|
|
103
|
+
* step-wizard shell) computes this from its own step definitions.
|
|
104
|
+
*/
|
|
105
|
+
fieldToStepIndex?: Record<string, number>;
|
|
106
|
+
/** Wired into `FormShellContextValue.goToStep` when the caller has real step navigation to drive (e.g. a step-wizard's `setCurrentStep`). */
|
|
107
|
+
onGoToStep?: (n: number) => void;
|
|
73
108
|
}
|
|
74
|
-
export declare function FormShellProvider({ children, onSaveDraft, isDirty, autoSaveDelayMs, values, }: FormShellProviderProps): React.JSX.Element;
|
|
109
|
+
export declare function FormShellProvider({ children, onSaveDraft, isDirty, autoSaveDelayMs, values, schema, fieldToStepIndex, onGoToStep, }: FormShellProviderProps): React.JSX.Element;
|
|
@@ -13,13 +13,23 @@ export function useFormShell() {
|
|
|
13
13
|
* `applyZodIssues` — pipe every issue from a Zod safeParse failure into the
|
|
14
14
|
* FormShellContext error map. Use after `schema.safeParse(values)` returns
|
|
15
15
|
* `success: false` to surface inline errors on `<FieldInput>` etc.
|
|
16
|
+
*
|
|
17
|
+
* Keys are the full dotted/indexed path (`"video.duration"`, `"images.2"`),
|
|
18
|
+
* not just the top-level segment — a plain `issue.path[0]` key would collapse
|
|
19
|
+
* every nested issue under one object onto the same map entry, silently
|
|
20
|
+
* dropping all but the last. Top-level-only fields (the common case) are
|
|
21
|
+
* unaffected: a single-segment path joins to the same string as before.
|
|
22
|
+
*
|
|
23
|
+
* Root-level `.refine()` issues (empty `path: []`, no owning field) are still
|
|
24
|
+
* skipped — there is no field to attach them to, and no schema in this
|
|
25
|
+
* codebase currently relies on one being surfaced this way.
|
|
16
26
|
*/
|
|
17
27
|
export function applyZodIssues(issues, setFieldError) {
|
|
18
28
|
for (const issue of issues) {
|
|
19
|
-
|
|
20
|
-
if (key == null)
|
|
29
|
+
if (!issue.path || issue.path.length === 0)
|
|
21
30
|
continue;
|
|
22
|
-
|
|
31
|
+
const key = issue.path.map(String).join(".");
|
|
32
|
+
setFieldError(key, issue.message);
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
35
|
/**
|
|
@@ -72,7 +82,7 @@ export function useFormShellState(schema) {
|
|
|
72
82
|
}), [errors, setFieldError]);
|
|
73
83
|
return { shellCtx, setFieldError, clearErrors, hasErrors: Object.keys(errors).length > 0, validate };
|
|
74
84
|
}
|
|
75
|
-
export function FormShellProvider({ children, onSaveDraft, isDirty = false, autoSaveDelayMs = 2000, values = {}, }) {
|
|
85
|
+
export function FormShellProvider({ children, onSaveDraft, isDirty = false, autoSaveDelayMs = 2000, values = {}, schema, fieldToStepIndex, onGoToStep, }) {
|
|
76
86
|
const [errors, setErrors] = useState({});
|
|
77
87
|
const [touched, setTouched] = useState({});
|
|
78
88
|
const autoSaveTimer = useRef(null);
|
|
@@ -89,6 +99,25 @@ export function FormShellProvider({ children, onSaveDraft, isDirty = false, auto
|
|
|
89
99
|
return () => { if (autoSaveTimer.current)
|
|
90
100
|
clearTimeout(autoSaveTimer.current); };
|
|
91
101
|
}, [values, isDirty, onSaveDraft, autoSaveDelayMs]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!schema)
|
|
104
|
+
return;
|
|
105
|
+
// audit-unvalidated-safeparse-ok: bulk-replaces the whole errors map from
|
|
106
|
+
// the full parse result (so a field that just became valid is cleared),
|
|
107
|
+
// which applyZodIssues (incremental setFieldError-per-issue) can't do.
|
|
108
|
+
const parsed = schema.safeParse(values);
|
|
109
|
+
if (parsed.success) {
|
|
110
|
+
setErrors({});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const next = {};
|
|
114
|
+
for (const issue of parsed.error.issues) {
|
|
115
|
+
if (!issue.path || issue.path.length === 0)
|
|
116
|
+
continue;
|
|
117
|
+
next[issue.path.map(String).join(".")] = issue.message;
|
|
118
|
+
}
|
|
119
|
+
setErrors(next);
|
|
120
|
+
}, [schema, values]);
|
|
92
121
|
const setFieldError = useCallback((name, error) => {
|
|
93
122
|
setErrors((prev) => {
|
|
94
123
|
if (!error) {
|
|
@@ -121,13 +150,14 @@ export function FormShellProvider({ children, onSaveDraft, isDirty = false, auto
|
|
|
121
150
|
clearFieldError,
|
|
122
151
|
steps: [],
|
|
123
152
|
currentStep: 0,
|
|
124
|
-
goToStep: () => { },
|
|
153
|
+
goToStep: onGoToStep ?? (() => { }),
|
|
125
154
|
nextStep: () => { },
|
|
126
155
|
prevStep: () => { },
|
|
127
|
-
isPublishReady:
|
|
156
|
+
isPublishReady: Object.keys(errors).length === 0,
|
|
128
157
|
isDirty,
|
|
129
158
|
isSubmitting: false,
|
|
130
159
|
stepErrorCounts: [],
|
|
131
|
-
|
|
160
|
+
fieldToStepIndex,
|
|
161
|
+
}), [errors, touched, setFieldError, setFieldTouched, clearFieldError, isDirty, onGoToStep, fieldToStepIndex]);
|
|
132
162
|
return _jsx(FormShellContext.Provider, { value: ctx, children: children });
|
|
133
163
|
}
|
|
@@ -142,6 +142,23 @@
|
|
|
142
142
|
gap: var(--appkit-spacing-1, 0.25rem);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
.appkit-formshell__error-summary__step-link {
|
|
146
|
+
background: none;
|
|
147
|
+
border: none;
|
|
148
|
+
padding: 0;
|
|
149
|
+
margin: 0;
|
|
150
|
+
font: inherit;
|
|
151
|
+
color: inherit;
|
|
152
|
+
text-align: left;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
text-decoration: underline;
|
|
155
|
+
text-underline-offset: 2px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.appkit-formshell__error-summary__step-link:hover {
|
|
159
|
+
color: var(--appkit-color-error, #dc2626);
|
|
160
|
+
}
|
|
161
|
+
|
|
145
162
|
/* ─── Body + Steps ───────────────────────────────────────────────────────── */
|
|
146
163
|
|
|
147
164
|
.appkit-formshell__body {
|
package/dist/ui/forms/index.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export type { FieldTextareaProps } from "./FieldTextarea";
|
|
|
10
10
|
export { FieldTextarea } from "./FieldTextarea";
|
|
11
11
|
export type { ColorPickerFieldProps } from "./ColorPickerField";
|
|
12
12
|
export { ColorPickerField } from "./ColorPickerField";
|
|
13
|
+
export type { FormErrorSummaryProps } from "./FormErrorSummary";
|
|
14
|
+
export { FormErrorSummary } from "./FormErrorSummary";
|
package/dist/ui/forms/index.js
CHANGED
package/dist/ui/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type { FieldCheckboxProps } from "./forms";
|
|
|
10
10
|
export { FieldCheckbox } from "./forms";
|
|
11
11
|
export type { FieldTextareaProps } from "./forms";
|
|
12
12
|
export { FieldTextarea } from "./forms";
|
|
13
|
+
export type { FormErrorSummaryProps } from "./forms";
|
|
14
|
+
export { FormErrorSummary } from "./forms";
|
|
13
15
|
export { buildColumns, createColumnBuilder, renderBoolean, renderCurrency, renderCurrencyCompact, renderCount, renderNullable, renderRating, getStatusTone, getResourceIcon, } from "./columns";
|
|
14
16
|
export type { BooleanRenderOpts, RatingMode, RatingRenderOpts, StatusTone, } from "./columns";
|
|
15
17
|
export type { SectionProps, SectionBackgroundConfig, ArticleProps, MainProps, AsideProps, NavProps, BlockHeaderProps, BlockFooterProps, UlProps, OlProps, LiProps, TableProps, TheadProps, TbodyProps, TrProps, ThProps, TdProps, CodeProps, PreProps, BlockquoteProps, FigureProps, FigcaptionProps, DlProps, DtProps, DdProps, } from "./components/Semantic";
|
|
@@ -229,3 +231,5 @@ export { Show, Hide } from "./components/Responsive";
|
|
|
229
231
|
export type { ShowProps, HideProps } from "./components/Responsive";
|
|
230
232
|
export { CollapsibleSection } from "./components/CollapsibleSection";
|
|
231
233
|
export type { CollapsibleSectionProps } from "./components/CollapsibleSection";
|
|
234
|
+
export { RecordDetailModal } from "./components/RecordDetailModal";
|
|
235
|
+
export type { RecordDetailModalProps, RecordDetailField, RecordDetailItem, RecordDetailBadge, } from "./components/RecordDetailModal";
|
package/dist/ui/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { FieldInput } from "./forms";
|
|
|
5
5
|
export { FieldSelect } from "./forms";
|
|
6
6
|
export { FieldCheckbox } from "./forms";
|
|
7
7
|
export { FieldTextarea } from "./forms";
|
|
8
|
+
export { FormErrorSummary } from "./forms";
|
|
8
9
|
// --- Column utilities ---------------------------------------------------------
|
|
9
10
|
export { buildColumns, createColumnBuilder, renderBoolean, renderCurrency, renderCurrencyCompact, renderCount, renderNullable, renderRating, getStatusTone, getResourceIcon, } from "./columns";
|
|
10
11
|
export { Section, sectionBackgroundStyle, Article, Main, Aside, Nav, BlockHeader, BlockFooter, Ul, Ol, Li, Table, Thead, Tbody, Tr, Th, Td, Code, Pre, Blockquote, Figure, Figcaption, Dl, Dt, Dd, } from "./components/Semantic";
|
|
@@ -120,3 +121,7 @@ export { Kbd } from "./components/Kbd";
|
|
|
120
121
|
export { StickyToolbar } from "./components/StickyToolbar";
|
|
121
122
|
export { Show, Hide } from "./components/Responsive";
|
|
122
123
|
export { CollapsibleSection } from "./components/CollapsibleSection";
|
|
124
|
+
// RecordDetailModal — shared "open one listing row and read it" surface.
|
|
125
|
+
// Backs the 2026-08-21 dead-end-listing sweep; see the component's own
|
|
126
|
+
// header comment and scripts/audit-listing-detail-affordance.mjs.
|
|
127
|
+
export { RecordDetailModal } from "./components/RecordDetailModal";
|
|
@@ -10,9 +10,11 @@ export declare const GCS_HOST = "storage.googleapis.com";
|
|
|
10
10
|
/**
|
|
11
11
|
* Normalise any image URL so it goes through the watermark proxy:
|
|
12
12
|
* - /media/<path> → return as-is (already proxied via Firebase Storage)
|
|
13
|
+
* - blob: / data: URI → return as-is (local-only, never fetchable server-side)
|
|
13
14
|
* - Firebase Storage URL → extract /o/ path → /media/<path>
|
|
15
|
+
* - GCS (storage.googleapis.com) URL → drop the bucket segment → /media/<path>
|
|
14
16
|
* - Any other absolute URL → /api/media/ext?url=<encoded> (ext watermark proxy)
|
|
15
|
-
* - Relative
|
|
17
|
+
* - Relative URI → return as-is
|
|
16
18
|
* - Falsy → undefined
|
|
17
19
|
*/
|
|
18
20
|
export declare function resolveMediaUrl(url: string | null | undefined): string | undefined;
|
package/dist/utils/media-url.js
CHANGED
|
@@ -13,9 +13,11 @@ const PROXY_PREFIX = "/media/";
|
|
|
13
13
|
/**
|
|
14
14
|
* Normalise any image URL so it goes through the watermark proxy:
|
|
15
15
|
* - /media/<path> → return as-is (already proxied via Firebase Storage)
|
|
16
|
+
* - blob: / data: URI → return as-is (local-only, never fetchable server-side)
|
|
16
17
|
* - Firebase Storage URL → extract /o/ path → /media/<path>
|
|
18
|
+
* - GCS (storage.googleapis.com) URL → drop the bucket segment → /media/<path>
|
|
17
19
|
* - Any other absolute URL → /api/media/ext?url=<encoded> (ext watermark proxy)
|
|
18
|
-
* - Relative
|
|
20
|
+
* - Relative URI → return as-is
|
|
19
21
|
* - Falsy → undefined
|
|
20
22
|
*/
|
|
21
23
|
export function resolveMediaUrl(url) {
|
|
@@ -23,6 +25,13 @@ export function resolveMediaUrl(url) {
|
|
|
23
25
|
return undefined;
|
|
24
26
|
if (url.startsWith(PROXY_PREFIX))
|
|
25
27
|
return url;
|
|
28
|
+
// A blob:/data: URI is only ever valid in the tab that created it (e.g. a
|
|
29
|
+
// freshly-selected file preview via URL.createObjectURL, or a FileReader
|
|
30
|
+
// data URL fed to a crop modal). Routing it through the external-URL
|
|
31
|
+
// watermark proxy would try to fetch it server-side and 400 — it must be
|
|
32
|
+
// rendered directly instead.
|
|
33
|
+
if (url.startsWith("blob:") || url.startsWith("data:"))
|
|
34
|
+
return url;
|
|
26
35
|
try {
|
|
27
36
|
const parsed = new URL(url);
|
|
28
37
|
if (parsed.hostname.endsWith(FIREBASE_STORAGE_HOST)) {
|
|
@@ -30,6 +39,16 @@ export function resolveMediaUrl(url) {
|
|
|
30
39
|
if (m)
|
|
31
40
|
return `${PROXY_PREFIX}${decodeURIComponent(m[1])}`;
|
|
32
41
|
}
|
|
42
|
+
if (parsed.hostname === GCS_HOST) {
|
|
43
|
+
// Path shape: /<bucket>/<object-path...> — drop the bucket segment,
|
|
44
|
+
// proxy the rest like a Firebase Storage URL (consistent watermarking
|
|
45
|
+
// + caching instead of falling through to the external-URL proxy).
|
|
46
|
+
const parts = parsed.pathname.replace(/^\//, "").split("/");
|
|
47
|
+
if (parts.length > 1) {
|
|
48
|
+
const objectPath = parts.slice(1).join("/");
|
|
49
|
+
return `${PROXY_PREFIX}${decodeURIComponent(objectPath)}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
33
52
|
return MEDIA_ENDPOINTS.EXT_URL(url);
|
|
34
53
|
}
|
|
35
54
|
catch (_err) {
|
|
@@ -114,17 +114,17 @@ export declare const changePasswordSchema: z.ZodEffects<z.ZodObject<{
|
|
|
114
114
|
currentPassword: z.ZodString;
|
|
115
115
|
newPassword: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
116
116
|
}, "strip", z.ZodTypeAny, {
|
|
117
|
-
currentPassword: string;
|
|
118
117
|
newPassword: string;
|
|
119
|
-
}, {
|
|
120
118
|
currentPassword: string;
|
|
119
|
+
}, {
|
|
121
120
|
newPassword: string;
|
|
122
|
-
}>, {
|
|
123
121
|
currentPassword: string;
|
|
122
|
+
}>, {
|
|
124
123
|
newPassword: string;
|
|
125
|
-
}, {
|
|
126
124
|
currentPassword: string;
|
|
125
|
+
}, {
|
|
127
126
|
newPassword: string;
|
|
127
|
+
currentPassword: string;
|
|
128
128
|
}>;
|
|
129
129
|
export declare const cropDataSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
130
130
|
sourceUrl: z.ZodEffects<z.ZodString, string, string>;
|