@mohasinac/appkit 4.1.1 → 4.2.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.
Files changed (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -0,0 +1,210 @@
1
+ export * from "./firestore";
2
+ import { z } from "zod";
3
+ export declare const aboutHowItemSchema: z.ZodObject<{
4
+ title: z.ZodString;
5
+ text: z.ZodString;
6
+ icon: z.ZodString;
7
+ tone: z.ZodOptional<z.ZodEnum<["indigo", "teal", "amber", "rose"]>>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ title: string;
10
+ text: string;
11
+ icon: string;
12
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
13
+ }, {
14
+ title: string;
15
+ text: string;
16
+ icon: string;
17
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
18
+ }>;
19
+ export declare const aboutValueItemSchema: z.ZodObject<{
20
+ title: z.ZodString;
21
+ text: z.ZodString;
22
+ icon: z.ZodString;
23
+ }, "strip", z.ZodTypeAny, {
24
+ title: string;
25
+ text: string;
26
+ icon: string;
27
+ }, {
28
+ title: string;
29
+ text: string;
30
+ icon: string;
31
+ }>;
32
+ export declare const aboutMilestoneSchema: z.ZodObject<{
33
+ year: z.ZodString;
34
+ text: z.ZodString;
35
+ }, "strip", z.ZodTypeAny, {
36
+ text: string;
37
+ year: string;
38
+ }, {
39
+ text: string;
40
+ year: string;
41
+ }>;
42
+ export declare const aboutTeamMemberSchema: z.ZodObject<{
43
+ name: z.ZodString;
44
+ role: z.ZodString;
45
+ bio: z.ZodString;
46
+ photoUrl: z.ZodOptional<z.ZodString>;
47
+ isFounder: z.ZodOptional<z.ZodBoolean>;
48
+ isDeveloper: z.ZodOptional<z.ZodBoolean>;
49
+ }, "strip", z.ZodTypeAny, {
50
+ role: string;
51
+ name: string;
52
+ bio: string;
53
+ photoUrl?: string | undefined;
54
+ isFounder?: boolean | undefined;
55
+ isDeveloper?: boolean | undefined;
56
+ }, {
57
+ role: string;
58
+ name: string;
59
+ bio: string;
60
+ photoUrl?: string | undefined;
61
+ isFounder?: boolean | undefined;
62
+ isDeveloper?: boolean | undefined;
63
+ }>;
64
+ export declare const aboutContentSchema: z.ZodObject<{
65
+ title: z.ZodString;
66
+ subtitle: z.ZodString;
67
+ missionTitle: z.ZodString;
68
+ missionText: z.ZodString;
69
+ howItWorksTitle: z.ZodString;
70
+ howItems: z.ZodArray<z.ZodObject<{
71
+ title: z.ZodString;
72
+ text: z.ZodString;
73
+ icon: z.ZodString;
74
+ tone: z.ZodOptional<z.ZodEnum<["indigo", "teal", "amber", "rose"]>>;
75
+ }, "strip", z.ZodTypeAny, {
76
+ title: string;
77
+ text: string;
78
+ icon: string;
79
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
80
+ }, {
81
+ title: string;
82
+ text: string;
83
+ icon: string;
84
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
85
+ }>, "many">;
86
+ valuesTitle: z.ZodString;
87
+ valueItems: z.ZodArray<z.ZodObject<{
88
+ title: z.ZodString;
89
+ text: z.ZodString;
90
+ icon: z.ZodString;
91
+ }, "strip", z.ZodTypeAny, {
92
+ title: string;
93
+ text: string;
94
+ icon: string;
95
+ }, {
96
+ title: string;
97
+ text: string;
98
+ icon: string;
99
+ }>, "many">;
100
+ milestonesTitle: z.ZodString;
101
+ milestones: z.ZodArray<z.ZodObject<{
102
+ year: z.ZodString;
103
+ text: z.ZodString;
104
+ }, "strip", z.ZodTypeAny, {
105
+ text: string;
106
+ year: string;
107
+ }, {
108
+ text: string;
109
+ year: string;
110
+ }>, "many">;
111
+ teamTitle: z.ZodString;
112
+ teamSubtitle: z.ZodString;
113
+ teamMembers: z.ZodArray<z.ZodObject<{
114
+ name: z.ZodString;
115
+ role: z.ZodString;
116
+ bio: z.ZodString;
117
+ photoUrl: z.ZodOptional<z.ZodString>;
118
+ isFounder: z.ZodOptional<z.ZodBoolean>;
119
+ isDeveloper: z.ZodOptional<z.ZodBoolean>;
120
+ }, "strip", z.ZodTypeAny, {
121
+ role: string;
122
+ name: string;
123
+ bio: string;
124
+ photoUrl?: string | undefined;
125
+ isFounder?: boolean | undefined;
126
+ isDeveloper?: boolean | undefined;
127
+ }, {
128
+ role: string;
129
+ name: string;
130
+ bio: string;
131
+ photoUrl?: string | undefined;
132
+ isFounder?: boolean | undefined;
133
+ isDeveloper?: boolean | undefined;
134
+ }>, "many">;
135
+ ctaTitle: z.ZodString;
136
+ ctaSell: z.ZodString;
137
+ ctaShop: z.ZodString;
138
+ }, "strip", z.ZodTypeAny, {
139
+ title: string;
140
+ subtitle: string;
141
+ missionTitle: string;
142
+ missionText: string;
143
+ howItWorksTitle: string;
144
+ valuesTitle: string;
145
+ milestonesTitle: string;
146
+ teamTitle: string;
147
+ teamSubtitle: string;
148
+ ctaTitle: string;
149
+ ctaSell: string;
150
+ ctaShop: string;
151
+ howItems: {
152
+ title: string;
153
+ text: string;
154
+ icon: string;
155
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
156
+ }[];
157
+ valueItems: {
158
+ title: string;
159
+ text: string;
160
+ icon: string;
161
+ }[];
162
+ milestones: {
163
+ text: string;
164
+ year: string;
165
+ }[];
166
+ teamMembers: {
167
+ role: string;
168
+ name: string;
169
+ bio: string;
170
+ photoUrl?: string | undefined;
171
+ isFounder?: boolean | undefined;
172
+ isDeveloper?: boolean | undefined;
173
+ }[];
174
+ }, {
175
+ title: string;
176
+ subtitle: string;
177
+ missionTitle: string;
178
+ missionText: string;
179
+ howItWorksTitle: string;
180
+ valuesTitle: string;
181
+ milestonesTitle: string;
182
+ teamTitle: string;
183
+ teamSubtitle: string;
184
+ ctaTitle: string;
185
+ ctaSell: string;
186
+ ctaShop: string;
187
+ howItems: {
188
+ title: string;
189
+ text: string;
190
+ icon: string;
191
+ tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
192
+ }[];
193
+ valueItems: {
194
+ title: string;
195
+ text: string;
196
+ icon: string;
197
+ }[];
198
+ milestones: {
199
+ text: string;
200
+ year: string;
201
+ }[];
202
+ teamMembers: {
203
+ role: string;
204
+ name: string;
205
+ bio: string;
206
+ photoUrl?: string | undefined;
207
+ isFounder?: boolean | undefined;
208
+ isDeveloper?: boolean | undefined;
209
+ }[];
210
+ }>;
@@ -0,0 +1,45 @@
1
+ export * from "./firestore";
2
+ import { z } from "zod";
3
+ const REQUIRED_TITLE = "Title is required";
4
+ const REQUIRED_TEXT = "Text is required";
5
+ export const aboutHowItemSchema = z.object({
6
+ title: z.string().min(1, REQUIRED_TITLE),
7
+ text: z.string().min(1, REQUIRED_TEXT),
8
+ icon: z.string().min(1, "Icon is required"),
9
+ tone: z.enum(["indigo", "teal", "amber", "rose"]).optional(),
10
+ });
11
+ export const aboutValueItemSchema = z.object({
12
+ title: z.string().min(1, REQUIRED_TITLE),
13
+ text: z.string().min(1, REQUIRED_TEXT),
14
+ icon: z.string().min(1, "Icon is required"),
15
+ });
16
+ export const aboutMilestoneSchema = z.object({
17
+ year: z.string().min(1, "Year is required"),
18
+ text: z.string().min(1, REQUIRED_TEXT),
19
+ });
20
+ export const aboutTeamMemberSchema = z.object({
21
+ name: z.string().min(1, "Name is required"),
22
+ role: z.string().min(1, "Role is required"),
23
+ bio: z.string().min(1, "Bio is required"),
24
+ photoUrl: z.string().optional(),
25
+ isFounder: z.boolean().optional(),
26
+ isDeveloper: z.boolean().optional(),
27
+ });
28
+ export const aboutContentSchema = z.object({
29
+ title: z.string().min(1, REQUIRED_TITLE),
30
+ subtitle: z.string().min(1, "Subtitle is required"),
31
+ missionTitle: z.string().min(1, "Mission title is required"),
32
+ missionText: z.string().min(1, "Mission text is required"),
33
+ howItWorksTitle: z.string().min(1, "How it works title is required"),
34
+ howItems: z.array(aboutHowItemSchema),
35
+ valuesTitle: z.string().min(1, "Values title is required"),
36
+ valueItems: z.array(aboutValueItemSchema),
37
+ milestonesTitle: z.string().min(1, "Milestones title is required"),
38
+ milestones: z.array(aboutMilestoneSchema),
39
+ teamTitle: z.string().min(1, "Team title is required"),
40
+ teamSubtitle: z.string().min(1, "Team subtitle is required"),
41
+ teamMembers: z.array(aboutTeamMemberSchema),
42
+ ctaTitle: z.string().min(1, "CTA title is required"),
43
+ ctaSell: z.string().min(1, "Sell CTA copy is required"),
44
+ ctaShop: z.string().min(1, "Shop CTA copy is required"),
45
+ });
@@ -17,6 +17,9 @@ export function useUpdateCurrentProfile(options) {
17
17
  mutationFn: (data) => apiClient.patch(endpoint, data),
18
18
  onSuccess: async (data) => {
19
19
  await queryClient.invalidateQueries({ queryKey: ["profile"] });
20
+ // useCurrentUser()/useRBAC() read identity via a separate ["auth","me"]
21
+ // query — without this, displayName/photoURL stay stale for up to 5min.
22
+ await queryClient.invalidateQueries({ queryKey: ["auth", "me"] });
20
23
  options?.onSuccess?.(data);
21
24
  },
22
25
  onError: options?.onError,
@@ -14,11 +14,14 @@ export interface SendNotificationInput extends NotificationCreateInput {
14
14
  userPhone?: string;
15
15
  /** Pre-rendered email HTML — falls back to a plain-text <p> when absent. */
16
16
  emailHtml?: string;
17
+ /** Buyer paid the ₹10 WhatsApp order-updates addon — enables WhatsApp channel for this notification even when the user has no standing WhatsApp subscription. */
18
+ orderWhatsappAddonPaid?: boolean;
17
19
  }
18
20
  export interface SendNotificationResult {
19
21
  notification: NotificationDocument;
20
22
  email: "sent" | "skipped" | "failed";
21
- whatsapp: "sent" | "skipped" | "failed";
23
+ /** WhatsApp dispatch is now async (enqueued as a `whatsappNotify` job) — "queued" replaces "sent" as the immediate outcome; the real delivery result lands on `notification.whatsappStatus`. */
24
+ whatsapp: "queued" | "skipped" | "failed";
22
25
  }
23
26
  /**
24
27
  * Create an in-app notification and fan out to enabled external channels.
@@ -3,10 +3,39 @@ import { notificationRepository } from "../repository/notification.repository";
3
3
  import { siteSettingsRepository } from "../repository/site-settings.repository";
4
4
  import { userRepository } from "../../auth/repository/user.repository";
5
5
  import { createResendProvider } from "../../../providers/email-resend/provider";
6
- import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
6
+ import { enqueueJob } from "../../jobs/actions/enqueue-job";
7
7
  import { serverLogger } from "../../../monitoring";
8
8
  import { decryptPii } from "../../../security/index";
9
9
  import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore";
10
+ /**
11
+ * Notification types whose WhatsApp send is gated behind the buyer's
12
+ * per-order ₹10 WhatsApp-updates addon (`OrderDocument.whatsappNotifyAddon`)
13
+ * — see `orderWhatsappAddonPaid` on `SendNotificationInput`. Every other
14
+ * type (bid_won, offers, scam reports, support tickets, admin alerts, …)
15
+ * keeps the original two-gate behavior (admin channel enabled + user hasn't
16
+ * opted out) unchanged.
17
+ */
18
+ const ORDER_GATED_TYPES = new Set([
19
+ "order_placed",
20
+ "order_confirmed",
21
+ "order_shipped",
22
+ "order_delivered",
23
+ "order_cancelled",
24
+ "refund_initiated",
25
+ ]);
26
+ /** Notification type → the flat `SiteSettingsCredentials` field holding its approved Meta template name. */
27
+ const WHATSAPP_TEMPLATE_FIELD = {
28
+ order_placed: "whatsappTemplateOrderPlaced",
29
+ order_confirmed: "whatsappTemplateOrderConfirmed",
30
+ order_shipped: "whatsappTemplateOrderShipped",
31
+ order_delivered: "whatsappTemplateOrderDelivered",
32
+ order_cancelled: "whatsappTemplateOrderCancelled",
33
+ refund_initiated: "whatsappTemplateRefundInitiated",
34
+ };
35
+ function resolveWhatsAppTemplateName(type, creds) {
36
+ const field = WHATSAPP_TEMPLATE_FIELD[type];
37
+ return (field ? creds[field] : undefined)?.trim() ?? "";
38
+ }
10
39
  export async function markNotificationRead(id) {
11
40
  await notificationRepository.markAsRead(id);
12
41
  }
@@ -121,28 +150,45 @@ export async function sendNotification(input) {
121
150
  }
122
151
  }
123
152
  }
