@mohasinac/appkit 4.12.0 → 4.12.2
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/server/features/auctions/service.d.ts +7 -1
- package/dist/_internal/server/features/auctions/service.js +13 -5
- package/dist/_internal/server/features/checkout/actions.d.ts +0 -15
- package/dist/_internal/server/features/checkout/actions.js +7 -3
- package/dist/_internal/server/features/orders/actions.js +5 -3
- package/dist/_internal/server/functions/firestore.d.ts +1 -2
- package/dist/_internal/server/functions/firestore.js +1 -9
- package/dist/_internal/server/jobs/core/index.d.ts +0 -1
- package/dist/_internal/server/jobs/core/index.js +0 -1
- package/dist/_internal/server/jobs/core/offerExpiry.js +33 -1
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -2
- package/dist/_internal/server/jobs/handlers/index.js +1 -2
- package/dist/_internal/shared/features/auctions/config.d.ts +34 -0
- package/dist/_internal/shared/features/auctions/config.js +47 -3
- package/dist/_internal/shared/listing-types/auction/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +7 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +16 -4
- package/dist/features/admin/components/AdminOrdersView.js +2 -1
- package/dist/features/auctions/actions/bid-actions.js +71 -9
- package/dist/features/auctions/components/AuctionDetailPageView.js +8 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
- package/dist/features/auctions/components/PlaceBidFormClient.js +89 -14
- package/dist/features/auctions/schemas/bid-input.d.ts +19 -6
- package/dist/features/auctions/schemas/bid-input.js +19 -7
- package/dist/features/auth/repository/user.repository.js +6 -0
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
- package/dist/features/layout/BackToTop.js +1 -1
- package/dist/features/layout/BottomActions.js +1 -1
- package/dist/features/layout/FooterLayout.js +2 -2
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/repository/products.repository.d.ts +0 -4
- package/dist/features/products/repository/products.repository.js +0 -15
- package/dist/features/promotions/repository/coupons.repository.d.ts +12 -0
- package/dist/features/promotions/repository/coupons.repository.js +12 -0
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +279 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +1 -1
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +1 -1
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/site-settings-seed-data.js +18 -0
- package/dist/styles.css +55 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/color-pairs.js +1 -0
- package/dist/tokens/tokens.css +33 -0
- package/dist/ui/components/Button.style.css +21 -0
- package/dist/ui/components/ImageLightbox.js +7 -4
- package/dist/ui/components/surface-tokens.d.ts +7 -0
- package/dist/ui/components/surface-tokens.js +7 -0
- package/package.json +1 -1
- package/dist/_internal/server/jobs/core/onBidPlaced.d.ts +0 -15
- package/dist/_internal/server/jobs/core/onBidPlaced.js +0 -63
- package/dist/_internal/server/jobs/handlers/onBidPlaced.d.ts +0 -3
- package/dist/_internal/server/jobs/handlers/onBidPlaced.js +0 -9
|
@@ -51,6 +51,7 @@ export const SURFACE_TEXT_PAIR_MAP = {
|
|
|
51
51
|
textMutedVar: "var(--appkit-color-info)",
|
|
52
52
|
textFaintVar: "var(--appkit-color-text-muted)",
|
|
53
53
|
},
|
|
54
|
+
frost: ON_PRIMARY_PAIR,
|
|
54
55
|
"overlay-xs": ON_PRIMARY_PAIR,
|
|
55
56
|
"overlay-sm": ON_PRIMARY_PAIR,
|
|
56
57
|
"overlay-md": ON_PRIMARY_PAIR,
|
package/dist/tokens/tokens.css
CHANGED
|
@@ -284,6 +284,31 @@
|
|
|
284
284
|
--appkit-color-info-surface: #f0f9ff; /* sky-50 */
|
|
285
285
|
--appkit-color-star: #facc15; /* yellow-400 — star ratings (decorative convention) */
|
|
286
286
|
|
|
287
|
+
/* Solid status fills — the OVERLAY counterpart to the `-surface` tints above.
|
|
288
|
+
`-surface` is a light tint in light themes and a dark tint in dark themes,
|
|
289
|
+
so it must pair with the matching `--appkit-color-STATUS` ink (which flips
|
|
290
|
+
the opposite way) to stay readable. That pairing is correct for an inline
|
|
291
|
+
chip, but too pale for a badge sitting on a product photo. These `-solid`
|
|
292
|
+
values are deliberately NOT theme-inverted: they stay a dark, saturated
|
|
293
|
+
fill in every theme so `-on-solid` can stay white in every theme.
|
|
294
|
+
Pairing rule, using `warning` as the worked example:
|
|
295
|
+
chip -> bg-warning-surface + text-warning
|
|
296
|
+
overlay -> bg-warning-solid + text-warning-on-solid
|
|
297
|
+
NB: never write a curly brace in a comment in this file. audit-theme-drift
|
|
298
|
+
matches each rule block with a non-greedy `:root ... ` regex, so a stray
|
|
299
|
+
closing brace inside a comment truncates the block and silently hides
|
|
300
|
+
every token declared below it from the drift check.
|
|
301
|
+
Both naive spellings are broken in exactly one theme, which is why
|
|
302
|
+
`audit-status-color-pairs.mjs` blocks them. */
|
|
303
|
+
--appkit-color-success-solid: #15803d; /* green-700 */
|
|
304
|
+
--appkit-color-success-on-solid: #ffffff;
|
|
305
|
+
--appkit-color-warning-solid: #b45309; /* amber-700 */
|
|
306
|
+
--appkit-color-warning-on-solid: #ffffff;
|
|
307
|
+
--appkit-color-error-solid: #b91c1c; /* red-700 */
|
|
308
|
+
--appkit-color-error-on-solid: #ffffff;
|
|
309
|
+
--appkit-color-info-solid: #0369a1; /* sky-700 */
|
|
310
|
+
--appkit-color-info-on-solid: #ffffff;
|
|
311
|
+
|
|
287
312
|
--appkit-color-focus-ring: #0d9488; /* primary-600 = teal */
|
|
288
313
|
}
|
|
289
314
|
|
|
@@ -616,6 +641,14 @@
|
|
|
616
641
|
etc. — the highest of those is 65). Set above tooltip so it's the
|
|
617
642
|
unconditional top of the interactive-overlay stack. */
|
|
618
643
|
--appkit-z-confirm: 90;
|
|
644
|
+
/* Back-to-top floating control — the frontmost layer. It previously shared
|
|
645
|
+
`--appkit-z-bottom-nav` (40), so it tied with the bottom navbar and lost
|
|
646
|
+
to every sticky toolbar, drawer and toast on the page: the button was
|
|
647
|
+
scrolled to but unclickable (or entirely hidden) on most long pages,
|
|
648
|
+
which is exactly the surface it exists for. It is a small, dismissible,
|
|
649
|
+
non-blocking affordance, so sitting above the overlay stack costs
|
|
650
|
+
nothing — it never covers content, only floats in the corner gutter. */
|
|
651
|
+
--appkit-z-back-to-top: 95;
|
|
619
652
|
|
|
620
653
|
/* Layout dimensions */
|
|
621
654
|
--appkit-navbar-height: 2.5rem; /* h-10 = 40px (mobile), h-12 = 48px (md) */
|
|
@@ -205,9 +205,30 @@
|
|
|
205
205
|
animation: appkit-spin 1s linear infinite;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/*
|
|
209
|
+
* The content wrapper exists purely so the label paints above the ripple
|
|
210
|
+
* (z-index above). It must NOT introduce a new sizing context — before it
|
|
211
|
+
* existed, a child was a direct flex item of the button and could resolve
|
|
212
|
+
* `width:100%` / `height:100%` against the button's own definite box.
|
|
213
|
+
*
|
|
214
|
+
* Without `flex: 1 1 auto` + `align-self: stretch` this span is shrink-to-fit
|
|
215
|
+
* in BOTH axes, so any fill-style child (`<MediaImage>`, an `absolute inset-0`
|
|
216
|
+
* overlay, a `<Image fill>`) collapses to 0x0 and the button renders blank.
|
|
217
|
+
* That is exactly what happened to every image-tile button in the app when
|
|
218
|
+
* the ripple landed.
|
|
219
|
+
*
|
|
220
|
+
* `justify-content` / `gap` are inherited so the button's own `justify` and
|
|
221
|
+
* `gap` props keep reaching the real children now that they sit one level
|
|
222
|
+
* deeper than the flex container that declares them.
|
|
223
|
+
*/
|
|
208
224
|
.appkit-button__content {
|
|
209
225
|
display: inline-flex;
|
|
210
226
|
align-items: center;
|
|
227
|
+
justify-content: inherit;
|
|
228
|
+
gap: inherit;
|
|
229
|
+
flex: 1 1 auto;
|
|
230
|
+
align-self: stretch;
|
|
231
|
+
min-width: 0;
|
|
211
232
|
}
|
|
212
233
|
|
|
213
234
|
.appkit-button__content--loading {
|
|
@@ -8,7 +8,7 @@ import { Div } from "./Div";
|
|
|
8
8
|
import { Text, Span } from "./Typography";
|
|
9
9
|
import { MediaImage } from "../../features/media/MediaImage";
|
|
10
10
|
import { getYouTubeVideoId } from "../../utils/media-url";
|
|
11
|
-
const CLS_CLOSE_BTN = "w-10 h-10 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-error-
|
|
11
|
+
const CLS_CLOSE_BTN = "w-10 h-10 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-error-solid text-error-on-solid flex items-center justify-center";
|
|
12
12
|
const MIN_ZOOM = 10;
|
|
13
13
|
const MAX_ZOOM = 300;
|
|
14
14
|
const ZOOM_STEP = 20;
|
|
@@ -124,19 +124,22 @@ export function ImageLightbox({ images, activeIndex, onClose, onNavigate, showTh
|
|
|
124
124
|
const hasMultiple = images.length > 1;
|
|
125
125
|
const youtubeId = image.kind === "video" ? getYouTubeVideoId(image.src) : null;
|
|
126
126
|
const iconBtnClass = "w-10 h-10 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-white/30 text-white flex items-center justify-center";
|
|
127
|
-
return createPortal(_jsxs("div", { ref: overlayRef, tabIndex: -1, className: "appkit-lightbox", role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", onKeyDown: handleKeyDown, onWheel: handleWheel, children: [_jsxs("div", { className: "absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-gradient-to-b from-black/60 to-transparent", children: [_jsx(Span, { size: "sm", weight: "medium", className: "text-white/70", children: hasMultiple ? `${currentIndex + 1} / ${images.length}` : "" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(-ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom out", disabled: zoom <= MIN_ZOOM, children: _jsx(ZoomOut, { className: "w-4 h-4" }) }), _jsxs("
|
|
127
|
+
return createPortal(_jsxs("div", { ref: overlayRef, tabIndex: -1, className: "appkit-lightbox", role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", onKeyDown: handleKeyDown, onWheel: handleWheel, children: [_jsxs("div", { className: "absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-gradient-to-b from-black/60 to-transparent", children: [_jsx(Span, { size: "sm", weight: "medium", className: "text-white/70", children: hasMultiple ? `${currentIndex + 1} / ${images.length}` : "" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(-ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom out", disabled: zoom <= MIN_ZOOM, children: _jsx(ZoomOut, { className: "w-4 h-4" }) }), _jsxs(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => { setZoom(100); setRotation(0); }, className: "text-white/70 hover:text-white text-xs font-mono min-w-[3rem] text-center p-0 !min-h-0", "aria-label": "Reset zoom", children: [zoom, "%"] }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => adjustZoom(ZOOM_STEP), className: iconBtnClass, "aria-label": "Zoom in", disabled: zoom >= MAX_ZOOM, children: _jsx(ZoomIn, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => setRotation((r) => (r + 90) % 360), className: iconBtnClass, "aria-label": "Rotate 90\u00B0", children: _jsx(RotateCw, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: toggleFullscreen, className: iconBtnClass, "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen", children: isFullscreen ? _jsx(Minimize2, { className: "w-4 h-4" }) : _jsx(Maximize2, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: onClose, className: CLS_CLOSE_BTN, "aria-label": "Close lightbox", children: _jsx(X, { className: "w-5 h-5" }) })] })] }), hasMultiple && (_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => navigate(-1), className: "absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 p-0 !min-h-0 rounded-full bg-white/15 hover:bg-white/30 text-white z-10 flex items-center justify-center", "aria-label": "Previous image", children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsxs("div", { className: "appkit-lightbox__image-wrap", style: { cursor: zoom > 100 ? "grab" : "default" }, children: [image.kind === "video" ? (youtubeId ? (_jsx("iframe", { src: `https://www.youtube-nocookie.com/embed/${youtubeId}?playsinline=1&rel=0`, title: image.alt ?? "Video", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true, className: "appkit-lightbox__img appkit-lightbox__video border-0" })) : (_jsx("video", { src: image.src, poster: image.poster, controls: true, playsInline: true, "aria-label": image.alt ?? "Video", className: "appkit-lightbox__img appkit-lightbox__video", style: {
|
|
128
128
|
transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
|
|
129
129
|
transition: "transform 0.2s ease",
|
|
130
130
|
} }))) : (_jsx(MediaImage, { src: image.src, alt: image.alt ?? "", size: "hero", objectFit: "contain", className: "appkit-lightbox__img", style: {
|
|
131
131
|
transform: `scale(${zoom / 100}) rotate(${rotation}deg)`,
|
|
132
132
|
transition: "transform 0.2s ease",
|
|
133
|
-
} })), image.badge && (_jsx(Div, { className: "absolute left-3 top-3 rounded bg-black/70 px-2 py-1 text-xs font-semibold text-white pointer-events-none", children: image.badge }))] }), (image.caption || image.sub) && (_jsxs("div", { className: "flex-shrink-0 text-center px-8 pb-2", children: [image.caption && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/80", children: image.caption })), image.sub && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/50", children: image.sub }))] })), showThumbnails && images.length > 1 && (_jsx("div", { className: "flex-shrink-0 flex gap-2 overflow-x-auto px-4 pb-3 justify-center", children: images.map((thumb, i) => (_jsx("
|
|
133
|
+
} })), image.badge && (_jsx(Div, { className: "absolute left-3 top-3 rounded bg-black/70 px-2 py-1 text-xs font-semibold text-white pointer-events-none", children: image.badge }))] }), (image.caption || image.sub) && (_jsxs("div", { className: "flex-shrink-0 text-center px-8 pb-2", children: [image.caption && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/80", children: image.caption })), image.sub && (_jsx(Text, { size: "sm", variant: "secondary", className: "!text-white/50", children: image.sub }))] })), showThumbnails && images.length > 1 && (_jsx("div", { className: "flex-shrink-0 flex gap-2 overflow-x-auto px-4 pb-3 justify-center", children: images.map((thumb, i) => (_jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => {
|
|
134
134
|
setCurrentIndex(i);
|
|
135
135
|
setZoom(100);
|
|
136
136
|
setRotation(0);
|
|
137
137
|
onNavigate?.(i);
|
|
138
138
|
}, className: [
|
|
139
|
-
|
|
139
|
+
// p-0/!min-h-0 neutralise the Button's own padding and min-height —
|
|
140
|
+
// this is a 56px image tile, and without them the primitive's
|
|
141
|
+
// internal spacing stretches the thumbnail strip (Root Cause #15).
|
|
142
|
+
"h-14 w-14 flex-shrink-0 overflow-hidden rounded-lg border-2 transition-all p-0 !min-h-0",
|
|
140
143
|
i === currentIndex
|
|
141
144
|
? "scale-110 border-white"
|
|
142
145
|
: "border-transparent opacity-50 hover:opacity-90",
|
|
@@ -14,6 +14,13 @@ export declare const SURFACE_MAP: {
|
|
|
14
14
|
readonly "danger-surface": "bg-[var(--appkit-color-error-surface)]";
|
|
15
15
|
readonly "warning-surface": "bg-[var(--appkit-color-warning-surface)]";
|
|
16
16
|
readonly "info-surface": "bg-[var(--appkit-color-info-surface)]";
|
|
17
|
+
/** Translucent WHITE chip on a dark/branded backdrop (hero banner, gradient
|
|
18
|
+
* card, media overlay). The light counterpart to the `overlay-*` scrims:
|
|
19
|
+
* those darken, this lightens. Deliberately not theme-tinted — it exists
|
|
20
|
+
* precisely for surfaces that stay dark in every theme, where a
|
|
21
|
+
* theme-relative surface token would resolve to white and give
|
|
22
|
+
* white-on-white. Pairs with on-primary (white) text. */
|
|
23
|
+
readonly frost: "bg-white/15";
|
|
17
24
|
readonly "overlay-xs": "bg-black/40";
|
|
18
25
|
readonly "overlay-sm": "bg-black/50";
|
|
19
26
|
readonly "overlay-md": "bg-black/55";
|
|
@@ -27,6 +27,13 @@ export const SURFACE_MAP = {
|
|
|
27
27
|
// Dark overlay scrim presets — used for image dimming, modal backdrops,
|
|
28
28
|
// hover overlays. Backdrop-blur should be paired separately via className
|
|
29
29
|
// when needed (backdrop-blur is not a colour utility, not in audit scope).
|
|
30
|
+
/** Translucent WHITE chip on a dark/branded backdrop (hero banner, gradient
|
|
31
|
+
* card, media overlay). The light counterpart to the `overlay-*` scrims:
|
|
32
|
+
* those darken, this lightens. Deliberately not theme-tinted — it exists
|
|
33
|
+
* precisely for surfaces that stay dark in every theme, where a
|
|
34
|
+
* theme-relative surface token would resolve to white and give
|
|
35
|
+
* white-on-white. Pairs with on-primary (white) text. */
|
|
36
|
+
frost: "bg-white/15",
|
|
30
37
|
"overlay-xs": "bg-black/40",
|
|
31
38
|
"overlay-sm": "bg-black/50",
|
|
32
39
|
"overlay-md": "bg-black/55",
|
package/package.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { JobContext } from "../runtime/types";
|
|
2
|
-
export interface NewBid {
|
|
3
|
-
id: string;
|
|
4
|
-
productId: string;
|
|
5
|
-
productTitle: string;
|
|
6
|
-
userId: string;
|
|
7
|
-
userName: string;
|
|
8
|
-
bidAmount: number;
|
|
9
|
-
currency: string;
|
|
10
|
-
}
|
|
11
|
-
export interface HandleBidPlacedInput {
|
|
12
|
-
bidId: string;
|
|
13
|
-
bid: NewBid;
|
|
14
|
-
}
|
|
15
|
-
export declare function handleBidPlaced(input: HandleBidPlacedInput, ctx: JobContext): Promise<void>;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
-
import { bidRepository, productRepository, } from "../../../../repositories";
|
|
3
|
-
import { decryptPii } from "../../../../security/index";
|
|
4
|
-
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
5
|
-
import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
|
|
6
|
-
import { BID_MESSAGES } from "../handlers/messages";
|
|
7
|
-
const BIDS_COLLECTION = "bids";
|
|
8
|
-
export async function handleBidPlaced(input, ctx) {
|
|
9
|
-
const { bidId, bid: newBid } = input;
|
|
10
|
-
newBid.userName = decryptPii(newBid.userName);
|
|
11
|
-
try {
|
|
12
|
-
const prevWinning = await bidRepository.getWinningBid(newBid.productId);
|
|
13
|
-
const batch = ctx.db.batch();
|
|
14
|
-
const newBidRef = ctx.db.collection(BIDS_COLLECTION).doc(bidId);
|
|
15
|
-
bidRepository.markWinning(batch, newBidRef);
|
|
16
|
-
let outbidUserId = null;
|
|
17
|
-
if (prevWinning && prevWinning.data.userId !== newBid.userId) {
|
|
18
|
-
outbidUserId = prevWinning.data.userId;
|
|
19
|
-
bidRepository.markOutbid(batch, prevWinning.ref);
|
|
20
|
-
}
|
|
21
|
-
productRepository.incrementBidCountInBatch(batch, newBid.productId, newBid.bidAmount, newBid.userId);
|
|
22
|
-
await batch.commit();
|
|
23
|
-
if (outbidUserId) {
|
|
24
|
-
const outbidMessage = BID_MESSAGES.OUTBID_MESSAGE(newBid.productTitle, newBid.currency, newBid.bidAmount);
|
|
25
|
-
await sendNotification({
|
|
26
|
-
userId: outbidUserId,
|
|
27
|
-
type: "bid_outbid",
|
|
28
|
-
priority: "high",
|
|
29
|
-
title: BID_MESSAGES.OUTBID_TITLE,
|
|
30
|
-
message: outbidMessage,
|
|
31
|
-
relatedId: newBid.productId,
|
|
32
|
-
relatedType: "product",
|
|
33
|
-
});
|
|
34
|
-
try {
|
|
35
|
-
await getAdminRealtimeDb()
|
|
36
|
-
.ref(`notifications/${outbidUserId}`)
|
|
37
|
-
.push({
|
|
38
|
-
type: "bid_outbid",
|
|
39
|
-
title: BID_MESSAGES.OUTBID_TITLE,
|
|
40
|
-
message: outbidMessage,
|
|
41
|
-
timestamp: Date.now(),
|
|
42
|
-
read: false,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
catch (rtdbError) {
|
|
46
|
-
void normalizeError(rtdbError);
|
|
47
|
-
ctx.logger.error("Realtime DB push failed (non-fatal)", rtdbError);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
ctx.logger.info(`Bid placed on ${newBid.productId}`, {
|
|
51
|
-
bidId,
|
|
52
|
-
amount: newBid.bidAmount,
|
|
53
|
-
outbidUserId,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
ctx.logger.error("Error handling bid placement", error, {
|
|
58
|
-
bidId,
|
|
59
|
-
productId: newBid.productId,
|
|
60
|
-
});
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { handleBidPlaced } from "../core/onBidPlaced";
|
|
2
|
-
export const onBidPlacedHandler = async (event, ctx) => {
|
|
3
|
-
const newBid = event.after;
|
|
4
|
-
if (!newBid) {
|
|
5
|
-
ctx.logger.error("No snapshot data", null);
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
await handleBidPlaced({ bidId: event.params.bidId, bid: newBid }, ctx);
|
|
9
|
-
};
|