@mohasinac/appkit 4.1.1 → 4.1.3

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 (187) 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/maintenance/cloud-logs-data.d.ts +50 -0
  9. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  10. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  11. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  12. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  13. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  14. package/dist/_internal/shared/features/auctions/config.js +1 -1
  15. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  16. package/dist/_internal/shared/features/cart/schema.js +2 -2
  17. package/dist/_internal/shared/features/events/schema.js +1 -1
  18. package/dist/_internal/shared/features/orders/schema.js +1 -1
  19. package/dist/_internal/shared/features/products/schema.js +3 -3
  20. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  21. package/dist/_internal/shared/fees/calculator.d.ts +6 -7
  22. package/dist/client.d.ts +4 -4
  23. package/dist/client.js +4 -2
  24. package/dist/constants/api-endpoints.d.ts +9 -6
  25. package/dist/constants/api-endpoints.js +3 -2
  26. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  27. package/dist/features/about/components/AboutView.d.ts +6 -16
  28. package/dist/features/about/components/AboutView.js +3 -3
  29. package/dist/features/about/index.d.ts +2 -1
  30. package/dist/features/about/schemas/firestore.d.ts +47 -0
  31. package/dist/features/about/schemas/firestore.js +6 -0
  32. package/dist/features/about/schemas/index.d.ts +210 -0
  33. package/dist/features/about/schemas/index.js +45 -0
  34. package/dist/features/account/hooks/useProfile.js +3 -0
  35. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  36. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  37. package/dist/features/admin/components/AdminCartsView.js +16 -3
  38. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  39. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  40. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  41. package/dist/features/admin/components/AdminMediaView.js +1 -1
  42. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  43. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  44. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  45. package/dist/features/admin/components/AdminProductsView.js +17 -2
  46. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  47. package/dist/features/admin/components/AdminScammersView.js +7 -0
  48. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  49. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  50. package/dist/features/admin/components/AdminSiteSettingsView.js +44 -2
  51. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  52. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  53. package/dist/features/admin/components/AdminStoresView.js +1 -1
  54. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  55. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  56. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  57. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  58. package/dist/features/admin/components/index.d.ts +2 -0
  59. package/dist/features/admin/components/index.js +1 -0
  60. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  61. package/dist/features/admin/constants/filter-tabs.js +55 -27
  62. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  63. package/dist/features/auctions/actions/bid-actions.js +1 -1
  64. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  65. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  66. package/dist/features/auth/components/LoginForm.js +2 -2
  67. package/dist/features/auth/components/RegisterForm.js +2 -2
  68. package/dist/features/auth/permissions/constants.d.ts +16 -0
  69. package/dist/features/auth/permissions/constants.js +53 -0
  70. package/dist/features/auth/schemas/firestore.js +1 -0
  71. package/dist/features/blog/components/BlogFilters.js +1 -1
  72. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  73. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  74. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  75. package/dist/features/events/components/EventFilters.js +2 -0
  76. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  77. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  78. package/dist/features/layout/TitleBarLayout.js +4 -4
  79. package/dist/features/media/AvatarUpload.js +7 -3
  80. package/dist/features/media/MediaVideo.js +1 -1
  81. package/dist/features/media/finalize.d.ts +9 -13
  82. package/dist/features/media/finalize.js +23 -74
  83. package/dist/features/media/server.d.ts +1 -1
  84. package/dist/features/media/server.js +1 -1
  85. package/dist/features/orders/hooks/useOrders.js +16 -4
  86. package/dist/features/orders/schemas/firestore.d.ts +5 -5
  87. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  88. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  89. package/dist/features/products/components/ArtStickersListView.js +5 -0
  90. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  91. package/dist/features/products/components/ProductForm.js +14 -1
  92. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  93. package/dist/features/products/constants/action-defs.d.ts +1 -0
  94. package/dist/features/products/constants/action-defs.js +1 -0
  95. package/dist/features/products/schemas/firestore.d.ts +1 -1
  96. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  97. package/dist/features/products/types/index.d.ts +4 -0
  98. package/dist/features/scams/schemas/index.js +2 -2
  99. package/dist/features/search/components/Search.js +3 -3
  100. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  101. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  102. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  103. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  104. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  105. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  106. package/dist/features/seller/components/SellerProductShell.js +1 -1
  107. package/dist/features/seller/components/SellerProductsView.js +8 -6
  108. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  109. package/dist/features/seller/components/index.d.ts +0 -2
  110. package/dist/features/seller/components/index.js +0 -1
  111. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  112. package/dist/features/stores/components/StoreProductsView.js +1 -1
  113. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  114. package/dist/features/stores/schemas/firestore.js +5 -0
  115. package/dist/features/stores/schemas/index.d.ts +2 -2
  116. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  117. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  118. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  119. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  120. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  121. package/dist/features/tester/components/TesterHubView.js +34 -21
  122. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  123. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  124. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  125. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  126. package/dist/features/tester/schemas/firestore.js +6 -0
  127. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  128. package/dist/features/tester/utils/phases.d.ts +31 -0
  129. package/dist/features/tester/utils/phases.js +46 -0
  130. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  131. package/dist/features/whatsapp-bot/types/index.d.ts +1 -1
  132. package/dist/features/wishlist/types/index.d.ts +3 -0
  133. package/dist/index.d.ts +11 -4
  134. package/dist/index.js +15 -7
  135. package/dist/next/components/NotFoundView.js +2 -1
  136. package/dist/next/routing/route-map.d.ts +4 -12
  137. package/dist/next/routing/route-map.js +2 -4
  138. package/dist/providers/db-firebase/admin.d.ts +1 -0
  139. package/dist/providers/db-firebase/admin.js +1 -1
  140. package/dist/schemas/registry.d.ts +2 -2
  141. package/dist/seed/addresses-seed-data.js +18 -18
  142. package/dist/seed/bids-seed-data.js +4 -4
  143. package/dist/seed/blog-posts-seed-data.js +82 -82
  144. package/dist/seed/carousel-slides-seed-data.js +14 -10
  145. package/dist/seed/carousels-seed-data.js +3 -3
  146. package/dist/seed/cart-seed-data.js +92 -92
  147. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  148. package/dist/seed/conversations-seed-data.js +75 -75
  149. package/dist/seed/coupon-usage-seed-data.js +12 -12
  150. package/dist/seed/coupons-seed-data.js +41 -41
  151. package/dist/seed/events-seed-data.js +105 -105
  152. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  153. package/dist/seed/grouped-listings-seed-data.js +79 -143
  154. package/dist/seed/history-seed-data.js +38 -38
  155. package/dist/seed/homepage-sections-seed-data.js +9 -9
  156. package/dist/seed/notifications-seed-data.js +56 -56
  157. package/dist/seed/offers-seed-data.js +91 -91
  158. package/dist/seed/orders-seed-data.js +121 -106
  159. package/dist/seed/payouts-seed-data.js +42 -42
  160. package/dist/seed/products-auctions-seed-data.js +10 -2
  161. package/dist/seed/products-standard-seed-data.js +40 -8
  162. package/dist/seed/reviews-seed-data.js +42 -23
  163. package/dist/seed/scammers-seed-data.js +22 -22
  164. package/dist/seed/sessions-seed-data.js +7 -7
  165. package/dist/seed/shipments-seed-data.js +10 -10
  166. package/dist/seed/site-settings-seed-data.js +82 -0
  167. package/dist/seed/store-addresses-seed-data.js +2 -2
  168. package/dist/seed/store-extensions-seed-data.js +93 -93
  169. package/dist/seed/stores-seed-data.js +2 -0
  170. package/dist/seed/support-tickets-seed-data.js +47 -47
  171. package/dist/seed/users-seed-data.js +55 -89
  172. package/dist/seed/wishlists-seed-data.js +22 -22
  173. package/dist/server.d.ts +2 -1
  174. package/dist/server.js +5 -5
  175. package/dist/styles.css +1 -1
  176. package/dist/tailwind-utilities.css +1 -1
  177. package/dist/ui/components/PageLoader.js +8 -3
  178. package/dist/ui/components/Select.d.ts +8 -1
  179. package/dist/ui/components/Select.js +2 -2
  180. package/dist/ui/components/SiteMark.d.ts +23 -0
  181. package/dist/ui/components/SiteMark.js +76 -0
  182. package/dist/ui/index.d.ts +2 -0
  183. package/dist/ui/index.js +1 -0
  184. package/dist/validation/schemas.d.ts +0 -2
  185. package/dist/validation/schemas.js +0 -2
  186. package/package.json +1 -1
  187. package/scripts/seed-cli.mjs +17 -15