124
- // WhatsApp channel — admin enabled AND user hasn't opted out
153
+ // WhatsApp channel — admin enabled AND user hasn't opted out AND (for
154
+ // order-lifecycle types) the buyer paid the per-order WhatsApp addon.
125
155
  let whatsappStatus = "skipped";
126
156
  if (channels.whatsapp.enabled &&
127
157
  userChannelPrefs.whatsapp !== false &&
128
158
  resolvedPhone &&
129
159
  meetsMinPriority(priority, channels.whatsapp.minPriority) &&
130
- (!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type))) {
160
+ (!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type)) &&
161
+ (!ORDER_GATED_TYPES.has(type) || input.orderWhatsappAddonPaid === true)) {
131
162
  const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
132
163
  const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
133
164
  if (phoneNumberId && accessToken) {
134
165
  try {
135
- const ok = await sendWhatsAppBusinessMessage({
136
- toPhone: resolvedPhone,
137
- message: `*${title}*\n${message}`,
138
- phoneNumberId,
139
- accessToken,
166
+ const { jobId } = await enqueueJob({
167
+ jobType: "whatsappNotify",
168
+ payload: {
169
+ toPhone: resolvedPhone,
170
+ title,
171
+ message,
172
+ type,
173
+ templateName: resolveWhatsAppTemplateName(type, creds),
174
+ templateLanguage: creds.whatsappTemplateLanguage ?? "en",
175
+ notificationId: notification.id,
176
+ phoneNumberId,
177
+ accessToken,
178
+ },
179
+ requestedBy: input.userId,
180
+ });
181
+ whatsappStatus = "queued";
182
+ await notificationRepository
183
+ .update(notification.id, { whatsappStatus: "queued", whatsappJobId: jobId })
184
+ .catch((err) => {
185
+ void normalizeError(err);
186
+ serverLogger.warn("sendNotification: failed to write whatsappJobId onto notification doc", { notificationId: notification.id });
140
187
  });
141
- whatsappStatus = ok ? "sent" : "failed";
142
188
  }
143
189
  catch (err) {
144
190
  void normalizeError(err);
145
- serverLogger.error("sendNotification: WhatsApp dispatch failed", { userId: input.userId, err });
191
+ serverLogger.error("sendNotification: WhatsApp job enqueue failed", { userId: input.userId, err });
146
192
  whatsappStatus = "failed";
147
193
  }
148
194
  }
@@ -156,7 +156,7 @@ export function AdminAdsView({ endpoint = ADMIN_ENDPOINTS.ADS, labels = {}, crea
156
156
  settingsMutation.mutate(payload);
157
157
  };
158
158
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? "Ad Inventory", sections: [
159
- _jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-neutral-900 text-white bg-[var(--appkit-color-surface)] dark:text-[var(--appkit-color-text)]", children: "New ad" })] }),
159
+ _jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)]", children: "New ad" })] }),
160
160
  adsQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ads", children: adsQuery.error instanceof Error ? adsQuery.error.message : "Unknown error" })) : null,
161
161
  _jsx(AdsSettingsPanel, { adsenseClientId: adsenseClientId, setAdsenseClientId: setAdsenseClientId, thirdPartyScriptUrl: thirdPartyScriptUrl, setThirdPartyScriptUrl: setThirdPartyScriptUrl, consentRequired: consentRequired, setConsentRequired: setConsentRequired, serverCredentialIssues: serverCredentialIssues, localCredentialIssues: localCredentialIssues, credentialStatus: credentialStatus, providerCredentialsMasked: adsQuery.data?.providerCredentialsMasked, settingsMutation: settingsMutation, hasPendingCredentialInput: hasPendingCredentialInput, currentConsentRequired: Boolean(adsQuery.data?.consentRequired), settingsMessage: settingsMessage, onSave: saveSettings }),
162
162
  _jsx(AdsFilterRow, { q: q, setQ: setQ, status: status, setStatus: setStatus, provider: provider, setProvider: setProvider, placement: placement, setPlacement: setPlacement, placements: placements, onPageReset: () => setPage(1) }),
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import type { StackedViewShellProps } from "../../../ui";
3
+ export interface AdminCarouselGroupEditorViewProps extends Omit<StackedViewShellProps, "sections"> {
4
+ onCreated?: (id: string) => void;
5
+ }
6
+ export declare function AdminCarouselGroupEditorView({ onCreated, ...rest }: AdminCarouselGroupEditorViewProps): React.JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { z } from "zod";
5
+ import { useRouter } from "next/navigation";
6
+ import { Button, Form, Row, StackedViewShell, useToast } from "../../../ui";
7
+ import { FieldInput, FieldSelect } from "../../../ui/forms";
8
+ import { useApiMutation } from "@mohasinac/appkit/client";
9
+ import { apiClient } from "../../../http";
10
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ROUTES } from "../../../next/routing/route-map";
12
+ const carouselGroupFormSchema = z.object({
13
+ name: z.string().min(1, "Name is required").max(200),
14
+ status: z.enum(["active", "draft"]),
15
+ });
16
+ export function AdminCarouselGroupEditorView({ onCreated, ...rest }) {
17
+ const { showToast } = useToast();
18
+ const router = useRouter();
19
+ const [name, setName] = React.useState("");
20
+ const [status, setStatus] = React.useState("draft");
21
+ const createMutation = useApiMutation({
22
+ mutationFn: () => apiClient.post(ADMIN_ENDPOINTS.CAROUSELS, { name, status }),
23
+ onSuccess: (res) => {
24
+ const id = res?.data?.id;
25
+ showToast("Carousel created.", "success");
26
+ if (id) {
27
+ if (onCreated)
28
+ onCreated(id);
29
+ else
30
+ router.push(String(ROUTES.ADMIN.CAROUSEL_DETAIL(id)));
31
+ }
32
+ },
33
+ onError: (err) => {
34
+ showToast(err?.message ?? "Failed to create carousel.", "error");
35
+ },
36
+ });
37
+ return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: "New Named Carousel", sections: [
38
+ _jsx(Form, { schema: carouselGroupFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "name", label: "Carousel name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Homepage Hero" }), _jsx(FieldSelect, { name: "status", label: "Status", value: status, onChange: (v) => setStatus(v), options: [
39
+ { value: "draft", label: "Draft" },
40
+ { value: "active", label: "Active" },
41
+ ] }), _jsx(Row, { gap: "3", padding: "t-xs", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, disabled: !name || createMutation.isPending, onClick: () => {
42
+ clearErrors();
43
+ if (!name.trim()) {
44
+ setFieldError("name", "Carousel name is required");
45
+ return;
46
+ }
47
+ createMutation.mutate();
48
+ }, children: "Create carousel" }) })] })) }, "carousel-group-form"),
49
+ ] }));
50
+ }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { SIEVE_OP, sieveFilter } from "@mohasinac/appkit";
3
4
  import { sortBy } from "@mohasinac/appkit";
