@mohasinac/appkit 3.5.2 → 3.5.4

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 (70) hide show
  1. package/dist/features/homepage/components/CustomCardsSection.js +1 -1
  2. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  3. package/dist/features/seller/components/CategoryInlineSelect.d.ts +1 -1
  4. package/dist/features/seller/components/CategoryInlineSelect.js +1 -1
  5. package/dist/react/hooks/useBulkSelection.js +12 -3
  6. package/dist/react/hooks/useGesture.js +48 -4
  7. package/dist/react/hooks/useLongPress.d.ts +1 -0
  8. package/dist/react/hooks/useLongPress.js +5 -0
  9. package/dist/react/hooks/usePullToRefresh.js +12 -1
  10. package/dist/react/hooks/useRealtimeEvent.js +18 -1
  11. package/dist/react/hooks/useSwipe.js +14 -0
  12. package/dist/seed/addresses-seed-data.js +8 -99
  13. package/dist/seed/categories-seed-data.js +103 -2082
  14. package/dist/seed/homepage-sections-seed-data.js +9 -9
  15. package/dist/seed/products-art-seed-data.d.ts +1 -10
  16. package/dist/seed/products-art-seed-data.js +13 -122
  17. package/dist/seed/products-auctions-seed-data.js +39 -747
  18. package/dist/seed/products-classifieds-seed-data.d.ts +1 -9
  19. package/dist/seed/products-classifieds-seed-data.js +13 -195
  20. package/dist/seed/products-digital-codes-seed-data.d.ts +1 -9
  21. package/dist/seed/products-digital-codes-seed-data.js +13 -202
  22. package/dist/seed/products-live-items-seed-data.d.ts +1 -12
  23. package/dist/seed/products-live-items-seed-data.js +13 -188
  24. package/dist/seed/products-preorders-seed-data.js +18 -149
  25. package/dist/seed/products-prize-draws-seed-data.d.ts +1 -16
  26. package/dist/seed/products-prize-draws-seed-data.js +13 -385
  27. package/dist/seed/products-standard-seed-data.js +152 -1331
  28. package/dist/seed/products-stickers-seed-data.d.ts +1 -10
  29. package/dist/seed/products-stickers-seed-data.js +13 -122
  30. package/dist/seed/site-settings-seed-data.js +106 -108
  31. package/dist/seed/store-addresses-seed-data.js +14 -46
  32. package/dist/seed/stores-seed-data.js +13 -205
  33. package/dist/styles.css +19 -18
  34. package/dist/tailwind-utilities.css +1 -1
  35. package/dist/ui/components/BaseListingCard.d.ts +2 -1
  36. package/dist/ui/components/BaseListingCard.js +4 -5
  37. package/dist/ui/components/BulkActionBar.style.css +4 -4
  38. package/dist/ui/components/Button.js +52 -22
  39. package/dist/ui/components/Button.style.css +14 -13
  40. package/dist/ui/components/DateInput.js +1 -0
  41. package/dist/ui/components/DynamicBgDiv.js +8 -6
  42. package/dist/ui/components/FormField.js +1 -1
  43. package/dist/ui/components/HorizontalScroller.js +35 -16
  44. package/dist/ui/components/Iframe.d.ts +8 -1
  45. package/dist/ui/components/Layout.d.ts +4 -4
  46. package/dist/ui/components/Layout.js +8 -8
  47. package/dist/ui/components/ListingToolbar.js +4 -4
  48. package/dist/ui/components/Motion.js +4 -1
  49. package/dist/ui/components/OtpInput.js +1 -1
  50. package/dist/ui/components/PaginatedSelect.js +29 -3
  51. package/dist/ui/components/Pagination.js +10 -5
  52. package/dist/ui/components/RichTextEditor.js +64 -3
  53. package/dist/ui/components/Select.js +1 -0
  54. package/dist/ui/components/Semantic.d.ts +10 -10
  55. package/dist/ui/components/Semantic.js +25 -25
  56. package/dist/ui/components/SideDrawer.js +13 -11
  57. package/dist/ui/components/SideModal.js +9 -7
  58. package/dist/ui/components/SlottedListingView.d.ts +13 -2
  59. package/dist/ui/components/StickyToolbar.js +9 -5
  60. package/dist/ui/components/TagInput.js +15 -2
  61. package/dist/ui/components/Textarea.js +1 -0
  62. package/dist/ui/components/Toggle.js +8 -1
  63. package/dist/ui/components/UnsavedChangesModal.js +12 -1
  64. package/dist/ui/forms/ColorPickerField.js +7 -0
  65. package/dist/ui/forms/FieldCheckbox.js +1 -1
  66. package/dist/utils/id-generators.d.ts +7 -4
  67. package/dist/utils/id-generators.js +53 -31
  68. package/dist/utils/number.formatter.js +19 -6
  69. package/dist/utils/string.formatter.js +4 -1
  70. package/package.json +1 -1
