@mohasinac/appkit 2.7.45 → 2.7.47

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.
Files changed (88) hide show
  1. package/dist/client.d.ts +2 -2
  2. package/dist/client.js +1 -1
  3. package/dist/constants/field-names.d.ts +1 -0
  4. package/dist/constants/field-names.js +1 -0
  5. package/dist/core/unit-of-work.js +3 -4
  6. package/dist/features/account/components/AddressForm.js +23 -1
  7. package/dist/features/admin/components/AdminBlogEditorView.js +18 -11
  8. package/dist/features/admin/components/AdminBrandEditorView.js +18 -11
  9. package/dist/features/admin/components/AdminCarouselEditorView.js +18 -8
  10. package/dist/features/admin/components/AdminCategoryEditorView.js +18 -11
  11. package/dist/features/admin/components/AdminCouponEditorView.js +18 -11
  12. package/dist/features/admin/components/AdminFaqEditorView.js +18 -11
  13. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +18 -11
  14. package/dist/features/auth/actions/profile-actions.js +4 -1
  15. package/dist/features/auth/repository/user.repository.d.ts +1 -0
  16. package/dist/features/auth/repository/user.repository.js +3 -0
  17. package/dist/features/blog/api/route.js +3 -0
  18. package/dist/features/blog/components/BlogIndexListing.js +2 -0
  19. package/dist/features/blog/components/BlogPostView.js +2 -1
  20. package/dist/features/categories/api/route.js +1 -1
  21. package/dist/features/categories/components/BundleDetailView.js +5 -3
  22. package/dist/features/categories/components/CategoryBundlesListing.js +77 -41
  23. package/dist/features/categories/components/CategoryDetailPageView.js +12 -9
  24. package/dist/features/categories/components/CategoryProductsListing.js +3 -5
  25. package/dist/features/classified/components/ClassifiedIndexListing.js +3 -4
  26. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +3 -4
  27. package/dist/features/events/actions/event-actions.d.ts +2 -3
  28. package/dist/features/events/actions/event-actions.js +3 -5
  29. package/dist/features/events/api/route.js +7 -7
  30. package/dist/features/events/components/AdminEventEditorView.js +80 -6
  31. package/dist/features/events/components/AdminEventEntriesView.js +33 -4
  32. package/dist/features/events/components/EventFilters.d.ts +3 -0
  33. package/dist/features/events/components/EventFilters.js +1 -0
  34. package/dist/features/events/components/EventLeaderboard.js +1 -1
  35. package/dist/features/events/components/EventsIndexListing.js +10 -7
  36. package/dist/features/events/repository/event-entry.repository.d.ts +2 -2
  37. package/dist/features/events/repository/event-entry.repository.js +32 -7
  38. package/dist/features/events/types/index.d.ts +3 -1
  39. package/dist/features/homepage/components/BrandsSection.js +2 -2
  40. package/dist/features/homepage/components/CustomCardsSection.js +1 -1
  41. package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
  42. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  43. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  44. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  45. package/dist/features/live/components/LiveItemsIndexListing.js +3 -4
  46. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -4
  47. package/dist/features/products/components/AuctionsIndexListing.js +3 -4
  48. package/dist/features/products/components/PrizeDrawsIndexListing.js +3 -8
  49. package/dist/features/products/components/ProductDetailPageView.js +17 -2
  50. package/dist/features/products/components/ProductsIndexListing.js +10 -6
  51. package/dist/features/products/schemas/firestore.d.ts +2 -0
  52. package/dist/features/reviews/actions/review-actions.js +2 -0
  53. package/dist/features/reviews/components/ReviewDetailShell.js +3 -2
  54. package/dist/features/reviews/components/ReviewFilters.js +1 -1
  55. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -2
  56. package/dist/features/reviews/hooks/useReviews.js +2 -0
  57. package/dist/features/reviews/schemas/firestore.d.ts +4 -0
  58. package/dist/features/reviews/types/index.d.ts +3 -0
  59. package/dist/features/scams/components/ScamRegistryView.js +41 -8
  60. package/dist/features/seller/components/SellerProductShell.js +27 -3
  61. package/dist/features/shell/StepForm.d.ts +6 -2
  62. package/dist/features/shell/StepForm.js +13 -10
  63. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  64. package/dist/features/stores/components/StoreAuctionsListing.js +3 -4
  65. package/dist/features/stores/components/StoreDetailLayoutView.js +13 -5
  66. package/dist/features/stores/components/StorePreOrdersListing.js +3 -4
  67. package/dist/features/stores/components/StoreProductsListing.js +3 -4
  68. package/dist/index.d.ts +1 -0
  69. package/dist/providers/db-firebase/helpers.d.ts +4 -6
  70. package/dist/providers/db-firebase/helpers.js +18 -5
  71. package/dist/providers/db-firebase/sieve.js +26 -1
  72. package/dist/react/hooks/usePendingFilters.d.ts +2 -0
  73. package/dist/react/hooks/usePendingFilters.js +9 -0
  74. package/dist/react/hooks/usePendingTable.d.ts +5 -0
  75. package/dist/react/hooks/usePendingTable.js +1 -0
  76. package/dist/seed/homepage-sections-seed-data.js +2 -2
  77. package/dist/seed/products-standard-seed-data.js +1 -0
  78. package/dist/tailwind-utilities.css +1 -1
  79. package/dist/ui/components/Drawer.style.css +13 -0
  80. package/dist/ui/components/FilterDrawer.d.ts +4 -2
  81. package/dist/ui/components/FilterDrawer.js +2 -2
  82. package/dist/ui/components/Toast.d.ts +4 -1
  83. package/dist/ui/components/Toast.js +3 -3
  84. package/dist/ui/forms/FormShell.d.ts +19 -1
  85. package/dist/ui/forms/FormShell.js +92 -0
  86. package/dist/ui/forms/index.d.ts +2 -2
  87. package/dist/ui/forms/index.js +1 -1
  88. package/package.json +2 -1
