@mohasinac/appkit 2.7.32 → 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.
Files changed (50) hide show
  1. package/dist/_internal/server/features/faqs/index.d.ts +1 -0
  2. package/dist/_internal/server/features/faqs/index.js +1 -0
  3. package/dist/_internal/server/features/faqs/og.d.ts +9 -0
  4. package/dist/_internal/server/features/faqs/og.js +50 -0
  5. package/dist/_internal/server/features/reviews/index.d.ts +2 -0
  6. package/dist/_internal/server/features/reviews/index.js +2 -0
  7. package/dist/_internal/server/features/reviews/og-data.d.ts +1 -0
  8. package/dist/_internal/server/features/reviews/og-data.js +7 -0
  9. package/dist/_internal/server/features/reviews/og.d.ts +19 -0
  10. package/dist/_internal/server/features/reviews/og.js +70 -0
  11. package/dist/_internal/server/features/scams/index.d.ts +1 -0
  12. package/dist/_internal/server/features/scams/index.js +1 -0
  13. package/dist/_internal/server/features/scams/og.d.ts +18 -0
  14. package/dist/_internal/server/features/scams/og.js +59 -0
  15. package/dist/_internal/server/jobs/core/auctionSettlement.js +14 -14
  16. package/dist/_internal/server/jobs/core/offerExpiry.js +3 -2
  17. package/dist/_internal/server/jobs/core/onBidPlaced.js +14 -12
  18. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +6 -71
  19. package/dist/_internal/server/jobs/core/onSupportTicketCreate.js +6 -8
  20. package/dist/_internal/server/jobs/core/onSupportTicketUpdate.js +6 -7
  21. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +11 -10
  22. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +2 -2
  23. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +2 -2
  24. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +2 -2
  25. package/dist/features/admin/actions/notification-actions.js +18 -8
  26. package/dist/features/admin/schemas/firestore.d.ts +6 -2
  27. package/dist/features/admin/schemas/firestore.js +4 -0
  28. package/dist/features/orders/actions/refund-actions.js +2 -2
  29. package/dist/features/seller/actions/offer-actions.js +5 -5
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  33. package/dist/seed/conversations-seed-data.js +52 -0
  34. package/dist/seed/index.d.ts +1 -0
  35. package/dist/seed/index.js +1 -0
  36. package/dist/seed/manifest.js +5 -0
  37. package/dist/seed/notifications-seed-data.js +10 -0
  38. package/dist/seed/offers-seed-data.d.ts +10 -0
  39. package/dist/seed/offers-seed-data.js +264 -0
  40. package/dist/seed/orders-seed-data.js +116 -2
  41. package/dist/seed/sessions-seed-data.js +35 -1
  42. package/dist/seed/support-tickets-seed-data.js +43 -0
  43. package/dist/seed/users-seed-data.js +1 -1
  44. package/dist/seed/wishlists-seed-data.js +5 -1
  45. package/dist/server-entry.d.ts +3 -1
  46. package/dist/server-entry.js +3 -1
  47. package/dist/server.d.ts +5 -0
  48. package/dist/server.js +5 -0
  49. package/dist/ui/components/Input.style.css +42 -4
  50. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { notificationRepository } from "../../../../repositories";
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 notificationRepository.create({
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
- const userDoc = await userRepository.findById(input.userId);
58
- userChannelPrefs = userDoc?.notificationPreferences?.channels ?? {};
59
- userTypePrefs = userDoc?.notificationPreferences?.types ?? {};
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
- userEmail &&
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: userEmail,
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
- userPhone &&
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: userPhone,
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 { notificationRepository } from "../../admin/repository/notification.repository";
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 notificationRepository.create({
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 { notificationRepository } from "../../admin/repository/notification.repository";
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 notificationRepository.create({
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 notificationRepository.create({
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 notificationRepository.create({
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 notificationRepository.create({
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;
@@ -385,4 +385,56 @@ export const conversationsSeedData = [
385
385
  createdAt: minsAgo(240),
386
386
  updatedAt: minsAgo(200),
387
387
  },
388
+ // â"€â"€ 9. Admin as buyer â€" MAFEX Miles Morales post-purchase tracking query â"€â"€
389
+ {
390
+ id: "conv-mafex-admin-priya-009",
391
+ buyerId: "user-admin-letitrip",
392
+ buyerDisplayName: "LetItRip Admin",
393
+ sellerDisplayName: "Priya Anand",
394
+ storeId: "store-tokyo-toys-india",
395
+ storeName: "Tokyo Toys India",
396
+ productId: "product-mafex-miles-morales-spiderman",
397
+ productTitle: "MAFEX No.240: Miles Morales Spider-Man (Across the Spider-Verse)",
398
+ messages: [
399
+ {
400
+ id: "msg-009-1",
401
+ senderId: "user-admin-letitrip",
402
+ senderRole: "buyer",
403
+ body: "Hi! My order for MAFEX Miles Morales shipped 2 days ago - tracking shows it left your city but has not updated since. Can you check with Shiprocket?",
404
+ isRead: true,
405
+ sentAt: daysAgo(3),
406
+ },
407
+ {
408
+ id: "msg-009-2",
409
+ senderId: "user-priya-anand",
410
+ senderRole: "seller",
411
+ body: "Hi! Yes, I raised a ticket with Shiprocket about that. They confirmed it is in transit at the Mumbai sorting facility - tracking updates can lag by 24-48 hours. Should arrive tomorrow or day after. Apologies for the delay in the scan!",
412
+ isRead: true,
413
+ sentAt: daysAgo(3),
414
+ },
415
+ {
416
+ id: "msg-009-3",
417
+ senderId: "user-admin-letitrip",
418
+ senderRole: "buyer",
419
+ body: "Thanks for following up. Will keep an eye on it. The figure is for a platform demo so I do need it by Friday - hope it clears in time.",
420
+ isRead: true,
421
+ sentAt: daysAgo(2),
422
+ },
423
+ {
424
+ id: "msg-009-4",
425
+ senderId: "user-priya-anand",
426
+ senderRole: "seller",
427
+ body: "Completely understand. I have asked Shiprocket to flag it for priority delivery. If it does not arrive by Thursday let me know and I will arrange a direct courier at no extra charge.",
428
+ isRead: false,
429
+ sentAt: daysAgo(2),
430
+ },
431
+ ],
432
+ lastMessage: "Completely understand. I have asked Shiprocket to flag it for priority delivery. If it does not arrive by Thursday let me know and I will arrange a direct courier at no extra charge.",
433
+ lastMessageAt: daysAgo(2),
434
+ unreadBuyer: 1,
435
+ unreadSeller: 0,
436
+ status: "active",
437
+ createdAt: daysAgo(3),
438
+ updatedAt: daysAgo(2),
439
+ },
388
440
  ];
@@ -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";
@@ -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)
@@ -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
  };
@@ -204,6 +204,16 @@ export const notificationsSeedData = [
204
204
  { user: "user-anjali-verma", type: "WELCOME", title: "Welcome, Anjali!", message: "Use NEWUSER5 for ₹50 off your first order.", read: false, hours: 4 },
205
205
  { user: "user-pooja-sharma", type: "PROMOTION", title: "Wishlist sale — items in your wishlist are 15% off", message: "3 items in your wishlist are now on sale. Tap to view.", read: false, hours: 9 },
206
206
  ]),
207
+ // ── Admin role — system + operational notifications ─────────────────────
208
+ ...buildNotificationBatch([
209
+ { user: "user-admin-letitrip", type: "SYSTEM", title: "New seller registration: Gunpla Hub India", message: "Arjun Mehta has applied to open store Gunpla Hub India. Review their profile and approve or reject via the admin panel.", read: false, hours: 2 },
210
+ { user: "user-admin-letitrip", type: "SYSTEM", title: "Flagged listing: possible counterfeit Funko Pop", message: "Listing product-funko-pop-gojo-satoru was reported by 3 users for suspected counterfeit. Please review before the next buy cycle.", read: false, hours: 6 },
211
+ { user: "user-admin-letitrip", type: "SYSTEM", title: "Payout batch #12 processed — 6 stores, Rs 1.24L", message: "Payout batch for May Week 2 completed. 6 stores paid, 1 failed (IFSC mismatch — store-vintage-vault). Manual retry required.", read: true, hours: 24 },
212
+ { user: "user-admin-letitrip", type: "SYSTEM", title: "Support ticket escalated: order missing (high)", message: "Ticket ticket-rahul-order-001 was escalated from Simran Kaur. Buyer Rahul Sharma claims delivery was empty — courier claim filed. Needs admin review.", read: true, hours: 48 },
213
+ { user: "user-admin-letitrip", type: "SYSTEM", title: "Platform summary: week of May 12", message: "Week of May 12: 47 new orders (Rs 3.8L GMV), 12 new users, 2 new seller registrations, 0 chargebacks, 99.8% uptime. Full report in admin dashboard.", read: true, hours: 96 },
214
+ { user: "user-admin-letitrip", type: "REVIEW_APPROVED", title: "New 5-star review on LetItRip Official", message: "Naman Gupta left a 5-star review on figma Link (TotK): Best figma I own. Perfect packaging, zero damage.", read: true, hours: 36 },
215
+ { user: "user-admin-letitrip", type: "ORDER_SHIPPED", title: "Your Mew PSA 10 order has shipped", message: "Vintage Vault has dispatched your Mew 1st Edition PSA 10. Tracking: DTDC112233445. Estimated delivery in 3 business days.", read: true, hours: 240 },
216
+ ]),
207
217
  ];
208
218
  /**
209
219
  * Compact constructor for the P29 expansion. Keeps each row a single line of
@@ -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
+ ];