@@ -64,7 +64,7 @@ export function CustomCardsSection(config) {
64
64
  }
65
65
  // Row layout: horizontal scroller
66
66
  if (layout === "row") {
67
- return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: CLS_CONTAINER, children: [_jsx(SectionHeader, { title: title }), _jsx(HorizontalScroller, { gap: 16, showArrows: true, snapToItems: true, loop: true, children: cards.map((card) => (_jsx(Div, { className: "w-72 flex-shrink-0", children: _jsx(CardItem, { card: card }) }, card.id))) })] }) }));
67
+ return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: CLS_CONTAINER, children: [_jsx(SectionHeader, { title: title }), _jsx(HorizontalScroller, { gap: 16, showArrows: true, snapToItems: true, loop: true, items: cards, keyExtractor: (card) => card.id, renderItem: (card) => (_jsx(Div, { className: "w-72 flex-shrink-0", children: _jsx(CardItem, { card: card }) })) })] }) }));
68
68
  }
69
69
  // Masonry layout: CSS columns
70
70
  if (layout === "masonry") {
@@ -41,5 +41,5 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
41
41
  headingClass,
42
42
  ]
43
43
  .filter(Boolean)
44
- .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: rows === 1, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] border transition-colors ${useLightText ? "border-white/40 text-white hover:bg-[rgba(255,255,255,0.1)]" : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]"}`, size: "sm", weight: "medium", children: viewMoreLabel }) }))] })] }));
44
+ .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: true, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] border transition-colors ${useLightText ? "border-white/40 text-white hover:bg-[rgba(255,255,255,0.1)]" : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]"}`, size: "sm", weight: "medium", children: viewMoreLabel }) }))] })] }));
45
45
  }
@@ -4,7 +4,7 @@ export interface CategoryInlineSelectProps {
4
4
  onChange: (v: string) => void;
5
5
  placeholder?: string;
6
6
  disabled?: boolean;
7
- /** Show the "+ Create new category" option. Defaults to false (search-only for sellers). */
7
+ /** Show the "+ Create new category" option. Defaults to true sellers can create categories inline. */
8
8
  allowCreate?: boolean;
9
9
  }
10
10
  export declare function CategoryInlineSelect({ value, onChange, placeholder, disabled, allowCreate, }: CategoryInlineSelectProps): React.JSX.Element;
@@ -35,7 +35,7 @@ async function loadPublicCategoryOptions(query, _page) {
35
35
  const items = q ? all.filter((c) => c.label.toLowerCase().includes(q)) : all;
36
36
  return { items, hasMore: false, nextPage: 2 };
37
37
  }