@@ -28,7 +28,14 @@ function buildAuctionFilters(params) {
28
28
  else if (values.length > 1)
29
29
  parts.push(sieveFilter("storeId", SIEVE_OP.EQ, values.join("|")));
30
30
  }
31
- const dateFrom = sp(params, "dateFrom");
31
+ // Mirror AuctionsIndexListing's client-side default (Root Cause pattern —
32
+ // SSR initialData is seeded into React Query with staleTime:Infinity, so if
33
+ // this SSR filter doesn't already exclude ended auctions, the client never
34
+ // refetches and ended auctions leak into the default "Show ended" off view).
35
+ const showEnded = sp(params, "showEnded") === "true";
36
+ const dateFrom = showEnded
37
+ ? sp(params, "dateFrom")
38
+ : sp(params, "dateFrom") || new Date().toISOString();
32
39
  const dateTo = sp(params, "dateTo");
33
40
  if (dateFrom)
34
41
  parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, dateFrom));
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useState } from "react";
5
- import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
5
+ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } from "../../../ui";
6
6
  import { Form } from "../../../ui/components/Form";
7
7
  import { FieldInput } from "../../../ui/forms/FieldInput";
8
8
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
@@ -16,7 +16,7 @@ export function LoginForm({ onSubmit, onGoogleLogin, isLoading = false, error, l
16
16
  password: "",
17
17
  rememberMe: false,
18
18
  });
