@mohasinac/appkit 2.7.30 → 2.7.31

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 (73) hide show
  1. package/dist/_internal/client/features/filters/filter-load-options.d.ts +29 -0
  2. package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
  3. package/dist/_internal/server/features/classified/adapters.js +1 -1
  4. package/dist/_internal/server/features/digital-code/adapters.js +1 -1
  5. package/dist/_internal/server/features/live/adapters.js +1 -1
  6. package/dist/_internal/shared/actions/action-registry.js +194 -1
  7. package/dist/client.d.ts +2 -0
  8. package/dist/client.js +1 -0
  9. package/dist/constants/api-endpoints.d.ts +6 -0
  10. package/dist/constants/api-endpoints.js +2 -0
  11. package/dist/constants/field-names.d.ts +1 -0
  12. package/dist/constants/field-names.js +1 -0
  13. package/dist/features/about/components/PublicProfileView.js +2 -2
  14. package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
  15. package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
  16. package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
  17. package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
  18. package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
  19. package/dist/features/admin/components/AdminProductEditorView.js +12 -9
  20. package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
  21. package/dist/features/admin/components/AdminReviewsView.js +5 -4
  22. package/dist/features/admin/components/index.d.ts +2 -0
  23. package/dist/features/admin/components/index.js +1 -0
  24. package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
  25. package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
  26. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  27. package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
  28. package/dist/features/auctions/components/AuctionFilters.js +3 -2
  29. package/dist/features/auctions/components/index.d.ts +2 -0
  30. package/dist/features/auctions/components/index.js +1 -0
  31. package/dist/features/cart/components/CartView.js +2 -1
  32. package/dist/features/cart/components/CheckoutView.js +2 -1
  33. package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
  34. package/dist/features/categories/components/BrandDetailTabs.js +29 -15
  35. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
  36. package/dist/features/categories/components/CategoryDetailTabs.js +30 -15
  37. package/dist/features/classified/components/ClassifiedFilters.d.ts +5 -1
  38. package/dist/features/classified/components/ClassifiedFilters.js +3 -2
  39. package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +5 -1
  40. package/dist/features/digital-codes/components/DigitalCodeFilters.js +3 -2
  41. package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
  42. package/dist/features/events/repository/event-entry.repository.js +5 -0
  43. package/dist/features/filters/index.d.ts +2 -0
  44. package/dist/features/filters/index.js +1 -0
  45. package/dist/features/layout/AppLayoutShell.d.ts +1 -1
  46. package/dist/features/layout/AppLayoutShell.js +1 -1
  47. package/dist/features/live/components/LiveItemFilters.d.ts +5 -1
  48. package/dist/features/live/components/LiveItemFilters.js +3 -2
  49. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
  50. package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
  51. package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
  52. package/dist/features/products/columns/productTableColumns.js +1 -1
  53. package/dist/features/products/components/CompareOverlay.d.ts +2 -0
  54. package/dist/features/products/components/CompareOverlay.js +1 -1
  55. package/dist/features/products/components/ProductFilters.d.ts +7 -1
  56. package/dist/features/products/components/ProductFilters.js +3 -2
  57. package/dist/features/products/components/ProductForm.js +6 -6
  58. package/dist/features/products/components/ProductGrid.js +1 -1
  59. package/dist/features/products/repository/products.repository.d.ts +4 -0
  60. package/dist/features/products/repository/products.repository.js +22 -3
  61. package/dist/features/products/schemas/firestore.d.ts +7 -1
  62. package/dist/features/products/types/index.d.ts +4 -0
  63. package/dist/features/stores/components/StoreDetailLayoutView.js +16 -2
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.js +6 -0
  66. package/dist/next/routing/route-map.d.ts +14 -0
  67. package/dist/next/routing/route-map.js +7 -0
  68. package/dist/seed/products-auctions-seed-data.js +40 -40
  69. package/dist/seed/products-preorders-seed-data.js +16 -16
  70. package/dist/seed/products-prize-draws-seed-data.js +4 -4
  71. package/dist/seed/products-standard-seed-data.js +202 -202
  72. package/dist/tailwind-utilities.css +1 -1
  73. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ /**
2
+ * filter-load-options.ts
3
+ *
4
+ * Factory functions that return `loadOptions(query, page)` callbacks for use
5
+ * with AsyncFacetSection, PaginatedMultiSelect, DynamicSelect, and
6
+ * InlineCreateSelect. Each factory hits a paginated API endpoint and maps the
7
+ * response to { items: DynamicSelectOption[], hasMore: boolean }.
8
+ *
9
+ * Usage:
10
+ * const load = makeCategoryLoadOptions();
11
+ * <AsyncFacetSection loadOptions={load} ... />
12
+ */
13
+ import type { DynamicSelectOption, AsyncPage } from "../../../../ui/components/DynamicSelect";
14
+ import type { FacetOption } from "../../../../features/filters/FilterFacetSection";
15
+ export type LoadOptionsFn<T = DynamicSelectOption> = (query: string, page: number) => Promise<AsyncPage<T>>;
16
+ /**
17
+ * Returns a loadOptions function for categories.
18
+ * @param variant "admin" uses /api/admin/categories; "public" uses /api/categories.
19
+ * @param categoryType Optional filter to narrow to a specific category type (e.g. "brand").
20
+ */
21
+ export declare function makeCategoryLoadOptions(variant?: "public" | "admin", categoryType?: string): LoadOptionsFn;
22
+ export declare function makeCategoryFacetLoadOptions(variant?: "public" | "admin", categoryType?: string): LoadOptionsFn<FacetOption>;
23
+ export declare function makeBrandLoadOptions(variant?: "public" | "admin"): LoadOptionsFn;
24
+ export declare function makeBrandFacetLoadOptions(variant?: "public" | "admin"): LoadOptionsFn<FacetOption>;
25
+ export declare function makeStoreLoadOptions(): LoadOptionsFn;
26
+ export declare function makeStoreFacetLoadOptions(): LoadOptionsFn<FacetOption>;
27
+ export declare function makeProductLoadOptions(storeId?: string): LoadOptionsFn;
28
+ export declare function makeUserLoadOptions(role?: string): LoadOptionsFn;
29
+ export declare function makeAddressLoadOptions(ownerType?: "user" | "store"): LoadOptionsFn;
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ const PAGE_SIZE = 25;
3
+ function buildUrl(base, q, page, extra) {
4
+ const params = new URLSearchParams({
5
+ q,
6
+ page: String(page),
7
+ pageSize: String(PAGE_SIZE),
8
+ ...extra,
9
+ });
10
+ return `${base}?${params.toString()}`;
11
+ }
12
+ async function fetchPage(url, mapItem) {
13
+ const res = await fetch(url);
14
+ if (!res.ok)
15
+ return { items: [], hasMore: false };
16
+ const json = (await res.json());
17
+ const raw = json.data?.items ?? [];
18
+ const hasMore = json.data?.hasMore ?? false;
19
+ return {
20
+ items: raw.map(mapItem),
21
+ hasMore,
22
+ };
23
+ }
24
+ // ── Categories ────────────────────────────────────────────────────────────────
25
+ /**
26
+ * Returns a loadOptions function for categories.
27
+ * @param variant "admin" uses /api/admin/categories; "public" uses /api/categories.
28
+ * @param categoryType Optional filter to narrow to a specific category type (e.g. "brand").
29
+ */
30
+ export function makeCategoryLoadOptions(variant = "admin", categoryType) {
31
+ const base = variant === "admin" ? "/api/admin/categories" : "/api/categories";
32
+ return async (q, page) => fetchPage(buildUrl(base, q, page, categoryType ? { categoryType } : undefined), (item) => ({
33
+ value: String(item.id ?? item.slug ?? ""),
34
+ label: String(item.name ?? item.id ?? ""),
35
+ meta: item,
36
+ }));
37
+ }
38
+ export function makeCategoryFacetLoadOptions(variant = "public", categoryType) {
39
+ const base = variant === "admin" ? "/api/admin/categories" : "/api/categories";
40
+ return async (q, page) => fetchPage(buildUrl(base, q, page, categoryType ? { categoryType } : undefined), (item) => ({
41
+ value: String(item.id ?? item.slug ?? ""),
42
+ label: String(item.name ?? item.id ?? ""),
43
+ }));
44
+ }
45
+ // ── Brands ────────────────────────────────────────────────────────────────────
46
+ export function makeBrandLoadOptions(variant = "admin") {
47
+ const base = variant === "admin" ? "/api/admin/brands" : "/api/brands";
48
+ return async (q, page) => fetchPage(buildUrl(base, q, page), (item) => ({
49
+ value: String(item.id ?? item.slug ?? ""),
50
+ label: String(item.name ?? item.id ?? ""),
51
+ meta: item,
52
+ }));
53
+ }
54
+ export function makeBrandFacetLoadOptions(variant = "public") {
55
+ const base = variant === "admin" ? "/api/admin/brands" : "/api/brands";
56
+ return async (q, page) => fetchPage(buildUrl(base, q, page), (item) => ({
57
+ value: String(item.id ?? item.slug ?? ""),
58
+ label: String(item.name ?? item.id ?? ""),
59
+ }));
60
+ }
61
+ // ── Stores ────────────────────────────────────────────────────────────────────
62
+ export function makeStoreLoadOptions() {
63
+ return async (q, page) => fetchPage(buildUrl("/api/admin/stores", q, page), (item) => ({
64
+ value: String(item.id ?? item.slug ?? ""),
65
+ label: String(item.storeName ?? item.name ?? item.id ?? ""),
66
+ meta: item,
67
+ }));
68
+ }
69
+ export function makeStoreFacetLoadOptions() {
70
+ return async (q, page) => fetchPage(buildUrl("/api/admin/stores", q, page), (item) => ({
71
+ value: String(item.id ?? item.slug ?? ""),
72
+ label: String(item.storeName ?? item.name ?? item.id ?? ""),
73
+ }));
74
+ }
75
+ // ── Products ──────────────────────────────────────────────────────────────────
76
+ export function makeProductLoadOptions(storeId) {
77
+ return async (q, page) => fetchPage(buildUrl("/api/admin/products", q, page, storeId ? { storeId } : undefined), (item) => ({
78
+ value: String(item.id ?? item.slug ?? ""),
79
+ label: String(item.title ?? item.name ?? item.id ?? ""),
80
+ meta: item,
81
+ }));
82
+ }
83
+ // ── Users ─────────────────────────────────────────────────────────────────────
84
+ export function makeUserLoadOptions(role) {
85
+ return async (q, page) => fetchPage(buildUrl("/api/admin/users", q, page, role ? { role } : undefined), (item) => ({
86
+ value: String(item.id ?? item.uid ?? ""),
87
+ label: String(item.displayName ?? item.email ?? item.id ?? ""),
88
+ meta: item,
89
+ }));
90
+ }
91
+ // ── Addresses ─────────────────────────────────────────────────────────────────
92
+ export function makeAddressLoadOptions(ownerType) {
93
+ return async (q, page) => fetchPage(buildUrl("/api/admin/addresses", q, page, ownerType ? { ownerType } : undefined), (item) => ({
94
+ value: String(item.id ?? ""),
95
+ label: [item.label, item.city, item.state].filter(Boolean).join(", "),
96
+ meta: item,
97
+ }));
98
+ }
@@ -12,7 +12,7 @@ export function toClientClassified(doc) {
12
12
  images: doc.images ?? [],
13
13
  storeId: doc.storeId,
14
14
  storeName: doc.storeName ?? null,
15
- category: doc.category,
15
+ category: doc.categorySlugs?.[0] ?? doc.category ?? "",
16
16
  condition: doc.condition ?? null,
17
17
  tags: doc.tags ?? [],
18
18
  classified: doc.classified ?? null,
@@ -13,7 +13,7 @@ export function toClientDigitalCode(doc) {
13
13
  images: doc.images ?? [],
14
14
  storeId: doc.storeId,
15
15
  storeName: doc.storeName ?? null,
16
- category: doc.category,
16
+ category: doc.categorySlugs?.[0] ?? doc.category ?? "",
17
17
  tags: doc.tags ?? [],
18
18
  digitalCode: dc
19
19
  ? {
@@ -13,7 +13,7 @@ export function toClientLiveItem(doc) {
13
13
  images: doc.images ?? [],
14
14
  storeId: doc.storeId,
15
15
  storeName: doc.storeName ?? null,
16
- category: doc.category,
16
+ category: doc.categorySlugs?.[0] ?? doc.category ?? "",
17
17
  tags: doc.tags ?? [],
18
18
  liveItem: li
19
19
  ? {
@@ -386,7 +386,200 @@ export const ACTIONS = {
386
386
  },
387
387
  },
388
388
  SELLER: {},
389
- ADMIN: {},
389
+ ADMIN: {
390
+ // ── Product moderation ─────────────────────────────────────────────────
391
+ "approve-product": {
392
+ id: "admin.approve-product",
393
+ label: "Approve",
394
+ ariaLabel: "Approve listing",
395
+ description: "Mark a pending product listing as approved and visible to buyers.",
396
+ kind: "primary",
397
+ permissions: ["admin", "moderator"],
398
+ },
399
+ "reject-product": {
400
+ id: "admin.reject-product",
401
+ label: "Reject",
402
+ ariaLabel: "Reject listing",
403
+ description: "Reject a product listing and notify the seller with a reason.",
404
+ kind: "danger",
405
+ permissions: ["admin", "moderator"],
406
+ confirmation: {
407
+ title: "Reject this listing?",
408
+ body: "The listing will be rejected and the seller will be notified. You can restore it later.",
409
+ confirmLabel: "Reject listing",
410
+ confirmKind: "danger",
411
+ },
412
+ },
413
+ // ── User moderation ────────────────────────────────────────────────────
414
+ "ban-user": {
415
+ id: "admin.ban-user",
416
+ label: "Ban user",
417
+ ariaLabel: "Ban this user",
418
+ description: "Soft-ban a user account — blocks login and all marketplace activity.",
419
+ kind: "danger",
420
+ permissions: ["admin", "moderator"],
421
+ confirmation: {
422
+ title: "Ban this user?",
423
+ body: "The user will be blocked from logging in and all marketplace activity. You can lift the ban later.",
424
+ confirmLabel: "Ban user",
425
+ confirmKind: "danger",
426
+ },
427
+ },
428
+ "unban-user": {
429
+ id: "admin.unban-user",
430
+ label: "Lift ban",
431
+ ariaLabel: "Lift ban on this user",
432
+ description: "Remove a soft-ban and restore normal account access.",
433
+ kind: "secondary",
434
+ permissions: ["admin"],
435
+ },
436
+ "verify-vendor": {
437
+ id: "admin.verify-vendor",
438
+ label: "Verify vendor",
439
+ ariaLabel: "Grant verified-vendor status",
440
+ description: "Grant verified-vendor badge to a seller account.",
441
+ kind: "primary",
442
+ permissions: ["admin"],
443
+ },
444
+ "unverify-vendor": {
445
+ id: "admin.unverify-vendor",
446
+ label: "Remove verification",
447
+ ariaLabel: "Revoke verified-vendor status",
448
+ description: "Revoke the verified-vendor badge from a seller account.",
449
+ kind: "secondary",
450
+ permissions: ["admin"],
451
+ confirmation: {
452
+ title: "Remove vendor verification?",
453
+ body: "The verified badge will be removed from this seller's store.",
454
+ confirmLabel: "Remove verification",
455
+ confirmKind: "danger",
456
+ },
457
+ },
458
+ // ── Store moderation ───────────────────────────────────────────────────
459
+ "verify-store": {
460
+ id: "admin.verify-store",
461
+ label: "Verify store",
462
+ ariaLabel: "Verify this store",
463
+ description: "Grant verified status to a store.",
464
+ kind: "primary",
465
+ permissions: ["admin"],
466
+ },
467
+ "suspend-store": {
468
+ id: "admin.suspend-store",
469
+ label: "Suspend store",
470
+ ariaLabel: "Suspend this store",
471
+ description: "Suspend a store — hides all its listings from buyers.",
472
+ kind: "danger",
473
+ permissions: ["admin"],
474
+ confirmation: {
475
+ title: "Suspend this store?",
476
+ body: "All listings from this store will be hidden. The seller will be notified.",
477
+ confirmLabel: "Suspend store",
478
+ confirmKind: "danger",
479
+ },
480
+ },
481
+ // ── Review moderation ──────────────────────────────────────────────────
482
+ "approve-review": {
483
+ id: "admin.approve-review",
484
+ label: "Approve",
485
+ ariaLabel: "Approve review",
486
+ description: "Publish a pending product review.",
487
+ kind: "primary",
488
+ permissions: ["admin", "moderator"],
489
+ },
490
+ "reject-review": {
491
+ id: "admin.reject-review",
492
+ label: "Reject",
493
+ ariaLabel: "Reject review",
494
+ description: "Reject a product review and prevent it from being shown.",
495
+ kind: "danger",
496
+ permissions: ["admin", "moderator"],
497
+ confirmation: {
498
+ title: "Reject this review?",
499
+ body: "The review will be hidden from the product page.",
500
+ confirmLabel: "Reject review",
501
+ confirmKind: "danger",
502
+ },
503
+ },
504
+ // ── Return request moderation ──────────────────────────────────────────
505
+ "approve-return": {
506
+ id: "admin.approve-return",
507
+ label: "Approve return",
508
+ ariaLabel: "Approve return request",
509
+ description: "Approve a return request and initiate the refund flow.",
510
+ kind: "primary",
511
+ permissions: ["admin", "moderator"],
512
+ },
513
+ "reject-return": {
514
+ id: "admin.reject-return",
515
+ label: "Reject return",
516
+ ariaLabel: "Reject return request",
517
+ description: "Reject a return request with a reason.",
518
+ kind: "danger",
519
+ permissions: ["admin", "moderator"],
520
+ confirmation: {
521
+ title: "Reject return request?",
522
+ body: "The return request will be declined and the buyer will be notified.",
523
+ confirmLabel: "Reject return",
524
+ confirmKind: "danger",
525
+ },
526
+ },
527
+ // ── Payout management ──────────────────────────────────────────────────
528
+ "grant-payout": {
529
+ id: "admin.grant-payout",
530
+ label: "Approve payout",
531
+ ariaLabel: "Approve payout request",
532
+ description: "Approve a seller payout request and trigger the transfer.",
533
+ kind: "primary",
534
+ permissions: ["admin"],
535
+ },
536
+ "hold-payout": {
537
+ id: "admin.hold-payout",
538
+ label: "Hold payout",
539
+ ariaLabel: "Put payout on hold",
540
+ description: "Put a payout request on hold pending investigation.",
541
+ kind: "secondary",
542
+ permissions: ["admin"],
543
+ confirmation: {
544
+ title: "Hold this payout?",
545
+ body: "The payout will be paused. The seller will be notified.",
546
+ confirmLabel: "Hold payout",
547
+ confirmKind: "danger",
548
+ },
549
+ },
550
+ // ── Bundle management ──────────────────────────────────────────────────
551
+ "rebuild-bundle": {
552
+ id: "admin.rebuild-bundle",
553
+ label: "Rebuild bundle",
554
+ ariaLabel: "Trigger bundle stock rebuild",
555
+ description: "Recalculate bundle availability from its constituent product stock.",
556
+ kind: "secondary",
557
+ permissions: ["admin"],
558
+ },
559
+ // ── Dev / system ───────────────────────────────────────────────────────
560
+ "reset-seed-data": {
561
+ id: "admin.reset-seed-data",
562
+ label: "Reset seed data",
563
+ ariaLabel: "Clear and re-seed Firestore",
564
+ description: "Delete all seeded documents and reload from seed fixtures. Dev environments only.",
565
+ kind: "danger",
566
+ permissions: ["admin"],
567
+ confirmation: {
568
+ title: "Reset all seed data?",
569
+ body: "Every seeded document will be deleted and reloaded from fixtures. This cannot be undone. Only use this in a development environment.",
570
+ confirmLabel: "Reset seed data",
571
+ confirmKind: "danger",
572
+ },
573
+ },
574
+ // ── Generic admin forms ────────────────────────────────────────────────
575
+ "save-changes": {
576
+ id: "admin.save-changes",
577
+ label: "Save changes",
578
+ description: "Submit any admin editor form.",
579
+ kind: "primary",
580
+ permissions: ["admin", "moderator"],
581
+ },
582
+ },
390
583
  CART: {
391
584
  "clear-cart": {
392
585
  id: "cart.clear",
package/dist/client.d.ts CHANGED
@@ -79,6 +79,8 @@ export type { CouponsIndexListingProps } from "./features/promotions/components/
79
79
  export { NotificationBell } from "./features/account/components/NotificationBell";
80
80
  export { NotificationPreferencesPanel } from "./features/account/components/NotificationPreferencesPanel";
81
81
  export type { NotificationPreferencesPanelProps } from "./features/account/components/NotificationPreferencesPanel";
82
+ export { AuctionBidsTable } from "./features/auctions/components/AuctionBidsTable";
83
+ export type { AuctionBidsTableProps, AuctionWithBids } from "./features/auctions/components/AuctionBidsTable";
82
84
  export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, RegisterForm, ResetPasswordView, VerifyEmailView } from "./features/auth/index";
83
85
  export type { AuthGuardUser, ForgotPasswordViewProps, LoginFormProps, LoginFormValues, RegisterFormProps, RegisterFormValues, ResetPasswordViewProps, VerifyEmailViewProps, } from "./features/auth/index";
84
86
  export { useLogout, useLogin, useGoogleLogin, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangePassword, useChangeEmail } from "./features/auth/index";
package/dist/client.js CHANGED
@@ -137,6 +137,7 @@ export { UserSidebar } from "./features/account/components/UserSidebar";
137
137
  export { CouponsIndexListing } from "./features/promotions/components/CouponsIndexListing";
138
138
  export { NotificationBell } from "./features/account/components/NotificationBell";
139
139
  export { NotificationPreferencesPanel } from "./features/account/components/NotificationPreferencesPanel";
140
+ export { AuctionBidsTable } from "./features/auctions/components/AuctionBidsTable";
140
141
  export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, RegisterForm, ResetPasswordView, VerifyEmailView } from "./features/auth/index";
141
142
  export { useLogout, useLogin, useGoogleLogin, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangePassword, useChangeEmail } from "./features/auth/index";
142
143
  export { CartView, CartItemRow, CartSummary, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
@@ -132,6 +132,8 @@ export declare const ADMIN_ENDPOINTS: {
132
132
  readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
133
133
  readonly SCAMMERS: "/api/admin/scammers";
134
134
  readonly SCAMMER_BY_ID: (id: string) => string;
135
+ readonly ADDRESSES: "/api/admin/addresses";
136
+ readonly ADDRESS_BY_ID: (id: string) => string;
135
137
  };
136
138
  export declare const CHAT_ENDPOINTS: {
137
139
  readonly LIST: "/api/chat";
@@ -430,6 +432,8 @@ export declare const API_ENDPOINTS: {
430
432
  readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
431
433
  readonly SCAMMERS: "/api/admin/scammers";
432
434
  readonly SCAMMER_BY_ID: (id: string) => string;
435
+ readonly ADDRESSES: "/api/admin/addresses";
436
+ readonly ADDRESS_BY_ID: (id: string) => string;
433
437
  };
434
438
  readonly CHAT: {
435
439
  readonly LIST: "/api/chat";
@@ -730,6 +734,8 @@ export declare const API_ROUTES: {
730
734
  readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
731
735
  readonly SCAMMERS: "/api/admin/scammers";
732
736
  readonly SCAMMER_BY_ID: (id: string) => string;
737
+ readonly ADDRESSES: "/api/admin/addresses";
738
+ readonly ADDRESS_BY_ID: (id: string) => string;
733
739
  };
734
740
  readonly CHAT: {
735
741
  readonly LIST: "/api/chat";
@@ -151,6 +151,8 @@ export const ADMIN_ENDPOINTS = {
151
151
  SUPPORT_TICKET_BY_ID: (id) => `/api/admin/support-tickets/${id}`,
152
152
  SCAMMERS: "/api/admin/scammers",
153
153
  SCAMMER_BY_ID: (id) => `/api/admin/scammers/${id}`,
154
+ ADDRESSES: "/api/admin/addresses",
155
+ ADDRESS_BY_ID: (id) => `/api/admin/addresses/${id}`,
154
156
  };
155
157
  // ---------------------------------------------------------------------------
156
158
  // Chat
@@ -13,6 +13,7 @@ export declare const PRODUCT_FIELDS: {
13
13
  readonly DESCRIPTION: "description";
14
14
  readonly SLUG: "slug";
15
15
  readonly CATEGORY: "category";
16
+ readonly CATEGORY_SLUGS: "categorySlugs";
16
17
  readonly CATEGORY_SLUG: "categorySlug";
17
18
  readonly SUBCATEGORY: "subcategory";
18
19
  readonly BRAND: "brand";
@@ -16,6 +16,7 @@ export const PRODUCT_FIELDS = {
16
16
  DESCRIPTION: "description",
17
17
  SLUG: "slug",
18
18
  CATEGORY: "category",
19
+ CATEGORY_SLUGS: "categorySlugs",
19
20
  CATEGORY_SLUG: "categorySlug",
20
21
  SUBCATEGORY: "subcategory",
21
22
  BRAND: "brand",
@@ -27,8 +27,8 @@ function toProductItem(p) {
27
27
  isSold: p.isSold,
28
28
  isPromoted: p.isPromoted,
29
29
  featured: p.featured,
30
- category: p.category,
31
- categoryName: p.categoryName,
30
+ categorySlugs: p.categorySlugs ?? (p.category ? [p.category] : []),
31
+ categoryNames: p.categoryNames ?? (p.categoryName ? [p.categoryName] : []),
32
32
  brand: p.brand,
33
33
  brandSlug: p.brandSlug,
34
34
  storeId: p.storeId,
@@ -0,0 +1,7 @@
1
+ import type { StackedViewShellProps } from "../../../ui";
2
+ export interface AdminAddressEditorViewProps extends Omit<StackedViewShellProps, "sections"> {
3
+ addressId?: string;
4
+ onSaved?: (id: string) => void;
5
+ onDeleted?: () => void;
6
+ }
7
+ export declare function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest }: AdminAddressEditorViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { useMutation, useQuery } from "@tanstack/react-query";
5
+ import { Button, Card, CardBody, ConfirmDeleteModal, Form, Input, Select, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
6
+ import { apiClient } from "../../../http";
7
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
+ import { INDIAN_STATES } from "../../account/constants/addresses";
10
+ const STATE_OPTIONS = INDIAN_STATES.map((s) => ({ value: s, label: s }));
11
+ // ── Section heading helper ─────────────────────────────────────────────────────
12
+ function SectionHeading({ children }) {
13
+ return (_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: children }));
14
+ }
15
+ const EMPTY = {
16
+ ownerType: "user",
17
+ ownerId: "",
18
+ label: "",
19
+ fullName: "",
20
+ phone: "",
21
+ addressLine1: "",
22
+ addressLine2: "",
23
+ landmark: "",
24
+ city: "",
25
+ state: "",
26
+ postalCode: "",
27
+ country: "India",
28
+ isDefault: false,
29
+ };
30
+ export function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest }) {
31
+ const isEdit = Boolean(addressId);
32
+ const [deleteOpen, setDeleteOpen] = React.useState(false);
33
+ const { showToast } = useToast();
34
+ const [form, setForm] = React.useState(EMPTY);
35
+ const set = (key, value) => setForm((prev) => ({ ...prev, [key]: value }));
36
+ const { isLoading } = useQuery({
37
+ queryKey: ["admin", "address", addressId],
38
+ queryFn: async () => {
39
+ const res = await apiClient.get(ADMIN_ENDPOINTS.ADDRESS_BY_ID(addressId));
40
+ return (res?.data ?? res);
41
+ },
42
+ enabled: isEdit,
43
+ select: (data) => {
44
+ setForm({
45
+ ownerType: data.ownerType ?? "user",
46
+ ownerId: data.ownerId ?? "",
47
+ label: data.label ?? "",
48
+ fullName: data.fullName ?? "",
49
+ phone: data.phone ?? "",
50
+ addressLine1: data.addressLine1 ?? "",
51
+ addressLine2: data.addressLine2 ?? "",
52
+ landmark: data.landmark ?? "",
53
+ city: data.city ?? "",
54
+ state: data.state ?? "",
55
+ postalCode: data.postalCode ?? "",
56
+ country: data.country ?? "India",
57
+ isDefault: data.isDefault ?? false,
58
+ });
59
+ return data;
60
+ },
61
+ });
62
+ const saveMutation = useMutation({
63
+ mutationFn: async () => {
64
+ const payload = {
65
+ ...form,
66
+ addressLine2: form.addressLine2 || undefined,
67
+ landmark: form.landmark || undefined,
68
+ };
69
+ if (isEdit) {
70
+ return apiClient.patch(ADMIN_ENDPOINTS.ADDRESS_BY_ID(addressId), payload);
71
+ }
72
+ return apiClient.post(ADMIN_ENDPOINTS.ADDRESSES, payload);
73
+ },
74
+ onSuccess: (res) => {
75
+ const id = res?.data?.id ?? res?.id ?? addressId;
76
+ showToast(isEdit ? "Address updated." : "Address created.", "success");
77
+ if (onSaved && id)
78
+ onSaved(String(id));
79
+ },
80
+ onError: (err) => {
81
+ showToast(err?.message ?? "Failed to save address.", "error");
82
+ },
83
+ });
84
+ const deleteMutation = useMutation({
85
+ mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.ADDRESS_BY_ID(addressId)),
86
+ onSuccess: () => {
87
+ showToast("Address deleted.", "success");
88
+ setDeleteOpen(false);
89
+ if (onDeleted)
90
+ onDeleted();
91
+ },
92
+ onError: (err) => showToast(err?.message ?? "Failed to delete address.", "error"),
93
+ });
94
+ const isSubmitting = saveMutation.isPending || isLoading;
95
+ const canSave = form.ownerId &&
96
+ form.label &&
97
+ form.fullName &&
98
+ form.phone &&
99
+ form.addressLine1 &&
100
+ form.city &&
101
+ form.state &&
102
+ form.postalCode.length === 6;
103
+ // ── Action sidebar (desktop) ───────────────────────────────────────────────
104
+ const actionSidebar = (_jsxs(Card, { variant: "outlined", padding: "md", className: "space-y-3", children: [_jsx(Button, { type: "submit", form: "address-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? ACTIONS.ADMIN["save-changes"].label : "Create address" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete address" }))] }));
105
+ // ── Form sections ──────────────────────────────────────────────────────────
106
+ const formContent = (_jsxs(Form, { id: "address-editor-form", onSubmit: (e) => {
107
+ e.preventDefault();
108
+ saveMutation.mutate();
109
+ }, className: "space-y-6", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(SectionHeading, { children: "Ownership" }), _jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex gap-6", children: ["user", "store"].map((type) => (_jsxs("label", { className: "flex cursor-pointer items-center gap-2", children: [_jsx("input", { type: "radio", name: "ownerType", value: type, checked: form.ownerType === type, onChange: () => {
110
+ set("ownerType", type);
111
+ set("ownerId", "");
112
+ }, className: "accent-[var(--appkit-color-primary)]" }), _jsx("span", { className: "text-sm capitalize text-[var(--appkit-color-text)]", children: type })] }, type))) }), _jsx(Input, { label: form.ownerType === "user" ? "User ID" : "Store ID (slug)", value: form.ownerId, onChange: (e) => set("ownerId", e.target.value), required: true, placeholder: form.ownerType === "user" ? "user-mohsin-c" : "store-pokemon-palace", helperText: form.ownerType === "user"
113
+ ? "Firebase Auth UID or user slug"
114
+ : "Store slug (e.g. store-pokemon-palace)" })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(SectionHeading, { children: "Contact & Location" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Input, { label: "Label", value: form.label, onChange: (e) => set("label", e.target.value), required: true, placeholder: "Home / Office / Pickup" }), _jsx(Input, { label: "Full Name", value: form.fullName, onChange: (e) => set("fullName", e.target.value), required: true, placeholder: "Recipient name (PII)" })] }), _jsx(Input, { label: "Phone", value: form.phone, onChange: (e) => set("phone", e.target.value), required: true, type: "tel", placeholder: "+91 9876543210 (PII)" }), _jsx(Input, { label: "Address Line 1", value: form.addressLine1, onChange: (e) => set("addressLine1", e.target.value), required: true, placeholder: "House/flat no., street (PII)" }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Input, { label: "Address Line 2", value: form.addressLine2 ?? "", onChange: (e) => set("addressLine2", e.target.value), placeholder: "Area, locality (optional)" }), _jsx(Input, { label: "Landmark", value: form.landmark ?? "", onChange: (e) => set("landmark", e.target.value), placeholder: "Near hospital, etc. (optional)" })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsx(Input, { label: "City", value: form.city, onChange: (e) => set("city", e.target.value), required: true, placeholder: "Mumbai" }), _jsx(Select, { label: "State", value: form.state, onValueChange: (val) => set("state", val), options: STATE_OPTIONS, placeholder: "Select state", required: true }), _jsx(Input, { label: "Postal Code", value: form.postalCode, onChange: (e) => set("postalCode", e.target.value), required: true, placeholder: "400001", maxLength: 6 })] }), _jsx(Input, { label: "Country", value: form.country, onChange: (e) => set("country", e.target.value), placeholder: "India" })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(SectionHeading, { children: "Flags" }), _jsx(Toggle, { label: "Set as default address", checked: form.isDefault, onChange: (v) => set("isDefault", v) })] }), _jsxs("div", { className: "flex gap-3 lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? ACTIONS.ADMIN["save-changes"].label : "Create address" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete address" }))] })] }, "address-form"));
115
+ const twoPanel = (_jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_280px] lg:items-start", children: [_jsx(CardBody, { className: "min-w-0 space-y-6 p-0", children: formContent }), _jsx("div", { className: "hidden lg:block lg:sticky lg:top-[var(--header-height,0px)]", children: actionSidebar })] }));
116
+ return (_jsxs(_Fragment, { children: [_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Address" : "New Address", sections: [twoPanel] }), _jsx(ConfirmDeleteModal, { isOpen: deleteOpen, onClose: () => setDeleteOpen(false), onConfirm: () => deleteMutation.mutate(), isDeleting: deleteMutation.isPending, title: "Delete this address?", message: "This address will be permanently removed. This action cannot be undone.", confirmText: "Delete address", variant: "danger" })] }));
117
+ }