@@ -68,6 +68,19 @@
68
68
  .appkit-drawer__panel--md { width: 24rem; }
69
69
  }
70
70
 
71
+ /* FilterDrawer responsive size: full-width (lg) on mobile, fixed md-width on desktop */
72
+ .appkit-drawer__panel--filter {
73
+ width: 100%;
74
+ max-width: 30rem;
75
+ }
76
+
77
+ @media (min-width: 1024px) {
78
+ .appkit-drawer__panel--filter {
79
+ width: 24rem;
80
+ max-width: none;
81
+ }
82
+ }
83
+
71
84
  .appkit-drawer__drag-wrap {
72
85
  display: flex;
73
86
  justify-content: center;
@@ -12,7 +12,9 @@ export interface FilterDrawerProps {
12
12
  triggerLabel?: string;
13
13
  triggerClassName?: string;
14
14
  hideTrigger?: boolean;
15
- /** Which side the drawer slides in from. Default: "bottom" (slides up, mobile-first). */
15
+ /** Which side the drawer slides in from. Default: "right". */
16
16
  side?: "left" | "right" | "bottom";
17
+ /** Width for left/right drawers. Default: "md" (responsive: full-width on mobile, 24rem on desktop). */
18
+ size?: "sm" | "md" | "lg" | "full";
17
19
  }
18
- export declare function FilterDrawer({ children, title, open, onOpen, onClose, onApply, onReset, activeCount, pendingCount, triggerLabel, triggerClassName, hideTrigger, side, }: FilterDrawerProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function FilterDrawer({ children, title, open, onOpen, onClose, onApply, onReset, activeCount, pendingCount, triggerLabel, triggerClassName, hideTrigger, side, size, }: FilterDrawerProps): import("react/jsx-runtime").JSX.Element;
@@ -4,7 +4,7 @@ import React from "react";
4
4
  import { Button } from "./Button";
5
5
  import { Drawer } from "./Drawer";
6
6
  import { Span } from "./Typography";
7
- export function FilterDrawer({ children, title = "Filters", open, onOpen, onClose, onApply, onReset, activeCount = 0, pendingCount, triggerLabel = "Filters", triggerClassName = "", hideTrigger = false, side = "bottom", }) {
7
+ export function FilterDrawer({ children, title = "Filters", open, onOpen, onClose, onApply, onReset, activeCount = 0, pendingCount, triggerLabel = "Filters", triggerClassName = "", hideTrigger = false, side = "right", size = "md", }) {
8
8
  const [internalOpen, setInternalOpen] = React.useState(false);
9
9
  const isControlled = open !== undefined;
10
10
  const isOpen = isControlled ? open : internalOpen;
@@ -33,7 +33,7 @@ export function FilterDrawer({ children, title = "Filters", open, onOpen, onClos
33
33
  handleOpen();
34
34
  }, className: ["appkit-filter-drawer__trigger", triggerClassName]
35
35
  .filter(Boolean)
36
- .join(" "), "aria-label": triggerLabel, children: [_jsx(Span, { children: triggerLabel }), activeCount > 0 ? (_jsx(Span, { className: "appkit-filter-drawer__badge", children: activeCount })) : null] })) : null, _jsx(Drawer, { isOpen: isOpen, onClose: handleClose, title: title, side: side, footer: _jsxs("div", { className: "appkit-filter-drawer__footer", "data-section": "filterdrawer-div-494", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: onReset, children: "Reset all" }), _jsx(Button, { type: "button", variant: "primary", onClick: handleApply, children: pendingCount != null && pendingCount > 0
36
+ .join(" "), "aria-label": triggerLabel, children: [_jsx(Span, { children: triggerLabel }), activeCount > 0 ? (_jsx(Span, { className: "appkit-filter-drawer__badge", children: activeCount })) : null] })) : null, _jsx(Drawer, { isOpen: isOpen, onClose: handleClose, title: title, side: side, size: size, className: "appkit-drawer__panel--filter", footer: _jsxs("div", { className: "appkit-filter-drawer__footer", "data-section": "filterdrawer-div-494", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: onReset, children: "Reset all" }), _jsx(Button, { type: "button", variant: "primary", onClick: handleApply, children: pendingCount != null && pendingCount > 0
37
37
  ? `Apply (${pendingCount})`
38
38
  : "Apply" })] }), children: _jsx("div", { className: "appkit-filter-drawer__body", "data-section": "filterdrawer-div-495", children: children }) })] }));