38
- export function CategoryInlineSelect({ value, onChange, placeholder = "Search categories…", disabled, allowCreate = false, }) {
38
+ export function CategoryInlineSelect({ value, onChange, placeholder = "Search categories…", disabled, allowCreate = true, }) {
39
39
  const loadOptions = allowCreate ? loadAdminCategoryOptions : loadPublicCategoryOptions;
40
40
  return (_jsx(PaginatedSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "category", drawerTitle: "Create Category", renderCreateForm: allowCreate
41
41
  ? ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
@@ -19,7 +19,12 @@ export function useBulkSelection({ items, keyExtractor, maxSelection = BULK_MAX_
19
19
  }, [maxSelection]);
20
20
  const toggleAll = useCallback(() => {
21
21
  setSelectedSet((prev) => {
22
- const allSelected = prev.size === allIds.length && allIds.length > 0;
22
+ // Compare actual membership, not just set size `prev` can hold IDs
23
+ // from a page that's no longer rendered (e.g. after paginating), in
24
+ // which case the sizes coincidentally matching would wrongly report
25
+ // "all selected" and CLEAR the selection instead of selecting the
26
+ // currently-visible items.
27
+ const allSelected = allIds.length > 0 && allIds.every((id) => prev.has(id));
23
28
  if (allSelected)
24
29
  return new Set();
25
30
  return new Set(allIds.slice(0, maxSelection));
@@ -33,8 +38,12 @@ export function useBulkSelection({ items, keyExtractor, maxSelection = BULK_MAX_
33
38
  const selectedIds = useMemo(() => Array.from(selectedSet), [selectedSet]);
34
39
  const selectedCount = selectedSet.size;
35
40
  const isSelecting = selectedCount > 0;
36
- const isAllSelected = allIds.length > 0 && selectedCount === allIds.length;
37
- const isIndeterminate = selectedCount > 0 && selectedCount < allIds.length;
41
+ // Membership-based, not cardinality-based `selectedSet` can hold IDs
42
+ // from items that are no longer in `allIds` (e.g. the previous page),
43
+ // so comparing sizes alone can report "all selected" when none of the
44
+ // *currently rendered* rows are actually selected.
45
+ const isAllSelected = allIds.length > 0 && allIds.every((id) => selectedSet.has(id));
46
+ const isIndeterminate = !isAllSelected && allIds.some((id) => selectedSet.has(id));
38
47
  return {
39
48
  selectedIds,
40
49
  selectedIdSet: selectedSet,
@@ -28,6 +28,12 @@ export function useGesture(ref, options = {}) {
28
28
  const lastTapRef = useRef(0);
29
29
  const initialPinchDistanceRef = useRef(0);
30
30
  const initialRotationRef = useRef(0);
31
+ // Real devices lift two fingers in separate touchend events, so by the
32
+ // final touchend `e.changedTouches` only ever has one entry — tracking
33
+ // the last-known two-finger positions here lets us compute the true
34
+ // final pinch distance / rotation angle instead of pairing a touch with
35
+ // itself (which always yields 0).
36
+ const lastTwoTouchesRef = useRef(null);
31
37
  // Store callbacks in refs to avoid event listener churn
32
38
  const onTapRef = useRef(onTap);
33
39
  const onDoubleTapRef = useRef(onDoubleTap);
@@ -67,6 +73,14 @@ export function useGesture(ref, options = {}) {
67
73
  const dy = touch1.clientY - touch2.clientY;
68
74
  return Math.atan2(dy, dx) * (180 / Math.PI);
69
75
  };
76
+ const captureTwoTouches = (t1, t2) => {
77
+ lastTwoTouchesRef.current = {
78
+ x1: t1.clientX,
79
+ y1: t1.clientY,
80
+ x2: t2.clientX,
81
+ y2: t2.clientY,
82
+ };
83
+ };
70
84
  const handleTouchStart = (e) => {
71
85
  if (preventDefault)
72
86
  e.preventDefault();
@@ -79,6 +93,7 @@ export function useGesture(ref, options = {}) {
79
93
  if (e.touches.length === 2) {
80
94
  initialPinchDistanceRef.current = getDistance(e.touches[0], e.touches[1]);
81
95
  initialRotationRef.current = getAngle(e.touches[0], e.touches[1]);
96
+ captureTwoTouches(e.touches[0], e.touches[1]);
82
97
  }
83
98
  };
84
99
  const handleTouchMove = (e) => {
@@ -91,6 +106,7 @@ export function useGesture(ref, options = {}) {
91
106
  onRotatingRef.current)) {
92
107
  if (preventDefault)
93
108
  e.preventDefault();
109
+ captureTwoTouches(e.touches[0], e.touches[1]);
94
110
  const currentDistance = getDistance(e.touches[0], e.touches[1]);
95
111
  const currentAngle = getAngle(e.touches[0], e.touches[1]);
96
112
  if (initialPinchDistanceRef.current > 0) {
@@ -122,16 +138,40 @@ export function useGesture(ref, options = {}) {
122
138
  }
123
139
  }
124
140
  if (e.touches.length === 0 && initialPinchDistanceRef.current > 0) {
125
- const currentDistance = getDistance(e.changedTouches[0], e.changedTouches[1] || e.changedTouches[0]);
126
- onPinchRef.current?.(currentDistance / initialPinchDistanceRef.current, currentDistance);
141
+ // `e.changedTouches` only has the LAST-lifted finger at this point
142
+ // (the other one already ended in a prior touchend event), so pair
143
+ // it against the last-known two-finger positions instead of
144
+ // self-pairing it (which always yields a distance of 0).
145
+ const last = lastTwoTouchesRef.current;
146
+ if (last) {
147
+ const currentDistance = getDistance({ clientX: last.x1, clientY: last.y1 }, { clientX: last.x2, clientY: last.y2 });
148
+ onPinchRef.current?.(currentDistance / initialPinchDistanceRef.current, currentDistance);
149
+ }
127
150
  initialPinchDistanceRef.current = 0;
128
151
  }
129
152
  if (e.touches.length === 0 && initialRotationRef.current !== 0) {
130
- const currentAngle = getAngle(e.changedTouches[0], e.changedTouches[1] || e.changedTouches[0]);
131
- onRotateRef.current?.(currentAngle - initialRotationRef.current);
153
+ const last = lastTwoTouchesRef.current;
154
+ if (last) {
155
+ const currentAngle = getAngle({ clientX: last.x1, clientY: last.y1 }, { clientX: last.x2, clientY: last.y2 });
156
+ onRotateRef.current?.(currentAngle - initialRotationRef.current);
157
+ }
132
158
  initialRotationRef.current = 0;
133
159
  }
160
+ if (e.touches.length === 0) {
161
+ lastTwoTouchesRef.current = null;
162
+ }
163
+ touchStartRef.current = null;
164
+ };
165
+ const handleTouchCancel = () => {
166
+ // OS/browser gesture takeover interrupted the touch sequence — reset
167
+ // all tracked state without firing any callback, matching the "quick
168
+ // taps don't fire" contract. Without this, stale pinch/rotation refs
169
+ // survive into the next unrelated tap and spuriously fire onPinch /
170
+ // onRotate for it.
134
171
  touchStartRef.current = null;
172
+ initialPinchDistanceRef.current = 0;
173
+ initialRotationRef.current = 0;
174
+ lastTwoTouchesRef.current = null;
135
175
  };
136
176
  const handleMouseDown = (e) => {
137
177
  if (preventDefault)
@@ -168,12 +208,16 @@ export function useGesture(ref, options = {}) {
168
208
  element.addEventListener("touchend", handleTouchEnd, {
169
209
  passive: !preventDefault,
170
210
  });
211
+ element.addEventListener("touchcancel", handleTouchCancel, {
212
+ passive: true,
213
+ });
171
214
  element.addEventListener("mousedown", handleMouseDown);
172
215
  element.addEventListener("mouseup", handleMouseUp);
173
216
  return () => {
174
217
  element.removeEventListener("touchstart", handleTouchStart);
175
218
  element.removeEventListener("touchmove", handleTouchMove);
176
219
  element.removeEventListener("touchend", handleTouchEnd);
220
+ element.removeEventListener("touchcancel", handleTouchCancel);
177
221
  element.removeEventListener("mousedown", handleMouseDown);
178
222
  element.removeEventListener("mouseup", handleMouseUp);
179
223
  };
@@ -10,4 +10,5 @@ export declare function useLongPress(callback: () => void, ms?: number): {
10
10
  onMouseLeave: () => void;
11
11
  onTouchStart: () => void;
12
12
  onTouchEnd: () => void;
13
+ onTouchCancel: () => void;
13
14
  };
@@ -32,5 +32,10 @@ export function useLongPress(callback, ms = 500) {
32
32
  onMouseLeave: cancel,
33
33
  onTouchStart: start,
34
34
  onTouchEnd: cancel,
35
+ // Browsers fire touchcancel (not touchend) when a touch is reinterpreted
36
+ // as a scroll — common for cards in a scrollable list. Without this the
37
+ // pending timer survives and can fire the callback after what was really
38
+ // just a scroll, contradicting the "quick taps do NOT fire" contract.
39
+ onTouchCancel: cancel,
35
40
  };
36
41
  }
@@ -65,6 +65,15 @@ export function usePullToRefresh(onRefresh, options = {}) {
65
65
  await onRefreshRef.current();
66
66
  }
67
67
  }, []);
68
+ const handleTouchCancel = useCallback(() => {
69
+ // Interrupted pull (OS/browser gesture takeover) — reset without
70
+ // triggering onRefresh. Without this, isPulling/progress stay frozen
71
+ // mid-animation since only touchend resets them.
72
+ setIsPulling(false);
73
+ setProgress(0);
74
+ startYRef.current = null;
75
+ thresholdReachedRef.current = false;
76
+ }, []);
68
77
  useEffect(() => {
69
78
  const el = containerRef.current;
70
79
  if (!el)
@@ -72,11 +81,13 @@ export function usePullToRefresh(onRefresh, options = {}) {
72
81
  el.addEventListener("touchstart", handleTouchStart, { passive: true });
73
82
  el.addEventListener("touchmove", handleTouchMove, { passive: true });
74
83
  el.addEventListener("touchend", handleTouchEnd);
84
+ el.addEventListener("touchcancel", handleTouchCancel, { passive: true });
75
85
  return () => {
76
86
  el.removeEventListener("touchstart", handleTouchStart);
77
87
  el.removeEventListener("touchmove", handleTouchMove);
78
88
  el.removeEventListener("touchend", handleTouchEnd);
89
+ el.removeEventListener("touchcancel", handleTouchCancel);
79
90
  };
80
- }, [handleTouchStart, handleTouchMove, handleTouchEnd]);
91
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, handleTouchCancel]);
81
92
  return { containerRef, isPulling, progress };
82
93
  }
@@ -32,6 +32,13 @@ const DEFAULT_MESSAGES = {
32
32
  };
33
33
  export function useRealtimeEvent(config) {
34
34
  const configRef = useRef(config);
35
+ // Re-sync every render — callers commonly pass a fresh config object
36
+ // (new extractData/messages/onLogError) each render, and subscribe()/
37
+ // cleanup() only ever read configRef.current, so without this the very
38
+ // first render's config would be captured forever.
39
+ useEffect(() => {
40
+ configRef.current = config;
41
+ });
35
42
  const [status, setStatus] = useState(RealtimeEventStatus.IDLE);
36
43
  const [error, setError] = useState(null);
37
44
  const [data, setData] = useState(null);
@@ -71,13 +78,23 @@ export function useRealtimeEvent(config) {
71
78
  catch (authErr) {
72
79
  void normalizeError(authErr);
73
80
  onLogError?.(`useRealtimeEvent[${type}]: custom token sign-in failed`, authErr);
81
+ // A newer subscribe() call for a different eventId may have already
82
+ // superseded this one (and possibly already fully subscribed) while
83
+ // this sign-in was still in flight. Do NOT call cleanup() here —
84
+ // cleanup() tears down whatever is *currently* registered in the
85
+ // shared refs, which by now may be the newer call's live
86
+ // subscription, not anything this stale call ever set up itself.
87
+ if (eventIdRef.current !== eventId) {
88
+ return;
89
+ }
74
90
  setError(msg.tokenFailure);
75
91
  setStatus(RealtimeEventStatus.FAILED);
76
92
  cleanup();
77
93
  return;
78
94
  }
79
95
  if (eventIdRef.current !== eventId) {
80
- cleanup();
96
+ // Same reasoning as the catch branch above — don't tear down a
97
+ // newer call's already-live subscription via the shared refs.
81
98
  return;
82
99
  }
83
100
  setStatus(RealtimeEventStatus.PENDING);
@@ -118,6 +118,16 @@ export function useSwipe(ref, options = {}) {
118
118
  isSwiping.current = false;
119
119
  onSwipeEndRef.current?.();
120
120
  };
121
+ const handleTouchCancel = () => {
122
+ // OS/browser gesture takeover interrupted the touch — without this,
123
+ // isSwiping/touchStartRef stay stuck and onSwipeEnd (which callers
124
+ // rely on to clear a "dragging" UI flag) never fires.
125
+ if (!isSwiping.current)
126
+ return;
127
+ touchStartRef.current = null;
128
+ isSwiping.current = false;
129
+ onSwipeEndRef.current?.();
130
+ };
121
131
  const handleMouseDown = (e) => {
122
132
  if (preventDefault)
123
133
  e.preventDefault();
@@ -149,6 +159,9 @@ export function useSwipe(ref, options = {}) {
149
159
  element.addEventListener("touchend", handleTouchEnd, {
150
160
  passive: !preventDefault,
151
161
  });
162
+ element.addEventListener("touchcancel", handleTouchCancel, {
163
+ passive: true,
164
+ });
152
165
  element.addEventListener("mousedown", handleMouseDown);
153
166
  document.addEventListener("mousemove", handleMouseMove);
154
167
  document.addEventListener("mouseup", handleMouseUp);
@@ -156,6 +169,7 @@ export function useSwipe(ref, options = {}) {
156
169
  element.removeEventListener("touchstart", handleTouchStart);
157
170
  element.removeEventListener("touchmove", handleTouchMove);
158
171
  element.removeEventListener("touchend", handleTouchEnd);
172
+ element.removeEventListener("touchcancel", handleTouchCancel);
159
173
  element.removeEventListener("mousedown", handleMouseDown);
160
174
  document.removeEventListener("mousemove", handleMouseMove);
161
175
  document.removeEventListener("mouseup", handleMouseUp);
@@ -1,9 +1,14 @@
1
1
  /*
2
- * WHY: Seeds delivery addresses for users and pickup locations for stores in the YGO marketplace.
3
- * WHAT: Exports 8 addresses (5 user + 3 store) representing Domino City + Tokyo locations for Yugi/Kaiba/Admin. Top-level collection (SB-UNI-A 2026-05-13) with ownerType discriminator. PII encrypted via HMAC blind indices (emailIndex, phoneIndex). Composite indexes (ownerType, ownerId, createdAt desc) + (ownerType, ownerId, isDefault).
2
+ * WHY: Seeds a minimal set of buyer delivery addresses for the demo catalog. Store pickup
3
+ * addresses live entirely in store-addresses-seed-data.ts (merged in at the API route
4
+ * level) — this file only carries ownerType:"user" entries now, to avoid seeding two
5
+ * overlapping sources of store address data.
6
+ * WHAT: Exports 3 user addresses. Top-level collection (SB-UNI-A) with ownerType discriminator.
7
+ * PII encrypted via HMAC blind indices (emailIndex, phoneIndex). Composite indexes
8
+ * (ownerType, ownerId, createdAt desc) + (ownerType, ownerId, isDefault).
4
9
  *
5
10
  * EXPORTS:
6
- * addressesSeedData — Array of 8 address documents with ownerType/ownerId discrimination
11
+ * addressesSeedData — Array of 3 user address documents
7
12
  *
8
13
  * @tag domain:addresses,shipping
9
14
  * @tag layer:seed
@@ -15,9 +20,6 @@
15
20
  const NOW = new Date();
16
21
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
17
22
  const _rawAddressesSeedData = [
18
- // ────────────────────────────────────────────────────────────────────────────
19
- // User Addresses — Buyer delivery addresses
20
- // ────────────────────────────────────────────────────────────────────────────
21
23
  // Yugi Muto — Home
22
24
  {
23
25
  id: "addr-yugi-home",
@@ -36,24 +38,6 @@ const _rawAddressesSeedData = [
36
38
  createdAt: daysAgo(180),
37
39
  updatedAt: daysAgo(1),
38
40
  },
39
- // Yugi Muto — Grandpa's Card Shop
40
- {
41
- id: "addr-yugi-shop",
42
- ownerType: "user",
43
- ownerId: "user-yugi-muto",
44
- label: "Grandpa's Card Shop",
45
- fullName: "Yugi Muto",
46
- phone: "+91-99999-10002",
47
- addressLine1: "45 Trading Card Plaza",
48
- addressLine2: "Domino City Center",
49
- city: "Domino City",
50
- state: "Tokyo",
51
- postalCode: "110-0002",
52
- country: "Japan",
53
- isDefault: false,
54
- createdAt: daysAgo(150),
55
- updatedAt: daysAgo(30),
56
- },
57
41
  // Seto Kaiba — Kaiba Mansion
58
42
  {
59
43
  id: "addr-kaiba-mansion",
@@ -72,24 +56,6 @@ const _rawAddressesSeedData = [
72
56
  createdAt: daysAgo(200),
73
57
  updatedAt: daysAgo(10),
74
58
  },
75
- // Seto Kaiba — Kaiba Land Office
76
- {
77
- id: "addr-kaiba-land",
78
- ownerType: "user",
79
- ownerId: "user-seto-kaiba",
80
- label: "Kaiba Land",
81
- fullName: "Seto Kaiba",
82
- phone: "+91-99999-20002",
83
- addressLine1: "500 Kaiba Land Boulevard",
84
- addressLine2: "Tokyo Tower District",
85
- city: "Tokyo",
86
- state: "Tokyo",
87
- postalCode: "105-0001",
88
- country: "Japan",
89
- isDefault: false,
90
- createdAt: daysAgo(180),
91
- updatedAt: daysAgo(20),
92
- },
93
59
  // Admin (LetItRip) — HQ
94
60
  {
95
61
  id: "addr-letitrip-hq",
@@ -108,62 +74,5 @@ const _rawAddressesSeedData = [
108
74
  createdAt: daysAgo(365),
109
75
  updatedAt: daysAgo(5),
110
76
  },
111
- // ────────────────────────────────────────────────────────────────────────────
112
- // Store Addresses — Pickup / fulfillment locations
113
- // ────────────────────────────────────────────────────────────────────────────
114
- // Kaiba Corp Card Vault — HQ (fulfillment)
115
- {
116
- id: "addr-kaiba-corp-hq",
117
- ownerType: "store",
118
- ownerId: "store-kaiba-corp-cards",
119
- label: "Kaiba Corp HQ",
120
- fullName: "Kaiba Corp Card Vault",
121
- phone: "+91-99999-40001",
122
- addressLine1: "2000 Kaiba Corp Tower",
123
- addressLine2: "Domino Business District",
124
- city: "Domino City",
125
- state: "Tokyo",
126
- postalCode: "110-0030",
127
- country: "Japan",
128
- isDefault: true,
129
- createdAt: daysAgo(360),
130
- updatedAt: daysAgo(2),
131
- },
132
- // Kaiba Corp Card Vault — Warehouse (backup fulfillment)
133
- {
134
- id: "addr-kaiba-corp-warehouse",
135
- ownerType: "store",
136
- ownerId: "store-kaiba-corp-cards",
137
- label: "Kaiba Land Warehouse",
138
- fullName: "Kaiba Land Fulfillment Center",
139
- phone: "+91-99999-40002",
140
- addressLine1: "300 Kaiba Land Logistics",
141
- addressLine2: "Tokyo Harbor Zone",
142
- city: "Tokyo",
143
- state: "Tokyo",
144
- postalCode: "135-0064",
145
- country: "Japan",
146
- isDefault: false,
147
- createdAt: daysAgo(300),
148
- updatedAt: daysAgo(15),
149
- },
150
- // LetItRip Official — Fulfillment
151
- {
152
- id: "addr-letitrip-fulfillment",
153
- ownerType: "store",
154
- ownerId: "store-letitrip-official",
155
- label: "LetItRip Fulfillment",
156
- fullName: "LetItRip Fulfillment Center",
157
- phone: "+91-99999-50001",
158
- addressLine1: "200 Logistics Lane",
159
- addressLine2: "Mumbai Warehouse District",
160
- city: "Mumbai",
161
- state: "Maharashtra",
162
- postalCode: "400086",
163
- country: "India",
164
- isDefault: true,
165
- createdAt: daysAgo(365),
166
- updatedAt: daysAgo(1),
167
- },
168
77
  ];
169
78
  export const addressesSeedData = _rawAddressesSeedData;