@mohasinac/appkit 4.30.17 → 4.30.19
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/digital-code/CodeRevealPanel.js +5 -2
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -2
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +5 -2
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +11 -3
- package/dist/_internal/client/hand-mode/HandModeProvider.js +2 -0
- package/dist/_internal/server/features/auctions/actions.js +14 -3
- package/dist/_internal/server/features/auctions/service.js +1 -1
- package/dist/_internal/server/features/blog/data.js +2 -2
- package/dist/_internal/server/features/blog/service.js +3 -3
- package/dist/_internal/server/features/brands/data.js +1 -1
- package/dist/_internal/server/features/brands/service.js +3 -3
- package/dist/_internal/server/features/bundles/actions.js +6 -1
- package/dist/_internal/server/features/bundles/data.js +21 -4
- package/dist/_internal/server/features/cart/data.js +3 -1
- package/dist/_internal/server/features/cart/service.js +13 -2
- package/dist/_internal/server/features/categories/data.js +12 -3
- package/dist/_internal/server/features/events/actions.js +17 -4
- package/dist/_internal/server/features/events/data.js +1 -1
- package/dist/_internal/server/features/events/service.js +1 -1
- package/dist/_internal/server/features/grouped/data.js +7 -2
- package/dist/_internal/server/features/item-requests/data.js +1 -1
- package/dist/_internal/server/features/lottery/actions.js +2 -2
- package/dist/_internal/server/features/lottery/data.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +7 -7
- package/dist/_internal/server/features/orders/data.d.ts +1 -1
- package/dist/_internal/server/features/orders/data.js +14 -2
- package/dist/_internal/server/features/orders/service.js +1 -1
- package/dist/_internal/server/features/pre-orders/service.js +1 -1
- package/dist/_internal/server/features/prize-draws/actions.js +1 -1
- package/dist/_internal/server/features/products/data.js +7 -2
- package/dist/_internal/server/features/products/service.js +1 -1
- package/dist/_internal/server/features/promotions/actions.js +1 -1
- package/dist/_internal/server/features/promotions/data.js +1 -1
- package/dist/_internal/server/features/promotions/service.js +1 -1
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/reviews/data.js +6 -1
- package/dist/_internal/server/features/reviews/og-data.js +1 -1
- package/dist/_internal/server/features/shared/listing-data-factory.js +7 -2
- package/dist/_internal/server/features/stores/data.js +1 -1
- package/dist/_internal/server/features/sublisting-categories/data.js +1 -1
- package/dist/_internal/server/jobs/core/auctionSettlement.js +6 -1
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +7 -15
- package/dist/_internal/server/jobs/core/revenueRollup.js +7 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +27 -2
- package/dist/client/api/useApiQuery.js +17 -6
- package/dist/client.d.ts +1 -0
- package/dist/client.js +3 -0
- package/dist/contracts/client-realtime.d.ts +6 -0
- package/dist/contracts/realtime-session.d.ts +12 -12
- package/dist/contracts/realtime-session.js +72 -77
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/features/about/components/DeveloperView.js +7 -2
- package/dist/features/about/components/PolicyPageView.js +9 -1
- package/dist/features/about/components/PublicProfileView.js +23 -5
- package/dist/features/account/components/AddressForm.js +1 -1
- package/dist/features/account/components/NotificationBell.js +4 -3
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOffersPanel.js +1 -1
- package/dist/features/admin/components/AdminFulfillmentView.js +5 -2
- package/dist/features/admin/components/AdminMediaView.js +3 -3
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +13 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +6 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -1
- package/dist/features/auth/actions/profile-actions.js +4 -3
- package/dist/features/auth/components/ForgotPasswordView.js +1 -1
- package/dist/features/auth/components/LoginForm.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +1 -1
- package/dist/features/auth/components/ResetPasswordView.js +1 -1
- package/dist/features/auth/hooks/useAuth.js +3 -0
- package/dist/features/blog/repository/blog.repository.js +3 -1
- package/dist/features/cart/actions/cart-actions.js +13 -4
- package/dist/features/cart/utils/pending-ops.js +1 -1
- package/dist/features/categories/components/BrandDetailPageView.js +28 -12
- package/dist/features/categories/components/CategoryDetailPageView.js +37 -20
- package/dist/features/categories/repository/categories.repository.js +3 -1
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +10 -1
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +1 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +10 -2
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -1
- package/dist/features/events/components/EventBanner.js +5 -2
- package/dist/features/events/components/EventOfferCard.js +3 -1
- package/dist/features/events/components/EventPollWidget.js +1 -1
- package/dist/features/events/components/SpinWheelView.js +3 -2
- package/dist/features/grouped/components/GroupedListingEditorView.js +2 -1
- package/dist/features/history/hooks/useHistory.js +29 -27
- package/dist/features/homepage/components/AdSlot.js +3 -1
- package/dist/features/homepage/components/GoogleReviewsSection.js +8 -2
- package/dist/features/homepage/components/MarketplaceHomepageView.js +34 -10
- package/dist/features/homepage/components/SocialFeedSection.js +4 -1
- package/dist/features/homepage/lib/live-stats.js +8 -1
- package/dist/features/homepage/repository/carousel.repository.js +2 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +4 -1
- package/dist/features/media/finalize.js +3 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/modals/VideoThumbnailSelector.js +5 -2
- package/dist/features/media/modals/VideoTrimModal.js +5 -2
- package/dist/features/media/upload/MediaUploadField.js +2 -2
- package/dist/features/messages/hooks/useConversation.js +18 -4
- package/dist/features/messages/hooks/useConversations.js +25 -8
- package/dist/features/orders/components/RefundRequestView.js +5 -2
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -1
- package/dist/features/pre-orders/components/PreOrderActionsClient.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +4 -1
- package/dist/features/products/components/ProductDetailPageView.js +8 -2
- package/dist/features/products/components/ShareButton.js +3 -1
- package/dist/features/products/repository/loadProductFeatures.js +10 -2
- package/dist/features/products/repository/products.repository.js +2 -1
- package/dist/features/promotions/components/CouponCard.js +3 -1
- package/dist/features/reviews/actions/review-actions.js +8 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +13 -3
- package/dist/features/reviews/components/ReviewDetailShell.js +3 -1
- package/dist/features/scams/actions/scam-actions.d.ts +9 -1
- package/dist/features/scams/actions/scam-actions.js +52 -12
- package/dist/features/scams/components/SellerTrustBadge.d.ts +1 -1
- package/dist/features/scams/components/SellerTrustBadge.js +13 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +6 -1
- package/dist/features/seller/components/FulfillmentView.js +5 -2
- package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
- package/dist/features/seller/components/PrintCenterView.js +3 -1
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +3 -3
- package/dist/features/seller/components/SellerPayoutMethodsView.js +2 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +5 -0
- package/dist/features/seller/components/SellerProductShell.js +15 -5
- package/dist/features/seller/components/SellerProductsView.js +24 -10
- package/dist/features/seller/components/SellerReviewsView.js +47 -16
- package/dist/features/seller/components/SellerShippingConfigsView.js +5 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +3 -2
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +12 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +16 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
- package/dist/features/wishlist/hooks/useWishlistToggle.js +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -2
- package/dist/next/api/routeHandler.js +3 -1
- package/dist/providers/auth-firebase/session.js +3 -1
- package/dist/providers/db-firebase/admin-app-lite.js +8 -8
- package/dist/providers/db-firebase/admin.js +12 -12
- package/dist/providers/db-firebase/browser-stub.d.ts +1 -11
- package/dist/providers/db-firebase/browser-stub.js +6 -11
- package/dist/providers/db-firebase/index.d.ts +0 -1
- package/dist/providers/db-firebase/index.js +0 -1
- package/dist/providers/db-firebase/rtdb-paths.d.ts +15 -6
- package/dist/providers/db-firebase/rtdb-paths.js +15 -6
- package/dist/providers/db-firebase/rtdb-url.d.ts +27 -0
- package/dist/providers/db-firebase/rtdb-url.js +50 -0
- package/dist/providers/firebase-client/realtime.d.ts +20 -0
- package/dist/providers/firebase-client/realtime.js +34 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/react/hooks/use-action-dispatch.js +8 -2
- package/dist/react/hooks/useFormStatePreservation.js +3 -1
- package/dist/react/hooks/useRealtimeEvent.js +20 -9
- package/dist/ui/components/toolbar-collapse.js +1 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { normalizeError } from "../../../../errors/normalize";
|
|
4
|
+
import { toUserMessage } from "../../../../errors/error-display-map";
|
|
4
5
|
import { useState } from "react";
|
|
5
6
|
import { Button, Div, Row, Span, Stack, Text } from "../../../../ui";
|
|
6
7
|
const __P = {
|
|
@@ -19,8 +20,10 @@ export function CodeRevealPanel({ orderId, redemptionInstructions, fetchCode, })
|
|
|
19
20
|
setRevealed(data);
|
|
20
21
|
}
|
|
21
22
|
catch (e) {
|
|
22
|
-
|
|
23
|
-
setError(
|
|
23
|
+
const normalized = normalizeError(e);
|
|
24
|
+
setError(toUserMessage(normalized.code, undefined, {
|
|
25
|
+
fallback: "Could not retrieve your code. Please try again.",
|
|
26
|
+
}));
|
|
24
27
|
}
|
|
25
28
|
finally {
|
|
26
29
|
setPending(false);
|
|
@@ -69,7 +69,7 @@ function useResponsiveDrawer(storageKey) {
|
|
|
69
69
|
localStorage.setItem(storageKey, "true");
|
|
70
70
|
}
|
|
71
71
|
catch (_err) {
|
|
72
|
-
void normalizeError(_err);
|
|
72
|
+
void normalizeError(_err); /* localStorage unavailable (private-browse / quota) — the sidebar still opens, only the preference is not remembered */
|
|
73
73
|
}
|
|
74
74
|
setDesktopOpen(true);
|
|
75
75
|
}
|
|
@@ -84,7 +84,7 @@ function useResponsiveDrawer(storageKey) {
|
|
|
84
84
|
localStorage.setItem(storageKey, "false");
|
|
85
85
|
}
|
|
86
86
|
catch (_err) {
|
|
87
|
-
void normalizeError(_err);
|
|
87
|
+
void normalizeError(_err); /* localStorage unavailable (private-browse / quota) — the sidebar still closes, only the preference is not remembered */
|
|
88
88
|
}
|
|
89
89
|
setDesktopOpen(false);
|
|
90
90
|
}
|
|
@@ -105,7 +105,7 @@ function useResponsiveDrawer(storageKey) {
|
|
|
105
105
|
localStorage.setItem(storageKey, String(next));
|
|
106
106
|
}
|
|
107
107
|
catch (_err) {
|
|
108
|
-
void normalizeError(_err);
|
|
108
|
+
void normalizeError(_err); /* localStorage unavailable (private-browse / quota) — the toggle still applies, only the preference is not remembered */
|
|
109
109
|
}
|
|
110
110
|
return next;
|
|
111
111
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useTransition } from "react";
|
|
4
4
|
import { normalizeError } from "../../../../errors/normalize";
|
|
5
|
+
import { toUserMessage } from "../../../../errors/error-display-map";
|
|
5
6
|
import { Stack, Row, Text, Heading, Section, Container, Button, DataTable, } from "../../../../ui";
|
|
6
7
|
import { Form, FieldInput, FieldSelect, Input } from "../../../../ui";
|
|
7
8
|
import { lotteryConfigFormSchema } from "../../../shared/features/lottery/config-form";
|
|
@@ -77,8 +78,8 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
|
77
78
|
await onSubmit({ title, description, type: "lottery", lotteryConfig: parsed.data });
|
|
78
79
|
}
|
|
79
80
|
catch (err) {
|
|
80
|
-
|
|
81
|
-
setError(
|
|
81
|
+
const e = normalizeError(err);
|
|
82
|
+
setError(toUserMessage(e.code, undefined, { fallback: "Failed to save lottery." }));
|
|
82
83
|
}
|
|
83
84
|
});
|
|
84
85
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { normalizeError } from "../../../../../errors/normalize";
|
|
4
|
+
import { toUserMessage } from "../../../../../errors/error-display-map";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import { Button, Div, Heading, Input, Li, Section, Span, Table, Tbody, Td, Text, Th, Thead, Tr, Ul } from "@mohasinac/appkit/client";
|
|
6
7
|
import { Select } from "@mohasinac/appkit/ui";
|
|
@@ -29,8 +30,10 @@ export function AnalysisRunnerView() {
|
|
|
29
30
|
setReport(body.data);
|
|
30
31
|
}
|
|
31
32
|
catch (err) {
|
|
32
|
-
|
|
33
|
-
setError(
|
|
33
|
+
const e = normalizeError(err);
|
|
34
|
+
setError(toUserMessage(e.code, undefined, {
|
|
35
|
+
fallback: "Could not run the analysis. Check the server logs for detail.",
|
|
36
|
+
}));
|
|
34
37
|
}
|
|
35
38
|
finally {
|
|
36
39
|
setRunning(false);
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Button, Div, Heading, Span, Table, Tbody, Td, Text, Th, Thead, Tr } from "@mohasinac/appkit/client";
|
|
5
5
|
import { ADMIN_ENDPOINTS } from "../../../../../constants/api-endpoints";
|
|
6
6
|
import { normalizeError } from "../../../../../errors/normalize";
|
|
7
|
+
import { toUserMessage } from "../../../../../errors/error-display-map";
|
|
7
8
|
const BORDER_STYLE = "1px solid var(--appkit-color-border)";
|
|
8
9
|
const COLUMNS = [
|
|
9
10
|
{ key: "timestampFmt", label: "When", width: "180px" },
|
|
@@ -38,8 +39,10 @@ export function CloudLogsListView({ title, subtitle, initialEntries, initialNext
|
|
|
38
39
|
setNextPageToken(body.data.nextPageToken);
|
|
39
40
|
}
|
|
40
41
|
catch (err) {
|
|
41
|
-
|
|
42
|
-
setError(
|
|
42
|
+
const e = normalizeError(err);
|
|
43
|
+
setError(toUserMessage(e.code, undefined, {
|
|
44
|
+
fallback: "Could not load more log entries. Please try again.",
|
|
45
|
+
}));
|
|
43
46
|
}
|
|
44
47
|
finally {
|
|
45
48
|
setLoadingMore(false);
|
|
@@ -51,5 +54,10 @@ export function CloudLogsListView({ title, subtitle, initialEntries, initialNext
|
|
|
51
54
|
borderBottom: BORDER_STYLE,
|
|
52
55
|
width: c.width,
|
|
53
56
|
whiteSpace: "nowrap",
|
|
54
|
-
}, children: c.label }, c.key))) }) }), _jsx(Tbody, { children: entries.length === 0 ? (_jsx(Tr, { children: _jsx(Td, { colSpan: COLUMNS.length, style: { padding: "2rem", textAlign: "center", color: "var(--appkit-color-text-muted)" }, children: "No log entries in the selected window." }) })) : (
|
|
57
|
+
}, children: c.label }, c.key))) }) }), _jsx(Tbody, { children: entries.length === 0 ? (_jsx(Tr, { children: _jsx(Td, { colSpan: COLUMNS.length, style: { padding: "2rem", textAlign: "center", color: "var(--appkit-color-text-muted)" }, children: "No log entries in the selected window." }) })) : (
|
|
58
|
+
// Named `entry`, not `e`: this is a Cloud Logging record, not a
|
|
59
|
+
// caught error, and rendering its `.message` is the entire point
|
|
60
|
+
// of this admin viewer. The short name read as the defect
|
|
61
|
+
// audit-raw-error-text exists to catch.
|
|
62
|
+
entries.map((entry) => (_jsxs(Tr, { style: { borderBottom: "1px solid var(--appkit-color-border-subtle)" }, children: [_jsx(Td, { style: { padding: "0.5rem 0.75rem", verticalAlign: "top", whiteSpace: "nowrap" }, children: fmt(entry.timestamp) }), _jsx(Td, { style: { padding: "0.5rem 0.75rem", verticalAlign: "top" }, children: entry.severity }), _jsx(Td, { style: { padding: "0.5rem 0.75rem", verticalAlign: "top" }, children: entry.resourceType }), _jsx(Td, { style: { padding: "0.5rem 0.75rem", verticalAlign: "top", fontFamily: "monospace" }, children: entry.message })] }, entry.id)))) })] }) }), _jsx(Div, { style: { marginTop: "1rem", textAlign: "center" }, children: nextPageToken ? (_jsx(Button, { type: "button", onClick: loadMore, isLoading: loadingMore, children: "Load more" })) : entries.length > 0 ? (_jsx(Text, { style: { color: "var(--appkit-color-text-muted)", fontSize: "0.85rem" }, children: "No more entries." })) : null })] }));
|
|
55
63
|
}
|
|
@@ -54,6 +54,8 @@ export function HandModeProvider({ initialHand, children }) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
catch (_err) {
|
|
57
|
+
// localStorage unavailable (private-browse) — fall through to the value
|
|
58
|
+
// readHandPreference() already resolved; there is no preference to honour.
|
|
57
59
|
void normalizeError(_err);
|
|
58
60
|
}
|
|
59
61
|
return stored;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { wrapAction } from "@mohasinac/appkit/server";
|
|
3
|
+
import { safeRead } from "../../../../errors/safe-read";
|
|
3
4
|
import { bidRepository, productRepository, userRepository, siteSettingsRepository } from "../../../../repositories";
|
|
4
5
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers";
|
|
5
6
|
import { placeBidActionSchema } from "../../../shared/features/auctions/schema";
|
|
@@ -16,10 +17,20 @@ export async function placeBidAction(input) {
|
|
|
16
17
|
assertNotAuctionOwner(product, user.uid);
|
|
17
18
|
const settings = await siteSettingsRepository.getSingleton();
|
|
18
19
|
assertBidAmount(product, amount, settings.auctionConfig?.bidIncrementTiers ?? []);
|
|
19
|
-
const profile = await userRepository.findById(user.uid)
|
|
20
|
+
const profile = await safeRead(() => userRepository.findById(user.uid), {
|
|
21
|
+
route: "auction:placeBid",
|
|
22
|
+
key: "auctions.placeBid.bidderProfile",
|
|
23
|
+
fallback: null,
|
|
24
|
+
});
|
|
20
25
|
const userName = profile?.displayName ?? user.name ?? "Anonymous";
|
|
21
|
-
//
|
|
22
|
-
|
|
26
|
+
// (Removed: a `setWinningBid("__placeholder__", auctionId)` call whose
|
|
27
|
+
// rejection was swallowed. It accomplished NOTHING. setWinningBid builds a
|
|
28
|
+
// single batch — mark every bid for the product `outbid`, then update the
|
|
29
|
+
// winning bid's own doc — and `__placeholder__` is not a real bid id, so
|
|
30
|
+
// `batch.commit()` always threw and DISCARDED the whole batch, outbid
|
|
31
|
+
// marking included. It cost a full-collection query and an exception per
|
|
32
|
+
// bid placed, and the real `setWinningBid(bid.id, …)` below already marks
|
|
33
|
+
// every other bid for this product as outbid.)
|
|
23
34
|
const bid = await bidRepository.create({
|
|
24
35
|
productId: auctionId,
|
|
25
36
|
productTitle: product.title ?? auctionId,
|
|
@@ -4,7 +4,7 @@ import { AuctionNotFoundError, AuctionEndedError, BidTooLowError, BidOnOwnAuctio
|
|
|
4
4
|
import { AUCTION_SNIPING_WINDOW_SECONDS, AUCTION_DEFAULT_EXTENSION_MINUTES, resolveMinBid, } from "../../../shared/features/auctions/config";
|
|
5
5
|
/** Assert an auction exists, is published, and is still active. Returns the product. */
|
|
6
6
|
export async function assertAuctionActive(auctionId) {
|
|
7
|
-
const product = await productRepository.findByIdOrSlug(auctionId)
|
|
7
|
+
const product = await productRepository.findByIdOrSlug(auctionId);
|
|
8
8
|
if (!product || !isAuctionListing(product))
|
|
9
9
|
throw new AuctionNotFoundError(auctionId);
|
|
10
10
|
const endDate = product.auctionEndDate instanceof Date
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { blogRepository } from "../../../../repositories";
|
|
3
3
|
export const getBlogPostForDetail = cache(async (slug) => {
|
|
4
|
-
return (await blogRepository.findBySlug(slug)
|
|
4
|
+
return (await blogRepository.findBySlug(slug)) ?? null;
|
|
5
5
|
});
|
|
6
6
|
export const getBlogPostById = cache(async (id) => {
|
|
7
|
-
return (await blogRepository.findById(id)
|
|
7
|
+
return (await blogRepository.findById(id)) ?? null;
|
|
8
8
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { blogRepository } from "../../../../repositories";
|
|
2
2
|
import { BlogPostNotFoundError, BlogSlugConflictError } from "../../../shared/features/blog/errors";
|
|
3
3
|
export async function assertBlogPostExists(slugOrId) {
|
|
4
|
-
const post = await blogRepository.findBySlug(slugOrId)
|
|
5
|
-
?? await blogRepository.findById(slugOrId)
|
|
4
|
+
const post = await blogRepository.findBySlug(slugOrId)
|
|
5
|
+
?? await blogRepository.findById(slugOrId);
|
|
6
6
|
if (!post)
|
|
7
7
|
throw new BlogPostNotFoundError(slugOrId);
|
|
8
8
|
return post;
|
|
9
9
|
}
|
|
10
10
|
export async function assertBlogSlugUnique(slug) {
|
|
11
|
-
const existing = await blogRepository.findBySlug(slug)
|
|
11
|
+
const existing = await blogRepository.findBySlug(slug);
|
|
12
12
|
if (existing)
|
|
13
13
|
throw new BlogSlugConflictError(slug);
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@ import { categoriesRepository } from "../../../../repositories";
|
|
|
6
6
|
* `BrandDetailPageView` consume the same `CategoryDocument` shape.
|
|
7
7
|
*/
|
|
8
8
|
export const getBrandForDetail = cache(async (slug) => {
|
|
9
|
-
return categoriesRepository.findBySlugAndType(slug, "brand")
|
|
9
|
+
return categoriesRepository.findBySlugAndType(slug, "brand");
|
|
10
10
|
});
|
|
11
11
|
/**
|
|
12
12
|
* @deprecated Identical to getBrandForDetail after SB-UNI-C. Kept as an
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { categoriesRepository } from "../../../../repositories";
|
|
2
2
|
import { BrandNotFoundError, BrandSlugConflictError } from "../../../shared/features/brands/errors";
|
|
3
3
|
export async function assertBrandExists(slugOrId) {
|
|
4
|
-
const brand = (await categoriesRepository.findBySlugAndType(slugOrId, "brand")
|
|
5
|
-
(await categoriesRepository.findById(slugOrId)
|
|
4
|
+
const brand = (await categoriesRepository.findBySlugAndType(slugOrId, "brand")) ??
|
|
5
|
+
(await categoriesRepository.findById(slugOrId));
|
|
6
6
|
if (!brand || brand.categoryType !== "brand")
|
|
7
7
|
throw new BrandNotFoundError(slugOrId);
|
|
8
8
|
return brand;
|
|
9
9
|
}
|
|
10
10
|
export async function assertBrandSlugUnique(slug) {
|
|
11
|
-
const existing = await categoriesRepository.findBySlugAndType(slug, "brand")
|
|
11
|
+
const existing = await categoriesRepository.findBySlugAndType(slug, "brand");
|
|
12
12
|
if (existing)
|
|
13
13
|
throw new BrandSlugConflictError(slug);
|
|
14
14
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* helpers can fan out stock decrements to member products.
|
|
14
14
|
*/
|
|
15
15
|
import { NotFoundError, ValidationError } from "../../../../errors";
|
|
16
|
+
import { safeRead } from "../../../../errors/safe-read";
|
|
16
17
|
import { categoriesRepository } from "../../../../repositories";
|
|
17
18
|
import { cartRepository } from "../../../../features/cart/repository/cart.repository";
|
|
18
19
|
import { assertCanAddNewItems } from "../../../../features/cart/actions/cart-actions";
|
|
@@ -55,7 +56,11 @@ quantity = 1) {
|
|
|
55
56
|
// treating that as a stock quantity would silently change how much a
|
|
56
57
|
// prize-draw member decrements, which the stock fan-out has never done.
|
|
57
58
|
const memberIds = bundle.bundleProductIds ?? [];
|
|
58
|
-
const memberDocs = await Promise.all(memberIds.map((id) => productRepository.findById(id)
|
|
59
|
+
const memberDocs = await Promise.all(memberIds.map((id) => safeRead(() => productRepository.findById(id), {
|
|
60
|
+
route: "/bundles",
|
|
61
|
+
key: "bundles.addBundleToCart.member",
|
|
62
|
+
fallback: null,
|
|
63
|
+
})));
|
|
59
64
|
const groupMembers = memberDocs
|
|
60
65
|
.filter((p) => p !== null)
|
|
61
66
|
.map((p) => ({
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { cache } from "react";
|
|
9
9
|
import { categoriesRepository } from "../../../../repositories";
|
|
10
10
|
import { productRepository } from "../../../../repositories";
|
|
11
|
+
import { safeRead } from "../../../../errors/safe-read";
|
|
11
12
|
/**
|
|
12
13
|
* Fetch a single bundle by slug for the public detail page. Returns null
|
|
13
14
|
* when the slug is empty or no matching `categoryType:"bundle"` row exists.
|
|
@@ -43,7 +44,11 @@ export const listBundleMembers = cache(async (bundle, _opts) => {
|
|
|
43
44
|
const ids = resolveBundleMemberIds(bundle);
|
|
44
45
|
if (ids.length === 0)
|
|
45
46
|
return [];
|
|
46
|
-
const results = await Promise.all(ids.map((id) => productRepository.findById(id)
|
|
47
|
+
const results = await Promise.all(ids.map((id) => safeRead(() => productRepository.findById(id), {
|
|
48
|
+
route: "/bundles",
|
|
49
|
+
key: "bundles.listBundleMembers.member",
|
|
50
|
+
fallback: null,
|
|
51
|
+
})));
|
|
47
52
|
return results.filter((p) => p !== null);
|
|
48
53
|
});
|
|
49
54
|
/**
|
|
@@ -58,7 +63,11 @@ export const listBundleMembers = cache(async (bundle, _opts) => {
|
|
|
58
63
|
export async function resolveBundleOriginalTotal(productIds) {
|
|
59
64
|
if (productIds.length === 0)
|
|
60
65
|
return undefined;
|
|
61
|
-
const results = await Promise.all(productIds.map((id) => productRepository.findById(id)
|
|
66
|
+
const results = await Promise.all(productIds.map((id) => safeRead(() => productRepository.findById(id), {
|
|
67
|
+
route: "/bundles",
|
|
68
|
+
key: "bundles.resolveBundleOriginalTotal.member",
|
|
69
|
+
fallback: null,
|
|
70
|
+
})));
|
|
62
71
|
const prices = [];
|
|
63
72
|
for (const p of results) {
|
|
64
73
|
if (!p || typeof p.price !== "number")
|
|
@@ -89,7 +98,11 @@ export async function resolveBundleOriginalTotal(productIds) {
|
|
|
89
98
|
export async function findBundleMemberStores(productIds) {
|
|
90
99
|
if (productIds.length === 0)
|
|
91
100
|
return [];
|
|
92
|
-
const results = await Promise.all(productIds.map((id) => productRepository.findById(id)
|
|
101
|
+
const results = await Promise.all(productIds.map((id) => safeRead(() => productRepository.findById(id), {
|
|
102
|
+
route: "/bundles",
|
|
103
|
+
key: "bundles.findBundleMemberStores.member",
|
|
104
|
+
fallback: null,
|
|
105
|
+
})));
|
|
93
106
|
const stores = new Set();
|
|
94
107
|
for (const p of results) {
|
|
95
108
|
// An unresolvable member says nothing about seller spread — the price
|
|
@@ -118,7 +131,11 @@ export async function findBundleMemberStores(productIds) {
|
|
|
118
131
|
export async function resolveBundleCategorySlugs(productIds) {
|
|
119
132
|
if (productIds.length === 0)
|
|
120
133
|
return [];
|
|
121
|
-
const results = await Promise.all(productIds.map((id) => productRepository.findById(id)
|
|
134
|
+
const results = await Promise.all(productIds.map((id) => safeRead(() => productRepository.findById(id), {
|
|
135
|
+
route: "/bundles",
|
|
136
|
+
key: "bundles.resolveBundleCategorySlugs.member",
|
|
137
|
+
fallback: null,
|
|
138
|
+
})));
|
|
122
139
|
const slugs = new Set();
|
|
123
140
|
for (const p of results) {
|
|
124
141
|
for (const slug of p?.categorySlugs ?? [])
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { cartRepository } from "../../../../repositories";
|
|
3
3
|
export const getCartForUser = cache(async (userId) => {
|
|
4
|
-
|
|
4
|
+
// No catch: this cart IS the page. Degrading a Firestore failure to null
|
|
5
|
+
// renders an EMPTY CART to someone whose cart is full — money path.
|
|
6
|
+
return (await cartRepository.findByUserId(userId)) ?? null;
|
|
5
7
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { cartRepository } from "../../../../repositories";
|
|
2
2
|
import { CART_MAX_ITEMS } from "../../../shared/features/cart/config";
|
|
3
3
|
import { CartFullError, CartQuantityError } from "../../../shared/features/cart/errors";
|
|
4
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
5
|
+
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
4
6
|
export async function assertCartCapacity(userId, addingQty) {
|
|
5
|
-
|
|
7
|
+
// No catch. Swallowing to null made `currentItemCount` 0, so a Firestore
|
|
8
|
+
// failure silently DISABLED the cart cap and let the add through — a guard
|
|
9
|
+
// that fails open. A throw is caught by the action envelope and recorded.
|
|
10
|
+
const cart = await cartRepository.findByUserId(userId);
|
|
6
11
|
const currentItemCount = cart ? (cart.items ?? []).length : 0;
|
|
7
12
|
if (currentItemCount + addingQty > CART_MAX_ITEMS) {
|
|
8
13
|
throw new CartFullError(CART_MAX_ITEMS);
|
|
@@ -20,6 +25,12 @@ export async function upsertCartItem(userId, item) {
|
|
|
20
25
|
}
|
|
21
26
|
export async function mergeGuestItems(userId, guestItems) {
|
|
22
27
|
for (const item of guestItems) {
|
|
23
|
-
|
|
28
|
+
// A WRITE, not a read — safeRead does not apply. Guest-merge is
|
|
29
|
+
// best-effort per item so one bad line cannot lose the whole cart, but a
|
|
30
|
+
// dropped item must be visible rather than silent.
|
|
31
|
+
await cartRepository.addItem(userId, item).catch((err) => {
|
|
32
|
+
void normalizeError(err);
|
|
33
|
+
serverLogger.warn("cart.mergeGuestCart: item dropped", { userId, err });
|
|
34
|
+
});
|
|
24
35
|
}
|
|
25
36
|
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { cache } from "react";
|
|
3
3
|
import { categoriesRepository } from "../../../../repositories";
|
|
4
|
+
import { safeRead } from "../../../../errors/safe-read";
|
|
4
5
|
import { CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, CATEGORIES_SITEMAP_LIMIT } from "../../../shared/features/categories/config";
|
|
5
6
|
/** Full category document by slug — deduped per request via React.cache(). */
|
|
6
7
|
export const getCategoryForDetail = cache(async (slug) => {
|
|
7
|
-
const category = (await categoriesRepository.getCategoryBySlug(slug)
|
|
8
|
+
const category = (await categoriesRepository.getCategoryBySlug(slug)) ?? null;
|
|
8
9
|
if (category)
|
|
9
10
|
void categoriesRepository.incrementViewCount(category.id);
|
|
10
11
|
return category;
|
|
11
12
|
});
|
|
12
13
|
/** Flat list of all categories at tier 1 (roots) — for nav/sitemap. */
|
|
13
14
|
export const listRootCategories = cache(async () => {
|
|
14
|
-
return categoriesRepository.getCategoriesByTier(1)
|
|
15
|
+
return safeRead(() => categoriesRepository.getCategoriesByTier(1), {
|
|
16
|
+
route: "/categories",
|
|
17
|
+
key: "categories.listRootCategories",
|
|
18
|
+
fallback: [],
|
|
19
|
+
});
|
|
15
20
|
});
|
|
16
21
|
/** Featured categories for homepage display (showOnHomepage: true). */
|
|
17
22
|
export const listFeaturedCategories = cache(async () => {
|
|
@@ -29,7 +34,11 @@ export const listMenuCategories = cache(async () => {
|
|
|
29
34
|
});
|
|
30
35
|
/** Full category tree rooted at a given rootId — for sidebar navigation. */
|
|
31
36
|
export const getCategoryTree = cache(async (rootId) => {
|
|
32
|
-
return categoriesRepository.buildTree(rootId)
|
|
37
|
+
return safeRead(() => categoriesRepository.buildTree(rootId), {
|
|
38
|
+
route: "/categories",
|
|
39
|
+
key: "categories.getCategoryTree",
|
|
40
|
+
fallback: [],
|
|
41
|
+
});
|
|
33
42
|
});
|
|
34
43
|
/** Flat category list for sitemap generation. */
|
|
35
44
|
export const listSitemapCategories = cache(async () => {
|
|
@@ -6,6 +6,8 @@ import { eventInputSchema, eventUpdateSchema, registerForEventSchema } from "../
|
|
|
6
6
|
import { assertEventActive } from "./service";
|
|
7
7
|
import { AlreadyRegisteredError, EventNotFoundError } from "../../../shared/features/events/errors";
|
|
8
8
|
import { ValidationError } from "../../../shared/errors/index";
|
|
9
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
10
|
+
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
9
11
|
export async function createEventAction(input) {
|
|
10
12
|
return wrapAction(async () => {
|
|
11
13
|
const user = await requireRoleUser(["admin", "moderator"]);
|
|
@@ -23,7 +25,7 @@ export async function createEventAction(input) {
|
|
|
23
25
|
export async function updateEventAction(eventId, input) {
|
|
24
26
|
return wrapAction(async () => {
|
|
25
27
|
await requireRoleUser(["admin", "moderator"]);
|
|
26
|
-
const event = await eventRepository.findById(eventId)
|
|
28
|
+
const event = await eventRepository.findById(eventId);
|
|
27
29
|
if (!event)
|
|
28
30
|
throw new EventNotFoundError(eventId);
|
|
29
31
|
const parsed = eventUpdateSchema.safeParse(input);
|
|
@@ -52,15 +54,26 @@ export async function registerForEventAction(input) {
|
|
|
52
54
|
reviewStatus: "pending",
|
|
53
55
|
submittedAt: new Date(),
|
|
54
56
|
});
|
|
55
|
-
// Increment entry count
|
|
56
|
-
|
|
57
|
+
// Increment entry count. The entry document is already written and is the
|
|
58
|
+
// authoritative record, so a failed counter bump must not fail the
|
|
59
|
+
// registration — but it leaves `stats.totalEntries` under-reporting, and
|
|
60
|
+
// the capacity check in assertEventActive reads that mirror, so it has to
|
|
61
|
+
// be visible to an operator rather than dropped (Root Cause #42).
|
|
62
|
+
await eventRepository.incrementTotalEntries(eventId).catch((err) => {
|
|
63
|
+
void normalizeError(err);
|
|
64
|
+
serverLogger.warn("events: incrementTotalEntries failed — stats.totalEntries is now stale", {
|
|
65
|
+
eventId,
|
|
66
|
+
error: err instanceof Error ? err.message : String(err),
|
|
67
|
+
});
|
|
68
|
+
return null;
|
|
69
|
+
});
|
|
57
70
|
return entry;
|
|
58
71
|
});
|
|
59
72
|
}
|
|
60
73
|
export async function cancelEventRegistrationAction(entryId) {
|
|
61
74
|
return wrapAction(async () => {
|
|
62
75
|
const user = await requireRoleUser(["buyer", "seller", "admin"]);
|
|
63
|
-
const entry = await eventEntryRepository.findById(entryId)
|
|
76
|
+
const entry = await eventEntryRepository.findById(entryId);
|
|
64
77
|
if (!entry || entry.userId !== user.uid) {
|
|
65
78
|
throw new ValidationError("Registration not found or does not belong to you");
|
|
66
79
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eventRepository } from "../../../../repositories";
|
|
2
2
|
import { EventNotFoundError, EventNotActiveError, EventEndedError, EventFullError, } from "../../../shared/features/events/errors";
|
|
3
3
|
export async function assertEventActive(eventId) {
|
|
4
|
-
const event = await eventRepository.findById(eventId)
|
|
4
|
+
const event = await eventRepository.findById(eventId);
|
|
5
5
|
if (!event)
|
|
6
6
|
throw new EventNotFoundError(eventId);
|
|
7
7
|
const status = event.status;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
3
3
|
import { normalizeError } from "../../../../errors/normalize";
|
|
4
|
+
import { safeRead } from "../../../../errors/safe-read";
|
|
4
5
|
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
5
6
|
import { GROUPED_LISTINGS_COLLECTION, } from "../../../../features/grouped/schemas/firestore";
|
|
6
7
|
import { productRepository, groupedListingsRepository } from "../../../../repositories";
|
|
@@ -70,7 +71,7 @@ export const getGroupedListingWithItems = cache(async (slug) => {
|
|
|
70
71
|
return null;
|
|
71
72
|
const items = [];
|
|
72
73
|
for (const productId of doc.productIds) {
|
|
73
|
-
const product = await productRepository.findByIdOrSlug(productId).
|
|
74
|
+
const product = await safeRead(() => productRepository.findByIdOrSlug(productId), { route: "/groups", key: "grouped.getGroupedListingWithItems.member", fallback: null });
|
|
74
75
|
if (product)
|
|
75
76
|
items.push(product);
|
|
76
77
|
}
|
|
@@ -128,7 +129,11 @@ export const getGroupsWithItemsForProduct = cache(async (productId) => {
|
|
|
128
129
|
const groups = await getGroupsForProduct(productId);
|
|
129
130
|
return Promise.all(groups.map(async (group) => {
|
|
130
131
|
const otherIds = group.productIds.filter((id) => id !== productId);
|
|
131
|
-
const items = await Promise.all(otherIds.map((id) => productRepository.findByIdOrSlug(id)
|
|
132
|
+
const items = await Promise.all(otherIds.map((id) => safeRead(() => productRepository.findByIdOrSlug(id), {
|
|
133
|
+
route: "/products",
|
|
134
|
+
key: "grouped.getGroupsWithItemsForProduct.member",
|
|
135
|
+
fallback: null,
|
|
136
|
+
})));
|
|
132
137
|
return { ...group, items: items.filter((p) => p !== null) };
|
|
133
138
|
}));
|
|
134
139
|
});
|
|
@@ -39,7 +39,7 @@ export async function submitLotteryPullAction(input) {
|
|
|
39
39
|
// Load the event/product to get the lotteryConfig
|
|
40
40
|
let lotteryConfig;
|
|
41
41
|
if (data.sourceType === "event") {
|
|
42
|
-
const event = await eventRepository.findById(sourceId)
|
|
42
|
+
const event = await eventRepository.findById(sourceId);
|
|
43
43
|
if (!event || event.type !== "lottery") {
|
|
44
44
|
throw new LotteryError("LOTTERY_WINDOW_CLOSED", "Lottery event not found or not of type lottery.");
|
|
45
45
|
}
|
|
@@ -52,7 +52,7 @@ export async function submitLotteryPullAction(input) {
|
|
|
52
52
|
lotteryConfig = event.lotteryConfig;
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
|
-
const product = await productRepository.findById(sourceId)
|
|
55
|
+
const product = await productRepository.findById(sourceId);
|
|
56
56
|
if (!product || product.listingType !== "prize-draw") {
|
|
57
57
|
throw new LotteryError("LOTTERY_WINDOW_CLOSED", "Prize-draw product not found.");
|
|
58
58
|
}
|
|
@@ -4,7 +4,7 @@ import { lotteryEntryRepository } from "../../../../features/lottery/repository/
|
|
|
4
4
|
import { toClientLotteryConfig } from "./adapters";
|
|
5
5
|
/** Fetch a lottery-type event with price/weight stripped for client use. */
|
|
6
6
|
export const getLotteryEventCached = cache(async (id) => {
|
|
7
|
-
const event = await eventRepository.findById(id)
|
|
7
|
+
const event = await eventRepository.findById(id);
|
|
8
8
|
if (!event || event.type !== "lottery")
|
|
9
9
|
return null;
|
|
10
10
|
return {
|
|
@@ -53,7 +53,7 @@ export async function updateOrderStatusAction(input) {
|
|
|
53
53
|
const parsed = updateOrderStatusSchema.safeParse(input);
|
|
54
54
|
if (!parsed.success)
|
|
55
55
|
throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid input");
|
|
56
|
-
const order = await orderRepository.findById(parsed.data.orderId)
|
|
56
|
+
const order = await orderRepository.findById(parsed.data.orderId);
|
|
57
57
|
if (!order)
|
|
58
58
|
throw new OrderNotFoundError(parsed.data.orderId);
|
|
59
59
|
if (!isAdminUser(user) && order.storeId !== user.uid) {
|
|
@@ -126,7 +126,7 @@ function notifyAdminsOfPaymentProof(order) {
|
|
|
126
126
|
export async function attachPaymentProofAction(orderId, proof) {
|
|
127
127
|
return wrapAction(async () => {
|
|
128
128
|
const user = await requireRoleUser(["buyer", "seller", "admin"]);
|
|
129
|
-
const order = await orderRepository.findById(orderId)
|
|
129
|
+
const order = await orderRepository.findById(orderId);
|
|
130
130
|
if (!order)
|
|
131
131
|
throw new OrderNotFoundError(orderId);
|
|
132
132
|
if (!isAdminUser(user) && order.userId !== user.uid)
|
|
@@ -187,7 +187,7 @@ export async function adminVerifyPaymentAction(orderId) {
|
|
|
187
187
|
if (!isAdminUser(user) && !isModeratorUser(user)) {
|
|
188
188
|
throw new ValidationError("Only admin or moderator can verify payments");
|
|
189
189
|
}
|
|
190
|
-
const order = await orderRepository.findById(orderId)
|
|
190
|
+
const order = await orderRepository.findById(orderId);
|
|
191
191
|
if (!order)
|
|
192
192
|
throw new OrderNotFoundError(orderId);
|
|
193
193
|
if (order.paymentStatus === "paid")
|
|
@@ -226,7 +226,7 @@ export async function adminRequestProofReuploadAction(orderId, note) {
|
|
|
226
226
|
}
|
|
227
227
|
if (!note.trim())
|
|
228
228
|
throw new ValidationError(REASON_REQUIRED_MSG);
|
|
229
|
-
const order = await orderRepository.findById(orderId)
|
|
229
|
+
const order = await orderRepository.findById(orderId);
|
|
230
230
|
if (!order)
|
|
231
231
|
throw new OrderNotFoundError(orderId);
|
|
232
232
|
if (order.paymentStatus === "paid") {
|
|
@@ -275,7 +275,7 @@ export async function adminRejectPaymentAsFraudAction(orderId, note) {
|
|
|
275
275
|
}
|
|
276
276
|
if (!note.trim())
|
|
277
277
|
throw new ValidationError(REASON_REQUIRED_MSG);
|
|
278
|
-
const order = await orderRepository.findById(orderId)
|
|
278
|
+
const order = await orderRepository.findById(orderId);
|
|
279
279
|
if (!order)
|
|
280
280
|
throw new OrderNotFoundError(orderId);
|
|
281
281
|
if (order.paymentStatus === "paid") {
|
|
@@ -317,13 +317,13 @@ export async function raiseOrderDisputeAction(orderId, reason) {
|
|
|
317
317
|
const user = await requireRoleUser(["buyer", "seller", "admin"]);
|
|
318
318
|
if (!reason.trim())
|
|
319
319
|
throw new ValidationError(REASON_REQUIRED_MSG);
|
|
320
|
-
const order = await orderRepository.findById(orderId)
|
|
320
|
+
const order = await orderRepository.findById(orderId);
|
|
321
321
|
if (!order)
|
|
322
322
|
throw new OrderNotFoundError(orderId);
|
|
323
323
|
const isBuyer = order.userId === user.uid;
|
|
324
324
|
let isSeller = false;
|
|
325
325
|
if (!isBuyer && !isAdminUser(user) && !isModeratorUser(user) && order.storeId) {
|
|
326
|
-
const store = await storeRepository.findById(order.storeId)
|
|
326
|
+
const store = await storeRepository.findById(order.storeId);
|
|
327
327
|
isSeller = store?.ownerId === user.uid;
|
|
328
328
|
}
|
|
329
329
|
if (!isBuyer && !isSeller && !isAdminUser(user) && !isModeratorUser(user)) {
|
|
@@ -7,4 +7,4 @@ export declare const getOrdersForBuyer: (userId: string, page?: number, pageSize
|
|
|
7
7
|
totalPages: number;
|
|
8
8
|
hasMore: boolean;
|
|
9
9
|
}>;
|
|
10
|
-
export declare const getRecentOrdersForBuyer: (userId: string) => Promise<
|
|
10
|
+
export declare const getRecentOrdersForBuyer: (userId: string) => Promise<import("../../../..").OrderDocument[]>;
|