19
- return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
19
+ return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
20
20
  clearErrors();
21
21
  const parsed = loginSchema.safeParse(values);
22
22
  if (!parsed.success)
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useState } from "react";
5
5
  import { z } from "zod";
6
- import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
6
+ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } from "../../../ui";
7
7
  import { Form } from "../../../ui/components/Form";
8
8
  import { FieldInput } from "../../../ui/forms/FieldInput";
9
9
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
@@ -28,7 +28,7 @@ export function RegisterForm({ onSubmit, isLoading = false, error, success, labe
28
28
  displayName: "",
29
29
  acceptTerms: false,
30
30
  });
31
- return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
31
+ return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
32
32
  clearErrors();
33
33
  const parsed = registerClientSchema.safeParse(values);
34
34
  if (!parsed.success)
@@ -61,3 +61,19 @@ export declare function hasAllPermissions(permissions: Permission[], required: P
61
61
  * Check whether a store's capabilities include a specific capability.
62
62
  */
63
63
  export declare function hasCapability(capabilities: StoreCapability[], required: StoreCapability): boolean;
64
+ /**
65
+ * Groups permission prefixes into human-labeled domains for display —
66
+ * used by the employee-permission editor and the read-only permissions
67
+ * catalog page. `prefix` may hold multiple `|`-separated prefixes when a
68
+ * domain spans more than one resource namespace.
69
+ */
70
+ export declare const PERMISSION_DOMAINS: {
71
+ label: string;
72
+ prefix: string;
73
+ }[];
74
+ /** True when `perm` starts with any of the `|`-separated prefixes in `prefix`. */
75
+ export declare function matchesDomain(perm: string, prefix: string): boolean;
76
+ /** Every distinct permission (across all PERMISSION_GROUPS presets) belonging to a domain. */
77
+ export declare function getPermissionsForDomain(prefix: string): Permission[];
78
+ /** "admin:orders:mark-shipped" -> "Mark Shipped" */
79
+ export declare function formatPermLabel(perm: string): string;
@@ -342,3 +342,56 @@ export function hasAllPermissions(permissions, required) {
342
342
  export function hasCapability(capabilities, required) {
343
343
  return capabilities.includes(required);
344
344
  }
345
+ // ============================================================================
346
+ // PERMISSION DOMAINS — display grouping (S-ADMIN-7, 2026-08-19)
347
+ // ============================================================================
348
+ /**
349
+ * Groups permission prefixes into human-labeled domains for display —
350
+ * used by the employee-permission editor and the read-only permissions
351
+ * catalog page. `prefix` may hold multiple `|`-separated prefixes when a
352
+ * domain spans more than one resource namespace.
353
+ */
354
+ export const PERMISSION_DOMAINS = [
355
+ { label: "Dashboard", prefix: "admin:dashboard:" },
356
+ { label: "Users & Bans", prefix: "admin:users:|admin:user-bans:" },
357
+ { label: "Products", prefix: "admin:products:" },
358
+ { label: "Orders & Returns", prefix: "admin:orders:|admin:returns:" },
359
+ { label: "Stores", prefix: "admin:stores:|admin:store-addresses:" },
360
+ { label: "Finance", prefix: "admin:analytics:|admin:payouts:" },
361
+ { label: "Catalog", prefix: "admin:categories:|admin:brands:|admin:coupons:|admin:deals:|admin:featured:" },
362
+ {
363
+ label: "Content",
364
+ prefix: "admin:reviews:|admin:blog:|admin:bids:|admin:media:",
365
+ },
366
+ {
367
+ label: "Site / CMS",
368
+ prefix: "admin:site:|admin:navigation:|admin:sections:|admin:carousel:|admin:ads:|admin:faqs:|admin:newsletter:|admin:contact:",
369
+ },
370
+ { label: "Events", prefix: "admin:events:|admin:event-entries:" },
371
+ { label: "Support & Safety", prefix: "admin:support-tickets:|admin:scammers:" },
372
+ { label: "System", prefix: "admin:sessions:|admin:notifications:|admin:carts:|admin:wishlists:|admin:feature-flags:|admin:copilot:|admin:team:" },
373
+ ];
374
+ /** True when `perm` starts with any of the `|`-separated prefixes in `prefix`. */
375
+ export function matchesDomain(perm, prefix) {
376
+ return prefix.split("|").some((p) => perm.startsWith(p));
377
+ }
378
+ /** Every distinct permission (across all PERMISSION_GROUPS presets) belonging to a domain. */
379
+ export function getPermissionsForDomain(prefix) {
380
+ const allPerms = Object.values(PERMISSION_GROUPS).flat();
381
+ const seen = new Set();
382
+ const result = [];
383
+ for (const p of allPerms) {
384
+ if (matchesDomain(p, prefix) && !seen.has(p)) {
385
+ seen.add(p);
386
+ result.push(p);
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+ /** "admin:orders:mark-shipped" -> "Mark Shipped" */
392
+ export function formatPermLabel(perm) {
393
+ const parts = perm.split(":");
394
+ return parts[parts.length - 1]
395
+ .replace(/-/g, " ")
396
+ .replace(/\b\w/g, (c) => c.toUpperCase());
397
+ }
@@ -30,6 +30,7 @@ export const DEFAULT_USER_DATA = {
30
30
  emailVerified: false,
31
31
  phoneVerified: false,
32
32
  disabled: false,
33
+ isTester: false,
33
34
  photoURL: null,
34
35
  displayName: null,
35
36
  publicProfile: {
@@ -67,5 +67,5 @@ export function BlogFilters({ table, variant = "admin" }) {
67
67
  const selectedCategory = table.get("category")
68
68
  ? table.get("category").split("|").filter(Boolean)
69
69
  : [];
70
- return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : "") })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true }), _jsx(RangeFilter, { title: t("votesRange"), minValue: table.get("minVotes"), maxValue: table.get("maxVotes"), onMinChange: (v) => table.set("minVotes", v), onMaxChange: (v) => table.set("maxVotes", v), minBound: 0, maxBound: 100000, step: 10, minPlaceholder: t("minVotes"), maxPlaceholder: t("maxVotes"), defaultCollapsed: true })] }));
70
+ return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : "") })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true })] }));
71
71
  }
