@mohasinac/appkit 3.0.6 → 3.1.1

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 (186) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
  3. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
  4. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
  6. package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
  7. package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
  8. package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
  9. package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
  10. package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
  11. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
  12. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
  13. package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
  14. package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
  15. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
  16. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
  17. package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
  18. package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
  19. package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
  20. package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
  21. package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
  22. package/dist/_internal/server/features/lottery/actions.js +243 -0
  23. package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
  24. package/dist/_internal/server/features/lottery/adapters.js +21 -0
  25. package/dist/_internal/server/features/lottery/data.d.ts +20 -0
  26. package/dist/_internal/server/features/lottery/data.js +41 -0
  27. package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
  28. package/dist/_internal/server/features/lottery/metadata.js +31 -0
  29. package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
  30. package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
  31. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
  32. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
  33. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
  34. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
  35. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
  36. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
  37. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
  38. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
  39. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
  40. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
  41. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
  42. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
  43. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
  44. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
  45. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
  46. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
  47. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
  48. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
  49. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
  50. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +46 -0
  53. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  54. package/dist/client.d.ts +9 -0
  55. package/dist/client.js +9 -0
  56. package/dist/constants/field-names.d.ts +5 -1
  57. package/dist/constants/field-names.js +5 -1
  58. package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
  59. package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
  60. package/dist/features/admin/api/bids/route.d.ts +2 -2
  61. package/dist/features/admin/api/coupons/route.d.ts +2 -2
  62. package/dist/features/admin/api/products/route.d.ts +2 -2
  63. package/dist/features/admin/api/reviews/route.d.ts +2 -2
  64. package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
  65. package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
  66. package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
  67. package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
  68. package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
  69. package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
  70. package/dist/features/auth/api/route.d.ts +2 -2
  71. package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
  72. package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
  73. package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
  74. package/dist/features/auth/repository/__tests__/session.test.js +146 -0
  75. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
  76. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
  77. package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
  78. package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
  79. package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
  80. package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
  81. package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
  82. package/dist/features/auth/repository/__tests__/token.test.js +140 -0
  83. package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
  84. package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
  85. package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
  86. package/dist/features/auth/repository/__tests__/user.test.js +226 -0
  87. package/dist/features/auth/repository/session.repository.js +10 -1
  88. package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
  89. package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
  90. package/dist/features/cart/repository/cart.repository.js +2 -2
  91. package/dist/features/categories/api/[id]/route.d.ts +4 -4
  92. package/dist/features/categories/api/route.d.ts +2 -2
  93. package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
  94. package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
  95. package/dist/features/events/components/EventCard.js +1 -0
  96. package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
  97. package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
  98. package/dist/features/events/schemas/firestore.d.ts +3 -0
  99. package/dist/features/events/schemas/index.d.ts +834 -14
  100. package/dist/features/events/schemas/index.js +80 -1
  101. package/dist/features/events/types/index.d.ts +4 -1
  102. package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
  103. package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
  104. package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
  105. package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
  106. package/dist/features/homepage/api/[id]/route.d.ts +4 -4
  107. package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
  108. package/dist/features/homepage/api/carousel/route.d.ts +2 -2
  109. package/dist/features/homepage/api/route.d.ts +2 -2
  110. package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
  111. package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
  112. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
  113. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
  114. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
  115. package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
  116. package/dist/features/lottery/schemas/firestore.d.ts +49 -0
  117. package/dist/features/lottery/schemas/firestore.js +25 -0
  118. package/dist/features/lottery/schemas/zod.d.ts +113 -0
  119. package/dist/features/lottery/schemas/zod.js +39 -0
  120. package/dist/features/lottery/types/index.d.ts +67 -0
  121. package/dist/features/lottery/types/index.js +55 -0
  122. package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
  123. package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
  124. package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
  125. package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
  126. package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
  127. package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
  128. package/dist/features/products/api/[id]/route.d.ts +4 -4
  129. package/dist/features/products/api/route.d.ts +2 -2
  130. package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
  131. package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
  132. package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
  133. package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
  134. package/dist/features/products/schemas/firestore.d.ts +15 -1
  135. package/dist/features/products/schemas/index.d.ts +12 -12
  136. package/dist/features/products/schemas/index.js +4 -2
  137. package/dist/features/products/types/index.d.ts +1 -1
  138. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
  139. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
  140. package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
  141. package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
  142. package/dist/features/reviews/api/[id]/route.d.ts +4 -4
  143. package/dist/features/reviews/api/route.d.ts +2 -2
  144. package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
  145. package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
  146. package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
  147. package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
  148. package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
  149. package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
  150. package/dist/features/seller/api/coupons/route.d.ts +2 -2
  151. package/dist/features/seller/api/offers/route.d.ts +2 -2
  152. package/dist/features/seller/api/products/route.d.ts +2 -2
  153. package/dist/features/seller/api/store/route.d.ts +2 -2
  154. package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
  155. package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
  156. package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
  157. package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
  158. package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
  159. package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
  160. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
  161. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
  162. package/dist/index.d.ts +14 -0
  163. package/dist/index.js +17 -0
  164. package/dist/next/api/routeHandler.d.ts +1 -1
  165. package/dist/next/routing/route-map.d.ts +13 -0
  166. package/dist/next/routing/route-map.js +6 -0
  167. package/dist/repositories/index.d.ts +1 -0
  168. package/dist/repositories/index.js +2 -0
  169. package/dist/security/index.d.ts +1 -1
  170. package/dist/security/index.js +1 -1
  171. package/dist/security/pii-schemas.d.ts +2 -0
  172. package/dist/security/pii-schemas.js +6 -0
  173. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  174. package/dist/seed/events-seed-data.js +85 -2
  175. package/dist/seed/grouped-listings-seed-data.d.ts +3 -1
  176. package/dist/seed/index.d.ts +1 -0
  177. package/dist/seed/index.js +1 -0
  178. package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
  179. package/dist/seed/lottery-entries-seed-data.js +108 -0
  180. package/dist/seed/manifest.js +1 -0
  181. package/dist/seed/products-preorders-seed-data.d.ts +3 -1
  182. package/dist/server.d.ts +6 -0
  183. package/dist/server.js +6 -0
  184. package/dist/styles.css +1 -1
  185. package/dist/tailwind-utilities.css +1 -1
  186. package/package.json +1 -1
