@mohasinac/appkit 4.22.0 → 4.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/features/bundles/actions.js +7 -0
- package/dist/_internal/server/features/checkout/actions.js +24 -16
- package/dist/_internal/server/features/checkout/locked-lines.js +12 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +5 -0
- package/dist/_internal/server/jobs/core/auctionSettlement.js +10 -4
- package/dist/_internal/server/jobs/core/offerExpiry.js +10 -2
- package/dist/_internal/server/jobs/core/payoutBatch.js +15 -3
- package/dist/_internal/shared/actions/action-registry.js +41 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +23 -5
- package/dist/_internal/shared/checkout/order-math.js +29 -6
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +12 -2
- package/dist/_internal/shared/checkout/rules/_limits.js +12 -2
- package/dist/_internal/shared/features/cart/config.d.ts +2 -1
- package/dist/_internal/shared/features/cart/config.js +2 -1
- package/dist/_internal/shared/features/cart/schema.d.ts +6 -6
- package/dist/_internal/shared/features/notifications/action-url.d.ts +47 -0
- package/dist/_internal/shared/features/notifications/action-url.js +111 -0
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/_internal/shared/features/promotions/schema.d.ts +4 -4
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/types.d.ts +16 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/field-names.d.ts +12 -0
- package/dist/constants/field-names.js +12 -0
- package/dist/constants/notification-types.d.ts +38 -10
- package/dist/constants/notification-types.js +39 -28
- package/dist/features/account/types/index.d.ts +1 -10
- package/dist/features/admin/actions/admin-actions.js +13 -3
- package/dist/features/admin/actions/notification-actions.d.ts +12 -0
- package/dist/features/admin/actions/notification-actions.js +149 -4
- package/dist/features/admin/components/AdminBidsView.js +7 -1
- package/dist/features/admin/components/AdminScammerEditorView.d.ts +5 -1
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -2
- package/dist/features/admin/components/AdminSupportTicketDetailView.d.ts +5 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +3 -2
- package/dist/features/admin/schemas/firestore.d.ts +14 -3
- package/dist/features/admin/schemas/firestore.js +79 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +21 -8
- package/dist/features/auctions/repository/bid.repository.js +57 -33
- package/dist/features/auctions/schemas/firestore.d.ts +27 -0
- package/dist/features/auctions/schemas/firestore.js +21 -0
- package/dist/features/auth/schemas/index.d.ts +2 -2
- package/dist/features/cart/actions/cart-actions.d.ts +16 -0
- package/dist/features/cart/actions/cart-actions.js +26 -12
- package/dist/features/cart/components/CartDrawer.js +2 -1
- package/dist/features/cart/schemas/firestore.d.ts +132 -8
- package/dist/features/cart/schemas/firestore.js +22 -0
- package/dist/features/catalogue/actions/approve-catalogue-listing.js +7 -6
- package/dist/features/catalogue/actions/list-from-catalogue.js +2 -2
- package/dist/features/catalogue/actions/submit-for-approval.js +1 -4
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +5 -1
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +23 -0
- package/dist/features/catalogue/repository/catalogue.repository.js +28 -1
- package/dist/features/catalogue/schemas/firestore.d.ts +19 -0
- package/dist/features/catalogue/schemas/firestore.js +12 -0
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/email/index.d.ts +1 -1
- package/dist/features/email/index.js +5 -1
- package/dist/features/email/notification-templates.d.ts +35 -0
- package/dist/features/email/notification-templates.js +96 -0
- package/dist/features/messages/schemas/index.d.ts +6 -6
- package/dist/features/orders/schemas/index.d.ts +14 -14
- package/dist/features/payments/repository/payout.repository.d.ts +18 -5
- package/dist/features/payments/repository/payout.repository.js +45 -19
- package/dist/features/payments/schemas/firestore.d.ts +33 -1
- package/dist/features/payments/schemas/firestore.js +39 -0
- package/dist/features/payments/schemas/index.d.ts +12 -12
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/promotions/schemas/index.d.ts +12 -12
- package/dist/features/scams/repository/scammer.repository.d.ts +18 -1
- package/dist/features/scams/repository/scammer.repository.js +24 -2
- package/dist/features/scams/schemas/firestore.d.ts +24 -0
- package/dist/features/scams/schemas/firestore.js +30 -0
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -1
- package/dist/features/shipments/repository/shipments.repository.d.ts +18 -0
- package/dist/features/shipments/repository/shipments.repository.js +23 -1
- package/dist/features/shipments/schemas/firestore.d.ts +14 -0
- package/dist/features/shipments/schemas/firestore.js +19 -0
- package/dist/features/status-history/components/RecordStatusTimeline.d.ts +12 -0
- package/dist/features/status-history/components/RecordStatusTimeline.js +90 -0
- package/dist/features/status-history/index.d.ts +2 -0
- package/dist/features/status-history/index.js +1 -0
- package/dist/features/stores/repository/store.repository.d.ts +30 -2
- package/dist/features/stores/repository/store.repository.js +43 -4
- package/dist/features/stores/schemas/firestore.d.ts +29 -0
- package/dist/features/stores/schemas/firestore.js +31 -0
- package/dist/features/support/repository/support.repository.d.ts +47 -3
- package/dist/features/support/repository/support.repository.js +72 -12
- package/dist/features/support/schemas/firestore.d.ts +38 -1
- package/dist/features/support/schemas/firestore.js +34 -0
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +84 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +15 -2
- package/dist/schemas/registry.d.ts +34 -34
- package/dist/seed/bids-seed-data.js +8 -0
- package/dist/seed/factories/notification.factory.d.ts +2 -1
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +5 -0
- package/dist/server.d.ts +5 -1
- package/dist/server.js +10 -1
- package/dist/styles.css +58 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/QuantityStepper.d.ts +38 -0
- package/dist/ui/components/QuantityStepper.js +20 -0
- package/dist/ui/components/QuantityStepper.style.css +56 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where does a notification LAND?
|
|
3
|
+
*
|
|
4
|
+
* ## The problem this solves
|
|
5
|
+
*
|
|
6
|
+
* `actionUrl` was set by **5 of 40** `sendNotification()` call sites, so the
|
|
7
|
+
* other 35 arrived in the bell as text with nothing to click. Every writer
|
|
8
|
+
* that did set one hand-wrote the path, which is the same drift the ROUTES
|
|
9
|
+
* map exists to prevent.
|
|
10
|
+
*
|
|
11
|
+
* Worse, the destination is **role-dependent**: an offer notification sent to
|
|
12
|
+
* the buyer belongs at `/user/offers`, the same event sent to the seller at
|
|
13
|
+
* `/store/offers`, and an admin escalation at `/admin/offers`. A per-writer
|
|
14
|
+
* literal cannot express that, so writers that guessed a single path sent
|
|
15
|
+
* half their recipients somewhere they have no permission to be.
|
|
16
|
+
*
|
|
17
|
+
* ## Honest fallbacks, not invented pages
|
|
18
|
+
*
|
|
19
|
+
* `bid`, `offer` and `review` still have **no per-record page in any role** —
|
|
20
|
+
* W8 fixed those dead-end listings with a detail MODAL, not a route. So this
|
|
21
|
+
* resolver points them at the list the record lives in. That is a real,
|
|
22
|
+
* permitted, useful destination; fabricating `/user/bids/{id}` would produce
|
|
23
|
+
* a 404 and `audit-nav-page-wiring` would (correctly) fail on the ROUTES key.
|
|
24
|
+
*
|
|
25
|
+
* When those pages are built, change the entry here and all 40 call sites
|
|
26
|
+
* follow. That is the whole point of the indirection.
|
|
27
|
+
*/
|
|
28
|
+
import type { NotificationRelatedType } from "../../../../features/admin/schemas/firestore";
|
|
29
|
+
/**
|
|
30
|
+
* Which portal the recipient reads this in.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately NOT the `role` string off the session: a seller is also a
|
|
33
|
+
* buyer, and which portal a notification belongs to is a property of the
|
|
34
|
+
* EVENT, not of the person. The order-shipped notification goes to the buyer
|
|
35
|
+
* in `/user`, and the same shipment's payout notification goes to the same
|
|
36
|
+
* human in `/store`.
|
|
37
|
+
*/
|
|
38
|
+
export type NotificationAudience = "buyer" | "seller" | "admin";
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the page a notification should open.
|
|
41
|
+
*
|
|
42
|
+
* Returns `undefined` when there is genuinely nowhere to go — a `promotion`
|
|
43
|
+
* or a `welcome` has no record behind it — so the caller renders a plain,
|
|
44
|
+
* non-clickable row rather than a link to the dashboard the user is already
|
|
45
|
+
* looking at.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveNotificationActionUrl(relatedType: NotificationRelatedType | undefined, relatedId: string | undefined, audience: NotificationAudience): string | undefined;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where does a notification LAND?
|
|
3
|
+
*
|
|
4
|
+
* ## The problem this solves
|
|
5
|
+
*
|
|
6
|
+
* `actionUrl` was set by **5 of 40** `sendNotification()` call sites, so the
|
|
7
|
+
* other 35 arrived in the bell as text with nothing to click. Every writer
|
|
8
|
+
* that did set one hand-wrote the path, which is the same drift the ROUTES
|
|
9
|
+
* map exists to prevent.
|
|
10
|
+
*
|
|
11
|
+
* Worse, the destination is **role-dependent**: an offer notification sent to
|
|
12
|
+
* the buyer belongs at `/user/offers`, the same event sent to the seller at
|
|
13
|
+
* `/store/offers`, and an admin escalation at `/admin/offers`. A per-writer
|
|
14
|
+
* literal cannot express that, so writers that guessed a single path sent
|
|
15
|
+
* half their recipients somewhere they have no permission to be.
|
|
16
|
+
*
|
|
17
|
+
* ## Honest fallbacks, not invented pages
|
|
18
|
+
*
|
|
19
|
+
* `bid`, `offer` and `review` still have **no per-record page in any role** —
|
|
20
|
+
* W8 fixed those dead-end listings with a detail MODAL, not a route. So this
|
|
21
|
+
* resolver points them at the list the record lives in. That is a real,
|
|
22
|
+
* permitted, useful destination; fabricating `/user/bids/{id}` would produce
|
|
23
|
+
* a 404 and `audit-nav-page-wiring` would (correctly) fail on the ROUTES key.
|
|
24
|
+
*
|
|
25
|
+
* When those pages are built, change the entry here and all 40 call sites
|
|
26
|
+
* follow. That is the whole point of the indirection.
|
|
27
|
+
*/
|
|
28
|
+
import { ROUTES } from "../../../../next/routing/route-map";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the page a notification should open.
|
|
31
|
+
*
|
|
32
|
+
* Returns `undefined` when there is genuinely nowhere to go — a `promotion`
|
|
33
|
+
* or a `welcome` has no record behind it — so the caller renders a plain,
|
|
34
|
+
* non-clickable row rather than a link to the dashboard the user is already
|
|
35
|
+
* looking at.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveNotificationActionUrl(relatedType, relatedId, audience) {
|
|
38
|
+
if (!relatedType || !relatedId)
|
|
39
|
+
return undefined;
|
|
40
|
+
switch (relatedType) {
|
|
41
|
+
case "order":
|
|
42
|
+
return audience === "admin"
|
|
43
|
+
? String(ROUTES.ADMIN.ORDER_DETAIL(relatedId))
|
|
44
|
+
: audience === "seller"
|
|
45
|
+
? String(ROUTES.STORE.ORDER_DETAIL(relatedId))
|
|
46
|
+
: String(ROUTES.USER.ORDER_DETAIL(relatedId));
|
|
47
|
+
case "product":
|
|
48
|
+
// The public listing page — the one destination that is the same for
|
|
49
|
+
// everyone, because a product page is public.
|
|
50
|
+
return String(ROUTES.PUBLIC.PRODUCT_DETAIL(relatedId));
|
|
51
|
+
// ── List-level, because no per-record page exists in ANY role ─────────
|
|
52
|
+
// W8 gave these listings a detail modal rather than a route. Linking the
|
|
53
|
+
// list is honest; a fabricated `/…/{id}` would 404.
|
|
54
|
+
case "bid":
|
|
55
|
+
return audience === "admin"
|
|
56
|
+
? String(ROUTES.ADMIN.BIDS)
|
|
57
|
+
: audience === "seller"
|
|
58
|
+
? String(ROUTES.STORE.BIDS)
|
|
59
|
+
: String(ROUTES.USER.BIDS);
|
|
60
|
+
case "offer":
|
|
61
|
+
return audience === "admin"
|
|
62
|
+
? String(ROUTES.ADMIN.OFFERS)
|
|
63
|
+
: audience === "seller"
|
|
64
|
+
? String(ROUTES.STORE.OFFERS)
|
|
65
|
+
: String(ROUTES.USER.OFFERS);
|
|
66
|
+
case "review":
|
|
67
|
+
// The public review page exists; the admin/seller sides are modals.
|
|
68
|
+
return String(ROUTES.PUBLIC.REVIEW_DETAIL(relatedId));
|
|
69
|
+
case "blog":
|
|
70
|
+
return String(ROUTES.BLOG.ARTICLE(relatedId));
|
|
71
|
+
case "user":
|
|
72
|
+
// Only an admin has a per-user page. A user notified ABOUT themselves
|
|
73
|
+
// (a ban, a role change) belongs on their own account hub, not on a
|
|
74
|
+
// page they cannot open.
|
|
75
|
+
return audience === "admin"
|
|
76
|
+
? String(ROUTES.ADMIN.USER_DETAIL(relatedId))
|
|
77
|
+
: String(ROUTES.USER.PROFILE);
|
|
78
|
+
case "support_ticket":
|
|
79
|
+
return audience === "admin"
|
|
80
|
+
? String(ROUTES.ADMIN.SUPPORT_TICKET_BY_ID(relatedId))
|
|
81
|
+
: String(ROUTES.USER.SUPPORT_TICKET(relatedId));
|
|
82
|
+
case "scammer":
|
|
83
|
+
// Admin-only surface — the public scam registry is browsed, not
|
|
84
|
+
// notified about.
|
|
85
|
+
return audience === "admin" ? String(ROUTES.ADMIN.SCAMMER_BY_ID(relatedId)) : undefined;
|
|
86
|
+
case "catalogueItem":
|
|
87
|
+
// The owner edits it; an admin reviews it from the approvals queue,
|
|
88
|
+
// which is a list because approval is a queue-shaped job.
|
|
89
|
+
return audience === "admin"
|
|
90
|
+
? String(ROUTES.ADMIN.CATALOGUE_APPROVALS)
|
|
91
|
+
: String(ROUTES.USER.CATALOGUE_EDIT(relatedId));
|
|
92
|
+
case "payout":
|
|
93
|
+
return audience === "admin"
|
|
94
|
+
? String(ROUTES.ADMIN.PAYOUT_DETAIL(relatedId))
|
|
95
|
+
: String(ROUTES.STORE.PAYOUT_DETAIL(relatedId));
|
|
96
|
+
case "store":
|
|
97
|
+
return audience === "admin"
|
|
98
|
+
? String(ROUTES.ADMIN.STORE_DETAIL(relatedId))
|
|
99
|
+
: String(ROUTES.STORE.DASHBOARD);
|
|
100
|
+
default: {
|
|
101
|
+
/*
|
|
102
|
+
* Exhaustiveness. A new `relatedType` cannot compile without an answer
|
|
103
|
+
* to "where does this land", which is precisely the check that was
|
|
104
|
+
* missing when `catalogueItem` was smuggled in behind an `as never`.
|
|
105
|
+
*/
|
|
106
|
+
const _exhaustive = relatedType;
|
|
107
|
+
void _exhaustive;
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -41,7 +41,7 @@ export declare const productInputSchema: z.ZodObject<{
|
|
|
41
41
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
42
42
|
insurance?: boolean | undefined;
|
|
43
43
|
insuranceCost?: number | undefined;
|
|
44
|
-
shippingPaidBy?: "
|
|
44
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
45
45
|
seoTitle?: string | undefined;
|
|
46
46
|
seoDescription?: string | undefined;
|
|
47
47
|
seoKeywords?: string[] | undefined;
|
|
@@ -65,7 +65,7 @@ export declare const productInputSchema: z.ZodObject<{
|
|
|
65
65
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
66
66
|
insurance?: boolean | undefined;
|
|
67
67
|
insuranceCost?: number | undefined;
|
|
68
|
-
shippingPaidBy?: "
|
|
68
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
69
69
|
seoTitle?: string | undefined;
|
|
70
70
|
seoDescription?: string | undefined;
|
|
71
71
|
seoKeywords?: string[] | undefined;
|
|
@@ -114,7 +114,7 @@ export declare const productUpdateSchema: z.ZodObject<{
|
|
|
114
114
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
115
115
|
insurance?: boolean | undefined;
|
|
116
116
|
insuranceCost?: number | undefined;
|
|
117
|
-
shippingPaidBy?: "
|
|
117
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
118
118
|
seoTitle?: string | undefined;
|
|
119
119
|
seoDescription?: string | undefined;
|
|
120
120
|
seoKeywords?: string[] | undefined;
|
|
@@ -138,7 +138,7 @@ export declare const productUpdateSchema: z.ZodObject<{
|
|
|
138
138
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
139
139
|
insurance?: boolean | undefined;
|
|
140
140
|
insuranceCost?: number | undefined;
|
|
141
|
-
shippingPaidBy?: "
|
|
141
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
142
142
|
seoTitle?: string | undefined;
|
|
143
143
|
seoDescription?: string | undefined;
|
|
144
144
|
seoKeywords?: string[] | undefined;
|
|
@@ -201,7 +201,7 @@ export declare const auctionInputSchema: z.ZodObject<{
|
|
|
201
201
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
202
202
|
insurance?: boolean | undefined;
|
|
203
203
|
insuranceCost?: number | undefined;
|
|
204
|
-
shippingPaidBy?: "
|
|
204
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
205
205
|
reservePrice?: number | undefined;
|
|
206
206
|
buyNowPrice?: number | undefined;
|
|
207
207
|
minBidIncrement?: number | undefined;
|
|
@@ -233,7 +233,7 @@ export declare const auctionInputSchema: z.ZodObject<{
|
|
|
233
233
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
234
234
|
insurance?: boolean | undefined;
|
|
235
235
|
insuranceCost?: number | undefined;
|
|
236
|
-
shippingPaidBy?: "
|
|
236
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
237
237
|
reservePrice?: number | undefined;
|
|
238
238
|
buyNowPrice?: number | undefined;
|
|
239
239
|
minBidIncrement?: number | undefined;
|
|
@@ -299,7 +299,7 @@ export declare const preOrderInputSchema: z.ZodObject<{
|
|
|
299
299
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
300
300
|
insurance?: boolean | undefined;
|
|
301
301
|
insuranceCost?: number | undefined;
|
|
302
|
-
shippingPaidBy?: "
|
|
302
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
303
303
|
preOrderDepositPercent?: number | undefined;
|
|
304
304
|
preOrderMaxQuantity?: number | undefined;
|
|
305
305
|
seoTitle?: string | undefined;
|
|
@@ -327,7 +327,7 @@ export declare const preOrderInputSchema: z.ZodObject<{
|
|
|
327
327
|
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
328
328
|
insurance?: boolean | undefined;
|
|
329
329
|
insuranceCost?: number | undefined;
|
|
330
|
-
shippingPaidBy?: "
|
|
330
|
+
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
331
331
|
preOrderDepositPercent?: number | undefined;
|
|
332
332
|
preOrderMaxQuantity?: number | undefined;
|
|
333
333
|
preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
|
|
@@ -75,7 +75,7 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
75
75
|
}, "strip", z.ZodTypeAny, {
|
|
76
76
|
code: string;
|
|
77
77
|
type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
|
|
78
|
-
scope: "
|
|
78
|
+
scope: "seller" | "admin";
|
|
79
79
|
name: string;
|
|
80
80
|
discount: {
|
|
81
81
|
value: number;
|
|
@@ -99,7 +99,7 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
99
99
|
}, {
|
|
100
100
|
code: string;
|
|
101
101
|
type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
|
|
102
|
-
scope: "
|
|
102
|
+
scope: "seller" | "admin";
|
|
103
103
|
name: string;
|
|
104
104
|
discount: {
|
|
105
105
|
value: number;
|
|
@@ -177,7 +177,7 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
177
177
|
}>>>;
|
|
178
178
|
}, "code">, "strip", z.ZodTypeAny, {
|
|
179
179
|
type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
|
|
180
|
-
scope?: "
|
|
180
|
+
scope?: "seller" | "admin" | undefined;
|
|
181
181
|
name?: string | undefined;
|
|
182
182
|
discount?: {
|
|
183
183
|
value: number;
|
|
@@ -200,7 +200,7 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
200
200
|
} | undefined;
|
|
201
201
|
}, {
|
|
202
202
|
type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
|
|
203
|
-
scope?: "
|
|
203
|
+
scope?: "seller" | "admin" | undefined;
|
|
204
204
|
name?: string | undefined;
|
|
205
205
|
discount?: {
|
|
206
206
|
value: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { HistoryActorRole, HistoryActor, FieldChange, StatusChangeEntry, AppendHistoryResult, } from "./types";
|
|
1
|
+
export type { HistoryActorRole, HistoryActor, FieldChange, StatusChangeEntry, AppendHistoryResult, SerialisedStatusChangeEntry, } from "./types";
|
|
2
2
|
export { STATUS_HISTORY_MAX } from "./types";
|
|
3
3
|
export type { BuildHistoryEntryInput } from "./history";
|
|
4
4
|
export { diffTrackedFields, buildHistoryEntry, appendHistoryEntry, withHistory, } from "./history";
|
|
@@ -59,3 +59,19 @@ export interface AppendHistoryResult {
|
|
|
59
59
|
*/
|
|
60
60
|
truncatedCount: number;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* A `StatusChangeEntry` after it has crossed the RSC boundary.
|
|
64
|
+
*
|
|
65
|
+
* Identical except `at` is an ISO string. Server Components must serialise
|
|
66
|
+
* before handing history to a `"use client"` component: every other date prop
|
|
67
|
+
* on those drawers is already a string, and mixing the two is how a
|
|
68
|
+
* `toLocaleString` on a string ends up rendering "Invalid Date".
|
|
69
|
+
*
|
|
70
|
+
* Declared once here rather than restated per page — two client files had
|
|
71
|
+
* hand-written copies, which is both a duplication and (because each spelled
|
|
72
|
+
* `changes` as `Record<string, { from: unknown; to: unknown }>`) an untyped
|
|
73
|
+
* seam `audit-unknown-leakage` correctly refused.
|
|
74
|
+
*/
|
|
75
|
+
export interface SerialisedStatusChangeEntry extends Omit<StatusChangeEntry, "at"> {
|
|
76
|
+
at: string;
|
|
77
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -164,6 +164,8 @@ export type { ColorPickerFieldProps } from "./ui/forms";
|
|
|
164
164
|
export { ColorPickerField } from "./ui/forms";
|
|
165
165
|
export type { FormErrorSummaryProps } from "./ui/forms";
|
|
166
166
|
export { FormErrorSummary } from "./ui/forms";
|
|
167
|
+
export { RecordStatusTimeline } from "./features/status-history/index";
|
|
168
|
+
export type { SerialisedStatusChangeEntry } from "./_internal/shared/history/types";
|
|
167
169
|
export type { FormErrorListProps } from "./ui/forms";
|
|
168
170
|
export { FormErrorList } from "./ui/forms";
|
|
169
171
|
export { useFormBottomActions } from "./features/layout/index";
|
package/dist/client.js
CHANGED
|
@@ -222,6 +222,7 @@ export { FieldCheckbox } from "./ui/forms";
|
|
|
222
222
|
export { FieldTextarea } from "./ui/forms";
|
|
223
223
|
export { ColorPickerField } from "./ui/forms";
|
|
224
224
|
export { FormErrorSummary } from "./ui/forms";
|
|
225
|
+
export { RecordStatusTimeline } from "./features/status-history/index";
|
|
225
226
|
export { FormErrorList } from "./ui/forms";
|
|
226
227
|
export { useFormBottomActions } from "./features/layout/index";
|
|
227
228
|
export { OverlayContext, useIsInsideOverlay } from "./ui/components/overlay-context";
|
|
@@ -341,6 +341,18 @@ export declare const AD_FIELDS: {
|
|
|
341
341
|
readonly PAUSED: "paused";
|
|
342
342
|
};
|
|
343
343
|
};
|
|
344
|
+
/**
|
|
345
|
+
* The ad fields whose changes earn a `statusHistory` entry.
|
|
346
|
+
*
|
|
347
|
+
* Ads live inside the `siteSettings` singleton and have no repository, so
|
|
348
|
+
* `withHistory` is called from the route directly — it is a pure function, so
|
|
349
|
+
* that is fine. This list lives here beside `AD_FIELDS` rather than in a
|
|
350
|
+
* schema module because ads have no schema module of their own.
|
|
351
|
+
*
|
|
352
|
+
* The scheduling dates are tracked alongside `status`: an ad silently
|
|
353
|
+
* rescheduled reads on the timeline as the same kind of event as one paused.
|
|
354
|
+
*/
|
|
355
|
+
export declare const AD_TRACKED_FIELDS: readonly ["status", "startAt", "endAt"];
|
|
344
356
|
export declare const EVENT_FIELDS: {
|
|
345
357
|
readonly ID: "id";
|
|
346
358
|
readonly TITLE: "title";
|
|
@@ -390,6 +390,18 @@ export const AD_FIELDS = {
|
|
|
390
390
|
PAUSED: "paused",
|
|
391
391
|
},
|
|
392
392
|
};
|
|
393
|
+
/**
|
|
394
|
+
* The ad fields whose changes earn a `statusHistory` entry.
|
|
395
|
+
*
|
|
396
|
+
* Ads live inside the `siteSettings` singleton and have no repository, so
|
|
397
|
+
* `withHistory` is called from the route directly — it is a pure function, so
|
|
398
|
+
* that is fine. This list lives here beside `AD_FIELDS` rather than in a
|
|
399
|
+
* schema module because ads have no schema module of their own.
|
|
400
|
+
*
|
|
401
|
+
* The scheduling dates are tracked alongside `status`: an ad silently
|
|
402
|
+
* rescheduled reads on the timeline as the same kind of event as one paused.
|
|
403
|
+
*/
|
|
404
|
+
export const AD_TRACKED_FIELDS = ["status", "startAt", "endAt"];
|
|
393
405
|
// ============================================================================
|
|
394
406
|
// EVENT FIELDS
|
|
395
407
|
// ============================================================================
|
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Notification Type Registry —
|
|
2
|
+
* Notification Type Registry — DERIVED, not restated.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - AdminNotificationsView filter chips
|
|
6
|
-
* - NotificationPreferencesPanel (per-type toggles)
|
|
7
|
-
* - sendNotification() dispatch (type-based channel routing)
|
|
4
|
+
* ## What this file used to be, and why it mattered
|
|
8
5
|
*
|
|
9
|
-
*
|
|
6
|
+
* A hand-written 9-value list that called itself "canonical" while
|
|
7
|
+
* `NotificationDocument.type` held 27. Two of its nine —`review_posted` and
|
|
8
|
+
* `payout_processed` — were not real values at all, so those chips matched
|
|
9
|
+
* zero rows forever. Its two consumers were not cosmetic:
|
|
10
|
+
*
|
|
11
|
+
* · `AdminNotificationsView`'s filter chips, so an admin could not filter
|
|
12
|
+
* **18 of the 27 real types** and was offered two that never appear.
|
|
13
|
+
* · `AdminSiteSettingsView`'s per-channel allow-list, so an admin literally
|
|
14
|
+
* **could not allow-list `offer_received` or `payment_review`** for email
|
|
15
|
+
* or WhatsApp — the toggle did not exist.
|
|
16
|
+
*
|
|
17
|
+
* Everything here now derives from `NOTIFICATION_TYPE_VALUES`, so a new
|
|
18
|
+
* notification type is one edit and cannot be missing from either surface.
|
|
19
|
+
*
|
|
20
|
+
* ## Why the import points where it does
|
|
21
|
+
*
|
|
22
|
+
* `features/admin/schemas/firestore.ts` imports nothing from `constants/`,
|
|
23
|
+
* so this direction is acyclic. That is NOT true of `field-names.ts`, which
|
|
24
|
+
* is a deliberate leaf that `features/products/types` imports — which is why
|
|
25
|
+
* that file keeps its duplicated values and relies on an audit instead.
|
|
26
|
+
*/
|
|
27
|
+
import { NOTIFICATION_TYPE_VALUES } from "../features/admin/schemas/firestore";
|
|
28
|
+
import type { NotificationType } from "../features/admin/schemas/firestore";
|
|
29
|
+
export { NOTIFICATION_TYPE_VALUES };
|
|
30
|
+
export type { NotificationType };
|
|
31
|
+
/** Alias kept for the two existing importers. */
|
|
32
|
+
export declare const NOTIFICATION_TYPES: readonly ["order_placed", "order_confirmed", "order_shipped", "order_delivered", "order_cancelled", "bid_placed", "bid_outbid", "bid_won", "bid_lost", "auction_ended", "review_approved", "review_replied", "product_available", "promotion", "system", "welcome", "account_action", "offer_received", "offer_responded", "offer_expired", "offer_counter_accepted", "refund_initiated", "prize_won", "prize_reveal_expired", "emi_installment_due_soon", "emi_installment_overdue", "payment_review", "catalogue_images_stale"];
|
|
33
|
+
/**
|
|
34
|
+
* `order_shipped` → `Order shipped`.
|
|
35
|
+
*
|
|
36
|
+
* Derived rather than hand-labelled: a hand-written label map is the same
|
|
37
|
+
* drift risk one layer down, and every one of these values is already a
|
|
38
|
+
* readable snake_case phrase.
|
|
10
39
|
*/
|
|
11
|
-
export declare
|
|
12
|
-
export type NotificationType = (typeof NOTIFICATION_TYPES)[number];
|
|
40
|
+
export declare function notificationTypeLabel(type: NotificationType | string): string;
|
|
13
41
|
/**
|
|
14
|
-
* Shape used by filter-chip rows (id + display label)
|
|
15
|
-
*
|
|
42
|
+
* Shape used by filter-chip rows (id + display label), with the usual "All"
|
|
43
|
+
* option prepended.
|
|
16
44
|
*/
|
|
17
45
|
export declare const NOTIFICATION_TYPE_TABS: ReadonlyArray<{
|
|
18
46
|
id: string;
|
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Notification Type Registry —
|
|
2
|
+
* Notification Type Registry — DERIVED, not restated.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - AdminNotificationsView filter chips
|
|
6
|
-
* - NotificationPreferencesPanel (per-type toggles)
|
|
7
|
-
* - sendNotification() dispatch (type-based channel routing)
|
|
4
|
+
* ## What this file used to be, and why it mattered
|
|
8
5
|
*
|
|
9
|
-
*
|
|
6
|
+
* A hand-written 9-value list that called itself "canonical" while
|
|
7
|
+
* `NotificationDocument.type` held 27. Two of its nine —`review_posted` and
|
|
8
|
+
* `payout_processed` — were not real values at all, so those chips matched
|
|
9
|
+
* zero rows forever. Its two consumers were not cosmetic:
|
|
10
|
+
*
|
|
11
|
+
* · `AdminNotificationsView`'s filter chips, so an admin could not filter
|
|
12
|
+
* **18 of the 27 real types** and was offered two that never appear.
|
|
13
|
+
* · `AdminSiteSettingsView`'s per-channel allow-list, so an admin literally
|
|
14
|
+
* **could not allow-list `offer_received` or `payment_review`** for email
|
|
15
|
+
* or WhatsApp — the toggle did not exist.
|
|
16
|
+
*
|
|
17
|
+
* Everything here now derives from `NOTIFICATION_TYPE_VALUES`, so a new
|
|
18
|
+
* notification type is one edit and cannot be missing from either surface.
|
|
19
|
+
*
|
|
20
|
+
* ## Why the import points where it does
|
|
21
|
+
*
|
|
22
|
+
* `features/admin/schemas/firestore.ts` imports nothing from `constants/`,
|
|
23
|
+
* so this direction is acyclic. That is NOT true of `field-names.ts`, which
|
|
24
|
+
* is a deliberate leaf that `features/products/types` imports — which is why
|
|
25
|
+
* that file keeps its duplicated values and relies on an audit instead.
|
|
10
26
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
import { NOTIFICATION_TYPE_VALUES } from "../features/admin/schemas/firestore";
|
|
28
|
+
export { NOTIFICATION_TYPE_VALUES };
|
|
29
|
+
/** Alias kept for the two existing importers. */
|
|
30
|
+
export const NOTIFICATION_TYPES = NOTIFICATION_TYPE_VALUES;
|
|
31
|
+
/**
|
|
32
|
+
* `order_shipped` → `Order shipped`.
|
|
33
|
+
*
|
|
34
|
+
* Derived rather than hand-labelled: a hand-written label map is the same
|
|
35
|
+
* drift risk one layer down, and every one of these values is already a
|
|
36
|
+
* readable snake_case phrase.
|
|
37
|
+
*/
|
|
38
|
+
export function notificationTypeLabel(type) {
|
|
39
|
+
return String(type).replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
|
|
40
|
+
}
|
|
22
41
|
/**
|
|
23
|
-
* Shape used by filter-chip rows (id + display label)
|
|
24
|
-
*
|
|
42
|
+
* Shape used by filter-chip rows (id + display label), with the usual "All"
|
|
43
|
+
* option prepended.
|
|
25
44
|
*/
|
|
26
45
|
export const NOTIFICATION_TYPE_TABS = [
|
|
27
46
|
{ id: "All", label: "All" },
|
|
28
|
-
{ id
|
|
29
|
-
{ id: "order_shipped", label: "Order shipped" },
|
|
30
|
-
{ id: "order_delivered", label: "Order delivered" },
|
|
31
|
-
{ id: "order_cancelled", label: "Order cancelled" },
|
|
32
|
-
{ id: "bid_placed", label: "Bid placed" },
|
|
33
|
-
{ id: "bid_outbid", label: "Bid outbid" },
|
|
34
|
-
{ id: "bid_won", label: "Bid won" },
|
|
35
|
-
{ id: "review_posted", label: "Review posted" },
|
|
36
|
-
{ id: "payout_processed", label: "Payout processed" },
|
|
47
|
+
...NOTIFICATION_TYPE_VALUES.map((id) => ({ id, label: notificationTypeLabel(id) })),
|
|
37
48
|
];
|
|
@@ -37,16 +37,7 @@ export interface UserOrder {
|
|
|
37
37
|
createdAt?: string;
|
|
38
38
|
updatedAt?: string;
|
|
39
39
|
}
|
|
40
|
-
export type NotificationType
|
|
41
|
-
export interface UserNotification {
|
|
42
|
-
id: string;
|
|
43
|
-
title: string;
|
|
44
|
-
message: string;
|
|
45
|
-
type: NotificationType;
|
|
46
|
-
isRead: boolean;
|
|
47
|
-
createdAt: string;
|
|
48
|
-
href?: string;
|
|
49
|
-
}
|
|
40
|
+
export type { NotificationType } from "../../admin/schemas/firestore";
|
|
50
41
|
export type OfferStatus = "pending" | "countered" | "accepted" | "paid" | "declined" | "expired" | "withdrawn";
|
|
51
42
|
export interface UserOffer {
|
|
52
43
|
id: string;
|
|
@@ -72,8 +72,11 @@ export async function adminUpdatePayout(adminId, id, input) {
|
|
|
72
72
|
throw new NotFoundError("Payout not found");
|
|
73
73
|
}
|
|
74
74
|
const updateData = {
|
|
75
|
-
...(input.status !== undefined ? { status: input.status } : {}),
|
|
76
75
|
...(input.adminNote !== undefined ? { adminNote: input.adminNote } : {}),
|
|
76
|
+
// The payment reference. Absent from this pick until 2026-08-26, so every
|
|
77
|
+
// payout marked paid was stored WITHOUT the UTR the modal required — see
|
|
78
|
+
// PAYOUT_ADMIN_UPDATEABLE_FIELDS for the full trace.
|
|
79
|
+
...(input.transactionId !== undefined ? { transactionId: input.transactionId } : {}),
|
|
77
80
|
...(input.processedAt
|
|
78
81
|
? {
|
|
79
82
|
processedAt: input.processedAt instanceof Date
|
|
@@ -82,12 +85,19 @@ export async function adminUpdatePayout(adminId, id, input) {
|
|
|
82
85
|
}
|
|
83
86
|
: {}),
|
|
84
87
|
};
|
|
85
|
-
|
|
88
|
+
/*
|
|
89
|
+
* Through `updateStatus`, not a bare `update`, so the change lands on the
|
|
90
|
+
* payout's own timeline — `existing` is threaded in as `prior`, so that
|
|
91
|
+
* costs no second read of a document this function already holds.
|
|
92
|
+
*/
|
|
93
|
+
const updated = input.status !== undefined
|
|
94
|
+
? await payoutRepository.updateStatus(id, input.status, updateData, { actor: { role: "admin", uid: adminId }, trigger: "adminUpdatePayout" }, existing)
|
|
95
|
+
: await payoutRepository.update(id, updateData);
|
|
86
96
|
serverLogger.info("adminUpdatePayout", {
|
|
87
97
|
adminId,
|
|
88
98
|
payoutId: id,
|
|
89
99
|
});
|
|
90
|
-
if (
|
|
100
|
+
if (input.status === "paid") {
|
|
91
101
|
void recordAdminAction({
|
|
92
102
|
actorUid: adminId,
|
|
93
103
|
action: AdminAuditActionValues.PAYOUT_MARK_PAID,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NotificationDocument, NotificationCreateInput } from "../schemas";
|
|
2
|
+
import { type NotificationAudience } from "../../../_internal/shared/features/notifications/action-url";
|
|
2
3
|
export declare function markNotificationRead(id: string): Promise<void>;
|
|
3
4
|
export declare function markAllNotificationsRead(userId: string): Promise<number>;
|
|
4
5
|
export declare function deleteNotification(id: string): Promise<void>;
|
|
@@ -16,6 +17,17 @@ export interface SendNotificationInput extends NotificationCreateInput {
|
|
|
16
17
|
emailHtml?: string;
|
|
17
18
|
/** Buyer paid the ₹10 WhatsApp order-updates addon — enables WhatsApp channel for this notification even when the user has no standing WhatsApp subscription. */
|
|
18
19
|
orderWhatsappAddonPaid?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Which portal the recipient reads this in — decides where `actionUrl`
|
|
22
|
+
* points when the caller does not supply one.
|
|
23
|
+
*
|
|
24
|
+
* Defaults from the notification TYPE, not from the recipient's role: a
|
|
25
|
+
* seller is also a buyer, and `order_shipped` belongs in `/user` while the
|
|
26
|
+
* payout for that same order belongs in `/store`. Pass it explicitly only
|
|
27
|
+
* when one type genuinely reaches two audiences — `offer_received` goes to
|
|
28
|
+
* the seller, `offer_responded` back to the buyer.
|
|
29
|
+
*/
|
|
30
|
+
audience?: NotificationAudience;
|
|
19
31
|
}
|
|
20
32
|
export interface SendNotificationResult {
|
|
21
33
|
notification: NotificationDocument;
|