4
5
  import React from "react";
5
- import { ListingLayout } from "../../../ui";
6
+ import { FilterChipGroup, ListingLayout } from "../../../ui";
6
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
8
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
8
9
  import { DataListingView } from "./DataListingView";
@@ -11,7 +12,7 @@ const ADMIN_CARTS_CONFIG = {
11
12
  title: "Carts",
12
13
  searchPlaceholder: "Search by user ID or session",
13
14
  emptyLabel: "No carts found",
14
- filterKeys: [],
15
+ filterKeys: ["ownership"],
15
16
  defaultSort: sortBy("updatedAt", "DESC"),
16
17
  queryKey: ["admin", "carts", "listing"],
17
18
  endpoint: ADMIN_ENDPOINTS.ADMIN_CARTS,
@@ -34,7 +35,19 @@ const ADMIN_CARTS_CONFIG = {
34
35
  };
35
36
  }),
36
37
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
37
- buildFilters: () => undefined,
38
+ buildFilters: (state) =>
39
+ // Guest carts store userId as an empty string (CartDocument.userId is a
40
+ // required string, not optional, see mapRows below). Only the guest-only
41
+ // direction is offered: an authenticated-only filter would need a NEQ
42
+ // on userId combined with the default updatedAt sort, which Firestore
43
+ // rejects (inequality filters require orderBy to match the same
44
+ // field), and CartRepository Sieve config marks userId as not
45
+ // sortable, so no safe workaround sort exists.
46
+ state.ownership === "guest" ? sieveFilter("userId", SIEVE_OP.EQ, "") : undefined,
47
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Ownership", tabs: [
48
+ { id: "", label: "All" },
49
+ { id: "guest", label: "Guest only" },
50
+ ], value: pendingFilters.ownership ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, ownership: id })), allId: "" })),
38
51
  };