@@ -19,8 +19,11 @@ export const eventTypeSchema = z.enum([
19
19
  "poll",
20
20
  "survey",
21
21
  "feedback",
22
+ "raffle",
23
+ "spin_wheel",
24
+ "lottery",
22
25
  ]);
23
- export const eventStatusSchema = z.enum(["draft", "active", "paused", "ended"]);
26
+ export const eventStatusSchema = z.enum(["draft", "active", "paused", "ended", "cancelled"]);
24
27
  export const saleConfigSchema = z.object({
25
28
  discountPercent: z.number(),
26
29
  bannerText: z.string().optional(),
@@ -36,6 +39,60 @@ export const pollConfigSchema = z.object({
36
39
  allowComment: z.boolean(),
37
40
  options: z.array(z.object({ id: z.string(), label: z.string() })),
38
41
  resultsVisibility: z.enum(["always", "after_vote", "after_end"]),
42
+ requireLogin: z.boolean().optional(),
43
+ });
44
+ export const surveyFormFieldSchema = z.object({
45
+ id: z.string(),
46
+ type: z.enum(["text", "textarea", "email", "phone", "number", "select", "multiselect", "checkbox", "radio", "date", "rating", "file"]),
47
+ label: z.string(),
48
+ placeholder: z.string().optional(),
49
+ required: z.boolean(),
50
+ options: z.array(z.string()).optional(),
51
+ validation: z.object({
52
+ minLength: z.number().optional(),
53
+ maxLength: z.number().optional(),
54
+ min: z.number().optional(),
55
+ max: z.number().optional(),
56
+ pattern: z.string().optional(),
57
+ }).optional(),
58
+ order: z.number(),
59
+ });
60
+ export const surveyConfigSchema = z.object({
61
+ requireLogin: z.boolean(),
62
+ maxEntriesPerUser: z.number(),
63
+ hasLeaderboard: z.boolean(),
64
+ hasPointSystem: z.boolean(),
65
+ pointsLabel: z.string().optional(),
66
+ entryReviewRequired: z.boolean(),
67
+ formFields: z.array(surveyFormFieldSchema),
68
+ });
69
+ export const feedbackConfigSchema = z.object({
70
+ formFields: z.array(surveyFormFieldSchema),
71
+ anonymous: z.boolean(),
72
+ });
73
+ export const spinPrizeSchema = z.object({
74
+ id: z.string(),
75
+ label: z.string(),
76
+ couponId: z.string().optional(),
77
+ weight: z.number(),
78
+ isActive: z.boolean(),
79
+ });
80
+ /** Lottery slot schema — client-safe shape (no priceInPaise/weight sent to clients). */
81
+ export const lotterySlotClientSchema = z.object({
82
+ slotNumber: z.number().int().min(1),
83
+ name: z.string(),
84
+ isBooked: z.boolean(),
85
+ bookedByUserLotteryNumber: z.number().optional(),
86
+ bookedByDisplayName: z.string().optional(),
87
+ });
88
+ /** Client-safe lottery config schema — price/weight fields stripped server-side. */
89
+ export const lotteryConfigClientSchema = z.object({
90
+ slots: z.array(lotterySlotClientSchema),
91
+ totalSlots: z.number().int().min(1).max(200),
92
+ pricingMode: z.enum(["uniform", "variable"]),
93
+ drawWindowDurationMinutes: z.number(),
94
+ maxPullsPerTransaction: z.number().int().min(1),
95
+ maxPullsPerUser: z.number().int().min(1),
39
96
  });
40
97
  // --- Base item schema ---------------------------------------------------------
41
98
  /**
@@ -53,6 +110,7 @@ export const pollConfigSchema = z.object({
53
110
  */
54
111
  export const eventItemSchema = z.object({
55
112
  id: z.string(),
113
+ slug: z.string().optional(),
56
114
  type: eventTypeSchema,
57
115
  title: z.string(),
58
116
  description: z.string(),
@@ -67,6 +125,27 @@ export const eventItemSchema = z.object({
67
125
  saleConfig: saleConfigSchema.optional(),
68
126
  offerConfig: offerConfigSchema.optional(),
69
127
  pollConfig: pollConfigSchema.optional(),
128
+ surveyConfig: surveyConfigSchema.optional(),
129
+ feedbackConfig: feedbackConfigSchema.optional(),
130
+ // Raffle fields
131
+ hasRaffle: z.boolean().optional(),
132
+ raffleType: z.enum(["top_n_scorers", "top_n_participants", "open_raffle", "spin_wheel"]).optional(),
133
+ raffleTopN: z.number().optional(),
134
+ rafflePrize: z.string().optional(),
135
+ rafflePrizeCouponId: z.string().optional(),
136
+ rafflePrizeProductIds: z.array(z.string()).optional(),
137
+ raffleGithubFunctionUrl: z.string().optional(),
138
+ raffleWinnerUserId: z.string().optional(),
139
+ raffleWinnerDisplayName: z.string().optional(),
140
+ raffleTriggeredAt: z.string().optional(),
141
+ raffleEntryCount: z.number().optional(),
142
+ // Spin wheel fields
143
+ spinPrizes: z.array(spinPrizeSchema).optional(),
144
+ spinMaxPerUser: z.number().optional(),
145
+ spinWindowStart: z.string().optional(),
146
+ spinWindowEnd: z.string().optional(),
147
+ // Lottery config (new) — client-safe shape (price/weight stripped by adapter)
148
+ lotteryConfig: lotteryConfigClientSchema.optional(),
70
149
  stats: z.object({
71
150
  totalEntries: z.number(),
72
151
  approvedEntries: z.number(),
@@ -1,6 +1,7 @@
1
1
  import type { MediaField } from "../../media/types/index";
2
2
  import type { JsonValue } from "@mohasinac/appkit";
3
- export type EventType = "sale" | "offer" | "poll" | "survey" | "feedback" | "raffle" | "spin_wheel";
3
+ import type { ClientLotteryConfig } from "../../lottery/types";
4
+ export type EventType = "sale" | "offer" | "poll" | "survey" | "feedback" | "raffle" | "spin_wheel" | "lottery";
4
5
  export type RaffleType = "top_n_scorers" | "top_n_participants" | "open_raffle" | "spin_wheel";
5
6
  export interface SpinPrize {
6
7
  id: string;
@@ -96,6 +97,8 @@ export interface EventItem {
96
97
  spinMaxPerUser?: number;
97
98
  spinWindowStart?: string;
98
99
  spinWindowEnd?: string;
100
+ /** Lottery config — slots have price/weight stripped for client consumption. */
101
+ lotteryConfig?: ClientLotteryConfig;
99
102
  stats: {
100
103
  totalEntries: number;
101
104
  approvedEntries: number;
@@ -0,0 +1,311 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { makeMockDb, makeSnap, makeQuerySnap } from "../../../../../tests/helpers/mock-firestore";
3
+ const { db, mockDocRef, mockCollection, mockQuery } = makeMockDb();
4
+ vi.mock("../../../../providers/db-firebase/admin", () => ({
5
+ getAdminDb: () => db,
6
+ }));
7
+ vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ return {
10
+ ...actual,
11
+ prepareForFirestore: (d) => d,
12
+ };
13
+ });
14
+ vi.mock("../../../../errors/normalize", () => ({
15
+ normalizeError: vi.fn(),
16
+ }));
17
+ import { FirebaseFAQsRepository } from "../faqs.repository";
18
+ const repo = new FirebaseFAQsRepository();
19
+ function makeFaqDoc(overrides = {}) {
20
+ return {
21
+ id: "faq-how-does-bidding-work",
22
+ question: "How does bidding work?",
23
+ answer: { text: "You place a bid above the current price.", format: "plain" },
24
+ category: "Auctions",
25
+ tags: ["bidding", "auctions"],
26
+ searchTokens: ["how", "does", "bidding", "work"],
27
+ relatedFAQs: [],
28
+ seo: { slug: "how-does-bidding-work" },
29
+ isActive: true,
30
+ showOnHomepage: false,
31
+ showInFooter: false,
32
+ isPinned: false,
33
+ priority: 0,
34
+ order: 0,
35
+ useSiteSettings: false,
36
+ createdBy: "admin-1",
37
+ stats: { views: 0, helpful: 0, notHelpful: 0 },
38
+ createdAt: new Date(),
39
+ updatedAt: new Date(),
40
+ ...overrides,
41
+ };
42
+ }
43
+ beforeEach(() => {
44
+ vi.clearAllMocks();
45
+ db.collection.mockReturnValue(mockCollection);
46
+ mockCollection.doc.mockReturnValue(mockDocRef);
47
+ mockCollection.where.mockReturnValue(mockQuery);
48
+ mockQuery.where.mockReturnValue(mockQuery);
49
+ mockQuery.orderBy.mockReturnValue(mockQuery);
50
+ mockQuery.limit.mockReturnValue(mockQuery);
51
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
52
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
53
+ mockDocRef.set.mockResolvedValue(undefined);
54
+ mockDocRef.update.mockResolvedValue(undefined);
55
+ });
56
+ // ---------------------------------------------------------------------------
57
+ // createWithSlug
58
+ // ---------------------------------------------------------------------------
59
+ describe("FirebaseFAQsRepository.createWithSlug", () => {
60
+ it("uses seo.slug from input when provided", async () => {
61
+ const input = {
62
+ question: "How does bidding work?",
63
+ answer: { text: "You bid above the current price.", format: "plain" },
64
+ category: "Auctions",
65
+ tags: ["bidding"],
66
+ seo: { slug: "custom-slug" },
67
+ isActive: true,
68
+ };
69
+ const result = await repo.createWithSlug(input);
70
+ expect(result.seo.slug).toBe("custom-slug");
71
+ });
72
+ it("generates seo.slug from question when seo.slug not provided", async () => {
73
+ const input = {
74
+ question: "How does bidding work?",
75
+ answer: { text: "Details here.", format: "plain" },
76
+ category: "Auctions",
77
+ tags: [],
78
+ isActive: true,
79
+ };
80
+ const result = await repo.createWithSlug(input);
81
+ expect(result.seo.slug).toMatch(/bidding/i);
82
+ });
83
+ it("builds searchTokens from question, answer, category, and tags", async () => {
84
+ const input = {
85
+ question: "What is a reserve price?",
86
+ answer: { text: "Reserve is the minimum.", format: "plain" },
87
+ category: "Auctions",
88
+ tags: ["reserve", "price"],
89
+ seo: { slug: "what-is-reserve-price" },
90
+ isActive: true,
91
+ };
92
+ const result = await repo.createWithSlug(input);
93
+ expect(result.searchTokens).toContain("reserve");
94
+ expect(result.searchTokens).toContain("price");
95
+ expect(result.searchTokens).toContain("what");
96
+ });
97
+ it("sets stats.views=0, stats.helpful=0, stats.notHelpful=0", async () => {
98
+ const input = {
99
+ question: "New question?",
100
+ answer: { text: "Answer.", format: "plain" },
101
+ category: "General",
102
+ tags: [],
103
+ isActive: true,
104
+ };
105
+ const result = await repo.createWithSlug(input);
106
+ expect(result.stats).toEqual({ views: 0, helpful: 0, notHelpful: 0 });
107
+ });
108
+ it("persists the FAQ to Firestore", async () => {
109
+ const input = {
110
+ question: "Question?",
111
+ answer: { text: "Answer.", format: "plain" },
112
+ category: "General",
113
+ tags: [],
114
+ seo: { slug: "question" },
115
+ isActive: true,
116
+ };
117
+ await repo.createWithSlug(input);
118
+ expect(mockDocRef.set).toHaveBeenCalledOnce();
119
+ });
120
+ it("searchTokens deduplicated and lowercased", async () => {
121
+ const input = {
122
+ question: "What what what?",
123
+ answer: { text: "Answer.", format: "plain" },
124
+ category: "General",
125
+ tags: [],
126
+ isActive: true,
127
+ };
128
+ const result = await repo.createWithSlug(input);
129
+ const whatCount = result.searchTokens.filter((t) => t === "what").length;
130
+ expect(whatCount).toBe(1);
131
+ });
132
+ });
133
+ // ---------------------------------------------------------------------------
134
+ // interpolateVariables
135
+ // ---------------------------------------------------------------------------
136
+ describe("FirebaseFAQsRepository.interpolateVariables", () => {
137
+ it("replaces {{variable}} placeholders with provided values", async () => {
138
+ const faq = makeFaqDoc({
139
+ answer: { text: "Your order {{orderId}} will arrive in {{days}} days.", format: "plain" },
140
+ });
141
+ const result = await repo.interpolateVariables(faq, { orderId: "ORD-123", days: 3 });
142
+ expect(result.answer.interpolated).toBe("Your order ORD-123 will arrive in 3 days.");
143
+ });
144
+ it("falls back to faq.variables when runtime variable not provided", async () => {
145
+ const faq = makeFaqDoc({
146
+ answer: { text: "Contact us at {{supportEmail}}.", format: "plain" },
147
+ variables: { supportEmail: "support@letitrip.in" },
148
+ });
149
+ const result = await repo.interpolateVariables(faq);
150
+ expect(result.answer.interpolated).toBe("Contact us at support@letitrip.in.");
151
+ });
152
+ it("leaves unknown placeholders as-is", async () => {
153
+ const faq = makeFaqDoc({
154
+ answer: { text: "Hello {{unknownVar}}!", format: "plain" },
155
+ });
156
+ const result = await repo.interpolateVariables(faq, {});
157
+ expect(result.answer.interpolated).toBe("Hello {{unknownVar}}!");
158
+ });
159
+ it("returns unchanged interpolated when no placeholders present", async () => {
160
+ const faq = makeFaqDoc({
161
+ answer: { text: "No variables here.", format: "plain" },
162
+ });
163
+ const result = await repo.interpolateVariables(faq, { irrelevant: "x" });
164
+ expect(result.answer.interpolated).toBe("No variables here.");
165
+ });
166
+ it("replaces multiple occurrences of the same placeholder", async () => {
167
+ const faq = makeFaqDoc({
168
+ answer: { text: "{{name}} is {{name}}.", format: "plain" },
169
+ });
170
+ const result = await repo.interpolateVariables(faq, { name: "LetItRip" });
171
+ expect(result.answer.interpolated).toBe("LetItRip is LetItRip.");
172
+ });
173
+ });
174
+ // ---------------------------------------------------------------------------
175
+ // markHelpful
176
+ // ---------------------------------------------------------------------------
177
+ describe("FirebaseFAQsRepository.markHelpful", () => {
178
+ it("increments stats.helpful on the FAQ document", async () => {
179
+ await repo.markHelpful("faq-1");
180
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ "stats.helpful": expect.anything() }));
181
+ });
182
+ it("does not modify other stats fields", async () => {
183
+ await repo.markHelpful("faq-1");
184
+ const updateArg = mockDocRef.update.mock.calls[0][0];
185
+ expect(Object.keys(updateArg)).not.toContain("stats.views");
186
+ expect(Object.keys(updateArg)).not.toContain("stats.notHelpful");
187
+ });
188
+ });
189
+ // ---------------------------------------------------------------------------
190
+ // markNotHelpful
191
+ // ---------------------------------------------------------------------------
192
+ describe("FirebaseFAQsRepository.markNotHelpful", () => {
193
+ it("increments stats.notHelpful on the FAQ document", async () => {
194
+ await repo.markNotHelpful("faq-1");
195
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ "stats.notHelpful": expect.anything() }));
196
+ });
197
+ it("does not modify stats.helpful", async () => {
198
+ await repo.markNotHelpful("faq-1");
199
+ const updateArg = mockDocRef.update.mock.calls[0][0];
200
+ expect(Object.keys(updateArg)).not.toContain("stats.helpful");
201
+ });
202
+ });
203
+ // ---------------------------------------------------------------------------
204
+ // incrementViews
205
+ // ---------------------------------------------------------------------------
206
+ describe("FirebaseFAQsRepository.incrementViews", () => {
207
+ it("increments stats.views by 1", async () => {
208
+ await repo.incrementViews("faq-1");
209
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ "stats.views": expect.anything() }));
210
+ });
211
+ it("sets stats.lastViewed timestamp", async () => {
212
+ await repo.incrementViews("faq-1");
213
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ "stats.lastViewed": expect.any(Date) }));
214
+ });
215
+ });
216
+ // ---------------------------------------------------------------------------
217
+ // getHomepageFAQs
218
+ // ---------------------------------------------------------------------------
219
+ describe("FirebaseFAQsRepository.getHomepageFAQs", () => {
220
+ it("queries showOnHomepage=true AND isActive=true", async () => {
221
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
222
+ await repo.getHomepageFAQs();
223
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("showOnHomepage"), "==", true);
224
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("isActive"), "==", true);
225
+ });
226
+ it("orders results by priority descending", async () => {
227
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
228
+ await repo.getHomepageFAQs();
229
+ expect(mockQuery.orderBy).toHaveBeenCalledWith(expect.stringContaining("priority"), "desc");
230
+ });
231
+ it("returns matching FAQs", async () => {
232
+ const faq = makeFaqDoc({ showOnHomepage: true });
233
+ mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: faq.id, data: faq }]));
234
+ const results = await repo.getHomepageFAQs();
235
+ expect(results).toHaveLength(1);
236
+ });
237
+ });
238
+ // ---------------------------------------------------------------------------
239
+ // searchByTag
240
+ // ---------------------------------------------------------------------------
241
+ describe("FirebaseFAQsRepository.searchByTag", () => {
242
+ it("filters by array-contains on the tags field", async () => {
243
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
244
+ await repo.searchByTag("grading");
245
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("tags"), "array-contains", "grading");
246
+ });
247
+ it("also filters isActive=true", async () => {
248
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
249
+ await repo.searchByTag("returns");
250
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("isActive"), "==", true);
251
+ });
252
+ it("returns matching FAQs", async () => {
253
+ const faq = makeFaqDoc({ tags: ["grading"] });
254
+ mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: faq.id, data: faq }]));
255
+ const results = await repo.searchByTag("grading");
256
+ expect(results).toHaveLength(1);
257
+ });
258
+ });
259
+ // ---------------------------------------------------------------------------
260
+ // getPinnedFAQs
261
+ // ---------------------------------------------------------------------------
262
+ describe("FirebaseFAQsRepository.getPinnedFAQs", () => {
263
+ it("queries isPinned=true AND isActive=true", async () => {
264
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
265
+ await repo.getPinnedFAQs();
266
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("isPinned"), "==", true);
267
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("isActive"), "==", true);
268
+ });
269
+ it("orders by order field ascending", async () => {
270
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
271
+ await repo.getPinnedFAQs();
272
+ expect(mockQuery.orderBy).toHaveBeenCalledWith(expect.stringContaining("order"), "asc");
273
+ });
274
+ it("filters by category when provided", async () => {
275
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
276
+ await repo.getPinnedFAQs("Shipping");
277
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("category"), "==", "Shipping");
278
+ });
279
+ it("no category filter applied when category not provided", async () => {
280
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
281
+ await repo.getPinnedFAQs();
282
+ const whereCalls = mockQuery.where.mock.calls;
283
+ const categoryFilter = whereCalls.find((c) => c[0].includes("category"));
284
+ expect(categoryFilter).toBeUndefined();
285
+ });
286
+ });
287
+ // ---------------------------------------------------------------------------
288
+ // getMostHelpful
289
+ // ---------------------------------------------------------------------------
290
+ describe("FirebaseFAQsRepository.getMostHelpful", () => {
291
+ it("filters by isActive=true", async () => {
292
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
293
+ await repo.getMostHelpful();
294
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("isActive"), "==", true);
295
+ });
296
+ it("orders by stats.helpful descending", async () => {
297
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
298
+ await repo.getMostHelpful();
299
+ expect(mockQuery.orderBy).toHaveBeenCalledWith("stats.helpful", "desc");
300
+ });
301
+ it("applies default limit of 10", async () => {
302
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
303
+ await repo.getMostHelpful();
304
+ expect(mockQuery.limit).toHaveBeenCalledWith(10);
305
+ });
306
+ it("applies custom limit when provided", async () => {
307
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
308
+ await repo.getMostHelpful(5);
309
+ expect(mockQuery.limit).toHaveBeenCalledWith(5);
310
+ });
311
+ });
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { makeMockDb, makeSnap } from "../../../../../tests/helpers/mock-firestore";
3
+ const { db, mockDocRef, mockTxn } = makeMockDb();
4
+ vi.mock("../../../../providers/db-firebase", () => ({
5
+ getAdminDb: () => db,
6
+ }));
7
+ vi.mock("../../../../monitoring", () => ({
8
+ serverLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
9
+ }));
10
+ import { UserHistoryRepository } from "../user-history.repository";
11
+ import { HISTORY_MAX } from "../../../../constants/limits";
12
+ const repo = new UserHistoryRepository();
13
+ function makeHistoryItems(count, baseDate = new Date("2026-01-01")) {
14
+ return Array.from({ length: count }, (_, i) => ({
15
+ productId: `product-${i}`,
16
+ productType: "product",
17
+ viewedAt: new Date(baseDate.getTime() - i * 1000),
18
+ }));
19
+ }
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ db.runTransaction.mockImplementation(async (fn) => fn(mockTxn));
23
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
24
+ mockTxn.get.mockResolvedValue(makeSnap(null));
25
+ mockTxn.set.mockReturnValue(undefined);
26
+ });
27
+ describe("UserHistoryRepository.track", () => {
28
+ it("empty history → items has 1 entry, returns 1", async () => {
29
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
30
+ const count = await repo.track("u", { productId: "product-x", productType: "product" });
31
+ expect(count).toBe(1);
32
+ expect(mockTxn.set).toHaveBeenCalledOnce();
33
+ });
34
+ it("49 items + new productId → 50 items, new at position 0", async () => {
35
+ const items = makeHistoryItems(49);
36
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
37
+ const count = await repo.track("u", { productId: "product-new", productType: "product" });
38
+ expect(count).toBe(50);
39
+ const setCall = mockTxn.set.mock.calls[0][1];
40
+ expect(setCall.items[0].productId).toBe("product-new");
41
+ });
42
+ it("50 items + new productId → evicts last item, stays at 50 (FIFO)", async () => {
43
+ const items = makeHistoryItems(HISTORY_MAX);
44
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
45
+ const count = await repo.track("u", { productId: "product-new", productType: "product" });
46
+ expect(count).toBe(HISTORY_MAX);
47
+ const setCall = mockTxn.set.mock.calls[0][1];
48
+ expect(setCall.items).toHaveLength(HISTORY_MAX);
49
+ expect(setCall.items[0].productId).toBe("product-new");
50
+ // Last item from original list is evicted
51
+ expect(setCall.items[HISTORY_MAX - 1].productId).not.toBe(`product-${HISTORY_MAX - 1}`);
52
+ });
53
+ it("revisit existing productId → removes old, inserts at position 0, length unchanged", async () => {
54
+ const items = [
55
+ { productId: "product-A", productType: "product", viewedAt: new Date("2026-01-03") },
56
+ { productId: "product-B", productType: "product", viewedAt: new Date("2026-01-02") },
57
+ { productId: "product-C", productType: "product", viewedAt: new Date("2026-01-01") },
58
+ ];
59
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
60
+ const count = await repo.track("u", { productId: "product-C", productType: "product" });
61
+ expect(count).toBe(3);
62
+ const setCall = mockTxn.set.mock.calls[0][1];
63
+ expect(setCall.items[0].productId).toBe("product-C");
64
+ expect(setCall.items.filter((i) => i.productId === "product-C")).toHaveLength(1);
65
+ });
66
+ it("uses db.runTransaction", async () => {
67
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
68
+ await repo.track("u", { productId: "product-x", productType: "product" });
69
+ expect(db.runTransaction).toHaveBeenCalledOnce();
70
+ });
71
+ it("stores viewedAt as current timestamp when not provided", async () => {
72
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
73
+ const before = Date.now();
74
+ await repo.track("u", { productId: "product-x", productType: "product" });
75
+ const after = Date.now();
76
+ const setCall = mockTxn.set.mock.calls[0][1];
77
+ const ts = setCall.items[0].viewedAt.getTime();
78
+ expect(ts).toBeGreaterThanOrEqual(before);
79
+ expect(ts).toBeLessThanOrEqual(after);
80
+ });
81
+ });
82
+ describe("UserHistoryRepository.merge", () => {
83
+ it("guest items merged into empty history → all items present", async () => {
84
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
85
+ const incoming = [
86
+ { productId: "product-A", productType: "product" },
87
+ { productId: "product-B", productType: "product" },
88
+ ];
89
+ const count = await repo.merge("u", incoming);
90
+ expect(count).toBe(2);
91
+ });
92
+ it("duplicate productId: keeps newer viewedAt, discards older", async () => {
93
+ const older = new Date("2026-01-01");
94
+ const newer = new Date("2026-01-10");
95
+ const existingItems = [{ productId: "product-A", productType: "product", viewedAt: older }];
96
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: existingItems, updatedAt: new Date() }));
97
+ await repo.merge("u", [{ productId: "product-A", productType: "product", viewedAt: newer }]);
98
+ const setCall = mockTxn.set.mock.calls[0][1];
99
+ expect(setCall.items).toHaveLength(1);
100
+ expect(setCall.items[0].viewedAt.getTime()).toBe(newer.getTime());
101
+ });
102
+ it("result trimmed to HISTORY_MAX (50 items)", async () => {
103
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
104
+ const incoming = Array.from({ length: 60 }, (_, i) => ({
105
+ productId: `product-${i}`,
106
+ productType: "product",
107
+ viewedAt: new Date(Date.now() - i * 1000),
108
+ }));
109
+ const count = await repo.merge("u", incoming);
110
+ expect(count).toBe(HISTORY_MAX);
111
+ });
112
+ it("items sorted descending by viewedAt after merge", async () => {
113
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
114
+ const incoming = [
115
+ { productId: "product-old", productType: "product", viewedAt: new Date("2026-01-01") },
116
+ { productId: "product-new", productType: "product", viewedAt: new Date("2026-01-10") },
117
+ ];
118
+ await repo.merge("u", incoming);
119
+ const setCall = mockTxn.set.mock.calls[0][1];
120
+ expect(setCall.items[0].productId).toBe("product-new");
121
+ expect(setCall.items[1].productId).toBe("product-old");
122
+ });
123
+ it("uses db.runTransaction", async () => {
124
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items: [], updatedAt: new Date() }));
125
+ await repo.merge("u", []);
126
+ expect(db.runTransaction).toHaveBeenCalledOnce();
127
+ });
128
+ });
129
+ describe("UserHistoryRepository.removeOne", () => {
130
+ it("removes specified productId from items", async () => {
131
+ const items = [
132
+ { productId: "product-A", productType: "product", viewedAt: new Date() },
133
+ { productId: "product-B", productType: "product", viewedAt: new Date() },
134
+ ];
135
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
136
+ await repo.removeOne("u", "product-A");
137
+ const setCall = mockTxn.set.mock.calls[0][1];
138
+ expect(setCall.items).toHaveLength(1);
139
+ expect(setCall.items[0].productId).toBe("product-B");
140
+ });
141
+ it("non-existent productId → no-op, set not called", async () => {
142
+ const items = [{ productId: "product-A", productType: "product", viewedAt: new Date() }];
143
+ mockTxn.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
144
+ await repo.removeOne("u", "product-xxx");
145
+ expect(mockTxn.set).not.toHaveBeenCalled();
146
+ });
147
+ });
148
+ describe("UserHistoryRepository.clearForUser", () => {
149
+ it("sets items: [] without deleting the document", async () => {
150
+ await repo.clearForUser("u");
151
+ expect(mockDocRef.set).toHaveBeenCalledWith(expect.objectContaining({ items: [] }));
152
+ });
153
+ });
154
+ describe("UserHistoryRepository.getHistory", () => {
155
+ it("returns items array", async () => {
156
+ const items = [{ productId: "product-A", productType: "product", viewedAt: new Date() }];
157
+ mockDocRef.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
158
+ const result = await repo.getHistory("u");
159
+ expect(result).toHaveLength(1);
160
+ expect(result[0].productId).toBe("product-A");
161
+ });
162
+ it("returns empty array for user with no history document", async () => {
163
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
164
+ const result = await repo.getHistory("u");
165
+ expect(result).toEqual([]);
166
+ });
167
+ });
168
+ describe("UserHistoryRepository.countByUser", () => {
169
+ it("returns items.length", async () => {
170
+ const items = makeHistoryItems(5);
171
+ mockDocRef.get.mockResolvedValue(makeSnap({ userId: "u", items, updatedAt: new Date() }));
172
+ const count = await repo.countByUser("u");
173
+ expect(count).toBe(5);
174
+ });
175
+ it("returns 0 for missing document", async () => {
176
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
177
+ const count = await repo.countByUser("u");
178
+ expect(count).toBe(0);
179
+ });
180
+ });
@@ -15,9 +15,9 @@ export declare function GET(_request: Request, context: {
15
15
  }>;
16
16
  }): Promise<NextResponse>;
