@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.
- package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
- package/dist/_internal/client/features/maintenance/index.js +1 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
- package/dist/_internal/server/features/auctions/index.d.ts +1 -1
- package/dist/_internal/server/features/auctions/index.js +1 -1
- package/dist/_internal/server/features/auctions/service.js +2 -2
- package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
- package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
- package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
- package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
- package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
- package/dist/_internal/shared/features/auctions/config.js +1 -1
- package/dist/_internal/shared/features/auctions/schema.js +1 -1
- package/dist/_internal/shared/features/cart/schema.js +2 -2
- package/dist/_internal/shared/features/events/schema.js +1 -1
- package/dist/_internal/shared/features/orders/schema.js +1 -1
- package/dist/_internal/shared/features/products/schema.js +3 -3
- package/dist/_internal/shared/features/promotions/schema.js +3 -3
- package/dist/_internal/shared/fees/calculator.d.ts +6 -7
- package/dist/client.d.ts +4 -4
- package/dist/client.js +4 -2
- package/dist/constants/api-endpoints.d.ts +9 -6
- package/dist/constants/api-endpoints.js +3 -2
- package/dist/contracts/client-payment-gateway.d.ts +1 -2
- package/dist/features/about/components/AboutView.d.ts +6 -16
- package/dist/features/about/components/AboutView.js +3 -3
- package/dist/features/about/index.d.ts +2 -1
- package/dist/features/about/schemas/firestore.d.ts +47 -0
- package/dist/features/about/schemas/firestore.js +6 -0
- package/dist/features/about/schemas/index.d.ts +210 -0
- package/dist/features/about/schemas/index.js +45 -0
- package/dist/features/account/hooks/useProfile.js +3 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
- package/dist/features/admin/components/AdminCartsView.js +16 -3
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +14 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
- package/dist/features/admin/components/AdminMediaView.js +1 -1
- package/dist/features/admin/components/AdminNotificationsView.js +31 -19
- package/dist/features/admin/components/AdminOrdersView.js +7 -0
- package/dist/features/admin/components/AdminPayoutsView.js +18 -3
- package/dist/features/admin/components/AdminProductsView.js +17 -2
- package/dist/features/admin/components/AdminReviewsView.js +16 -2
- package/dist/features/admin/components/AdminScammersView.js +7 -0
- package/dist/features/admin/components/AdminSectionsView.js +4 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +44 -2
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
- package/dist/features/admin/components/AdminStoresView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
- package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
- package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
- package/dist/features/admin/constants/filter-tabs.js +55 -27
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/auctions/actions/bid-actions.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/AuctionsListView.js +8 -1
- package/dist/features/auth/components/LoginForm.js +2 -2
- package/dist/features/auth/components/RegisterForm.js +2 -2
- package/dist/features/auth/permissions/constants.d.ts +16 -0
- package/dist/features/auth/permissions/constants.js +53 -0
- package/dist/features/auth/schemas/firestore.js +1 -0
- package/dist/features/blog/components/BlogFilters.js +1 -1
- package/dist/features/cart/schemas/firestore.d.ts +1 -1
- package/dist/features/categories/schemas/firestore.d.ts +1 -1
- package/dist/features/consultation/components/ConsultationForm.js +1 -1
- package/dist/features/events/components/EventFilters.js +2 -0
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +2 -2
- package/dist/features/layout/TitleBarLayout.js +4 -4
- package/dist/features/media/AvatarUpload.js +7 -3
- package/dist/features/media/MediaVideo.js +1 -1
- package/dist/features/media/finalize.d.ts +9 -13
- package/dist/features/media/finalize.js +23 -74
- package/dist/features/media/server.d.ts +1 -1
- package/dist/features/media/server.js +1 -1
- package/dist/features/orders/hooks/useOrders.js +16 -4
- package/dist/features/orders/schemas/firestore.d.ts +5 -5
- package/dist/features/payments/schemas/firestore.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
- package/dist/features/products/components/ArtStickersListView.js +5 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
- package/dist/features/products/components/ProductForm.js +14 -1
- package/dist/features/products/components/ProductsIndexPageView.js +7 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/firestore.d.ts +1 -1
- package/dist/features/products/schemas/product-templates.d.ts +1 -1
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/scams/schemas/index.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/seller/components/SellerAuctionsView.js +27 -2
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutsView.js +24 -10
- package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +1 -1
- package/dist/features/seller/components/SellerProductsView.js +8 -6
- package/dist/features/seller/components/SellerReviewsView.js +27 -6
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreProductsView.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +11 -0
- package/dist/features/stores/schemas/firestore.js +5 -0
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
- package/dist/features/tester/actions/checklist-item-actions.js +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
- package/dist/features/tester/components/TesterHubView.js +34 -21
- package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
- package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
- package/dist/features/tester/schemas/firestore.d.ts +8 -3
- package/dist/features/tester/schemas/firestore.js +6 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
- package/dist/features/tester/utils/phases.d.ts +31 -0
- package/dist/features/tester/utils/phases.js +46 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
- package/dist/features/whatsapp-bot/types/index.d.ts +1 -1
- package/dist/features/wishlist/types/index.d.ts +3 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +15 -7
- package/dist/next/components/NotFoundView.js +2 -1
- package/dist/next/routing/route-map.d.ts +4 -12
- package/dist/next/routing/route-map.js +2 -4
- package/dist/providers/db-firebase/admin.d.ts +1 -0
- package/dist/providers/db-firebase/admin.js +1 -1
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seed/addresses-seed-data.js +18 -18
- package/dist/seed/bids-seed-data.js +4 -4
- package/dist/seed/blog-posts-seed-data.js +82 -82
- package/dist/seed/carousel-slides-seed-data.js +14 -10
- package/dist/seed/carousels-seed-data.js +3 -3
- package/dist/seed/cart-seed-data.js +92 -92
- package/dist/seed/claimed-coupons-seed-data.js +10 -10
- package/dist/seed/conversations-seed-data.js +75 -75
- package/dist/seed/coupon-usage-seed-data.js +12 -12
- package/dist/seed/coupons-seed-data.js +41 -41
- package/dist/seed/events-seed-data.js +105 -105
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
- package/dist/seed/grouped-listings-seed-data.js +79 -143
- package/dist/seed/history-seed-data.js +38 -38
- package/dist/seed/homepage-sections-seed-data.js +9 -9
- package/dist/seed/notifications-seed-data.js +56 -56
- package/dist/seed/offers-seed-data.js +91 -91
- package/dist/seed/orders-seed-data.js +121 -106
- package/dist/seed/payouts-seed-data.js +42 -42
- package/dist/seed/products-auctions-seed-data.js +10 -2
- package/dist/seed/products-standard-seed-data.js +40 -8
- package/dist/seed/reviews-seed-data.js +42 -23
- package/dist/seed/scammers-seed-data.js +22 -22
- package/dist/seed/sessions-seed-data.js +7 -7
- package/dist/seed/shipments-seed-data.js +10 -10
- package/dist/seed/site-settings-seed-data.js +82 -0
- package/dist/seed/store-addresses-seed-data.js +2 -2
- package/dist/seed/store-extensions-seed-data.js +93 -93
- package/dist/seed/stores-seed-data.js +2 -0
- package/dist/seed/support-tickets-seed-data.js +47 -47
- package/dist/seed/users-seed-data.js +55 -89
- package/dist/seed/wishlists-seed-data.js +22 -22
- package/dist/server.d.ts +2 -1
- package/dist/server.js +5 -5
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PageLoader.js +8 -3
- package/dist/ui/components/Select.d.ts +8 -1
- package/dist/ui/components/Select.js +2 -2
- package/dist/ui/components/SiteMark.d.ts +23 -0
- package/dist/ui/components/SiteMark.js +76 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/validation/schemas.d.ts +0 -2
- package/dist/validation/schemas.js +0 -2
- package/package.json +1 -1
- 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
|
-
|
|
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
|
+
}
|
|
@@ -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 })
|
|
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
|
|
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
|
|
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 })),
|
|
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)]" }),
|
|
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):
|
|
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):
|
|
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:
|
|
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 ||
|
|
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
|
-
|
|
69
|
+
const finalizedCropData = { ...pendingCropData, url: downloadURL };
|
|
70
|
+
await onUploadSuccess?.(downloadURL, finalizedCropData);
|
|
67
71
|
setPreviewUrl(downloadURL);
|
|
68
|
-
setCropData(
|
|
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
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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/*
|
|
24
|
+
* Promote a staged tmp/* upload to its canonical media/* path.
|
|
51
25
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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 {
|
|
1
|
+
export { finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "./finalize";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|