39
52
  export function AdminCartsView({ children, ...props }) {
40
53
  if (React.Children.count(children) > 0) {
@@ -50,7 +50,7 @@ function toDateInputValue(val) {
50
50
  }
51
51
  }
52
52
  function CouponDiscountFields({ type, discountValue, setDiscountValue, maxDiscount, setMaxDiscount, minPurchase, setMinPurchase, buyQty, setBuyQty, getQty, setGetQty, discountLabel, }) {
53
- return (_jsxs(_Fragment, { children: [type !== "free_shipping" && type !== "buy_x_get_y" && (_jsx(Input, { label: discountLabel, value: discountValue, onChange: (e) => setDiscountValue(e.target.value), type: "number", min: 0, required: true, placeholder: type === "percentage" ? "e.g. 20" : "e.g. 5000" })), type === "percentage" && (_jsx(Input, { label: "Max discount cap (paise, optional)", value: maxDiscount, onChange: (e) => setMaxDiscount(e.target.value), type: "number", min: 0, placeholder: "e.g. 20000", helperText: "Leave blank for no cap." })), (type === "percentage" || type === "fixed") && (_jsx(Input, { label: "Min order value (paise, optional)", value: minPurchase, onChange: (e) => setMinPurchase(e.target.value), type: "number", min: 0, placeholder: "e.g. 50000", helperText: "Leave blank for no minimum." })), type === "buy_x_get_y" && (_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Buy quantity", value: buyQty, onChange: (e) => setBuyQty(e.target.value), type: "number", min: 1, required: true }), _jsx(Input, { label: "Get quantity", value: getQty, onChange: (e) => setGetQty(e.target.value), type: "number", min: 1, required: true })] }))] }));
53
+ return (_jsxs(_Fragment, { children: [type !== "free_shipping" && type !== "buy_x_get_y" && (_jsx(Input, { label: discountLabel, value: discountValue, onChange: (e) => setDiscountValue(e.target.value), type: "number", min: 0, required: true, placeholder: type === "percentage" ? "e.g. 20" : "e.g. 5000" })), type === "percentage" && (_jsx(Input, { label: "Max discount cap (\u20B9, optional)", value: maxDiscount, onChange: (e) => setMaxDiscount(e.target.value), type: "number", min: 0, placeholder: "e.g. 200", helperText: "Leave blank for no cap." })), (type === "percentage" || type === "fixed") && (_jsx(Input, { label: "Min order value (\u20B9, optional)", value: minPurchase, onChange: (e) => setMinPurchase(e.target.value), type: "number", min: 0, placeholder: "e.g. 500", helperText: "Leave blank for no minimum." })), type === "buy_x_get_y" && (_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Buy quantity", value: buyQty, onChange: (e) => setBuyQty(e.target.value), type: "number", min: 1, required: true }), _jsx(Input, { label: "Get quantity", value: getQty, onChange: (e) => setGetQty(e.target.value), type: "number", min: 1, required: true })] }))] }));
54
54
  }
55
55
  function CouponValidityFields({ startDate, setStartDate, endDate, setEndDate, isActive, setIsActive, totalLimit, setTotalLimit, perUserLimit, setPerUserLimit, isEdit, currentUsage, }) {
56
56
  return (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Total usage limit (optional)", value: totalLimit, onChange: (e) => setTotalLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" }), _jsx(Input, { label: "Per-user limit (optional)", value: perUserLimit, onChange: (e) => setPerUserLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" })] }), isEdit && (_jsx(Input, { label: "Current usage", value: String(currentUsage), disabled: true, helperText: "Read-only \u2014 updated by orders." })), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Start date", value: startDate, onChange: (e) => setStartDate(e.target.value), type: "date", required: true }), _jsx(Input, { label: "End date (optional)", value: endDate, onChange: (e) => setEndDate(e.target.value), type: "date", helperText: "Leave blank for no expiry." })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive })] }));
@@ -203,7 +203,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
203
203
  const discountLabel = type === "percentage"
204
204
  ? "Discount percentage (%)"
205
205
  : type === "fixed"
206
- ? "Discount amount (paise)"
206
+ ? "Discount amount ()"
207
207
  : "Discount value";
208
208
  const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: couponFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Coupon type", options: TYPE_OPTIONS, value: type, onValueChange: (v) => setType(v), required: true }), _jsx(FieldInput, { name: "name", label: "Campaign name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Summer Sale 20%" }), !isEdit && (_jsx(Input, { label: "Coupon code", value: code, onChange: (e) => {
209
209
  setCode(toCouponCode(e.target.value));
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
5
5
  import { sortBy } from "@mohasinac/appkit";
@@ -54,7 +54,7 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
54
54
  title: "Coupons",
55
55
  searchPlaceholder: "Search codes, campaigns, or seller scopes",
56
56
  emptyLabel: "No coupons found",
57
- filterKeys: ["type"],
57
+ filterKeys: ["type", "showExpired"],
58
58
  defaultSort: sortBy("createdAt", "DESC"),
59
59
  queryKey: ["admin", "coupons", "listing"],
60
60
  endpoint: ADMIN_ENDPOINTS.COUPONS,
@@ -69,7 +69,14 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
69
69
  raw: item,
70
70
  })),
71
71
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
72
- buildFilters: (state) => state.type && state.type !== "All" ? sieveFilter("type", SIEVE_OP.EQ, state.type) : undefined,
72
+ buildFilters: (state) => {
73
+ const parts = [];
74
+ if (state.type && state.type !== "All")
75
+ parts.push(sieveFilter("type", SIEVE_OP.EQ, state.type));
76
+ if (state.showExpired !== "true")
77
+ parts.push(sieveFilter("validity.isActive", SIEVE_OP.EQ, true));
78
+ return parts.join(",") || undefined;
79
+ },
73
80
  primaryAction: {
74
81
  label: "Add Coupon",
75
82
  onClick: ({ openCreatePanel }) => openCreatePanel(),
@@ -95,7 +102,10 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
95
102
  onClick: handlers[id],
96
103
  }));