@@ -50,7 +50,7 @@ export interface CartItemDocument {
50
50
  * ShippingPicker writes this when the buyer selects a provider.
51
51
  */
52
52
  chosenShippingProviderId?: string;
53
- /** Fee in paise for the chosen provider, snapshotted at cart time. */
53
+ /** Fee in decimal rupees for the chosen provider, snapshotted at cart time. */
54
54
  chosenShippingFee?: number;
55
55
  addedAt: Date;
56
56
  updatedAt: Date;
@@ -135,7 +135,7 @@ export interface CategoryDocument extends BaseDocument {
135
135
  * Does NOT update `partOfBundleIds`; used as a discovery surface only.
136
136
  */
137
137
  bundleKind?: "special" | "brand";
138
- /** Discounted bundle price in paise. */
138
+ /** Discounted bundle price in decimal rupees. */
139
139
  bundlePrice?: number;
140
140
  /** Rule resolving the bundle's member products — static list or live query. */
141
141
  bundleQueryRule?: BundleQueryRule;
@@ -43,7 +43,7 @@ export function ConsultationForm({ onSubmit, isPending, concerns = [], }) {
43
43
  }
44
44
  return (_jsxs(Form, { onSubmit: handleSubmit, spacing: "md", children: [_jsx(FieldInput, { name: "name", type: "text", placeholder: "Full name", value: form.name, onChange: (v) => handleValueChange("name", v), required: true }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Email address", value: form.email, onChange: (v) => handleValueChange("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => handleValueChange("phone", v), required: true }), concerns.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: concerns.map((c) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => toggleConcern(c), className: `rounded-full border px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] transition ${form.concern.includes(c)
45
45
  ? "border-primary bg-primary text-white"
46
- : "border-neutral-300 text-neutral-700 hover:border-primary"}`, children: c }, c))) })), _jsxs(Row, { gap: "3", children: [_jsx(FieldInput, { name: "preferredDate", type: "date", value: form.preferredDate, onChange: (v) => handleValueChange("preferredDate", v), required: true, className: "flex-1" }), _jsx(Select, { value: form.preferredTime, onValueChange: (value) => handleValueChange("preferredTime", value), options: TIME_SLOTS.map((t) => ({ value: t, label: t })), className: "flex-1" })] }), _jsx(Select, { value: form.mode, onValueChange: (value) => handleValueChange("mode", value), options: [
46
+ : "border-neutral-300 text-neutral-700 hover:border-primary"}`, children: c }, c))) })), _jsxs(Row, { gap: "3", children: [_jsx(FieldInput, { name: "preferredDate", type: "date", value: form.preferredDate, onChange: (v) => handleValueChange("preferredDate", v), required: true, className: "flex-1" }), _jsx(Select, { value: form.preferredTime, onValueChange: (value) => handleValueChange("preferredTime", value), options: TIME_SLOTS.map((t) => ({ value: t, label: t })), wrapperClassName: "flex-1" })] }), _jsx(Select, { value: form.mode, onValueChange: (value) => handleValueChange("mode", value), options: [
47
47
  { value: "remote", label: "Remote (Video Call)" },
48
48
  { value: "in-person", label: "In-Person" },
49
49
  ] }), _jsx(FieldTextarea, { name: "message", placeholder: "Any additional notes (optional)", value: form.message, onChange: (v) => handleValueChange("message", v), rows: 3 }), _jsx(Button, { type: "submit", isLoading: isPending, disabled: isPending, variant: "primary", className: "w-full", children: isPending ? "Booking..." : "Book Consultation" })] }));
@@ -56,6 +56,8 @@ export function EventFilters({ table, variant = "admin" }) {
56
56
  { value: "poll", label: t("eventTypePoll") },
57
57
  { value: "survey", label: t("eventTypeSurvey") },
58
58
  { value: "feedback", label: t("eventTypeFeedback") },
59
+ { value: "raffle", label: t("eventTypeRaffle") },
60
+ { value: "spin_wheel", label: t("eventTypeSpinWheel") },
59
61
  ];
60
62
  const adminStatusOptions = [
61
63
  { value: "draft", label: t("eventStatusDraft") },
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { THEMED_TEXT_SECONDARY, FLEX_CENTER } from "../../../_internal/shared/styles/themed";
4
- import { Button, Div, Grid, Heading, Row, Section, SiteLogo, Span, Text, TextLink } from "../../../ui";
4
+ import { Button, Div, Grid, Heading, Row, Section, SiteLogo, SiteMark, Span, Stack, Text, TextLink } from "../../../ui";
5
5
  // --- Component ---------------------------------------------------------------
6
6
  export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, ctaLabel = "Shop now", ctaHref, onCtaClick, secondaryCtaLabel = "Browse products", secondaryCtaHref, onSecondaryCtaClick, trustChips = [], isLoading = false, brandLogoText = "", className = "", }) {
7
7
  const themed = { textSecondary: THEMED_TEXT_SECONDARY };
@@ -9,5 +9,5 @@ export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, cta
9
9
  if (isLoading) {
10
10
  return (_jsx(Section, { className: `relative overflow-hidden md:py-[6rem] ${className}`, paddingY: "y-4xl", paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
11
11
  }
12
- return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsx(Row, { centered: true, className: `absolute inset-0 ${flex.center} px-[2.5rem]`, children: _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "hero" }) })] }) })] }) })] }));
12
+ return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} px-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "hero" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "hero" })] })] }) })] }) })] }));
13
13
  }
@@ -61,9 +61,9 @@ export interface TitleBarLayoutProps {
61
61
  * TitleBarLayout — generic top sticky title-bar shell.
62
62
  *
63
63
  * Layout:
64
- * TB1 (h-14, all screens): logo | navSlot? | [secondary actions lg+] | search | deals | theme | hamburger
64
+ * TB1 (h-14, all screens): wordmark (+ mark on mobile) | centred mark (md+) | [secondary actions lg+] | search | deals | theme | hamburger
65
65
  * TB2 (h-10, below lg only): wishlist | cart | profile — mirrors what TB1 hides below lg
66
66
  *
67
67
  * Receives all domain data as props — zero domain imports.
68
68
  */
69
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
69
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot: _navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, Span } from "../../ui";
3
+ import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
4
4
  /** Shared icon-button class for all action buttons in the title bar. */
5
5
  const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
6
6
  /** Badge counter class for wishlist/cart counts. */
@@ -9,12 +9,12 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
9
9
  * TitleBarLayout — generic top sticky title-bar shell.
10
10
  *
11
11
  * Layout:
12
- * TB1 (h-14, all screens): logo | navSlot? | [secondary actions lg+] | search | deals | theme | hamburger
12
+ * TB1 (h-14, all screens): wordmark (+ mark on mobile) | centred mark (md+) | [secondary actions lg+] | search | deals | theme | hamburger
13
13
  * TB2 (h-10, below lg only): wishlist | cart | profile — mirrors what TB1 hides below lg
14
14
  *
15
15
  * Receives all domain data as props — zero domain imports.
16
16
  */
17
- export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
17
+ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot: _navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
18
18
  // ── Element builders ────────────────────────────────────────────────────────
19
19
  const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
20
20
  const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
@@ -33,7 +33,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
33
33
  const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
34
34
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
35
35
  // ── Render ───────────────────────────────────────────────────────────────────
36
- return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, size: "md" }) }) }), siteLogoUrl ? (_jsx(Row, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, size: "md" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex ml-auto", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
36
+ return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
37
37
  ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
38
38
  : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, tourBtn, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { border: "subtle", as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t px-[var(--appkit-space-1)]", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
39
39
  }
@@ -17,7 +17,10 @@ export function AvatarUpload({ currentPhotoURL, currentCropData, userId, display
17
17
  readyToSave: labels?.readyToSave ?? "Ready to save",
18
18
  };
19
19
  const [previewUrl, setPreviewUrl] = useState(currentPhotoURL || null);
20
- const [cropData, setCropData] = useState(currentCropData || null);
20
+ const [cropData, setCropData] = useState(currentCropData ||
21
+ (currentPhotoURL
22
+ ? { url: currentPhotoURL, position: { x: 50, y: 50 }, zoom: 1 }
23
+ : null));
21
24
  const [tempImageUrl, setTempImageUrl] = useState(null);
22
25
  const [showCropModal, setShowCropModal] = useState(false);
23
26
  const [pendingFile, setPendingFile] = useState(null);
@@ -63,9 +66,10 @@ export function AvatarUpload({ currentPhotoURL, currentCropData, userId, display
63
66
  firstName,
64
67
  lastName,
65
68
  });
66
- await onUploadSuccess?.(downloadURL, pendingCropData);
69
+ const finalizedCropData = { ...pendingCropData, url: downloadURL };
70
+ await onUploadSuccess?.(downloadURL, finalizedCropData);
67
71
  setPreviewUrl(downloadURL);
68
- setCropData(pendingCropData);
72
+ setCropData(finalizedCropData);
69
73
  setPendingCropData(null);
70
74
  setPendingUploadFile(null);
71
75
  showToast("Avatar uploaded", "success");
@@ -38,7 +38,7 @@ export function MediaVideo({ src, thumbnailUrl, alt = "Video", controls = true,
38
38
  const { data: siteSettings } = useSiteSettings();
39
39
  const effectiveWatermark = watermark === null
40
40
  ? null
41
- : (watermark ?? siteSettings?.watermark ?? null);
41
+ : (watermark ?? siteSettings?.effectiveWatermark ?? siteSettings?.watermark ?? null);
42
42
  if (!resolvedSrc) {
43
43
  return (_jsx(Row
44
44
  // audit-color-pair-function-ok: deliberate faded placeholder-icon shade, not a readability regression
@@ -1,19 +1,15 @@
1
1
  import type { MediaField } from "./types";
2
2
  /**
3
- * Extract a storage object path from public/download URLs for the same bucket.
4
- */
5
- export declare function extractStoragePathFromUrl(url: string, bucketName: string): string | null;
6
- /**
7
- * Promote staged tmp/* uploads to canonical media/* path.
3
+ * Promote a staged tmp/* upload to its canonical media/* path.
8
4
  *
9
- * Handles two URL formats:
10
- * 1. `/media/{shortId}` the new SEO-friendly short-ID format.
11
- * Looks up the asset in the mediaAssets Firestore collection, moves the
12
- * file from tmp/ media/, updates the storagePath, and returns the same
13
- * URL unchanged (the URL is stable across the move).
14
- * 2. Raw GCS URLs (`https://storage.googleapis.com/...` or Firebase Storage URLs)
15
- * legacy path kept for backward compatibility; moves the file and returns a
16
- * /media/{finalStoragePath} proxy URL.
5
+ * Every producer of these URLs funnels through POST /api/media/finalize,
6
+ * which always returns a single-segment `/media/{shortId}` proxy URL (see
7
+ * that route's `downloadURL` construction) so that's the only shape this
8
+ * ever needs to handle. Looks up the asset in the mediaAssets Firestore
9
+ * collection, moves the file from tmp/ media/, updates the storagePath,
10
+ * and returns the same URL unchanged (the URL is stable across the move).
11
+ * Any other input (already-finalized `/media/...` path, or a URL that isn't
12
+ * a `/media/` proxy URL at all) is returned as-is — nothing to promote.
17
13
  */
18
14
  export declare function finalizeStagedMediaUrl(url: string): Promise<string>;
19
15
  export declare function finalizeStagedMediaField(url: string | null | undefined): Promise<string | undefined>;
@@ -1,35 +1,9 @@
1
1
  import { normalizeError } from "../../errors/normalize";
2
2
  import { getAdminStorage } from "../../providers/db-firebase";
3
- import { FIREBASE_STORAGE_HOST, GCS_HOST } from "../../utils/media-url";
4
3
  import { mediaAssetsRepository } from "./repository/media-assets.repository";
5
4
  const TMP_MEDIA_PREFIX = "tmp/";
6
5
  const FINAL_MEDIA_PREFIX = "media/";
7
6
  const PROXY_URL_PREFIX = "/media/";
8
- /**
9
- * Extract a storage object path from public/download URLs for the same bucket.
10
- */
11
- export function extractStoragePathFromUrl(url, bucketName) {
12
- try {
13
- const parsed = new URL(url);
14
- if (parsed.hostname === GCS_HOST) {
15
- const parts = parsed.pathname.split("/").filter(Boolean);
16
- if (parts.length >= 2 && parts[0] === bucketName) {
17
- return parts.slice(1).join("/");
18
- }
19
- }
20
- if (parsed.hostname === FIREBASE_STORAGE_HOST) {
21
- const match = parsed.pathname.match(/\/v0\/b\/([^/]+)\/o\/(.+)$/);
22
- if (match && match[1] === bucketName) {
23
- return decodeURIComponent(match[2]);
24
- }
25
- }
26
- return null;
27
- }
28
- catch (_err) {
29
- void normalizeError(_err);
30
- return null;
31
- }
32
- }
33
7
  /**
34
8
  * Move a file from tmp/ to media/ in Firebase Storage.
35
9
  * Returns the new storage path.
@@ -47,60 +21,35 @@ async function moveToFinalPath(sourcePath) {
47
21
  return destinationPath;
48
22
  }
49
23
  /**
50
- * Promote staged tmp/* uploads to canonical media/* path.
24
+ * Promote a staged tmp/* upload to its canonical media/* path.
51
25
  *
52
- * Handles two URL formats:
53
- * 1. `/media/{shortId}` the new SEO-friendly short-ID format.
54
- * Looks up the asset in the mediaAssets Firestore collection, moves the
55
- * file from tmp/ media/, updates the storagePath, and returns the same
56
- * URL unchanged (the URL is stable across the move).
57
- * 2. Raw GCS URLs (`https://storage.googleapis.com/...` or Firebase Storage URLs)
58
- * legacy path kept for backward compatibility; moves the file and returns a
59
- * /media/{finalStoragePath} proxy URL.
26
+ * Every producer of these URLs funnels through POST /api/media/finalize,
27
+ * which always returns a single-segment `/media/{shortId}` proxy URL (see
28
+ * that route's `downloadURL` construction) so that's the only shape this
29
+ * ever needs to handle. Looks up the asset in the mediaAssets Firestore
30
+ * collection, moves the file from tmp/ media/, updates the storagePath,
31
+ * and returns the same URL unchanged (the URL is stable across the move).
32
+ * Any other input (already-finalized `/media/...` path, or a URL that isn't
33
+ * a `/media/` proxy URL at all) is returned as-is — nothing to promote.
60
34
  */
61
35
  export async function finalizeStagedMediaUrl(url) {
62
- // ── Path 1: /media/{shortId} proxy URLs (new system) ──────────────────────
63
- if (url.startsWith(PROXY_URL_PREFIX)) {
64
- const afterPrefix = url.slice(PROXY_URL_PREFIX.length);
65
- // Single-segment = shortId. Multi-segment = legacy raw path embedded in URL.
66
- if (!afterPrefix.includes("/")) {
67
- const shortId = afterPrefix;
68
- try {
69
- const asset = await mediaAssetsRepository.findById(shortId);
70
- if (asset && asset.status === "staged" && asset.storagePath.startsWith(TMP_MEDIA_PREFIX)) {
71
- const finalPath = await moveToFinalPath(asset.storagePath);
72
- await mediaAssetsRepository.promoteToFinalized(shortId, finalPath);
73
- }
74
- }
75
- catch (_err) {
76
- void normalizeError(_err);
77
- // Non-fatal: URL is still valid; proxy will serve whatever storagePath points to.
78
- }
79
- return url; // URL is stable — same before and after the file move
80
- }
81
- // Multi-segment legacy: /media/tmp/folder/uid/file.jpg → move + return proxy URL
82
- const rawPath = afterPrefix;
83
- if (rawPath.startsWith(TMP_MEDIA_PREFIX)) {
84
- try {
85
- const finalPath = await moveToFinalPath(rawPath);
86
- return `${PROXY_URL_PREFIX}${finalPath}`;
87
- }
88
- catch (_err) {
89
- void normalizeError(_err);
90
- return url;
91
- }
36
+ if (!url.startsWith(PROXY_URL_PREFIX))
37
+ return url;
38
+ const shortId = url.slice(PROXY_URL_PREFIX.length);
39
+ if (shortId.includes("/"))
40
+ return url; // not a shortId — nothing to promote
41
+ try {
42
+ const asset = await mediaAssetsRepository.findById(shortId);
43
+ if (asset && asset.status === "staged" && asset.storagePath.startsWith(TMP_MEDIA_PREFIX)) {
44
+ const finalPath = await moveToFinalPath(asset.storagePath);
45
+ await mediaAssetsRepository.promoteToFinalized(shortId, finalPath);
92
46
  }
93
- return url; // already at media/ or other path — no-op
94
47
  }
95
- // ── Path 2: Legacy raw GCS URLs ────────────────────────────────────────────
96
- const storage = getAdminStorage();
97
- const bucket = storage.bucket();
98
- const sourcePath = extractStoragePathFromUrl(url, bucket.name);
99
- if (!sourcePath || !sourcePath.startsWith(TMP_MEDIA_PREFIX)) {
100
- return url;
48
+ catch (_err) {
49
+ void normalizeError(_err);
50
+ // Non-fatal: URL is still valid; proxy will serve whatever storagePath points to.
101
51
  }
102
- const finalPath = await moveToFinalPath(sourcePath);
103
- return `${PROXY_URL_PREFIX}${finalPath}`;
52
+ return url; // URL is stable — same before and after the file move
104
53
  }
105
54
  export async function finalizeStagedMediaField(url) {
106
55
  if (!url)
@@ -1 +1 @@
1
- export { extractStoragePathFromUrl, finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "./finalize";
1
+ export { finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "./finalize";
@@ -1 +1 @@
1
- export { extractStoragePathFromUrl, finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "./finalize";
1
+ export { finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "./finalize";
@@ -1,12 +1,17 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
2
  import { apiClient } from "../../../http";
3
- import { ORDER_ENDPOINTS } from "../../../constants/api-endpoints";
3
+ import { ORDER_ENDPOINTS, ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
4
4
  export function useOrders(params = {}, opts) {
5
5
  const sp = new URLSearchParams();
6
6
  if (params.userId)
7
7
  sp.set("userId", params.userId);
8
+ // Wire key is "status", not "orderStatus" — matches what /api/user/orders
9
+ // (the only real handler backing this hook's default endpoint) reads via
10
+ // getStringParam(searchParams, "status"). The OrderListParams prop stays
11
+ // named `orderStatus` since existing call sites pass it that way; only the
12
+ // serialized query-string key changes.
8
13
  if (params.orderStatus)
9
- sp.set("orderStatus", params.orderStatus);
14
+ sp.set("status", params.orderStatus);
10
15
  if (params.paymentStatus)
11
16
  sp.set("paymentStatus", params.paymentStatus);
12
17
  if (params.sort)
@@ -16,7 +21,10 @@ export function useOrders(params = {}, opts) {
16
21
  if (params.perPage)
17
22
  sp.set("perPage", String(params.perPage));
18
23
  const qs = sp.toString();
19
- const endpoint = opts?.endpoint ?? `${ORDER_ENDPOINTS.LIST}${qs ? `?${qs}` : ""}`;
24
+ // ORDER_ENDPOINTS.LIST ("/api/orders") has no GET route handler every
25
+ // real caller of this hook wants the current user's own orders, served by
26
+ // /api/user/orders. Default here, not ORDER_ENDPOINTS.LIST.
27
+ const endpoint = opts?.endpoint ?? `${ACCOUNT_ENDPOINTS.ORDERS}${qs ? `?${qs}` : ""}`;
20
28
  const query = useQuery({
21
29
  queryKey: ["orders", qs],
22
30
  queryFn: () => apiClient.get(endpoint),
@@ -32,7 +40,11 @@ export function useOrders(params = {}, opts) {
32
40
  };
33
41
  }
34
42
  export function useOrder(id, opts) {
35
- const endpoint = opts?.endpoint ?? ORDER_ENDPOINTS.BY_ID(id);
43
+ // Same fix as useOrders() above — ORDER_ENDPOINTS.BY_ID ("/api/orders/{id}")
44
+ // has no route.ts handler (only [id]/code, [id]/invoice, [id]/payment-proof,
45
+ // [id]/refund, [id]/dispute subroutes exist). The real single-order handler
46
+ // for the current user is /api/user/orders/[id].
47
+ const endpoint = opts?.endpoint ?? ACCOUNT_ENDPOINTS.ORDER_BY_ID(id);
36
48
  const query = useQuery({
37
49
  queryKey: ["orders", id],
38
50
  queryFn: () => apiClient.get(endpoint),
@@ -63,7 +63,7 @@ export interface EmiInstallment {
63
63
  /** 1-based installment number. */
64
64
  index: number;
65
65
  dueDate: Date;
66
- /** Installment amount in paise (principal share + surcharge share for this installment). */
66
+ /** Installment amount in decimal rupees (principal share + surcharge share for this installment). */
67
67
  amount: number;
68
68
  status: EmiInstallmentStatus;
69
69
  paidAt?: Date;
@@ -116,7 +116,7 @@ export interface OrderDocumentItem {
116
116
  export interface OrderRefundEvent {
117
117
  refundId: string;
118
118
  type: RefundType;
119
- /** Amount in paise. */
119
+ /** Amount in decimal rupees. */
120
120
  amount: number;
121
121
  /** ProductIds / itemIds that were refunded (for partial refunds). */
122
122
  itemIds?: string[];
@@ -195,12 +195,12 @@ export interface OrderDocument extends BaseDocument {
195
195
  emiEnabled?: boolean;
196
196
  /** Number of monthly installments the buyer chose (2–6). */
197
197
  emiTenureMonths?: number;
198
- /** Down payment collected at checkout, in paise (emiTokenPercent × seller subtotal). */
198
+ /** Down payment collected at checkout, in decimal rupees (emiTokenPercent × seller subtotal). */
199
199
  emiTokenAmount?: number;
200
- /** Extra cost of choosing EMI over full payment, in paise — split between platform and seller. */
200
+ /** Extra cost of choosing EMI over full payment, in decimal rupees — split between platform and seller. */
201
201
  emiSurchargeAmount?: number;
202
202
  emiInstallments?: EmiInstallment[];
203
- /** Sum of unpaid installment amounts, in paise. 0 once all installments are paid. */
203
+ /** Sum of unpaid installment amounts, in decimal rupees. 0 once all installments are paid. */
204
204
  emiRemainingBalance?: number;
205
205
  /** True once every installment's status is "paid". Gates shipment unless the product's `allowShipBeforeEmiComplete` flag is set. */
206
206
  emiComplete?: boolean;