39
39
  }
@@ -2,7 +2,10 @@ import React from "react";
2
2
  export type ToastVariant = "success" | "error" | "warning" | "info";
3
3
  export type ToastPosition = "top-right" | "top-left" | "bottom-right" | "bottom-left" | "top-center" | "bottom-center";
4
4
  interface ToastContextValue {
5
- showToast: (message: string, variant?: ToastVariant, duration?: number) => void;
5
+ showToast: (message: string, variant?: ToastVariant, duration?: number, action?: {
6
+ label: string;
7
+ onClick: () => void;
8
+ }) => void;
6
9
  hideToast: (id: string) => void;
7
10
  }
8
11
  export declare function useToast(): ToastContextValue;
@@ -38,9 +38,9 @@ export function ToastProvider({ children, position = "top-right", }) {
38
38
  const hideToast = React.useCallback((id) => {
39
39
  setToasts((current) => current.filter((toast) => toast.id !== id));
40
40
  }, []);
41
- const showToast = React.useCallback((message, variant = "info", duration = 5000) => {
41
+ const showToast = React.useCallback((message, variant = "info", duration = 5000, action) => {
42
42
  const id = buildToastId();
43
- setToasts((current) => [...current, { id, message, variant, duration }]);
43
+ setToasts((current) => [...current, { id, message, variant, duration, action }]);
44
44
  if (duration > 0) {
45
45
  window.setTimeout(() => hideToast(id), duration);
46
46
  }
@@ -56,5 +56,5 @@ function ToastRow({ toast, onClose, }) {
56
56
  warning: _jsx("span", { "aria-hidden": "true", children: "!" }),
57
57
  info: _jsx("span", { "aria-hidden": "true", children: "i" }),
58
58
  };
59
- return (_jsxs("div", { role: "alert", "data-testid": "toast", className: [UI_TOAST.row, UI_TOAST.variants[toast.variant]].join(" "), "data-section": "toast-div-624", children: [_jsx("div", { className: UI_TOAST.icon, "data-section": "toast-div-625", children: iconMap[toast.variant] }), _jsx(Text, { as: "div", size: "sm", weight: "medium", className: "flex-1 pr-1", children: toast.message }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onClose(toast.id), className: "min-h-0 shrink-0 px-2 py-1", "aria-label": "Close notification", children: "\u00D7" })] }));
59
+ return (_jsxs("div", { role: "alert", "data-testid": "toast", className: [UI_TOAST.row, UI_TOAST.variants[toast.variant]].join(" "), "data-section": "toast-div-624", children: [_jsx("div", { className: UI_TOAST.icon, "data-section": "toast-div-625", children: iconMap[toast.variant] }), _jsx(Text, { as: "div", size: "sm", weight: "medium", className: "flex-1 pr-1", children: toast.message }), toast.action && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => { toast.action.onClick(); onClose(toast.id); }, className: "min-h-0 shrink-0 px-2 py-1 font-semibold underline", children: toast.action.label })), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onClose(toast.id), className: "min-h-0 shrink-0 px-2 py-1", "aria-label": "Close notification", children: "\u00D7" })] }));
60
60
  }
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { type ReactNode } from "react";
2
2
  export interface FormShellStep {
3
3
  id: string;
4
4
  label: string;
@@ -45,4 +45,22 @@ export interface FormShellProps {
45
45
  }
46
46
  export declare const FormShellContext: React.Context<FormShellContextValue | null>;
47
47
  export declare function useFormShell(): FormShellContextValue;
48
+ export interface UseFormShellStateResult {
49
+ shellCtx: FormShellContextValue;
50
+ setFieldError: (name: string, error: string | null) => void;
51
+ clearErrors: () => void;
52
+ hasErrors: boolean;
53
+ }
54
+ export declare function useFormShellState(): UseFormShellStateResult;
55
+ export interface FormShellProviderProps {
56
+ children: ReactNode;
57
+ /** Called on debounced auto-save when isDirty changes */
58
+ onSaveDraft?: (values: Record<string, unknown>) => Promise<void>;
59
+ /** Whether any values differ from the persisted state — triggers auto-save */
60
+ isDirty?: boolean;
61
+ autoSaveDelayMs?: number;
62
+ /** Current form values — passed into onSaveDraft on auto-save */
63
+ values?: Record<string, unknown>;
64
+ }
65
+ export declare function FormShellProvider({ children, onSaveDraft, isDirty, autoSaveDelayMs, values, }: FormShellProviderProps): import("react/jsx-runtime").JSX.Element;
48
66
  export declare function FormShell({ steps, onSaveDraft, onPublish, values, isDirty, autoSaveDelayMs, publishLabel, saveDraftLabel, title, className, mobileAccordion, }: FormShellProps): import("react/jsx-runtime").JSX.Element;
@@ -11,6 +11,39 @@ export function useFormShell() {
11
11
  throw new Error("useFormShell must be used inside <FormShell>");
12
12
  return ctx;
13
13
  }
14
+ export function useFormShellState() {
15
+ const [errors, setErrors] = useState({});
16
+ const setFieldError = useCallback((name, error) => {
17
+ setErrors((prev) => {
18
+ if (!error) {
19
+ const n = { ...prev };
20
+ delete n[name];
21
+ return n;
22
+ }
23
+ if (prev[name] === error)
24
+ return prev;
25
+ return { ...prev, [name]: error };
26
+ });
27
+ }, []);
28
+ const clearErrors = useCallback(() => setErrors({}), []);
29
+ const shellCtx = useMemo(() => ({
30
+ errors,
31
+ touched: Object.fromEntries(Object.keys(errors).map((k) => [k, true])),
32
+ setFieldError,
33
+ setFieldTouched: () => { },
34
+ clearFieldError: (name) => setFieldError(name, null),
35
+ steps: [],
36
+ currentStep: 0,
37
+ goToStep: () => { },
38
+ nextStep: () => { },
39
+ prevStep: () => { },
40
+ isPublishReady: Object.keys(errors).length === 0,
41
+ isDirty: false,
42
+ isSubmitting: false,
43
+ stepErrorCounts: [],
44
+ }), [errors, setFieldError]);
45
+ return { shellCtx, setFieldError, clearErrors, hasErrors: Object.keys(errors).length > 0 };
46
+ }
14
47
  function StepIndicator({ steps, currentStep, stepErrorCounts, onGoTo, }) {
15
48
  return (_jsx("nav", { className: "appkit-formshell__step-indicator", "aria-label": "Form steps", children: steps.map((step, i) => {
16
49
  const isActive = i === currentStep;
@@ -30,6 +63,65 @@ function ErrorSummary({ errors, visible }) {
30
63
  return null;
31
64
  return (_jsxs("div", { className: "appkit-formshell__error-summary", role: "alert", "aria-live": "assertive", children: [_jsxs(Text, { size: "sm", variant: "error", className: "appkit-formshell__error-summary__title", children: ["Please fix ", entries.length, " error", entries.length !== 1 ? "s" : "", " before publishing."] }), _jsx("ul", { className: "appkit-formshell__error-summary__list", children: entries.map(([field, msg]) => (_jsx("li", { children: _jsxs(Text, { size: "sm", variant: "error", children: [_jsx("strong", { children: field }), ": ", msg] }) }, field))) })] }));
32
65
  }
66
+ export function FormShellProvider({ children, onSaveDraft, isDirty = false, autoSaveDelayMs = 2000, values = {}, }) {
67
+ const [errors, setErrors] = useState({});
68
+ const [touched, setTouched] = useState({});
69
+ const autoSaveTimer = useRef(null);
70
+ const valuesRef = useRef(values);
71
+ valuesRef.current = values;
72
+ useEffect(() => {
73
+ if (!onSaveDraft || !isDirty)
74
+ return;
75
+ if (autoSaveTimer.current)
76
+ clearTimeout(autoSaveTimer.current);
77
+ autoSaveTimer.current = setTimeout(() => {
78
+ onSaveDraft(valuesRef.current).catch(() => { });
79
+ }, autoSaveDelayMs);
80
+ return () => { if (autoSaveTimer.current)
81
+ clearTimeout(autoSaveTimer.current); };
82
+ }, [values, isDirty, onSaveDraft, autoSaveDelayMs]);
83
+ const setFieldError = useCallback((name, error) => {
84
+ setErrors((prev) => {
85
+ if (!error) {
86
+ const next = { ...prev };
87
+ delete next[name];
88
+ return next;
89
+ }
90
+ if (prev[name] === error)
91
+ return prev;
92
+ return { ...prev, [name]: error };
93
+ });
94
+ }, []);
95
+ const clearFieldError = useCallback((name) => {
96
+ setErrors((prev) => {
97
+ if (!prev[name])
98
+ return prev;
99
+ const next = { ...prev };
100
+ delete next[name];
101
+ return next;
102
+ });
103
+ }, []);
104
+ const setFieldTouched = useCallback((name) => {
105
+ setTouched((prev) => (prev[name] ? prev : { ...prev, [name]: true }));
106
+ }, []);
107
+ const ctx = useMemo(() => ({
108
+ errors,
109
+ touched,
110
+ setFieldError,
111
+ setFieldTouched,
112
+ clearFieldError,
113
+ steps: [],
114
+ currentStep: 0,
115
+ goToStep: () => { },
116
+ nextStep: () => { },
117
+ prevStep: () => { },
118
+ isPublishReady: true,
119
+ isDirty,
120
+ isSubmitting: false,
121
+ stepErrorCounts: [],
122
+ }), [errors, touched, setFieldError, setFieldTouched, clearFieldError, isDirty]);
123
+ return _jsx(FormShellContext.Provider, { value: ctx, children: children });
124
+ }
33
125
  // ─── FormShell ────────────────────────────────────────────────────────────────
34
126
  export function FormShell({ steps, onSaveDraft, onPublish, values, isDirty = false, autoSaveDelayMs = 2000, publishLabel = "Publish", saveDraftLabel = "Save Draft", title, className = "", mobileAccordion = true, }) {
35
127
  const [errors, setErrors] = useState({});
@@ -1,5 +1,5 @@
1
- export type { FormShellProps, FormShellStep, FormShellContextValue, } from "./FormShell";
2
- export { FormShell, useFormShell } from "./FormShell";
1
+ export type { FormShellProps, FormShellProviderProps, FormShellStep, FormShellContextValue, UseFormShellStateResult, } from "./FormShell";
2
+ export { FormShell, FormShellProvider, FormShellContext, useFormShell, useFormShellState } from "./FormShell";
3
3
  export type { FieldInputProps } from "./FieldInput";
4
4
  export { FieldInput } from "./FieldInput";
5
5
  export type { FieldSelectProps } from "./FieldSelect";
@@ -1,4 +1,4 @@
1
- export { FormShell, useFormShell } from "./FormShell";
1
+ export { FormShell, FormShellProvider, FormShellContext, useFormShell, useFormShellState } from "./FormShell";
2
2
  export { FieldInput } from "./FieldInput";
3
3
  export { FieldSelect } from "./FieldSelect";
4
4
  export { FieldCheckbox } from "./FieldCheckbox";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "2.7.45",
3
+ "version": "2.7.47",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -143,6 +143,7 @@
143
143
  },
144
144
  "scripts": {
145
145
  "build": "node -e \"const fs=require('fs');try{fs.rmSync('tsconfig.build.tsbuildinfo')}catch(e){}\" && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs && tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --minify && node scripts/verify-css-build.mjs",
146
+ "prepublishOnly": "npm run build",
146
147
  "watch": "concurrently --kill-others --restart-tries 0 --names \"ts,css\" \"tsc -p tsconfig.build.json --watch\" \"npm run watch:css\"",
147
148
  "watch:css": "tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --watch",
148
149
  "audit": "node scripts/audit-violations.mjs",