17
17
  export { GET as homepageSectionItemGET };
18
- export declare const homepageSectionItemPATCH: (request: Request, context: {
18
+ export declare const homepageSectionItemPATCH: (request: Request, context?: {
19
19
  params: Promise<Record<string, string>>;
20
- }) => Promise<Response>;
21
- export declare const homepageSectionItemDELETE: (request: Request, context: {
20
+ } | undefined) => Promise<Response>;
21
+ export declare const homepageSectionItemDELETE: (request: Request, context?: {
22
22
  params: Promise<Record<string, string>>;
23
- }) => Promise<Response>;
23
+ } | undefined) => Promise<Response>;
@@ -15,9 +15,9 @@ export declare function GET(_request: Request, context: {
15
15
  }>;
16
16
  }): Promise<NextResponse>;
17
17
  export { GET as carouselItemGET };
18
- export declare const carouselItemPATCH: (request: Request, context: {
18
+ export declare const carouselItemPATCH: (request: Request, context?: {
19
19
  params: Promise<Record<string, string>>;
20
- }) => Promise<Response>;
21
- export declare const carouselItemDELETE: (request: Request, context: {
20
+ } | undefined) => Promise<Response>;
21
+ export declare const carouselItemDELETE: (request: Request, context?: {
22
22
  params: Promise<Record<string, string>>;
23
- }) => Promise<Response>;
23
+ } | undefined) => Promise<Response>;
@@ -10,6 +10,6 @@
10
10
  import { NextResponse } from "next/server.js";
11
11
  export declare function GET(request: Request): Promise<NextResponse>;
12
12
  export { GET as carouselGET };
13
- export declare const carouselPOST: (request: Request, context: {
13
+ export declare const carouselPOST: (request: Request, context?: {
14
14
  params: Promise<Record<string, string>>;
15
- }) => Promise<Response>;
15
+ } | undefined) => Promise<Response>;