@mohasinac/appkit 2.7.33 → 2.7.34
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/jobs/core/auctionSettlement.js +14 -14
- package/dist/_internal/server/jobs/core/offerExpiry.js +3 -2
- package/dist/_internal/server/jobs/core/onBidPlaced.js +14 -12
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +6 -71
- package/dist/_internal/server/jobs/core/onSupportTicketCreate.js +6 -8
- package/dist/_internal/server/jobs/core/onSupportTicketUpdate.js +6 -7
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +11 -10
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +2 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +2 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +2 -2
- package/dist/features/admin/actions/notification-actions.js +18 -8
- package/dist/features/admin/schemas/firestore.d.ts +6 -2
- package/dist/features/admin/schemas/firestore.js +4 -0
- package/dist/features/orders/actions/refund-actions.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/manifest.js +5 -0
- package/dist/seed/offers-seed-data.d.ts +10 -0
- package/dist/seed/offers-seed-data.js +264 -0
- package/dist/seed/orders-seed-data.js +116 -2
- package/dist/seed/support-tickets-seed-data.js +43 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/dist/ui/components/Input.style.css +42 -4
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { bidRepository,
|
|
1
|
+
import { bidRepository, orderRepository, productRepository, } from "../../../../repositories";
|
|
2
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
3
|
import { ProductStatusValues } from "../../../../features/products/schemas/firestore";
|
|
3
4
|
import { AUCTION_MESSAGES } from "../handlers/messages";
|
|
4
5
|
async function settleAuction(ctx, product) {
|
|
@@ -25,7 +26,9 @@ async function settleAuction(ctx, product) {
|
|
|
25
26
|
auctionProductId: product.id,
|
|
26
27
|
});
|
|
27
28
|
productRepository.updateStatusInBatch(batch, product.id, ProductStatusValues.SOLD);
|
|
28
|
-
|
|
29
|
+
await batch.commit();
|
|
30
|
+
// Fan-out notifications after batch commits.
|
|
31
|
+
await sendNotification({
|
|
29
32
|
userId: winnerEntry.data.userId,
|
|
30
33
|
type: "bid_won",
|
|
31
34
|
priority: "high",
|
|
@@ -34,18 +37,15 @@ async function settleAuction(ctx, product) {
|
|
|
34
37
|
relatedId: product.id,
|
|
35
38
|
relatedType: "product",
|
|
36
39
|
});
|
|
37
|
-
loserEntries.slice(0, 50).
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
await batch.commit();
|
|
40
|
+
await Promise.allSettled(loserEntries.slice(0, 50).map(({ data: bid }) => sendNotification({
|
|
41
|
+
userId: bid.userId,
|
|
42
|
+
type: "bid_lost",
|
|
43
|
+
priority: "normal",
|
|
44
|
+
title: AUCTION_MESSAGES.LOST_TITLE,
|
|
45
|
+
message: AUCTION_MESSAGES.LOST_MESSAGE(product.title),
|
|
46
|
+
relatedId: product.id,
|
|
47
|
+
relatedType: "product",
|
|
48
|
+
})));
|
|
49
49
|
ctx.logger.info(`Settled auction ${product.id}`, {
|
|
50
50
|
winner: winnerEntry.data.userId,
|
|
51
51
|
winningBid: winnerEntry.data.bidAmount,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { offerRepository
|
|
1
|
+
import { offerRepository } from "../../../../repositories";
|
|
2
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
3
|
export async function runOfferExpiry(ctx) {
|
|
3
4
|
ctx.logger.info("Starting offer expiry sweep");
|
|
4
5
|
let expiredOffers;
|
|
@@ -18,7 +19,7 @@ export async function runOfferExpiry(ctx) {
|
|
|
18
19
|
for (const offer of expiredOffers) {
|
|
19
20
|
try {
|
|
20
21
|
expiredIds.push(offer.id);
|
|
21
|
-
await
|
|
22
|
+
await sendNotification({
|
|
22
23
|
userId: offer.buyerUid,
|
|
23
24
|
type: "offer_expired",
|
|
24
25
|
priority: "normal",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { bidRepository,
|
|
1
|
+
import { bidRepository, productRepository, } from "../../../../repositories";
|
|
2
2
|
import { decryptPii } from "../../../../security/index";
|
|
3
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
3
4
|
import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
|
|
4
5
|
import { BID_MESSAGES } from "../handlers/messages";
|
|
5
6
|
const BIDS_COLLECTION = "bids";
|
|
@@ -15,33 +16,34 @@ export async function handleBidPlaced(input, ctx) {
|
|
|
15
16
|
if (prevWinning && prevWinning.data.userId !== newBid.userId) {
|
|
16
17
|
outbidUserId = prevWinning.data.userId;
|
|
17
18
|
bidRepository.markOutbid(batch, prevWinning.ref);
|
|
18
|
-
|
|
19
|
+
}
|
|
20
|
+
productRepository.incrementBidCountInBatch(batch, newBid.productId, newBid.bidAmount);
|
|
21
|
+
await batch.commit();
|
|
22
|
+
if (outbidUserId) {
|
|
23
|
+
const outbidMessage = BID_MESSAGES.OUTBID_MESSAGE(newBid.productTitle, newBid.currency, newBid.bidAmount);
|
|
24
|
+
await sendNotification({
|
|
19
25
|
userId: outbidUserId,
|
|
20
26
|
type: "bid_outbid",
|
|
21
27
|
priority: "high",
|
|
22
28
|
title: BID_MESSAGES.OUTBID_TITLE,
|
|
23
|
-
message:
|
|
29
|
+
message: outbidMessage,
|
|
24
30
|
relatedId: newBid.productId,
|
|
25
31
|
relatedType: "product",
|
|
26
32
|
});
|
|
27
|
-
|
|
28
|
-
productRepository.incrementBidCountInBatch(batch, newBid.productId, newBid.bidAmount);
|
|
29
|
-
await batch.commit();
|
|
30
|
-
try {
|
|
31
|
-
if (outbidUserId) {
|
|
33
|
+
try {
|
|
32
34
|
await getAdminRealtimeDb()
|
|
33
35
|
.ref(`notifications/${outbidUserId}`)
|
|
34
36
|
.push({
|
|
35
37
|
type: "bid_outbid",
|
|
36
38
|
title: BID_MESSAGES.OUTBID_TITLE,
|
|
37
|
-
message:
|
|
39
|
+
message: outbidMessage,
|
|
38
40
|
timestamp: Date.now(),
|
|
39
41
|
read: false,
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
catch (rtdbError) {
|
|
45
|
+
ctx.logger.error("Realtime DB push failed (non-fatal)", rtdbError);
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
ctx.logger.info(`Bid placed on ${newBid.productId}`, {
|
|
47
49
|
bidId,
|
|
@@ -1,87 +1,33 @@
|
|
|
1
|
-
import { notificationRepository } from "../../../../repositories";
|
|
2
|
-
import { decryptPii } from "../../../../security/index";
|
|
3
1
|
import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
|
|
4
|
-
import {
|
|
2
|
+
import { decryptPii } from "../../../../security/index";
|
|
3
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
4
|
+
import { ORDER_MESSAGES } from "../handlers/messages";
|
|
5
5
|
const STATUS_CONFIG = {
|
|
6
6
|
confirmed: {
|
|
7
7
|
type: "order_confirmed",
|
|
8
8
|
title: ORDER_MESSAGES.CONFIRMED_TITLE,
|
|
9
9
|
message: (o) => ORDER_MESSAGES.CONFIRMED_MESSAGE(o.productTitle),
|
|
10
10
|
priority: "normal",
|
|
11
|
-
sendEmail: true,
|
|
12
11
|
},
|
|
13
12
|
shipped: {
|
|
14
13
|
type: "order_shipped",
|
|
15
14
|
title: ORDER_MESSAGES.SHIPPED_TITLE,
|
|
16
15
|
message: (o) => ORDER_MESSAGES.SHIPPED_MESSAGE(o.productTitle, o.trackingNumber),
|
|
17
16
|
priority: "high",
|
|
18
|
-
sendEmail: true,
|
|
19
17
|
},
|
|
20
18
|
delivered: {
|
|
21
19
|
type: "order_delivered",
|
|
22
20
|
title: ORDER_MESSAGES.DELIVERED_TITLE,
|
|
23
21
|
message: (o) => ORDER_MESSAGES.DELIVERED_MESSAGE(o.productTitle),
|
|
24
22
|
priority: "normal",
|
|
25
|
-
sendEmail: true,
|
|
26
23
|
},
|
|
27
24
|
cancelled: {
|
|
28
25
|
type: "order_cancelled",
|
|
29
26
|
title: ORDER_MESSAGES.CANCELLED_TITLE,
|
|
30
27
|
message: (o) => `Your order for "${o.productTitle}" has been cancelled.`,
|
|
31
28
|
priority: "normal",
|
|
32
|
-
sendEmail: false,
|
|
33
29
|
},
|
|
34
30
|
};
|
|
35
|
-
async function sendResendEmail(params) {
|
|
36
|
-
const subject = params.status === "confirmed"
|
|
37
|
-
? EMAIL_SUBJECTS.ORDER_CONFIRMED(params.productTitle)
|
|
38
|
-
: params.status === "shipped"
|
|
39
|
-
? EMAIL_SUBJECTS.ORDER_SHIPPED(params.productTitle)
|
|
40
|
-
: params.status === "delivered"
|
|
41
|
-
? EMAIL_SUBJECTS.ORDER_DELIVERED(params.productTitle)
|
|
42
|
-
: EMAIL_SUBJECTS.ORDER_UPDATE_FALLBACK(params.productTitle);
|
|
43
|
-
const response = await fetch("https://api.resend.com/emails", {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: {
|
|
46
|
-
Authorization: `Bearer ${params.apiKey}`,
|
|
47
|
-
"Content-Type": "application/json",
|
|
48
|
-
},
|
|
49
|
-
body: JSON.stringify({
|
|
50
|
-
from: params.fromAddress,
|
|
51
|
-
to: [params.to],
|
|
52
|
-
subject,
|
|
53
|
-
html: `<p>Hi,</p><p>Your order for <strong>${params.productTitle}</strong> is now <strong>${params.status}</strong>.</p>${params.trackingNumber ? `<p>Tracking number: ${params.trackingNumber}</p>` : ""}<p>Thanks,<br/>${params.brandName} Team</p>`,
|
|
54
|
-
}),
|
|
55
|
-
});
|
|
56
|
-
if (!response.ok) {
|
|
57
|
-
const body = await response.text();
|
|
58
|
-
throw new Error(JOB_ERROR_MESSAGES.RESEND_API_ERROR(response.status, body));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function sendStatusChangeEmail(ctx, userEmail, newStatus, orderId, after, apiKey) {
|
|
62
|
-
try {
|
|
63
|
-
const fromAddress = ctx.env("ORDER_EMAIL_FROM") ?? ctx.env("RESEND_FROM_ADDRESS") ?? "";
|
|
64
|
-
const brandName = ctx.env("APPKIT_BRAND_NAME") ?? "";
|
|
65
|
-
if (!fromAddress || !brandName) {
|
|
66
|
-
ctx.logger.error("ORDER_EMAIL_FROM / APPKIT_BRAND_NAME not configured — skipping order status email", null, { orderId });
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
await sendResendEmail({
|
|
70
|
-
to: userEmail,
|
|
71
|
-
status: newStatus,
|
|
72
|
-
orderId,
|
|
73
|
-
productTitle: after.productTitle,
|
|
74
|
-
trackingNumber: after.trackingNumber,
|
|
75
|
-
apiKey,
|
|
76
|
-
fromAddress,
|
|
77
|
-
brandName,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (emailError) {
|
|
82
|
-
ctx.logger.error("Email send failed (non-fatal)", emailError, { orderId });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
31
|
export async function handleOrderStatusChange(input, ctx) {
|
|
86
32
|
const { orderId, before, after } = input;
|
|
87
33
|
if (!before || !after)
|
|
@@ -100,7 +46,7 @@ export async function handleOrderStatusChange(input, ctx) {
|
|
|
100
46
|
productTitle: after.productTitle,
|
|
101
47
|
trackingNumber: after.trackingNumber,
|
|
102
48
|
});
|
|
103
|
-
await
|
|
49
|
+
await sendNotification({
|
|
104
50
|
userId: after.userId,
|
|
105
51
|
type: config.type,
|
|
106
52
|
priority: config.priority,
|
|
@@ -108,6 +54,7 @@ export async function handleOrderStatusChange(input, ctx) {
|
|
|
108
54
|
message: messageText,
|
|
109
55
|
relatedId: orderId,
|
|
110
56
|
relatedType: "order",
|
|
57
|
+
userEmail,
|
|
111
58
|
});
|
|
112
59
|
try {
|
|
113
60
|
await getAdminRealtimeDb().ref(`notifications/${after.userId}`).push({
|
|
@@ -121,19 +68,7 @@ export async function handleOrderStatusChange(input, ctx) {
|
|
|
121
68
|
catch (rtdbError) {
|
|
122
69
|
ctx.logger.error("Realtime DB push failed (non-fatal)", rtdbError);
|
|
123
70
|
}
|
|
124
|
-
|
|
125
|
-
const apiKey = ctx.env("RESEND_API_KEY") ?? "";
|
|
126
|
-
if (!apiKey) {
|
|
127
|
-
ctx.logger.error(JOB_ERROR_MESSAGES.RESEND_KEY_MISSING, null);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
await sendStatusChangeEmail(ctx, userEmail, newStatus, orderId, after, apiKey);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
ctx.logger.info(`Order ${orderId} status → ${newStatus}`, {
|
|
134
|
-
userId: after.userId,
|
|
135
|
-
emailSent: config.sendEmail,
|
|
136
|
-
});
|
|
71
|
+
ctx.logger.info(`Order ${orderId} status → ${newStatus}`, { userId: after.userId });
|
|
137
72
|
}
|
|
138
73
|
catch (error) {
|
|
139
74
|
ctx.logger.error("Error handling order status change", error, { orderId });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
2
|
export async function handleSupportTicketCreate(input, ctx) {
|
|
3
3
|
const { ticketId, ticket } = input;
|
|
4
4
|
const userId = ticket.userId;
|
|
@@ -6,17 +6,15 @@ export async function handleSupportTicketCreate(input, ctx) {
|
|
|
6
6
|
ctx.logger.warn("onSupportTicketCreate: no userId on ticket", { ticketId });
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
// Confirm to the user their ticket was received
|
|
10
9
|
try {
|
|
11
|
-
await
|
|
10
|
+
await sendNotification({
|
|
12
11
|
userId,
|
|
13
12
|
type: "account_action",
|
|
13
|
+
priority: "normal",
|
|
14
14
|
title: "Support ticket received",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
entityType: "support_ticket",
|
|
19
|
-
createdAt: new Date(),
|
|
15
|
+
message: `We received your support request: "${ticket.subject ?? "your ticket"}". We'll get back to you soon.`,
|
|
16
|
+
relatedId: ticketId,
|
|
17
|
+
relatedType: "support_ticket",
|
|
20
18
|
});
|
|
21
19
|
}
|
|
22
20
|
catch (err) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
2
|
const USER_NOTIFY_STATUSES = new Set(["resolved", "closed", "waiting_on_user", "in_progress"]);
|
|
3
3
|
const STATUS_MESSAGES = {
|
|
4
4
|
resolved: {
|
|
@@ -32,15 +32,14 @@ export async function handleSupportTicketUpdate(input, ctx) {
|
|
|
32
32
|
if (!msg)
|
|
33
33
|
return;
|
|
34
34
|
try {
|
|
35
|
-
await
|
|
35
|
+
await sendNotification({
|
|
36
36
|
userId,
|
|
37
37
|
type: "account_action",
|
|
38
|
+
priority: "normal",
|
|
38
39
|
title: msg.title,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
entityType: "support_ticket",
|
|
43
|
-
createdAt: new Date(),
|
|
40
|
+
message: msg.body(subject),
|
|
41
|
+
relatedId: ticketId,
|
|
42
|
+
relatedType: "support_ticket",
|
|
44
43
|
});
|
|
45
44
|
}
|
|
46
45
|
catch (err) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { orderRepository } from "../../../../repositories";
|
|
2
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
3
|
import { ORDER_MESSAGES } from "../handlers/messages";
|
|
3
4
|
const DEFAULT_TIMEOUT_HOURS = 24;
|
|
4
5
|
export async function runPendingOrderTimeout(ctx) {
|
|
@@ -12,16 +13,16 @@ export async function runPendingOrderTimeout(ctx) {
|
|
|
12
13
|
const batch = ctx.db.batch();
|
|
13
14
|
for (const entry of timedOut) {
|
|
14
15
|
orderRepository.cancelInBatch(batch, entry.ref);
|
|
15
|
-
notificationRepository.createInBatch(batch, {
|
|
16
|
-
userId: entry.data.userId,
|
|
17
|
-
type: "order_cancelled",
|
|
18
|
-
priority: "normal",
|
|
19
|
-
title: ORDER_MESSAGES.CANCELLED_TITLE,
|
|
20
|
-
message: ORDER_MESSAGES.CANCELLED_TIMEOUT_MESSAGE(entry.data.productTitle, timeoutHours),
|
|
21
|
-
relatedId: entry.data.id,
|
|
22
|
-
relatedType: "order",
|
|
23
|
-
});
|
|
24
16
|
}
|
|
25
17
|
await batch.commit();
|
|
18
|
+
await Promise.allSettled(timedOut.map((entry) => sendNotification({
|
|
19
|
+
userId: entry.data.userId,
|
|
20
|
+
type: "order_cancelled",
|
|
21
|
+
priority: "normal",
|
|
22
|
+
title: ORDER_MESSAGES.CANCELLED_TITLE,
|
|
23
|
+
message: ORDER_MESSAGES.CANCELLED_TIMEOUT_MESSAGE(entry.data.productTitle, timeoutHours),
|
|
24
|
+
relatedId: entry.data.id,
|
|
25
|
+
relatedType: "order",
|
|
26
|
+
})));
|
|
26
27
|
ctx.logger.info("Pending order timeout complete", { cancelled: timedOut.length });
|
|
27
28
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
2
|
import { ORDER_FIELDS, PRODUCT_FIELDS, COMMON_FIELDS } from "../../../../constants/field-names";
|
|
3
3
|
const ORDER_COLLECTION = "orders";
|
|
4
4
|
export async function runPrizeRevealExpiry(ctx) {
|
|
@@ -34,7 +34,7 @@ export async function runPrizeRevealExpiry(ctx) {
|
|
|
34
34
|
});
|
|
35
35
|
if (order.userId) {
|
|
36
36
|
try {
|
|
37
|
-
await
|
|
37
|
+
await sendNotification({
|
|
38
38
|
userId: order.userId,
|
|
39
39
|
type: "prize_reveal_expired",
|
|
40
40
|
priority: "high",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
2
|
import { PRODUCT_FIELDS, ORDER_FIELDS, COMMON_FIELDS } from "../../../../constants/field-names";
|
|
3
3
|
const PRODUCT_COLLECTION = "products";
|
|
4
4
|
const ORDER_COLLECTION = "orders";
|
|
@@ -38,7 +38,7 @@ export async function runPrizeRevealOpen(ctx) {
|
|
|
38
38
|
if (!order.userId || order.prizeWon)
|
|
39
39
|
continue;
|
|
40
40
|
try {
|
|
41
|
-
await
|
|
41
|
+
await sendNotification({
|
|
42
42
|
userId: order.userId,
|
|
43
43
|
type: "prize_reveal_ready",
|
|
44
44
|
priority: "high",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
2
2
|
import { ORDER_FIELDS, PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
3
3
|
const ORDER_COLLECTION = "orders";
|
|
4
4
|
const ONE_DAY_MS = 86400000;
|
|
@@ -23,7 +23,7 @@ export async function runPrizeRevealReminder(ctx) {
|
|
|
23
23
|
if (!order.userId || order.prizeWon || !order.prizeDrawProductId)
|
|
24
24
|
continue;
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
await sendNotification({
|
|
27
27
|
userId: order.userId,
|
|
28
28
|
type: "prize_reveal_reminder",
|
|
29
29
|
priority: "normal",
|
|
@@ -4,6 +4,7 @@ import { userRepository } from "../../auth/repository/user.repository";
|
|
|
4
4
|
import { createResendProvider } from "../../../providers/email-resend/provider";
|
|
5
5
|
import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
|
|
6
6
|
import { serverLogger } from "../../../monitoring";
|
|
7
|
+
import { decryptPii } from "../../../security/index";
|
|
7
8
|
import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore";
|
|
8
9
|
export async function markNotificationRead(id) {
|
|
9
10
|
await notificationRepository.markAsRead(id);
|
|
@@ -53,14 +54,20 @@ export async function sendNotification(input) {
|
|
|
53
54
|
// Load user notification preferences (best-effort — if missing, all channels allowed).
|
|
54
55
|
let userChannelPrefs = {};
|
|
55
56
|
let userTypePrefs = {};
|
|
57
|
+
let fetchedUserDoc;
|
|
56
58
|
try {
|
|
57
|
-
|
|
58
|
-
userChannelPrefs =
|
|
59
|
-
userTypePrefs =
|
|
59
|
+
fetchedUserDoc = await userRepository.findById(input.userId);
|
|
60
|
+
userChannelPrefs = fetchedUserDoc?.notificationPreferences?.channels ?? {};
|
|
61
|
+
userTypePrefs = fetchedUserDoc?.notificationPreferences?.types ?? {};
|
|
60
62
|
}
|
|
61
63
|
catch {
|
|
62
64
|
// non-fatal: fall through with empty prefs (all enabled)
|
|
63
65
|
}
|
|
66
|
+
// If caller didn't supply userEmail, try to decrypt it from the already-fetched user doc.
|
|
67
|
+
const resolvedEmail = userEmail ??
|
|
68
|
+
(fetchedUserDoc?.email ? decryptPii(fetchedUserDoc.email) : undefined);
|
|
69
|
+
const resolvedPhone = userPhone ??
|
|
70
|
+
(fetchedUserDoc?.phoneNumber ? decryptPii(fetchedUserDoc.phoneNumber) : undefined);
|
|
64
71
|
// Map NotificationType to a NotificationTypePrefs key.
|
|
65
72
|
const typeToPrefsKey = {
|
|
66
73
|
order_placed: "orderUpdates", order_confirmed: "orderUpdates",
|
|
@@ -69,11 +76,14 @@ export async function sendNotification(input) {
|
|
|
69
76
|
bid_placed: "bids", bid_outbid: "bids", bid_won: "bids", bid_lost: "bids",
|
|
70
77
|
review_approved: "reviews", review_replied: "reviews",
|
|
71
78
|
promotion: "promotions",
|
|
72
|
-
system: "system", welcome: "system",
|
|
79
|
+
system: "system", welcome: "system", account_action: "system",
|
|
73
80
|
offer_received: "offers", offer_responded: "offers",
|
|
74
81
|
offer_expired: "offers", offer_counter_accepted: "offers",
|
|
75
82
|
refund_initiated: "orderUpdates",
|
|
76
83
|
product_available: "system",
|
|
84
|
+
prize_reveal_ready: "orderUpdates",
|
|
85
|
+
prize_reveal_expired: "orderUpdates",
|
|
86
|
+
prize_reveal_reminder: "orderUpdates",
|
|
77
87
|
};
|
|
78
88
|
const typeKey = typeToPrefsKey[type];
|
|
79
89
|
// If the user has explicitly disabled this notification type, skip all external channels.
|
|
@@ -84,7 +94,7 @@ export async function sendNotification(input) {
|
|
|
84
94
|
let emailStatus = "skipped";
|
|
85
95
|
if (channels.email.enabled &&
|
|
86
96
|
userChannelPrefs.email !== false &&
|
|
87
|
-
|
|
97
|
+
resolvedEmail &&
|
|
88
98
|
meetsMinPriority(priority, channels.email.minPriority) &&
|
|
89
99
|
(!channels.email.types?.length || channels.email.types.includes(type))) {
|
|
90
100
|
const apiKey = creds.resendApiKey?.trim() ?? "";
|
|
@@ -94,7 +104,7 @@ export async function sendNotification(input) {
|
|
|
94
104
|
try {
|
|
95
105
|
const emailProvider = createResendProvider({ apiKey, fromEmail, fromName });
|
|
96
106
|
await emailProvider.send({
|
|
97
|
-
to:
|
|
107
|
+
to: resolvedEmail,
|
|
98
108
|
subject: title,
|
|
99
109
|
html: emailHtml ?? `<p>${message}</p>`,
|
|
100
110
|
text: message,
|
|
@@ -111,7 +121,7 @@ export async function sendNotification(input) {
|
|
|
111
121
|
let whatsappStatus = "skipped";
|
|
112
122
|
if (channels.whatsapp.enabled &&
|
|
113
123
|
userChannelPrefs.whatsapp !== false &&
|
|
114
|
-
|
|
124
|
+
resolvedPhone &&
|
|
115
125
|
meetsMinPriority(priority, channels.whatsapp.minPriority) &&
|
|
116
126
|
(!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type))) {
|
|
117
127
|
const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
|
|
@@ -119,7 +129,7 @@ export async function sendNotification(input) {
|
|
|
119
129
|
if (phoneNumberId && accessToken) {
|
|
120
130
|
try {
|
|
121
131
|
const ok = await sendWhatsAppBusinessMessage({
|
|
122
|
-
toPhone:
|
|
132
|
+
toPhone: resolvedPhone,
|
|
123
133
|
message: `*${title}*\n${message}`,
|
|
124
134
|
phoneNumberId,
|
|
125
135
|
accessToken,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Admin Feature Firestore Document Types & Constants
|
|
3
3
|
* Covers: notifications, chat rooms, site settings
|
|
4
4
|
*/
|
|
5
|
-
export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated";
|
|
5
|
+
export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_reveal_ready" | "prize_reveal_expired" | "prize_reveal_reminder";
|
|
6
6
|
export type NotificationPriority = "low" | "normal" | "high";
|
|
7
7
|
export interface NotificationDocument {
|
|
8
8
|
id: string;
|
|
@@ -17,7 +17,7 @@ export interface NotificationDocument {
|
|
|
17
17
|
isRead: boolean;
|
|
18
18
|
readAt?: Date;
|
|
19
19
|
relatedId?: string;
|
|
20
|
-
relatedType?: "order" | "product" | "bid" | "review" | "blog" | "user" | "offer";
|
|
20
|
+
relatedType?: "order" | "product" | "bid" | "review" | "blog" | "user" | "offer" | "support_ticket";
|
|
21
21
|
createdAt: Date;
|
|
22
22
|
updatedAt: Date;
|
|
23
23
|
}
|
|
@@ -60,6 +60,10 @@ export declare const NOTIFICATION_FIELDS: {
|
|
|
60
60
|
readonly OFFER_EXPIRED: NotificationType;
|
|
61
61
|
readonly OFFER_COUNTER_ACCEPTED: NotificationType;
|
|
62
62
|
readonly REFUND_INITIATED: NotificationType;
|
|
63
|
+
readonly ACCOUNT_ACTION: NotificationType;
|
|
64
|
+
readonly PRIZE_REVEAL_READY: NotificationType;
|
|
65
|
+
readonly PRIZE_REVEAL_EXPIRED: NotificationType;
|
|
66
|
+
readonly PRIZE_REVEAL_REMINDER: NotificationType;
|
|
63
67
|
};
|
|
64
68
|
readonly PRIORITY_VALUES: {
|
|
65
69
|
readonly LOW: NotificationPriority;
|
|
@@ -46,6 +46,10 @@ export const NOTIFICATION_FIELDS = {
|
|
|
46
46
|
OFFER_EXPIRED: "offer_expired",
|
|
47
47
|
OFFER_COUNTER_ACCEPTED: "offer_counter_accepted",
|
|
48
48
|
REFUND_INITIATED: "refund_initiated",
|
|
49
|
+
ACCOUNT_ACTION: "account_action",
|
|
50
|
+
PRIZE_REVEAL_READY: "prize_reveal_ready",
|
|
51
|
+
PRIZE_REVEAL_EXPIRED: "prize_reveal_expired",
|
|
52
|
+
PRIZE_REVEAL_REMINDER: "prize_reveal_reminder",
|
|
49
53
|
},
|
|
50
54
|
PRIORITY_VALUES: {
|
|
51
55
|
LOW: "low",
|
|
@@ -10,7 +10,7 @@ import { serverLogger } from "../../../monitoring";
|
|
|
10
10
|
import { orderRepository } from "../../orders/repository/orders.repository";
|
|
11
11
|
import { RefundStatusValues } from "../../orders/schemas";
|
|
12
12
|
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
13
|
-
import {
|
|
13
|
+
import { sendNotification } from "../../admin/actions/notification-actions";
|
|
14
14
|
const DEFAULT_PLATFORM_FEE_PERCENT = 5;
|
|
15
15
|
const DEFAULT_GST_PERCENT = 18;
|
|
16
16
|
export async function issuePartialRefund(adminUid, orderId, deductFees, refundNote) {
|
|
@@ -39,7 +39,7 @@ export async function issuePartialRefund(adminUid, orderId, deductFees, refundNo
|
|
|
39
39
|
paymentStatus: "refunded",
|
|
40
40
|
updatedAt: new Date(),
|
|
41
41
|
});
|
|
42
|
-
await
|
|
42
|
+
await sendNotification({
|
|
43
43
|
userId: order.userId,
|
|
44
44
|
type: "refund_initiated",
|
|
45
45
|
priority: "high",
|
|
@@ -8,7 +8,7 @@ import { serverLogger } from "../../../monitoring";
|
|
|
8
8
|
import { offerRepository } from "../repository/offer.repository";
|
|
9
9
|
import { productRepository } from "../../products/repository/products.repository";
|
|
10
10
|
import { ProductStatusValues } from "../../products/schemas";
|
|
11
|
-
import {
|
|
11
|
+
import { sendNotification } from "../../admin/actions/notification-actions";
|
|
12
12
|
import { userRepository } from "../../auth/repository/user.repository";
|
|
13
13
|
import { storeRepository } from "../../stores/repository/store.repository";
|
|
14
14
|
import { cartRepository } from "../../cart/repository/cart.repository";
|
|
@@ -57,7 +57,7 @@ export async function makeOffer(userId, userEmail, input) {
|
|
|
57
57
|
});
|
|
58
58
|
const sellerStore = product.storeId ? await storeRepository.findById(product.storeId) : null;
|
|
59
59
|
if (sellerStore?.ownerId) {
|
|
60
|
-
await
|
|
60
|
+
await sendNotification({
|
|
61
61
|
userId: sellerStore.ownerId,
|
|
62
62
|
type: "offer_received",
|
|
63
63
|
priority: "normal",
|
|
@@ -109,7 +109,7 @@ export async function respondToOffer(userId, input) {
|
|
|
109
109
|
: action === "decline"
|
|
110
110
|
? `Your offer on "${offer.productTitle}" was declined.`
|
|
111
111
|
: `${offer.storeName} countered with ₹${counterAmount} on "${offer.productTitle}".`;
|
|
112
|
-
await
|
|
112
|
+
await sendNotification({
|
|
113
113
|
userId: offer.buyerUid,
|
|
114
114
|
type: "offer_responded",
|
|
115
115
|
priority: action === "accept" ? "high" : "normal",
|
|
@@ -143,7 +143,7 @@ export async function acceptCounterOffer(userId, offerId) {
|
|
|
143
143
|
const updated = await offerRepository.acceptCounter(offerId);
|
|
144
144
|
const counterStore = offer.storeId ? await storeRepository.findById(offer.storeId) : null;
|
|
145
145
|
if (counterStore?.ownerId)
|
|
146
|
-
await
|
|
146
|
+
await sendNotification({
|
|
147
147
|
userId: counterStore.ownerId,
|
|
148
148
|
type: "offer_counter_accepted",
|
|
149
149
|
priority: "high",
|
|
@@ -199,7 +199,7 @@ export async function counterOfferByBuyer(userId, userEmail, input) {
|
|
|
199
199
|
});
|
|
200
200
|
const buyerCounterStore = offer.storeId ? await storeRepository.findById(offer.storeId) : null;
|
|
201
201
|
if (buyerCounterStore?.ownerId)
|
|
202
|
-
await
|
|
202
|
+
await sendNotification({
|
|
203
203
|
userId: buyerCounterStore.ownerId,
|
|
204
204
|
type: "offer_received",
|
|
205
205
|
priority: "normal",
|
package/dist/index.d.ts
CHANGED
|
@@ -532,6 +532,7 @@ export { supportRepository, SupportRepository } from "./repositories/index";
|
|
|
532
532
|
export type { SupportTicketDocument, SupportTicketCreateInput, SupportTicketUpdateInput, TicketMessage, TicketCategory, TicketStatus, TicketPriority, } from "./repositories/index";
|
|
533
533
|
export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, SUPPORT_TICKET_FIELDS, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
|
|
534
534
|
export { supportTicketsSeedData } from "./seed/index";
|
|
535
|
+
export { offersSeedData } from "./seed/index";
|
|
535
536
|
export { productFeaturesRepository } from "./repositories/index";
|
|
536
537
|
export type { ProductFeatureListFilter } from "./repositories/index";
|
|
537
538
|
export { loadProductFeaturesForStore } from "./repositories/index";
|
package/dist/index.js
CHANGED
|
@@ -1088,6 +1088,7 @@ export { supportRepository, SupportRepository } from "./repositories/index";
|
|
|
1088
1088
|
export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, SUPPORT_TICKET_FIELDS, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
|
|
1089
1089
|
// Support tickets â€" seed data
|
|
1090
1090
|
export { supportTicketsSeedData } from "./seed/index";
|
|
1091
|
+
export { offersSeedData } from "./seed/index";
|
|
1091
1092
|
// SB-UNI-B â€" sublistingCategoriesRepository + SublistingCategoryDocument deleted.
|
|
1092
1093
|
// Use categoriesRepository.findBySlugAndType(slug, "sublisting") and CategoryDocument with categoryType:"sublisting".
|
|
1093
1094
|
// [DB]-Database layer â€" uses firebase-admin; server-only.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the actual collection-specific seeding logic (800+ lines with PII encryption,
|
|
7
7
|
* Auth user creation, subcollection handling, etc.).
|
|
8
8
|
*/
|
|
9
|
-
export type SeedCollectionName = "users" | "addresses" | "categories" | "stores" | "products" | "orders" | "reviews" | "bids" | "coupons" | "carousels" | "carouselSlides" | "homepageSections" | "siteSettings" | "faqs" | "notifications" | "payouts" | "blogPosts" | "events" | "eventEntries" | "sessions" | "carts" | "wishlists" | "history" | "conversations" | "groupedListings" | "scammerProfiles" | "supportTickets" | "productFeatures";
|
|
9
|
+
export type SeedCollectionName = "users" | "addresses" | "categories" | "stores" | "products" | "orders" | "reviews" | "bids" | "coupons" | "carousels" | "carouselSlides" | "homepageSections" | "siteSettings" | "faqs" | "notifications" | "payouts" | "blogPosts" | "events" | "eventEntries" | "sessions" | "carts" | "wishlists" | "history" | "conversations" | "groupedListings" | "scammerProfiles" | "supportTickets" | "productFeatures" | "offers";
|
|
10
10
|
export interface SeedOperationResult {
|
|
11
11
|
success?: boolean;
|
|
12
12
|
message: string;
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export { groupedListingsSeedData } from "./grouped-listings-seed-data";
|
|
|
72
72
|
export { scammersSeedData } from "./scammers-seed-data";
|
|
73
73
|
export { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
74
74
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
75
|
+
export { offersSeedData } from "./offers-seed-data";
|
|
75
76
|
export type { SeedManifest, SeedManifestEntry } from "./manifest";
|
|
76
77
|
export { SEED_MANIFEST } from "./manifest";
|
|
77
78
|
export type { FirestoreIndexConfig } from "./firestore-indexes";
|
package/dist/seed/index.js
CHANGED
|
@@ -61,6 +61,7 @@ export { groupedListingsSeedData } from "./grouped-listings-seed-data";
|
|
|
61
61
|
export { scammersSeedData } from "./scammers-seed-data";
|
|
62
62
|
export { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
63
63
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
64
|
+
export { offersSeedData } from "./offers-seed-data";
|
|
64
65
|
export { SEED_MANIFEST } from "./manifest";
|
|
65
66
|
export { mergeFirestoreIndices, generateMergedFirestoreIndexFile, } from "./firestore-indexes";
|
|
66
67
|
// Test utilities (Firestore emulator + PII assertion)
|
package/dist/seed/manifest.js
CHANGED
|
@@ -39,6 +39,7 @@ import { groupedListingsSeedData } from "./grouped-listings-seed-data";
|
|
|
39
39
|
import { scammersSeedData } from "./scammers-seed-data";
|
|
40
40
|
import { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
41
41
|
import { productFeaturesSeedData } from "./product-features-seed-data";
|
|
42
|
+
import { offersSeedData } from "./offers-seed-data";
|
|
42
43
|
function asArr(items) {
|
|
43
44
|
return items ?? [];
|
|
44
45
|
}
|
|
@@ -167,4 +168,8 @@ export const SEED_MANIFEST = {
|
|
|
167
168
|
...f,
|
|
168
169
|
name: f.label ?? f.id,
|
|
169
170
|
}))),
|
|
171
|
+
offers: pick(asArr(offersSeedData).map((o) => ({
|
|
172
|
+
...o,
|
|
173
|
+
name: o.productTitle ?? o.id,
|
|
174
|
+
}))),
|
|
170
175
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offers Seed Data — 12 offers across all statuses.
|
|
3
|
+
*
|
|
4
|
+
* Covers: pending (×3), accepted (×1), declined (×2), countered (×2),
|
|
5
|
+
* expired (×2), withdrawn (×1), paid (×2).
|
|
6
|
+
* Admin as buyer: 1 pending (Hot Wheels Banana Camaro) + 1 paid (ALTER Rem Wedding).
|
|
7
|
+
* Paid offers link to order-admin-043-alter-rem-offer and order-arjun-044-bx01-offer.
|
|
8
|
+
*/
|
|
9
|
+
import type { OfferDocument } from "../features/seller/schemas/firestore";
|
|
10
|
+
export declare const offersSeedData: Partial<OfferDocument>[];
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offers Seed Data — 12 offers across all statuses.
|
|
3
|
+
*
|
|
4
|
+
* Covers: pending (×3), accepted (×1), declined (×2), countered (×2),
|
|
5
|
+
* expired (×2), withdrawn (×1), paid (×2).
|
|
6
|
+
* Admin as buyer: 1 pending (Hot Wheels Banana Camaro) + 1 paid (ALTER Rem Wedding).
|
|
7
|
+
* Paid offers link to order-admin-043-alter-rem-offer and order-arjun-044-bx01-offer.
|
|
8
|
+
*/
|
|
9
|
+
import { OfferStatusValues } from "../features/seller/schemas/firestore";
|
|
10
|
+
const NOW = new Date();
|
|
11
|
+
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
12
|
+
const daysFromNow = (n) => new Date(NOW.getTime() + n * 86400000);
|
|
13
|
+
export const offersSeedData = [
|
|
14
|
+
// ── 1. PENDING — admin as buyer — Hot Wheels Banana Camaro Redline ─────────
|
|
15
|
+
{
|
|
16
|
+
id: "offer-admin-hw-banana-pending",
|
|
17
|
+
productId: "product-hot-wheels-redline-banana-1968",
|
|
18
|
+
productTitle: "Hot Wheels 1968 Redline Banana Camaro (Custom)",
|
|
19
|
+
storeId: "store-diecast-depot",
|
|
20
|
+
storeName: "Diecast Depot",
|
|
21
|
+
buyerUid: "user-admin-letitrip",
|
|
22
|
+
buyerName: "LetItRip Admin",
|
|
23
|
+
buyerEmail: "admin@letitrip.in",
|
|
24
|
+
listedPrice: 1299900,
|
|
25
|
+
offerAmount: 999900,
|
|
26
|
+
currency: "INR",
|
|
27
|
+
status: OfferStatusValues.PENDING,
|
|
28
|
+
buyerNote: "Interested in this piece. Would you accept Rs 9,999?",
|
|
29
|
+
expiresAt: daysFromNow(3),
|
|
30
|
+
createdAt: daysAgo(1),
|
|
31
|
+
updatedAt: daysAgo(1),
|
|
32
|
+
},
|
|
33
|
+
// ── 2. PAID — admin as buyer — ALTER Rem Wedding (order placed) ───────────
|
|
34
|
+
{
|
|
35
|
+
id: "offer-admin-alter-rem-paid",
|
|
36
|
+
productId: "product-alter-rem-wedding-scale",
|
|
37
|
+
productTitle: "ALTER: Re:ZERO — Rem (Wedding Ver.) 1/7 Scale Figure",
|
|
38
|
+
storeId: "store-tokyo-toys-india",
|
|
39
|
+
storeName: "Tokyo Toys India",
|
|
40
|
+
buyerUid: "user-admin-letitrip",
|
|
41
|
+
buyerName: "LetItRip Admin",
|
|
42
|
+
buyerEmail: "admin@letitrip.in",
|
|
43
|
+
listedPrice: 1299900,
|
|
44
|
+
offerAmount: 1099900,
|
|
45
|
+
lockedPrice: 1099900,
|
|
46
|
+
currency: "INR",
|
|
47
|
+
status: OfferStatusValues.PAID,
|
|
48
|
+
buyerNote: "Would you accept Rs 10,999 for this?",
|
|
49
|
+
sellerNote: "Accepted. Thank you!",
|
|
50
|
+
expiresAt: daysAgo(3),
|
|
51
|
+
acceptedAt: daysAgo(12),
|
|
52
|
+
respondedAt: daysAgo(12),
|
|
53
|
+
createdAt: daysAgo(14),
|
|
54
|
+
updatedAt: daysAgo(11),
|
|
55
|
+
},
|
|
56
|
+
// ── 3. PENDING — Rahul — Charizard ex SIR from pokemon-palace ────────────
|
|
57
|
+
{
|
|
58
|
+
id: "offer-rahul-charizard-sar-pending",
|
|
59
|
+
productId: "product-pokemon-charizard-ex-sar-sv",
|
|
60
|
+
productTitle: "Pokemon TCG: Charizard ex Special Illustration Rare (SV3)",
|
|
61
|
+
storeId: "store-pokemon-palace",
|
|
62
|
+
storeName: "Pokemon Palace",
|
|
63
|
+
buyerUid: "user-rahul-sharma",
|
|
64
|
+
buyerName: "Rahul Sharma",
|
|
65
|
+
buyerEmail: "rahul.sharma@gmail.com",
|
|
66
|
+
listedPrice: 349900,
|
|
67
|
+
offerAmount: 280000,
|
|
68
|
+
currency: "INR",
|
|
69
|
+
status: OfferStatusValues.PENDING,
|
|
70
|
+
buyerNote: "Can you do Rs 2,800 for this card?",
|
|
71
|
+
expiresAt: daysFromNow(5),
|
|
72
|
+
createdAt: daysAgo(0),
|
|
73
|
+
updatedAt: daysAgo(0),
|
|
74
|
+
},
|
|
75
|
+
// ── 4. DECLINED — Priya — MAFEX Miles Morales from letitrip-official ───────
|
|
76
|
+
{
|
|
77
|
+
id: "offer-priya-mafex-miles-declined",
|
|
78
|
+
productId: "product-mafex-miles-morales-spiderman",
|
|
79
|
+
productTitle: "MAFEX No.240: Miles Morales Spider-Man",
|
|
80
|
+
storeId: "store-letitrip-official",
|
|
81
|
+
storeName: "LetItRip Official",
|
|
82
|
+
buyerUid: "user-priya-patel",
|
|
83
|
+
buyerName: "Priya Patel",
|
|
84
|
+
buyerEmail: "priya.patel@gmail.com",
|
|
85
|
+
listedPrice: 849900,
|
|
86
|
+
offerAmount: 650000,
|
|
87
|
+
currency: "INR",
|
|
88
|
+
status: OfferStatusValues.DECLINED,
|
|
89
|
+
buyerNote: "Offering Rs 6,500 — slightly above my budget.",
|
|
90
|
+
sellerNote: "Sorry, we cannot go below Rs 7,999 for this item.",
|
|
91
|
+
expiresAt: daysAgo(5),
|
|
92
|
+
respondedAt: daysAgo(8),
|
|
93
|
+
createdAt: daysAgo(10),
|
|
94
|
+
updatedAt: daysAgo(8),
|
|
95
|
+
},
|
|
96
|
+
// ── 5. COUNTERED — Arjun — SHF Goku Ultra Instinct from letitrip-official ──
|
|
97
|
+
{
|
|
98
|
+
id: "offer-arjun-shf-goku-countered",
|
|
99
|
+
productId: "product-shf-goku-ultra-instinct",
|
|
100
|
+
productTitle: "S.H.Figuarts Goku Ultra Instinct -Sign-",
|
|
101
|
+
storeId: "store-letitrip-official",
|
|
102
|
+
storeName: "LetItRip Official",
|
|
103
|
+
buyerUid: "user-arjun-singh",
|
|
104
|
+
buyerName: "Arjun Singh",
|
|
105
|
+
buyerEmail: "arjun.singh@gmail.com",
|
|
106
|
+
listedPrice: 699900,
|
|
107
|
+
offerAmount: 549900,
|
|
108
|
+
counterAmount: 649900,
|
|
109
|
+
currency: "INR",
|
|
110
|
+
status: OfferStatusValues.COUNTERED,
|
|
111
|
+
buyerNote: "Rs 5,499 — will you take it?",
|
|
112
|
+
sellerNote: "We can do Rs 6,499 as our best price.",
|
|
113
|
+
expiresAt: daysFromNow(2),
|
|
114
|
+
respondedAt: daysAgo(1),
|
|
115
|
+
createdAt: daysAgo(3),
|
|
116
|
+
updatedAt: daysAgo(1),
|
|
117
|
+
},
|
|
118
|
+
// ── 6. EXPIRED — Neha — Blue-Eyes LOB from cardgame-hub ──────────────────
|
|
119
|
+
{
|
|
120
|
+
id: "offer-neha-blue-eyes-expired",
|
|
121
|
+
productId: "product-yugioh-blue-eyes-lob-nm",
|
|
122
|
+
productTitle: "Yu-Gi-Oh! Blue-Eyes White Dragon LOB 1st Edition (NM)",
|
|
123
|
+
storeId: "store-cardgame-hub",
|
|
124
|
+
storeName: "CardGame Hub",
|
|
125
|
+
buyerUid: "user-neha-gupta",
|
|
126
|
+
buyerName: "Neha Gupta",
|
|
127
|
+
buyerEmail: "neha.gupta@gmail.com",
|
|
128
|
+
listedPrice: 299900,
|
|
129
|
+
offerAmount: 220000,
|
|
130
|
+
currency: "INR",
|
|
131
|
+
status: OfferStatusValues.EXPIRED,
|
|
132
|
+
buyerNote: "I can offer Rs 2,200. Let me know.",
|
|
133
|
+
expiresAt: daysAgo(2),
|
|
134
|
+
createdAt: daysAgo(9),
|
|
135
|
+
updatedAt: daysAgo(2),
|
|
136
|
+
},
|
|
137
|
+
// ── 7. WITHDRAWN — Vikram — Twin Mill 1970 Redline from vintage-vault ──────
|
|
138
|
+
{
|
|
139
|
+
id: "offer-vikram-twin-mill-withdrawn",
|
|
140
|
+
productId: "product-hot-wheels-twin-mill-1970-redline",
|
|
141
|
+
productTitle: "Hot Wheels Vintage: Twin Mill I 1970 Redline (Brown, Near Mint)",
|
|
142
|
+
storeId: "store-vintage-vault",
|
|
143
|
+
storeName: "Vintage Vault",
|
|
144
|
+
buyerUid: "user-vikram-rao",
|
|
145
|
+
buyerName: "Vikram Rao",
|
|
146
|
+
buyerEmail: "vikram.rao@gmail.com",
|
|
147
|
+
listedPrice: 699900,
|
|
148
|
+
offerAmount: 549900,
|
|
149
|
+
currency: "INR",
|
|
150
|
+
status: OfferStatusValues.WITHDRAWN,
|
|
151
|
+
buyerNote: "Offering Rs 5,499 for the Twin Mill.",
|
|
152
|
+
expiresAt: daysAgo(3),
|
|
153
|
+
createdAt: daysAgo(7),
|
|
154
|
+
updatedAt: daysAgo(5),
|
|
155
|
+
},
|
|
156
|
+
// ── 8. PAID — Arjun — BX-01 Dran Sword from beyblade-arena (offer order) ───
|
|
157
|
+
{
|
|
158
|
+
id: "offer-arjun-bx01-paid",
|
|
159
|
+
productId: "product-beyblade-x-bx01-dran-sword-starter-pack",
|
|
160
|
+
productTitle: "Beyblade X: BX-01 Dran Sword 3-60F Starter Pack",
|
|
161
|
+
storeId: "store-beyblade-arena",
|
|
162
|
+
storeName: "Beyblade Arena",
|
|
163
|
+
buyerUid: "user-arjun-singh",
|
|
164
|
+
buyerName: "Arjun Singh",
|
|
165
|
+
buyerEmail: "arjun.singh@gmail.com",
|
|
166
|
+
listedPrice: 159900,
|
|
167
|
+
offerAmount: 129900,
|
|
168
|
+
lockedPrice: 129900,
|
|
169
|
+
currency: "INR",
|
|
170
|
+
status: OfferStatusValues.PAID,
|
|
171
|
+
buyerNote: "Rs 1,299 for the BX-01 starter — deal?",
|
|
172
|
+
sellerNote: "Sure, accepted!",
|
|
173
|
+
expiresAt: daysAgo(10),
|
|
174
|
+
acceptedAt: daysAgo(20),
|
|
175
|
+
respondedAt: daysAgo(20),
|
|
176
|
+
createdAt: daysAgo(22),
|
|
177
|
+
updatedAt: daysAgo(19),
|
|
178
|
+
},
|
|
179
|
+
// ── 9. ACCEPTED — Pooja — GSC Aqua 1/7 from tokyo-toys (pending payment) ───
|
|
180
|
+
{
|
|
181
|
+
id: "offer-pooja-gsc-aqua-accepted",
|
|
182
|
+
productId: "product-gsc-aqua-konosuba-scale",
|
|
183
|
+
productTitle: "Good Smile Company: KonoSuba — Aqua 1/7 Scale Figure",
|
|
184
|
+
storeId: "store-tokyo-toys-india",
|
|
185
|
+
storeName: "Tokyo Toys India",
|
|
186
|
+
buyerUid: "user-pooja-sharma",
|
|
187
|
+
buyerName: "Pooja Sharma",
|
|
188
|
+
buyerEmail: "pooja.sharma@gmail.com",
|
|
189
|
+
listedPrice: 899900,
|
|
190
|
+
offerAmount: 749900,
|
|
191
|
+
lockedPrice: 749900,
|
|
192
|
+
currency: "INR",
|
|
193
|
+
status: OfferStatusValues.ACCEPTED,
|
|
194
|
+
buyerNote: "Rs 7,499 final offer on the Aqua figure.",
|
|
195
|
+
sellerNote: "Accepted! Please complete payment within 24 hours.",
|
|
196
|
+
expiresAt: daysFromNow(1),
|
|
197
|
+
acceptedAt: daysAgo(0),
|
|
198
|
+
respondedAt: daysAgo(0),
|
|
199
|
+
createdAt: daysAgo(1),
|
|
200
|
+
updatedAt: daysAgo(0),
|
|
201
|
+
},
|
|
202
|
+
// ── 10. DECLINED — Meera — PG Unleashed RX-78-2 from gundam-galaxy ─────────
|
|
203
|
+
{
|
|
204
|
+
id: "offer-meera-pg-rx78-declined",
|
|
205
|
+
productId: "product-pg-unleashed-rx78-2",
|
|
206
|
+
productTitle: "Bandai PG Unleashed 1/60: RX-78-2 Gundam",
|
|
207
|
+
storeId: "store-gundam-galaxy",
|
|
208
|
+
storeName: "Gundam Galaxy",
|
|
209
|
+
buyerUid: "user-meera-nair",
|
|
210
|
+
buyerName: "Meera Nair",
|
|
211
|
+
buyerEmail: "meera.nair@gmail.com",
|
|
212
|
+
listedPrice: 2499900,
|
|
213
|
+
offerAmount: 1999900,
|
|
214
|
+
currency: "INR",
|
|
215
|
+
status: OfferStatusValues.DECLINED,
|
|
216
|
+
buyerNote: "Rs 19,999 — this is a big spend, would you consider it?",
|
|
217
|
+
sellerNote: "Unfortunately we cannot discount below Rs 22,999 for this kit.",
|
|
218
|
+
expiresAt: daysAgo(1),
|
|
219
|
+
respondedAt: daysAgo(3),
|
|
220
|
+
createdAt: daysAgo(6),
|
|
221
|
+
updatedAt: daysAgo(3),
|
|
222
|
+
},
|
|
223
|
+
// ── 11. COUNTERED — Rohit — OP-05 Booster Box from cardgame-hub ─────────────
|
|
224
|
+
{
|
|
225
|
+
id: "offer-rohit-op05-countered",
|
|
226
|
+
productId: "product-onepiece-op05-booster-box",
|
|
227
|
+
productTitle: "One Piece Card Game: OP-05 Awakening of the New Era Booster Box",
|
|
228
|
+
storeId: "store-cardgame-hub",
|
|
229
|
+
storeName: "CardGame Hub",
|
|
230
|
+
buyerUid: "user-rohit-verma",
|
|
231
|
+
buyerName: "Rohit Verma",
|
|
232
|
+
buyerEmail: "rohit.verma@gmail.com",
|
|
233
|
+
listedPrice: 499900,
|
|
234
|
+
offerAmount: 399900,
|
|
235
|
+
counterAmount: 449900,
|
|
236
|
+
currency: "INR",
|
|
237
|
+
status: OfferStatusValues.COUNTERED,
|
|
238
|
+
buyerNote: "Can you do Rs 3,999 for the OP-05 box?",
|
|
239
|
+
sellerNote: "Best we can do is Rs 4,499 — final price.",
|
|
240
|
+
expiresAt: daysFromNow(1),
|
|
241
|
+
respondedAt: daysAgo(1),
|
|
242
|
+
createdAt: daysAgo(2),
|
|
243
|
+
updatedAt: daysAgo(1),
|
|
244
|
+
},
|
|
245
|
+
// ── 12. EXPIRED — Kavya — Nendoroid Rem from letitrip-official ───────────
|
|
246
|
+
{
|
|
247
|
+
id: "offer-kavya-nendo-rem-expired",
|
|
248
|
+
productId: "product-nendoroid-rem-rezero",
|
|
249
|
+
productTitle: "Nendoroid: Re:ZERO — Rem #663",
|
|
250
|
+
storeId: "store-letitrip-official",
|
|
251
|
+
storeName: "LetItRip Official",
|
|
252
|
+
buyerUid: "user-kavya-nair",
|
|
253
|
+
buyerName: "Kavya Nair",
|
|
254
|
+
buyerEmail: "kavya.nair@gmail.com",
|
|
255
|
+
listedPrice: 399900,
|
|
256
|
+
offerAmount: 329900,
|
|
257
|
+
currency: "INR",
|
|
258
|
+
status: OfferStatusValues.EXPIRED,
|
|
259
|
+
buyerNote: "Rs 3,299 for the Nendoroid Rem — is this okay?",
|
|
260
|
+
expiresAt: daysAgo(3),
|
|
261
|
+
createdAt: daysAgo(10),
|
|
262
|
+
updatedAt: daysAgo(3),
|
|
263
|
+
},
|
|
264
|
+
];
|
|
@@ -1405,9 +1405,123 @@ export const ordersSeedData = [
|
|
|
1405
1405
|
createdAt: daysAgo(3),
|
|
1406
1406
|
updatedAt: daysAgo(3),
|
|
1407
1407
|
},
|
|
1408
|
-
// ──
|
|
1408
|
+
// ── 43. DELIVERED — admin as buyer — ALTER Rem Wedding via accepted offer ────
|
|
1409
1409
|
{
|
|
1410
|
-
id: "order-admin-
|
|
1410
|
+
id: "order-admin-043-alter-rem-offer",
|
|
1411
|
+
productId: "product-alter-rem-wedding-scale",
|
|
1412
|
+
productTitle: "ALTER: Re:ZERO — Rem (Wedding Ver.) 1/7 Scale Figure",
|
|
1413
|
+
userId: "user-admin-letitrip",
|
|
1414
|
+
userName: "LetItRip Admin",
|
|
1415
|
+
userEmail: "admin@letitrip.in",
|
|
1416
|
+
storeId: "store-tokyo-toys-india",
|
|
1417
|
+
storeName: "Tokyo Toys India",
|
|
1418
|
+
offerId: "offer-admin-alter-rem-paid",
|
|
1419
|
+
items: [
|
|
1420
|
+
{
|
|
1421
|
+
productId: "product-alter-rem-wedding-scale",
|
|
1422
|
+
productTitle: "ALTER Rem Wedding Ver. 1/7 (offer price)",
|
|
1423
|
+
quantity: 1,
|
|
1424
|
+
unitPrice: 1099900,
|
|
1425
|
+
totalPrice: 1099900,
|
|
1426
|
+
},
|
|
1427
|
+
],
|
|
1428
|
+
quantity: 1,
|
|
1429
|
+
unitPrice: 1099900,
|
|
1430
|
+
totalPrice: 1099900,
|
|
1431
|
+
currency: "INR",
|
|
1432
|
+
status: OrderStatusValues.DELIVERED,
|
|
1433
|
+
paymentStatus: PaymentStatusValues.PAID,
|
|
1434
|
+
paymentMethod: PaymentMethodValues.RAZORPAY,
|
|
1435
|
+
paymentId: "pay_razorpay_043_demo",
|
|
1436
|
+
shippingAddress: "LetItRip HQ, 10th Floor, BKC G Block, Mumbai 400051",
|
|
1437
|
+
trackingNumber: "DTDC443322110",
|
|
1438
|
+
shippingCarrier: "DTDC",
|
|
1439
|
+
shippingFee: 0,
|
|
1440
|
+
platformFee: 54995,
|
|
1441
|
+
orderDate: daysAgo(11),
|
|
1442
|
+
shippingDate: daysAgo(9),
|
|
1443
|
+
deliveryDate: daysAgo(7),
|
|
1444
|
+
payoutStatus: "paid",
|
|
1445
|
+
createdAt: daysAgo(11),
|
|
1446
|
+
updatedAt: daysAgo(7),
|
|
1447
|
+
},
|
|
1448
|
+
// ── 44. DELIVERED — Arjun / BX-01 via accepted offer ─────────────────────
|
|
1449
|
+
{
|
|
1450
|
+
id: "order-arjun-044-bx01-offer",
|
|
1451
|
+
productId: "product-beyblade-x-bx01-dran-sword-starter-pack",
|
|
1452
|
+
productTitle: "Beyblade X: BX-01 Dran Sword 3-60F — Starter Pack",
|
|
1453
|
+
userId: "user-arjun-singh",
|
|
1454
|
+
userName: "Arjun Singh",
|
|
1455
|
+
userEmail: "arjun.singh@gmail.com",
|
|
1456
|
+
storeId: "store-beyblade-arena",
|
|
1457
|
+
storeName: "Beyblade Arena",
|
|
1458
|
+
offerId: "offer-arjun-bx01-paid",
|
|
1459
|
+
items: [
|
|
1460
|
+
{
|
|
1461
|
+
productId: "product-beyblade-x-bx01-dran-sword-starter-pack",
|
|
1462
|
+
productTitle: "BX-01 Dran Sword Starter Pack (offer price)",
|
|
1463
|
+
quantity: 1,
|
|
1464
|
+
unitPrice: 129900,
|
|
1465
|
+
totalPrice: 129900,
|
|
1466
|
+
},
|
|
1467
|
+
],
|
|
1468
|
+
quantity: 1,
|
|
1469
|
+
unitPrice: 129900,
|
|
1470
|
+
totalPrice: 129900,
|
|
1471
|
+
currency: "INR",
|
|
1472
|
+
status: OrderStatusValues.DELIVERED,
|
|
1473
|
+
paymentStatus: PaymentStatusValues.PAID,
|
|
1474
|
+
paymentMethod: PaymentMethodValues.UPI_MANUAL,
|
|
1475
|
+
shippingAddress: "78 Indiranagar 100 Ft Road, Bengaluru 560038",
|
|
1476
|
+
trackingNumber: "SHPR221100334",
|
|
1477
|
+
shippingCarrier: "Shiprocket",
|
|
1478
|
+
shippingFee: 0,
|
|
1479
|
+
platformFee: 6495,
|
|
1480
|
+
orderDate: daysAgo(19),
|
|
1481
|
+
shippingDate: daysAgo(17),
|
|
1482
|
+
deliveryDate: daysAgo(14),
|
|
1483
|
+
payoutStatus: "paid",
|
|
1484
|
+
createdAt: daysAgo(19),
|
|
1485
|
+
updatedAt: daysAgo(14),
|
|
1486
|
+
},
|
|
1487
|
+
// ── 45. DELIVERED — Kiran / Gundam Bundle (3 kits) ───────────────────────
|
|
1488
|
+
{
|
|
1489
|
+
id: "order-kiran-045-gundam-bundle",
|
|
1490
|
+
productId: "group-gundam-builder-essentials",
|
|
1491
|
+
productTitle: "Gundam Builder Essentials — MG + RG Bundle",
|
|
1492
|
+
userId: "user-kiran-reddy",
|
|
1493
|
+
userName: "Kiran Reddy",
|
|
1494
|
+
userEmail: "kiran.reddy@gmail.com",
|
|
1495
|
+
storeId: "store-gundam-galaxy",
|
|
1496
|
+
storeName: "Gundam Galaxy",
|
|
1497
|
+
items: [
|
|
1498
|
+
{ productId: "product-gundam-rx78-mg", productTitle: "MG RX-78-2 Gundam Ver. 3.0 1/100", quantity: 1, unitPrice: 399900, totalPrice: 399900 },
|
|
1499
|
+
{ productId: "product-gundam-wing-zero-rg", productTitle: "RG Wing Gundam Zero EW 1/144", quantity: 1, unitPrice: 249900, totalPrice: 249900 },
|
|
1500
|
+
{ productId: "product-hg-aerial-rebuild-gundam", productTitle: "HG Aerial Rebuild 1/144", quantity: 1, unitPrice: 249900, totalPrice: 249900 },
|
|
1501
|
+
],
|
|
1502
|
+
quantity: 3,
|
|
1503
|
+
unitPrice: 299900,
|
|
1504
|
+
totalPrice: 899700,
|
|
1505
|
+
currency: "INR",
|
|
1506
|
+
status: OrderStatusValues.DELIVERED,
|
|
1507
|
+
paymentStatus: PaymentStatusValues.PAID,
|
|
1508
|
+
paymentMethod: PaymentMethodValues.RAZORPAY,
|
|
1509
|
+
paymentId: "pay_razorpay_045_demo",
|
|
1510
|
+
shippingAddress: "18 Kondapur, Hyderabad, Telangana 500084",
|
|
1511
|
+
trackingNumber: "BLUE667788901",
|
|
1512
|
+
shippingCarrier: "Bluedart",
|
|
1513
|
+
shippingFee: 0,
|
|
1514
|
+
platformFee: 44985,
|
|
1515
|
+
orderDate: daysAgo(52),
|
|
1516
|
+
shippingDate: daysAgo(50),
|
|
1517
|
+
deliveryDate: daysAgo(47),
|
|
1518
|
+
payoutStatus: "paid",
|
|
1519
|
+
createdAt: daysAgo(52),
|
|
1520
|
+
updatedAt: daysAgo(47),
|
|
1521
|
+
},
|
|
1522
|
+
// ── Admin as buyer — 46. PROCESSING — admin / Good Smile Aqua Scale ────────
|
|
1523
|
+
{
|
|
1524
|
+
id: "order-admin-046-gsc-aqua",
|
|
1411
1525
|
productId: "product-gsc-aqua-konosuba-scale",
|
|
1412
1526
|
productTitle: "Good Smile Company: KonoSuba — Aqua 1/7 Scale Figure",
|
|
1413
1527
|
userId: "user-admin-letitrip",
|
|
@@ -133,4 +133,47 @@ export const supportTicketsSeedData = [
|
|
|
133
133
|
createdAt: daysBack(1),
|
|
134
134
|
updatedAt: daysBack(1),
|
|
135
135
|
},
|
|
136
|
+
// ── 7. Resolved — escalated fraud report (admin handled) ──────────────────
|
|
137
|
+
{
|
|
138
|
+
id: "ticket-meera-fraud-002",
|
|
139
|
+
userId: "user-meera-nair",
|
|
140
|
+
userEmail: "meera.nair@example.com",
|
|
141
|
+
userDisplayName: "Meera Nair",
|
|
142
|
+
category: "scam_report",
|
|
143
|
+
subject: "Seller sent empty box and is now unreachable",
|
|
144
|
+
description: "I paid Rs 8,499 for a S.H.Figuarts Broly via Razorpay. The seller (store-beyblade-arena) has not responded in 7 days and the tracking shows the box was 200g — way too light for the figure.",
|
|
145
|
+
status: SUPPORT_TICKET_FIELDS.STATUS_VALUES.RESOLVED,
|
|
146
|
+
priority: SUPPORT_TICKET_FIELDS.PRIORITY_VALUES.HIGH,
|
|
147
|
+
assignedTo: "user-admin-letitrip",
|
|
148
|
+
assignedToName: "LetItRip Admin",
|
|
149
|
+
messages: [
|
|
150
|
+
msg("msg-meera-002-u1", "user-meera-nair", "user", "I have the weight receipt from Delhivery. 200g is impossible for this figure. The seller is not replying on messages or phone.", 10),
|
|
151
|
+
msg("msg-meera-002-s1", "user-simran-kaur", "support", "Hi Meera, I have escalated this to our admin team for review as it qualifies as a potential fraud case. You will hear back within 24 hours.", 9),
|
|
152
|
+
msg("msg-meera-002-a1", "user-admin-letitrip", "support", "Hi Meera, this is the LetItRip admin. I have reviewed the shipment weight log and the seller communication history. We are initiating a full refund of Rs 8,499 under our buyer protection policy. The seller account has been suspended pending investigation.", 8),
|
|
153
|
+
msg("msg-meera-002-u2", "user-meera-nair", "user", "Thank you so much. I really appreciate the quick escalation.", 7),
|
|
154
|
+
],
|
|
155
|
+
resolvedAt: daysBack(7),
|
|
156
|
+
createdAt: daysBack(10),
|
|
157
|
+
updatedAt: daysBack(7),
|
|
158
|
+
},
|
|
159
|
+
// ── 8. In-progress — seller account ban appeal (admin handling) ────────────
|
|
160
|
+
{
|
|
161
|
+
id: "ticket-rohit-ban-appeal-001",
|
|
162
|
+
userId: "user-rohit-joshi",
|
|
163
|
+
userEmail: "rohit.joshi@example.com",
|
|
164
|
+
userDisplayName: "Rohit Joshi",
|
|
165
|
+
category: "account",
|
|
166
|
+
subject: "My store was suspended — I believe it was a mistake",
|
|
167
|
+
description: "My store Beyblade Arena was suspended 2 days ago. I received no email explanation. I have 100% positive reviews and have never violated any policy. Please review.",
|
|
168
|
+
status: SUPPORT_TICKET_FIELDS.STATUS_VALUES.IN_PROGRESS,
|
|
169
|
+
priority: SUPPORT_TICKET_FIELDS.PRIORITY_VALUES.HIGH,
|
|
170
|
+
assignedTo: "user-admin-letitrip",
|
|
171
|
+
assignedToName: "LetItRip Admin",
|
|
172
|
+
messages: [
|
|
173
|
+
msg("msg-rohit-001-u1", "user-rohit-joshi", "user", "I run a legitimate business. I have 47 verified reviews and never had a single complaint. Please explain the suspension.", 2),
|
|
174
|
+
msg("msg-rohit-001-a1", "user-admin-letitrip", "support", "Hi Rohit, your store was suspended as part of an automatic fraud flag triggered by a buyer report. I am manually reviewing your transaction history and the specific report. Please share your business GST number and a copy of your most recent bank statement showing the Razorpay settlements for verification.", 1),
|
|
175
|
+
],
|
|
176
|
+
createdAt: daysBack(2),
|
|
177
|
+
updatedAt: daysBack(1),
|
|
178
|
+
},
|
|
136
179
|
];
|
package/dist/server.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export { makeFullStore } from "./seed/index";
|
|
|
48
48
|
export { makeFullUser } from "./seed/index";
|
|
49
49
|
export { makeHomepageSection } from "./seed/index";
|
|
50
50
|
export { makeNotification } from "./seed/index";
|
|
51
|
+
export { sendNotification } from "./features/admin/actions/notification-actions";
|
|
51
52
|
export { makeOrder } from "./seed/index";
|
|
52
53
|
export { makePayout } from "./seed/index";
|
|
53
54
|
export { makeProduct } from "./seed/index";
|
package/dist/server.js
CHANGED
|
@@ -149,6 +149,7 @@ export { makeHomepageSection } from "./seed/index";
|
|
|
149
149
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
150
150
|
// makeNotification - Shared export for make notification.
|
|
151
151
|
export { makeNotification } from "./seed/index";
|
|
152
|
+
export { sendNotification } from "./features/admin/actions/notification-actions";
|
|
152
153
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
153
154
|
// makeOrder - Shared export for make order.
|
|
154
155
|
export { makeOrder } from "./seed/index";
|
|
@@ -7,22 +7,60 @@
|
|
|
7
7
|
padding: 0.375rem 0.625rem;
|
|
8
8
|
font-size: 0.875rem;
|
|
9
9
|
line-height: 1.4;
|
|
10
|
-
transition: border-color 0.
|
|
10
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.appkit-input::placeholder {
|
|
14
|
+
color: var(--appkit-color-text-faint);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.appkit-input:hover:not(:disabled):not(:focus-visible) {
|
|
18
|
+
border-color: var(--appkit-color-zinc-400);
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
.appkit-input:focus-visible {
|
|
14
22
|
outline: none;
|
|
15
23
|
border-color: var(--appkit-color-secondary);
|
|
16
|
-
box-shadow: 0 0 0
|
|
24
|
+
box-shadow: 0 0 0 3px rgba(101, 196, 8, 0.25), inset 0 0 0 1px rgba(101, 196, 8, 0.15);
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
.dark .appkit-input {
|
|
20
28
|
border-color: var(--appkit-color-border);
|
|
21
|
-
background: rgba(30, 41, 59, 0.
|
|
29
|
+
background: rgba(30, 41, 59, 0.7);
|
|
22
30
|
color: var(--appkit-color-text);
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
.dark .appkit-input::placeholder {
|
|
34
|
+
color: var(--appkit-color-text-muted);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.dark .appkit-input:hover:not(:disabled):not(:focus-visible) {
|
|
38
|
+
border-color: var(--appkit-color-slate-500);
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
.dark .appkit-input:focus-visible {
|
|
26
42
|
border-color: var(--appkit-color-primary);
|
|
27
|
-
box-shadow: 0 0 0
|
|
43
|
+
box-shadow: 0 0 0 3px rgba(233, 30, 140, 0.25), inset 0 0 0 1px rgba(233, 30, 140, 0.15);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Error variant */
|
|
47
|
+
.appkit-input--error {
|
|
48
|
+
border-color: var(--appkit-color-error);
|
|
49
|
+
background: rgba(254, 202, 202, 0.06);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.appkit-input--error:focus-visible {
|
|
53
|
+
border-color: var(--appkit-color-error);
|
|
54
|
+
box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.25), inset 0 0 0 1px rgba(248, 113, 113, 0.15);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Disabled state */
|
|
58
|
+
.appkit-input:disabled {
|
|
59
|
+
opacity: 0.55;
|
|
60
|
+
cursor: not-allowed;
|
|
61
|
+
background: var(--appkit-color-bg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.dark .appkit-input:disabled {
|
|
65
|
+
background: rgba(30, 41, 59, 0.4);
|
|
28
66
|
}
|