97
104
  },
98
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })),
105
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Expired coupons", tabs: [
106
+ { id: "", label: "Hide expired" },
107
+ { id: "true", label: "Show all" },
108
+ ], value: pendingFilters.showExpired ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showExpired: id })), allId: "" })] })),
99
109
  renderCards: (rows, _view, selectionCtx, isLoading) => {
100
110
  rowsRef.current = rows;
101
111
  return isLoading ? (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { border: "skeleton", className: "animate-pulse", gap: "3", rounded: "xl", padding: "md", children: [_jsx(Div, { className: "h-6 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-4xl", children: _jsx(Text, { color: "faint", children: "No coupons found" }) })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: selectionCtx.openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }));
@@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query";
6
6
  import { Button, Checkbox, ConfirmDeleteModal, Details, Div, Form, FormActions, Input, Label, Select, SideDrawer, Span, Stack, Summary, Text, useToast } from "../../../ui";
7
7
  import { apiClient } from "../../../http";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
- import { PERMISSION_GROUPS, } from "../../auth/permissions/constants";
9
+ import { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatPermLabel, } from "../../auth/permissions/constants";
10
10
  const __O = {
11
11
  yAuto: "overflow-y-auto",
12
12
  };
@@ -32,47 +32,6 @@ const GROUP_OPTIONS = [
32
32
  { label: "Scam Moderator", value: "scam_moderator" },
33
33
  { label: "Custom", value: "custom" },
34
34
  ];
35
- const PERMISSION_DOMAINS = [
36
- { label: "Dashboard", prefix: "admin:dashboard:" },
37
- { label: "Users & Bans", prefix: "admin:users:|admin:user-bans:" },
38
- { label: "Products", prefix: "admin:products:" },
39
- { label: "Orders & Returns", prefix: "admin:orders:|admin:returns:" },
40
- { label: "Stores", prefix: "admin:stores:|admin:store-addresses:" },
41
- { label: "Finance", prefix: "admin:analytics:|admin:payouts:" },
42
- { label: "Catalog", prefix: "admin:categories:|admin:brands:|admin:coupons:|admin:deals:|admin:featured:" },
43
- {
44
- label: "Content",
45
- prefix: "admin:reviews:|admin:blog:|admin:bids:|admin:media:",
46
- },
47
- {
48
- label: "Site / CMS",
49
- prefix: "admin:site:|admin:navigation:|admin:sections:|admin:carousel:|admin:ads:|admin:faqs:|admin:newsletter:|admin:contact:",
50
- },
51
- { label: "Events", prefix: "admin:events:|admin:event-entries:" },
52
- { label: "Support & Safety", prefix: "admin:support-tickets:|admin:scammers:" },
53
- { label: "System", prefix: "admin:sessions:|admin:notifications:|admin:carts:|admin:wishlists:|admin:feature-flags:|admin:copilot:|admin:team:" },
54
- ];
55
- function matchesDomain(perm, prefix) {
56
- return prefix.split("|").some((p) => perm.startsWith(p));
57
- }
58
- function getPermissionsForDomain(prefix) {
59
- const allPerms = Object.values(PERMISSION_GROUPS).flat();
60
- const seen = new Set();
61
- const result = [];
62
- for (const p of allPerms) {
63
- if (matchesDomain(p, prefix) && !seen.has(p)) {
64
- seen.add(p);
65
- result.push(p);
66
- }
67
- }
68
- return result;
69
- }
70
- function formatPermLabel(perm) {
71
- const parts = perm.split(":");
72
- return parts[parts.length - 1]
73
- .replace(/-/g, " ")
74
- .replace(/\b\w/g, (c) => c.toUpperCase());
75
- }
76
35
  function PermissionDomainsPanel({ permissions, togglePerm }) {
77
36
  return (_jsxs(Stack, { gap: "sm", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Permissions", _jsxs(Span, { size: "xs", weight: "normal", className: "ml-2", color: "muted", children: ["(", permissions.size, " selected)"] })] }), _jsx(Div, { className: `divide-y divide-zinc-100 divide-[var(--appkit-color-border)] max-h-[42vh] ${__O.yAuto}`, rounded: "xl", border: "default", children: PERMISSION_DOMAINS.map((domain) => {
78
37
  const domainPerms = getPermissionsForDomain(domain.prefix);
@@ -64,7 +64,7 @@ function MediaBrowser({ onCopy }) {
64
64
  return files;
65
65
  return files.filter((f) => f.name.toLowerCase().includes(q));
66
66
  }, [files, search]);
67
- return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", className: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6", children: [filtered.map((f) => {
67
+ return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", wrapperClassName: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6", children: [filtered.map((f) => {
68
68
  const isImage = (f.contentType ?? "").startsWith("image/");
69
69
  return (_jsxs(Div, { className: "group overflow-hidden", surface: "muted", rounded: "lg", border: "default", children: [_jsx(Row, { color: "muted", textSize: "xs", className: "relative aspect-square", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { align: "center", paddingX: "x-xs", className: "break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-[0.375rem]", children: [_jsx(Text, { className: "truncate text-[10px] font-mono", color: "muted", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", textSize: "xs", className: "mt-1 w-full", onClick: () => onCopy(f.downloadURL), children: ACTIONS.MEDIA["copy-url"].label })] })] }, f.name));
70
70
  }), filtered.length === 0 && !isLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", color: "muted", size: "sm", align: "center", children: "No files found." }))] }), _jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { size: "xs", color: "muted", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));