@mohasinac/appkit 2.6.9 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +207 -0
  2. package/dist/_internal/server/jobs/core/onScamReportCreate.d.ts +12 -0
  3. package/dist/_internal/server/jobs/core/onScamReportCreate.js +50 -0
  4. package/dist/_internal/server/jobs/core/onScamReportRejected.d.ts +11 -0
  5. package/dist/_internal/server/jobs/core/onScamReportRejected.js +28 -0
  6. package/dist/_internal/server/jobs/core/onScamReportVerified.d.ts +11 -0
  7. package/dist/_internal/server/jobs/core/onScamReportVerified.js +28 -0
  8. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  9. package/dist/_internal/server/jobs/handlers/index.js +3 -0
  10. package/dist/_internal/server/jobs/handlers/onScamReportCreate.d.ts +2 -0
  11. package/dist/_internal/server/jobs/handlers/onScamReportCreate.js +7 -0
  12. package/dist/_internal/server/jobs/handlers/onScamReportUpdate.d.ts +2 -0
  13. package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +25 -0
  14. package/dist/client.d.ts +2 -0
  15. package/dist/client.js +1 -0
  16. package/dist/features/scams/components/ScamAwarenessModal.d.ts +5 -0
  17. package/dist/features/scams/components/ScamAwarenessModal.js +31 -0
  18. package/dist/features/scams/components/index.d.ts +2 -0
  19. package/dist/features/scams/components/index.js +1 -0
  20. package/dist/index.d.ts +4 -2
  21. package/dist/index.js +4 -5
  22. package/dist/jobs.d.ts +1 -1
  23. package/dist/jobs.js +2 -0
  24. package/dist/react/contexts/SessionContext.d.ts +1 -0
  25. package/dist/react/contexts/SessionContext.js +6 -0
  26. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  27. package/dist/seed/index.d.ts +1 -0
  28. package/dist/seed/index.js +1 -0
  29. package/dist/seed/manifest.js +5 -0
  30. package/dist/seed/support-tickets-seed-data.d.ts +9 -0
  31. package/dist/seed/support-tickets-seed-data.js +135 -0
  32. package/package.json +210 -210
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # @mohasinac/appkit
2
+
3
+ Internal component library and server toolkit for the LetItRip collectibles marketplace.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ appkit/src/
9
+ ├── _internal/
10
+ │ ├── client/features/ # Client-side feature views (RSC-compatible islands)
11
+ │ ├── server/features/ # Server data-fetchers, adapters, server actions
12
+ │ └── shared/ # Types, config, constants shared across both
13
+ ├── features/ # Domain feature modules (auth, products, scams, support …)
14
+ ├── repositories/ # Firestore Admin SDK repository layer (one per collection)
15
+ ├── seed/ # Seed data + manifest for SeedPanel
16
+ ├── ui/ # Primitive UI components (Button, Input, Modal, Stack …)
17
+ ├── next/ # Next.js helpers (ROUTES, route-map, hooks, SSR utilities)
18
+ ├── seo/ # JSON-LD builders, metadata helpers
19
+ └── configs/ # Next.js + Tailwind shared config
20
+ ```
21
+
22
+ ## Entry Points
23
+
24
+ | Entry | Purpose |
25
+ |-------|---------|
26
+ | `@mohasinac/appkit` | Main barrel — UI, repositories, constants, seed data |
27
+ | `@mohasinac/appkit/client` | Client bundle — UI components, client hooks (firebase-admin free) |
28
+ | `@mohasinac/appkit/server` | Server bundle — Admin SDK providers, server actions |
29
+ | `@mohasinac/appkit/jobs` | Firebase Functions binders + pure job handlers |
30
+ | `@mohasinac/appkit/styles` | Pre-compiled Tailwind utilities CSS |
31
+
32
+ ## Development
33
+
34
+ ```bash
35
+ # From the letitrip.in root — watches src/ and recompiles on change
36
+ npm run watch:appkit
37
+
38
+ # Full build + CSS + asset copy
39
+ npm run build # inside appkit/
40
+ ```
41
+
42
+ The consumer app links via `"file:./appkit"` — no npm publish required during local dev.
43
+
44
+ ## Publishing
45
+
46
+ Only publish when explicitly requested. See `CLAUDE.md § Appkit Publish & Deploy Rules`.
47
+
48
+ ```bash
49
+ # 1. Commit all source changes
50
+ # 2. npm run build (inside appkit/)
51
+ # 3. npm publish (inside appkit/)
52
+ # 4. Update letitrip package.json + lockfile
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Version History
58
+
59
+ ### v2.7.0 — 2026-05-14
60
+
61
+ **Support Tickets seed data + Firestore indices + seed pipeline wiring (BAN9/SCAM9 followup)**
62
+
63
+ - `appkit/src/seed/support-tickets-seed-data.ts` — 6 sample tickets covering all 5 statuses (open, in_progress, waiting_on_user, resolved, closed) and 5 categories
64
+ - `SeedCollectionName` extended with `"supportTickets"`; wired into seed manifest + barrel + consumer seed route
65
+ - `SUPPORT_TICKET_COLLECTION`, `ACTIVE_TICKET_STATUSES`, `TicketCategoryValues`, `TicketStatusValues`, `TicketPriorityValues` exported from main barrel
66
+ - 6 new Firestore composite indices for `supportTickets` (userId+status+createdAt, assignedTo+status, status+priority, category+status, etc.)
67
+ - `firestore.indexes.json` regenerated via `firebase-merge.mjs`
68
+ - SeedPanel: `supportTickets` entry added to Trust & Safety group with full field schema + seededItems + uiPath
69
+ - CSS build fix: stale `tailwind-utilities.css` rebuilt; `verify-css-build.mjs` passes all 8 required breakpoints/classes
70
+
71
+ ### v2.6.9 — 2026-05-14
72
+
73
+ **Vercel Lambda transitive dependency tracing fix**
74
+
75
+ - `appkit/src/configs/next.ts`: added `@grpc/**`, `protobufjs/**`, `@protobufjs/**`, `object-hash/**`, `proto3-json-serializer/**`, `long/**`, `node-fetch/**`, `abort-controller/**`, `retry-request/**`, `duplexify/**`, `uuid/**`, `lodash.camelcase/**` to `defaultOutputFileTracingIncludes["/api/**"]`
76
+ - Fixes `MODULE_NOT_FOUND: object-hash` and `@protobufjs/*` sub-package `Cannot find module` errors in Vercel Lambdas (google-gax transitive deps)
77
+
78
+ ### v2.6.8 — 2026-05-14
79
+
80
+ **gRPC/protobuf transitive dep tracing**
81
+
82
+ - Added `grpc_node.node` native binding and `@grpc/grpc-js/**` to `defaultOutputFileTracingIncludes`
83
+
84
+ ### v2.6.7 — 2026-05-14
85
+
86
+ **TitleBar auth buttons + role badge + employee role + avatar fix**
87
+
88
+ - `TitleBarLayout`: `loginHref`/`registerHref` props; guest "Sign in"/"Register" buttons on desktop (lg+); replaced `next/image` with `<img>` for any-domain avatar support
89
+ - `AppLayoutShell`: `registerHref` prop forwarded to `TitleBar`; `RoleBadge` overlay replaced with 16px colored dot (role initial, no text overflow)
90
+ - `RoleBadge + Badge`: `employee` label/color/variant; `appkit-badge--employee` amber CSS (light + dark)
91
+ - Scam awareness: `ScamAwarenessModal` client component (non-dismissible, 7 category cards, checkbox ack); `scamAwarenessAcknowledgedAt` added to `SessionUser`; `LayoutShellClient` 30-day gate
92
+ - `LoginForm`/`RegisterForm`: `renderCreateAccountLink`, `renderForgotPasswordLink`, `renderLoginLink`, `renderTermsLink` slot props
93
+ - Seed users: `avatarMetadata` on 3 existing users; `user-deepak-verma` (moderator) + `user-simran-kaur` (employee) with avatarMetadata
94
+
95
+ ### v2.6.5 — 2026-05-13
96
+
97
+ **Dashboard listing quality pass + CSS var tokens**
98
+
99
+ - `AdminPrizeDrawsView`: CSS var token pass replacing hardcoded zinc/red dark pairs
100
+ - `SellerPreOrdersView`, `SellerPrizeDrawsView`: new seller listing views with URL state
101
+ - `SELLER_PRE_ORDER_STATUS_TABS`, `SELLER_PRIZE_DRAW_STATUS_TABS` added to `filter-tabs.ts`
102
+ - Client barrel exports for 3 new views
103
+
104
+ ### v2.6.4 — 2026-05-13
105
+
106
+ **S-SBUNI-RULES refund/payout/shipping constants quality pass**
107
+
108
+ - `REFUND_COPY` constants module — single source for all refund/shipping/sibling-payment strings
109
+ - `PayoutRefundDeduction` interface + `applyRefundDeductionAction`
110
+ - `buildShiprocketTrackingUrl()` + `SHIPROCKET_STATUS_PICKUP_SCHEDULED` constants
111
+ - `TB1/TB2/MNB-1/BN-1` layout system: breakpoint `lg` governs all four; no duplicate wishlist/notificationSlot
112
+
113
+ ### v2.6.3 — 2026-05-13
114
+
115
+ **Firebase Functions ADC cold-start fix**
116
+
117
+ - `admin.ts` + `admin-app-lite.ts`: detect `FUNCTION_TARGET || K_SERVICE || FIREBASE_CONFIG || GOOGLE_APPLICATION_CREDENTIALS` → `initializeApp()` with no credential
118
+ - Closes cold-start 500 on every HTTPS Cloud Function
119
+
120
+ ### v2.6.2 — 2026-05-13
121
+
122
+ **S9 RBAC complete — employee permission system + team management + store capabilities**
123
+
124
+ - `Permission` union (85+ strings) + `EmployeeGroup` union (18 presets) + `PERMISSION_GROUPS` bundles + `StoreCapability` flags
125
+ - `getServerPermissions()` resolver — admin bypasses, employee gated by `permissions[]`
126
+ - `makeAdminSectionLayout()` factory — generates per-section RSC layout with `admin:X:read` gate
127
+ - `AdminTeamView` + `AdminEmployeeEditorView` — invite/edit/revoke employee accounts with permission group picker
128
+ - `getStoreCapabilities()` + `storeHasCapability()` — `capabilities[]` array enforcement on auction/preorder creation
129
+ - `AdminStoreEditorView` Capabilities section — 3 collapsible tiers with per-capability checkboxes
130
+ - `ADMIN_NAV_GROUPS`: `requiredPermission` annotation on every item; `DashboardLayoutClient.filterAdminGroups` hides items by permissions
131
+ - `ROUTE_PERMISSION_MAP` — maps every `/admin/[section]` path to its required read permission
132
+
133
+ ### v2.6.1 — 2026-05-13
134
+
135
+ **S-SBUNI Phase 1 fully closed + SB-UNI-Z1/Z2/Z3 media upload reliability**
136
+
137
+ - Signed-URL upload flow replacing `/api/media/upload` (SB-UNI-Z1)
138
+ - MIME widening: `3gpp`, `3gpp2`, `x-matroska` (SB-UNI-Z2)
139
+ - Media limits centralized in `_internal/shared/media/limits.ts` (SB-UNI-Z3)
140
+ - `bundleStockStatus` propagation via `onProductStockChangeHandler` (SB-UNI-V)
141
+ - Bundle → `categoryType:"bundle"` migration complete; `BUNDLES_COLLECTION` dropped
142
+
143
+ ### v2.6.0 — 2026-05-13
144
+
145
+ **S8 Event Raffles + Spin Wheel + SB10 tab constants**
146
+
147
+ - SB9 raffle/spin schema (14 raffle fields on Event, 5 on EventEntry)
148
+ - `triggerEventRaffleAction` + `assignSpinPrizeAction` server actions (`crypto.randomInt`)
149
+ - `SpinWheelView` + public winner page
150
+ - Admin raffle section with manual trigger in event editor
151
+ - `SB10` filter-tab constants: `SELLER_LISTING_TABS`, `STORE_LISTINGS_TABS`, `filter-tabs.ts` with 6 view migrations
152
+ - SB11 homepage section builder types for `FeaturedBundles`, `PrizeDraws`, `EventRaffles`
153
+
154
+ ### v2.5.x — 2026-05-11 to 2026-05-13
155
+
156
+ **S7 Prize Draws cohort + SB-UNI Phases 1–5**
157
+
158
+ - SB4 reveal API (crypto.randomInt pool-exhaust auto-refund) + lock-on-reveal + theatrical 3.2s modal
159
+ - 7 Firebase Functions: prizeRevealOpen/Close/Expiry/Reminder, bundleStockSync, triggerEventRaffle, assignSpinPrize
160
+ - SB-UNI address top-level collection (`ownerType` discriminator), categories unification (sublisting/brand/bundle)
161
+ - `listingType` migration complete: `isAuction`/`isPreOrder` booleans removed; all queries via `where("listingType","==",X)`
162
+ - `isAuctionListing()`, `isPreOrderListing()`, `normalizeListingType()` canonical accessors
163
+
164
+ ### v2.4.x — 2026-05-10 to 2026-05-11
165
+
166
+ **S5/S6 seed scale + S4 bundle/pre-order foundation**
167
+
168
+ - Auction expansion (11→20) + bid ladder helper `buildBidLadder()`
169
+ - Bundle schema: `SB3` stock-sync hook, admin list/edit pages, Zod hardening
170
+ - OG edge-runtime fix: all 9 OG routes switched from `runtime="edge"` to Node runtime
171
+ - Wishlist (20-cap), History (50-cap FIFO), Cart (50-cap) per-user one-doc pattern
172
+
173
+ ### v2.3.x — 2026-05-08 to 2026-05-09
174
+
175
+ **S2/S3 cart, checkout, orders, SSR architecture**
176
+
177
+ - `_internal/server/features/` layers: cart, orders, promotions, reviews, wishlist, history, homepage
178
+ - `listingType` field introduced; `isAuction`/`isPreOrder` deprecated (fully removed in v2.5)
179
+ - Support ticket schema + `SupportRepository` (BAN1)
180
+ - Ban schema: `softBans[]`, `hardBanReason`, `hardBannedAt`; `isSoftBanned()` helper
181
+
182
+ ### v2.2.x — 2026-05-06 to 2026-05-07
183
+
184
+ **Scam registry foundation + homepage sections + prize draws schema**
185
+
186
+ - `ScammerDocument` + `scammerRepository` + 27 scam types + SCAM_CATEGORIES
187
+ - Public scam pages: `/scams`, `/scams/[slug]`, `/scams/types`
188
+ - Homepage sections (19 types): `SB11` full section-builder admin UI
189
+ - `DashboardLayoutClient` + `RoleGuard` — collapsed 3 separate layout shells
190
+
191
+ ### v2.1.x — 2026-05-04 to 2026-05-05
192
+
193
+ **Auth, addresses, messages foundation**
194
+
195
+ - RTDB signal channel for Google OAuth
196
+ - Addresses top-level collection (`SB-UNI-A`)
197
+ - `conversationsRepository` + RTDB ping-channel for messages (D5+VC7)
198
+ - `ScrollToTop` component; `BaseListingCard.Checkbox` + `useLongPress` card-selection pattern
199
+
200
+ ### v2.0.0 — 2026-05-03
201
+
202
+ **Initial appkit extraction from letitrip monorepo**
203
+
204
+ - Extracted from inline letitrip.in code into standalone `@mohasinac/appkit` package
205
+ - Firebase Admin SDK entry point separation (`server.ts` vs `client.ts`)
206
+ - `sideEffects: false` for Turbopack client-bundle safety
207
+ - Repository pattern: `BaseRepository` with PII encryption hooks
@@ -0,0 +1,12 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ export interface HandleScamReportCreateInput {
3
+ scammerId: string;
4
+ report: {
5
+ reportedBy?: string;
6
+ displayNames?: string[];
7
+ scamType?: string;
8
+ scamPlatform?: string;
9
+ amountLost?: number;
10
+ };
11
+ }
12
+ export declare function handleScamReportCreate(input: HandleScamReportCreateInput, ctx: JobContext): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { notificationRepository, userRepository } from "../../../../repositories";
2
+ import { SCAM_TYPE_LABELS } from "../../../../features/scams/constants/scam-types";
3
+ export async function handleScamReportCreate(input, ctx) {
4
+ const { scammerId, report } = input;
5
+ const { reportedBy, displayNames, scamType, scamPlatform, amountLost } = report;
6
+ const name = displayNames?.[0] ?? "Unknown";
7
+ // 1. Notify the reporter
8
+ if (reportedBy) {
9
+ try {
10
+ await notificationRepository.create({
11
+ userId: reportedBy,
12
+ type: "account_action",
13
+ title: "Scam report submitted",
14
+ body: `Your report for "${name}" has been received. Our team will review it within 48 hours.`,
15
+ isRead: false,
16
+ entityId: scammerId,
17
+ entityType: "scammer",
18
+ createdAt: new Date(),
19
+ });
20
+ }
21
+ catch (err) {
22
+ ctx.logger.error("Failed to notify reporter (non-fatal)", err, { scammerId, reportedBy });
23
+ }
24
+ }
25
+ // 2. Notify all employees with admin:scammers:read permission
26
+ try {
27
+ const result = await userRepository.list({
28
+ filters: "role==employee",
29
+ page: 1,
30
+ pageSize: 100,
31
+ });
32
+ const scamTypeLabel = scamType ? (SCAM_TYPE_LABELS[scamType] ?? scamType) : "Unknown";
33
+ const amountStr = amountLost ? ` ₹${(amountLost / 100).toLocaleString("en-IN")}` : "";
34
+ const platformStr = scamPlatform ? ` via ${scamPlatform}` : "";
35
+ await Promise.all(result.items.map((employee) => notificationRepository.create({
36
+ userId: employee.id,
37
+ type: "account_action",
38
+ title: "New scam report submitted",
39
+ body: `A report was submitted for "${name}" — ${scamTypeLabel}${platformStr}.${amountStr}`,
40
+ isRead: false,
41
+ entityId: scammerId,
42
+ entityType: "scammer",
43
+ createdAt: new Date(),
44
+ }).catch((err) => ctx.logger.error("Failed to notify employee (non-fatal)", err, { scammerId, employeeId: employee.id }))));
45
+ }
46
+ catch (err) {
47
+ ctx.logger.error("Failed to query employees for scam notification (non-fatal)", err, { scammerId });
48
+ }
49
+ ctx.logger.info("onScamReportCreate complete", { scammerId, reportedBy });
50
+ }
@@ -0,0 +1,11 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ export interface HandleScamReportRejectedInput {
3
+ scammerId: string;
4
+ report: {
5
+ reportedBy?: string;
6
+ displayNames?: string[];
7
+ prevStatus?: string;
8
+ nextStatus?: string;
9
+ };
10
+ }
11
+ export declare function handleScamReportRejected(input: HandleScamReportRejectedInput, ctx: JobContext): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { notificationRepository } from "../../../../repositories";
2
+ export async function handleScamReportRejected(input, ctx) {
3
+ const { scammerId, report } = input;
4
+ const { reportedBy, displayNames, prevStatus, nextStatus } = report;
5
+ if (prevStatus === nextStatus)
6
+ return;
7
+ if (nextStatus !== "rejected")
8
+ return;
9
+ if (!reportedBy)
10
+ return;
11
+ const name = displayNames?.[0] ?? "Unknown";
12
+ try {
13
+ await notificationRepository.create({
14
+ userId: reportedBy,
15
+ type: "account_action",
16
+ title: "Scam report not verified",
17
+ body: `Your report for "${name}" could not be verified with the evidence provided. You may submit a new report with additional evidence.`,
18
+ isRead: false,
19
+ entityId: scammerId,
20
+ entityType: "scammer",
21
+ createdAt: new Date(),
22
+ });
23
+ }
24
+ catch (err) {
25
+ ctx.logger.error("Failed to notify reporter of rejection (non-fatal)", err, { scammerId, reportedBy });
26
+ }
27
+ ctx.logger.info("onScamReportRejected complete", { scammerId, reportedBy });
28
+ }
@@ -0,0 +1,11 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ export interface HandleScamReportVerifiedInput {
3
+ scammerId: string;
4
+ report: {
5
+ reportedBy?: string;
6
+ displayNames?: string[];
7
+ prevStatus?: string;
8
+ nextStatus?: string;
9
+ };
10
+ }
11
+ export declare function handleScamReportVerified(input: HandleScamReportVerifiedInput, ctx: JobContext): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { notificationRepository } from "../../../../repositories";
2
+ export async function handleScamReportVerified(input, ctx) {
3
+ const { scammerId, report } = input;
4
+ const { reportedBy, displayNames, prevStatus, nextStatus } = report;
5
+ if (prevStatus === nextStatus)
6
+ return;
7
+ if (nextStatus !== "verified")
8
+ return;
9
+ if (!reportedBy)
10
+ return;
11
+ const name = displayNames?.[0] ?? "Unknown";
12
+ try {
13
+ await notificationRepository.create({
14
+ userId: reportedBy,
15
+ type: "account_action",
16
+ title: "Your scam report was verified",
17
+ body: `The report for "${name}" has been verified and published to the Scam Registry. Thank you for helping protect the community.`,
18
+ isRead: false,
19
+ entityId: scammerId,
20
+ entityType: "scammer",
21
+ createdAt: new Date(),
22
+ });
23
+ }
24
+ catch (err) {
25
+ ctx.logger.error("Failed to notify reporter of verification (non-fatal)", err, { scammerId, reportedBy });
26
+ }
27
+ ctx.logger.info("onScamReportVerified complete", { scammerId, reportedBy });
28
+ }
@@ -44,3 +44,5 @@ export { assignSpinPrizeHandler } from "./assignSpinPrize";
44
44
  export { onSupportTicketCreateHandler } from "./onSupportTicketCreate";
45
45
  export { onSupportTicketUpdateHandler } from "./onSupportTicketUpdate";
46
46
  export { onUserBanChangeHandler } from "./onUserBanChange";
47
+ export { onScamReportCreateHandler } from "./onScamReportCreate";
48
+ export { onScamReportUpdateHandler } from "./onScamReportUpdate";
@@ -50,3 +50,6 @@ export { assignSpinPrizeHandler } from "./assignSpinPrize";
50
50
  export { onSupportTicketCreateHandler } from "./onSupportTicketCreate";
51
51
  export { onSupportTicketUpdateHandler } from "./onSupportTicketUpdate";
52
52
  export { onUserBanChangeHandler } from "./onUserBanChange";
53
+ // SCAM8 — scam report notifications
54
+ export { onScamReportCreateHandler } from "./onScamReportCreate";
55
+ export { onScamReportUpdateHandler } from "./onScamReportUpdate";
@@ -0,0 +1,2 @@
1
+ import type { FirestoreTriggerHandler } from "../runtime/types";
2
+ export declare const onScamReportCreateHandler: FirestoreTriggerHandler<null, Record<string, unknown>>;
@@ -0,0 +1,7 @@
1
+ import { handleScamReportCreate } from "../core/onScamReportCreate";
2
+ export const onScamReportCreateHandler = async (event, ctx) => {
3
+ const report = event.after;
4
+ if (!report)
5
+ return;
6
+ await handleScamReportCreate({ scammerId: event.params.scammerId, report }, ctx);
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { FirestoreTriggerHandler } from "../runtime/types";
2
+ export declare const onScamReportUpdateHandler: FirestoreTriggerHandler<Record<string, unknown>, Record<string, unknown>>;
@@ -0,0 +1,25 @@
1
+ import { handleScamReportVerified } from "../core/onScamReportVerified";
2
+ import { handleScamReportRejected } from "../core/onScamReportRejected";
3
+ export const onScamReportUpdateHandler = async (event, ctx) => {
4
+ const before = event.before;
5
+ const after = event.after;
6
+ if (!after)
7
+ return;
8
+ const prevStatus = before?.status;
9
+ const nextStatus = after.status;
10
+ const baseInput = {
11
+ scammerId: event.params.scammerId,
12
+ report: {
13
+ reportedBy: (after.reportedBy ?? before?.reportedBy),
14
+ displayNames: (after.displayNames ?? before?.displayNames),
15
+ prevStatus,
16
+ nextStatus,
17
+ },
18
+ };
19
+ if (nextStatus === "verified") {
20
+ await handleScamReportVerified(baseInput, ctx);
21
+ }
22
+ else if (nextStatus === "rejected") {
23
+ await handleScamReportRejected(baseInput, ctx);
24
+ }
25
+ };
package/dist/client.d.ts CHANGED
@@ -189,3 +189,5 @@ export { LISTING_TYPE_REGISTRY, pluginFor } from "./_internal/shared/listing-typ
189
189
  export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
190
190
  export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, } from "./_internal/shared/media/limits";
191
191
  export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
192
+ export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
193
+ export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
package/dist/client.js CHANGED
@@ -218,3 +218,4 @@ export { LISTING_TYPE_CAPABILITIES, capabilityFor, canAddToCart, canBid, support
218
218
  export { LISTING_TYPE_REGISTRY, pluginFor } from "./_internal/shared/listing-types/_registry";
219
219
  // Media upload limits — shared by client uploaders + server sign/finalize routes.
220
220
  export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, } from "./_internal/shared/media/limits";
221
+ export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
@@ -0,0 +1,5 @@
1
+ export interface ScamAwarenessModalProps {
2
+ isOpen: boolean;
3
+ onAcknowledged: () => void;
4
+ }
5
+ export declare function ScamAwarenessModal({ isOpen, onAcknowledged }: ScamAwarenessModalProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
5
+ import { Shield, IndianRupee, UserX, CreditCard, Package, UserCheck, ShieldAlert, Truck } from "lucide-react";
6
+ import { Modal, Stack, Row, Text, Checkbox } from "../../../ui";
7
+ import { apiClient } from "../../../http";
8
+ import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { SCAM_CATEGORIES } from "../constants/scam-types";
10
+ import { ROUTES } from "../../../next/routing/route-map";
11
+ const CATEGORY_ICONS = {
12
+ price_manipulation: _jsx(IndianRupee, { className: "h-5 w-5" }),
13
+ social_engineering: _jsx(UserX, { className: "h-5 w-5" }),
14
+ payment_fraud: _jsx(CreditCard, { className: "h-5 w-5" }),
15
+ preorder_delivery_fraud: _jsx(Package, { className: "h-5 w-5" }),
16
+ identity_impersonation: _jsx(UserCheck, { className: "h-5 w-5" }),
17
+ item_authenticity_fraud: _jsx(ShieldAlert, { className: "h-5 w-5" }),
18
+ logistics_fraud: _jsx(Truck, { className: "h-5 w-5" }),
19
+ };
20
+ export function ScamAwarenessModal({ isOpen, onAcknowledged }) {
21
+ const [checked, setChecked] = useState(false);
22
+ const queryClient = useQueryClient();
23
+ const mutation = useMutation({
24
+ mutationFn: () => apiClient.patch(ACCOUNT_ENDPOINTS.PROFILE, { acknowledgeScamAwareness: true }),
25
+ onSuccess: () => {
26
+ queryClient.invalidateQueries({ queryKey: ["auth", "me"] });
27
+ onAcknowledged();
28
+ },
29
+ });
30
+ return (_jsx(Modal, { isOpen: isOpen, onClose: () => { }, showCloseButton: false, size: "lg", title: _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Shield, { className: "h-5 w-5 text-[color:var(--appkit-color-warning,theme(colors.amber.500))]" }), _jsx("span", { children: "Before you start \u2014 Stay Safe on LetItRip" })] }), actions: _jsx(Row, { gap: "sm", justify: "end", className: "w-full", children: _jsx("button", { className: "appkit-button appkit-button--primary appkit-button--md", disabled: !checked || mutation.isPending, onClick: () => mutation.mutate(), children: mutation.isPending ? "Saving…" : "Continue to LetItRip →" }) }), children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { variant: "secondary", className: "text-sm", children: "LetItRip connects you with verified sellers, but collectibles markets attract scams. Take 60 seconds to learn the most common patterns \u2014 it could save your money." }), _jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: SCAM_CATEGORIES.map((cat) => (_jsxs("div", { className: "flex gap-3 rounded-lg border border-[color:var(--appkit-color-border,theme(colors.zinc.200))] bg-[color:var(--appkit-color-surface,theme(colors.zinc.50))] p-3", children: [_jsx("span", { className: "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-[color:var(--appkit-color-warning,theme(colors.amber.500))]/10 text-[color:var(--appkit-color-warning,theme(colors.amber.600))]", children: CATEGORY_ICONS[cat.id] ?? _jsx(Shield, { className: "h-4 w-4" }) }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { weight: "semibold", className: "text-sm", children: cat.label }), _jsx(Text, { variant: "secondary", className: "text-xs leading-relaxed", children: cat.description })] })] }, cat.id))) }), _jsxs(Row, { gap: "md", className: "flex-wrap text-sm", children: [_jsx("a", { href: String(ROUTES.PUBLIC.SCAM_TYPES), target: "_blank", rel: "noopener noreferrer", className: "text-[color:var(--appkit-color-primary,theme(colors.blue.600))] hover:underline", children: "See all 27 scam types \u2192" }), _jsx("a", { href: String(ROUTES.PUBLIC.SCAM_FAQS), target: "_blank", rel: "noopener noreferrer", className: "text-[color:var(--appkit-color-primary,theme(colors.blue.600))] hover:underline", children: "Common scam FAQs \u2192" })] }), mutation.isError && (_jsx(Text, { variant: "error", className: "text-sm", children: "Something went wrong \u2014 please try again." })), _jsx(Checkbox, { id: "scam-awareness-ack", checked: checked, onChange: (e) => setChecked(e.target.checked), label: "I have read the above and understand the risks of buying and selling collectibles online." })] }) }));
31
+ }
@@ -2,3 +2,5 @@ export { ScamRegistryView } from "./ScamRegistryView";
2
2
  export type { ScamRegistryViewProps } from "./ScamRegistryView";
3
3
  export { ScamProfileView } from "./ScamProfileView";
4
4
  export type { ScamProfileViewProps } from "./ScamProfileView";
5
+ export { ScamAwarenessModal } from "./ScamAwarenessModal";
6
+ export type { ScamAwarenessModalProps } from "./ScamAwarenessModal";
@@ -1,2 +1,3 @@
1
1
  export { ScamRegistryView } from "./ScamRegistryView";
2
2
  export { ScamProfileView } from "./ScamProfileView";
3
+ export { ScamAwarenessModal } from "./ScamAwarenessModal";
package/dist/index.d.ts CHANGED
@@ -528,7 +528,8 @@ export { storeRepository } from "./repositories/index";
528
528
  export { scammerRepository } from "./repositories/index";
529
529
  export { supportRepository, SupportRepository } from "./repositories/index";
530
530
  export type { SupportTicketDocument, SupportTicketCreateInput, SupportTicketUpdateInput, TicketMessage, TicketCategory, TicketStatus, TicketPriority, } from "./repositories/index";
531
- export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET } from "./features/support/schemas/firestore";
531
+ export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
532
+ export { supportTicketsSeedData } from "./seed/index";
532
533
  export { productFeaturesRepository } from "./repositories/index";
533
534
  export type { ProductFeatureListFilter } from "./repositories/index";
534
535
  export { loadProductFeaturesForStore } from "./repositories/index";
@@ -875,7 +876,6 @@ export type { ShiprocketTrackingResponse } from "./providers/shipping-shiprocket
875
876
  export type { ShiprocketVerifyPickupOTPRequest } from "./providers/shipping-shiprocket/index";
876
877
  export type { ShiprocketVerifyPickupOTPResponse } from "./providers/shipping-shiprocket/index";
877
878
  export type { ShiprocketWebhookPayload } from "./providers/shipping-shiprocket/index";
878
- export { firebaseStorageProvider } from "./providers/storage-firebase/index";
879
879
  export { STORAGE_PATHS } from "./providers/storage-firebase/client";
880
880
  export { createStorageHelpers } from "./providers/storage-firebase/client";
881
881
  export { validateFileSize } from "./providers/storage-firebase/client";
@@ -3028,6 +3028,8 @@ export { ScamRegistryView } from "./features/scams/components/ScamRegistryView";
3028
3028
  export type { ScamRegistryViewProps } from "./features/scams/components/ScamRegistryView";
3029
3029
  export { ScamProfileView } from "./features/scams/components/ScamProfileView";
3030
3030
  export type { ScamProfileViewProps } from "./features/scams/components/ScamProfileView";
3031
+ export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
3032
+ export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
3031
3033
  export { listVerifiedScammers, getPublicScammerById, getScammerProfilePageData, } from "./features/scams/actions/scam-actions";
3032
3034
  export type { ScammerProfilePageData } from "./features/scams/actions/scam-actions";
3033
3035
  export type { ScammerListResult } from "./features/scams/actions/scam-actions";
package/dist/index.js CHANGED
@@ -1084,7 +1084,9 @@ export { storeRepository } from "./repositories/index";
1084
1084
  export { scammerRepository } from "./repositories/index";
1085
1085
  // supportRepository - Server-only repository for support tickets (BAN1).
1086
1086
  export { supportRepository, SupportRepository } from "./repositories/index";
1087
- export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET } from "./features/support/schemas/firestore";
1087
+ export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
1088
+ // Support tickets — seed data
1089
+ export { supportTicketsSeedData } from "./seed/index";
1088
1090
  // SB-UNI-B — sublistingCategoriesRepository + SublistingCategoryDocument deleted.
1089
1091
  // Use categoriesRepository.findBySlugAndType(slug, "sublisting") and CategoryDocument with categoryType:"sublisting".
1090
1092
  // [DB]-Database layer — uses firebase-admin; server-only.
@@ -1806,10 +1808,6 @@ export { shiprocketTrackByAWB } from "./providers/shipping-shiprocket/index";
1806
1808
  // [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
1807
1809
  // shiprocketVerifyPickupOTP - Shared export for shiprocket verify pickup otp.
1808
1810
  export { shiprocketVerifyPickupOTP } from "./providers/shipping-shiprocket/index";
1809
- // ./providers/storage-firebase/index
1810
- // [CLIENT-SSR]-Runs in both SSR and browser — React component or hook that does not depend on browser-only APIs.
1811
- // firebaseStorageProvider - Component for firebase storage provider.
1812
- export { firebaseStorageProvider } from "./providers/storage-firebase/index";
1813
1811
  // ./providers/storage-firebase/client
1814
1812
  // [CLIENT-SSR]-Runs in both SSR and browser — React component or hook that does not depend on browser-only APIs.
1815
1813
  // STORAGE_PATHS - Constant used across modules.
@@ -5496,6 +5494,7 @@ export { SCAM_TYPES, SCAM_CATEGORIES, SCAM_TYPE_LABELS, SCAM_CATEGORY_LABELS, ge
5496
5494
  // Scam Registry view components (SCAM3–SCAM4)
5497
5495
  export { ScamRegistryView } from "./features/scams/components/ScamRegistryView";
5498
5496
  export { ScamProfileView } from "./features/scams/components/ScamProfileView";
5497
+ export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
5499
5498
  // Scam server actions
5500
5499
  export { listVerifiedScammers, getPublicScammerById, getScammerProfilePageData, } from "./features/scams/actions/scam-actions";
5501
5500
  // --- Shell primitives (UX1/UX2/UX3/UX6) ----------------------------------------
package/dist/jobs.d.ts CHANGED
@@ -20,5 +20,5 @@
20
20
  * listingProcessorHandler,
21
21
  * } from "@mohasinac/appkit/jobs";
22
22
  */
23
- export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onBidPlacedHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections, prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealReminderHandler, bundleStockSyncHandler, onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
23
+ export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onBidPlacedHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections, prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealReminderHandler, bundleStockSyncHandler, onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, onScamReportCreateHandler, onScamReportUpdateHandler, bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
24
24
  export type { PromotionsCallableResult, AdminAnalyticsResult, StoreAnalyticsInput, StoreAnalyticsResult, ListingRequestBody, ListingResponseBody, JobContext, JobLogger, JobHandlers, ScheduleHandler, FirestoreTriggerHandler, FirestoreTriggerEvent, CallableHandler, BindHttpsOptions, } from "./_internal/server/jobs/index.js";
package/dist/jobs.js CHANGED
@@ -29,5 +29,7 @@ prizeRevealOpenHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prize
29
29
  onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler,
30
30
  // BAN9 — support ticket lifecycle + ban audit trail
31
31
  onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler,
32
+ // SCAM8 — scam report notifications
33
+ onScamReportCreateHandler, onScamReportUpdateHandler,
32
34
  // Firebase binder adapter
33
35
  bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
@@ -29,6 +29,7 @@ export interface SessionUser {
29
29
  publicProfile?: Record<string, unknown>;
30
30
  stats?: Record<string, unknown>;
31
31
  metadata?: Record<string, unknown>;
32
+ scamAwarenessAcknowledgedAt?: Date | null;
32
33
  }
33
34
  export interface SessionContextValue {
34
35
  user: SessionUser | null;
@@ -71,6 +71,9 @@ function buildSessionUser(authUser, serverData) {
71
71
  publicProfile: serverData.publicProfile,
72
72
  stats: serverData.stats,
73
73
  metadata: serverData.metadata,
74
+ scamAwarenessAcknowledgedAt: serverData.scamAwarenessAcknowledgedAt
75
+ ? new Date(serverData.scamAwarenessAcknowledgedAt)
76
+ : null,
74
77
  };
75
78
  }
76
79
  // ---------------------------------------------------------------------------
@@ -134,6 +137,9 @@ export function SessionProvider({ children, initialUser, endpoints: endpointOver
134
137
  publicProfile: data.publicProfile,
135
138
  stats: data.stats,
136
139
  metadata: data.metadata,
140
+ scamAwarenessAcknowledgedAt: data.scamAwarenessAcknowledgedAt
141
+ ? new Date(data.scamAwarenessAcknowledgedAt)
142
+ : null,
137
143
  };
138
144
  }
139
145
  catch {
@@ -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" | "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";
10
10
  export interface SeedOperationResult {
11
11
  success?: boolean;
12
12
  message: string;
@@ -70,6 +70,7 @@ export { historySeedData } from "./history-seed-data";
70
70
  export { conversationsSeedData } from "./conversations-seed-data";
71
71
  export { groupedListingsSeedData } from "./grouped-listings-seed-data";
72
72
  export { scammersSeedData } from "./scammers-seed-data";
73
+ export { supportTicketsSeedData } from "./support-tickets-seed-data";
73
74
  export { productFeaturesSeedData } from "./product-features-seed-data";
74
75
  export type { SeedManifest, SeedManifestEntry } from "./manifest";
75
76
  export { SEED_MANIFEST } from "./manifest";
@@ -59,6 +59,7 @@ export { conversationsSeedData } from "./conversations-seed-data";
59
59
  export { groupedListingsSeedData } from "./grouped-listings-seed-data";
60
60
  // SB-UNI-V — bundlesSeedData merged into categoriesSeedData with categoryType:"bundle".
61
61
  export { scammersSeedData } from "./scammers-seed-data";
62
+ export { supportTicketsSeedData } from "./support-tickets-seed-data";
62
63
  export { productFeaturesSeedData } from "./product-features-seed-data";
63
64
  export { SEED_MANIFEST } from "./manifest";
64
65
  export { mergeFirestoreIndices, generateMergedFirestoreIndexFile, } from "./firestore-indexes";
@@ -37,6 +37,7 @@ import { conversationsSeedData } from "./conversations-seed-data";
37
37
  import { groupedListingsSeedData } from "./grouped-listings-seed-data";
38
38
  // SB-UNI-V — bundlesSeedData absorbed into categoriesSeedData.
39
39
  import { scammersSeedData } from "./scammers-seed-data";
40
+ import { supportTicketsSeedData } from "./support-tickets-seed-data";
40
41
  import { productFeaturesSeedData } from "./product-features-seed-data";
41
42
  function asArr(items) {
42
43
  return items ?? [];
@@ -158,6 +159,10 @@ export const SEED_MANIFEST = {
158
159
  ...s,
159
160
  name: s.displayNames?.[0] ?? s.id,
160
161
  }))),
162
+ supportTickets: pick(asArr(supportTicketsSeedData).map((t) => ({
163
+ ...t,
164
+ name: t.subject ?? t.id,
165
+ }))),
161
166
  productFeatures: pick(asArr(productFeaturesSeedData).map((f) => ({
162
167
  ...f,
163
168
  name: f.label ?? f.id,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Support Tickets Seed Data — 6 tickets across all statuses and categories.
3
+ *
4
+ * Covers: open, in_progress, waiting_on_user, resolved, closed (2).
5
+ * Users: mix of buyers (rahul-sharma, priya-patel, arjun-singh, meera-nair).
6
+ * Employee assignee: user-simran-kaur (employee role).
7
+ */
8
+ import type { SupportTicketDocument } from "../features/support/schemas/firestore";
9
+ export declare const supportTicketsSeedData: Partial<SupportTicketDocument>[];
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Support Tickets Seed Data — 6 tickets across all statuses and categories.
3
+ *
4
+ * Covers: open, in_progress, waiting_on_user, resolved, closed (2).
5
+ * Users: mix of buyers (rahul-sharma, priya-patel, arjun-singh, meera-nair).
6
+ * Employee assignee: user-simran-kaur (employee role).
7
+ */
8
+ function msg(id, authorId, authorRole, body, daysAgo) {
9
+ const createdAt = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000);
10
+ return { id, authorId, authorRole, body, createdAt };
11
+ }
12
+ const BASE = Date.now();
13
+ function daysBack(n) {
14
+ return new Date(BASE - n * 24 * 60 * 60 * 1000);
15
+ }
16
+ export const supportTicketsSeedData = [
17
+ // ── 1. Order issue — in progress ─────────────────────────────────────────
18
+ {
19
+ id: "ticket-rahul-order-001",
20
+ userId: "user-rahul-sharma",
21
+ userEmail: "rahul.sharma@example.com",
22
+ userDisplayName: "Rahul Sharma",
23
+ category: "order_issue",
24
+ subject: "Order delivered but item is missing from the box",
25
+ description: "I received order #order-rahul-001-pokemon-etb but the ETB box arrived empty — only the outer packaging was present. Please help.",
26
+ orderId: "order-rahul-001-pokemon-etb",
27
+ status: "in_progress",
28
+ priority: "high",
29
+ assignedTo: "user-simran-kaur",
30
+ assignedToName: "Simran Kaur",
31
+ messages: [
32
+ msg("msg-rahul-001-u1", "user-rahul-sharma", "user", "I received my order but it was empty! The seal was broken on arrival.", 5),
33
+ msg("msg-rahul-001-s1", "user-simran-kaur", "support", "Hi Rahul, we're sorry to hear this. I've raised a claim with the courier. Could you please share a photo of the packaging?", 4),
34
+ msg("msg-rahul-001-u2", "user-rahul-sharma", "user", "Photos attached. The outer tape was clearly cut and resealed.", 3),
35
+ ],
36
+ createdAt: daysBack(5),
37
+ updatedAt: daysBack(3),
38
+ },
39
+ // ── 2. Open ticket — refund request ───────────────────────────────────────
40
+ {
41
+ id: "ticket-priya-refund-001",
42
+ userId: "user-priya-patel",
43
+ userEmail: "priya.patel@example.com",
44
+ userDisplayName: "Priya Patel",
45
+ category: "refund_request",
46
+ subject: "Requesting refund for cancelled pre-order",
47
+ description: "I placed a pre-order for the Nendoroid Rem figure 3 months ago. The store has now closed. I would like a full refund of ₹2,499.",
48
+ status: "open",
49
+ priority: "normal",
50
+ messages: [
51
+ msg("msg-priya-001-u1", "user-priya-patel", "user", "The store closed without shipping. I paid via Razorpay. Transaction ID: pay_test_abc123.", 2),
52
+ ],
53
+ createdAt: daysBack(2),
54
+ updatedAt: daysBack(2),
55
+ },
56
+ // ── 3. Waiting on user — account recovery ─────────────────────────────────
57
+ {
58
+ id: "ticket-arjun-account-001",
59
+ userId: "user-arjun-singh",
60
+ userEmail: "arjun.singh@example.com",
61
+ userDisplayName: "Arjun Singh",
62
+ category: "account",
63
+ subject: "Cannot log in — OTP not arriving on new phone number",
64
+ description: "I changed my phone number and now OTP for login is going to the old number. I cannot access my account.",
65
+ status: "waiting_on_user",
66
+ priority: "normal",
67
+ assignedTo: "user-simran-kaur",
68
+ assignedToName: "Simran Kaur",
69
+ messages: [
70
+ msg("msg-arjun-001-u1", "user-arjun-singh", "user", "I'm locked out. My old number is no longer active.", 7),
71
+ msg("msg-arjun-001-s1", "user-simran-kaur", "support", "Hi Arjun, to verify ownership we need your registered email and last 4 digits of the payment card used on this account. Please reply here.", 6),
72
+ ],
73
+ createdAt: daysBack(7),
74
+ updatedAt: daysBack(6),
75
+ },
76
+ // ── 4. Resolved — listing dispute ─────────────────────────────────────────
77
+ {
78
+ id: "ticket-meera-dispute-001",
79
+ userId: "user-meera-nair",
80
+ userEmail: "meera.nair@example.com",
81
+ userDisplayName: "Meera Nair",
82
+ category: "listing_dispute",
83
+ subject: "Product description says mint condition but item is heavily played",
84
+ description: "The Beyblade BX-01 I received is scratched and worn. The listing said mint/unused. I want to return it.",
85
+ status: "resolved",
86
+ priority: "normal",
87
+ assignedTo: "user-simran-kaur",
88
+ assignedToName: "Simran Kaur",
89
+ messages: [
90
+ msg("msg-meera-001-u1", "user-meera-nair", "user", "The item is clearly not mint. Here are photos showing the scratches.", 12),
91
+ msg("msg-meera-001-s1", "user-simran-kaur", "support", "Thank you for the photos Meera. We've contacted the seller and initiated a return + full refund.", 11),
92
+ msg("msg-meera-001-s2", "user-simran-kaur", "support", "Refund of ₹1,200 has been processed back to your original payment method. This ticket is now resolved.", 8),
93
+ ],
94
+ resolvedAt: daysBack(8),
95
+ createdAt: daysBack(12),
96
+ updatedAt: daysBack(8),
97
+ },
98
+ // ── 5. Closed — auction dispute (terminal) ────────────────────────────────
99
+ {
100
+ id: "ticket-rahul-auction-001",
101
+ userId: "user-rahul-sharma",
102
+ userEmail: "rahul.sharma@example.com",
103
+ userDisplayName: "Rahul Sharma",
104
+ category: "auction_dispute",
105
+ subject: "Winning bid was removed from auction",
106
+ description: "I won the PSA 9 Charizard auction but my winning bid was removed without explanation.",
107
+ status: "closed",
108
+ priority: "low",
109
+ messages: [
110
+ msg("msg-rahul-002-u1", "user-rahul-sharma", "user", "My winning bid was cancelled. I have a screenshot.", 20),
111
+ msg("msg-rahul-002-s1", "user-simran-kaur", "support", "Hi Rahul, after reviewing the auction logs we found a duplicate bid was submitted. The correct winning bid remains active. No action needed.", 18),
112
+ msg("msg-rahul-002-u2", "user-rahul-sharma", "user", "Understood, thanks for clarifying.", 17),
113
+ ],
114
+ closedAt: daysBack(17),
115
+ createdAt: daysBack(20),
116
+ updatedAt: daysBack(17),
117
+ },
118
+ // ── 6. Open — general inquiry ─────────────────────────────────────────────
119
+ {
120
+ id: "ticket-kavya-general-001",
121
+ userId: "user-kavya-iyer",
122
+ userEmail: "kavya.iyer@example.com",
123
+ userDisplayName: "Kavya Iyer",
124
+ category: "general",
125
+ subject: "How do I become a verified seller?",
126
+ description: "I would like to start selling Pokémon cards on LetItRip. What are the steps to get verified and open a store?",
127
+ status: "open",
128
+ priority: "low",
129
+ messages: [
130
+ msg("msg-kavya-001-u1", "user-kavya-iyer", "user", "I've read the FAQ but couldn't find the exact verification requirements.", 1),
131
+ ],
132
+ createdAt: daysBack(1),
133
+ updatedAt: daysBack(1),
134
+ },
135
+ ];
package/package.json CHANGED
@@ -1,210 +1,210 @@
1
- {
2
- "name": "@mohasinac/appkit",
3
- "version": "2.6.9",
4
- "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "sideEffects": [
9
- "**/*.css"
10
- ],
11
- "type": "module",
12
- "exports": {
13
- ".": {
14
- "react-server": "./dist/server-entry.js",
15
- "edge-light": "./dist/server-entry.js",
16
- "worker": "./dist/client-entry.js",
17
- "browser": "./dist/client-entry.js",
18
- "types": "./dist/server-entry.d.ts",
19
- "import": "./dist/server-entry.js",
20
- "default": "./dist/server-entry.js"
21
- },
22
- "./client": {
23
- "types": "./dist/client.d.ts",
24
- "import": "./dist/client.js"
25
- },
26
- "./next": {
27
- "types": "./dist/next/index.d.ts",
28
- "import": "./dist/next/index.js"
29
- },
30
- "./features/events": {
31
- "types": "./dist/features/events/index.d.ts",
32
- "import": "./dist/features/events/index.js"
33
- },
34
- "./ui": {
35
- "types": "./dist/ui/index.d.ts",
36
- "import": "./dist/ui/index.js"
37
- },
38
- "./server": {
39
- "types": "./dist/server.d.ts",
40
- "import": "./dist/server.js"
41
- },
42
- "./jobs": {
43
- "types": "./dist/jobs.d.ts",
44
- "import": "./dist/jobs.js"
45
- },
46
- "./providers": {
47
- "types": "./dist/providers.d.ts",
48
- "import": "./dist/providers.js"
49
- },
50
- "./instrumentation": {
51
- "types": "./dist/instrumentation/index.d.ts",
52
- "import": "./dist/instrumentation/index.js"
53
- },
54
- "./contracts/registry": {
55
- "types": "./dist/contracts/registry.d.ts",
56
- "import": "./dist/contracts/registry.js"
57
- },
58
- "./core/baseline-resolver": {
59
- "types": "./dist/core/baseline-resolver.d.ts",
60
- "import": "./dist/core/baseline-resolver.js"
61
- },
62
- "./providers/auth-firebase": {
63
- "types": "./dist/providers/auth-firebase/index.d.ts",
64
- "import": "./dist/providers/auth-firebase/index.js"
65
- },
66
- "./providers/db-firebase": {
67
- "types": "./dist/providers/db-firebase/index.d.ts",
68
- "import": "./dist/providers/db-firebase/index.js"
69
- },
70
- "./providers/email-resend": {
71
- "types": "./dist/providers/email-resend/index.d.ts",
72
- "import": "./dist/providers/email-resend/index.js"
73
- },
74
- "./providers/storage-firebase": {
75
- "types": "./dist/providers/storage-firebase/index.d.ts",
76
- "import": "./dist/providers/storage-firebase/index.js"
77
- },
78
- "./style/tailwind": {
79
- "types": "./dist/style/tailwind/index.d.ts",
80
- "import": "./dist/style/tailwind/index.js"
81
- },
82
- "./repositories/site-settings": {
83
- "types": "./dist/features/admin/repository/site-settings.repository.d.ts",
84
- "import": "./dist/features/admin/repository/site-settings.repository.js"
85
- },
86
- "./features/whatsapp-bot/server": {
87
- "types": "./dist/features/whatsapp-bot/server.d.ts",
88
- "import": "./dist/features/whatsapp-bot/server.js"
89
- },
90
- "./styles": "./dist/styles.css",
91
- "./tokens": {
92
- "types": "./dist/tokens/index.d.ts",
93
- "import": "./dist/tokens/index.js",
94
- "style": "./dist/tokens/tokens.css"
95
- },
96
- "./tokens/*": {
97
- "types": "./dist/tokens/*.d.ts",
98
- "import": "./dist/tokens/*.js",
99
- "require": "./dist/tokens/*.js",
100
- "style": "./dist/tokens/*.css"
101
- },
102
- "./tokens/tokens.css": {
103
- "style": "./dist/tokens/tokens.css",
104
- "default": "./dist/tokens/tokens.css"
105
- },
106
- "./configs": {
107
- "types": "./dist/configs/index.d.ts",
108
- "import": "./dist/configs/index.js",
109
- "default": "./dist/configs/index.js"
110
- },
111
- "./tsconfig.base.json": "./tsconfig.base.json",
112
- "./messages/*": "./dist/features/*/messages/en.json"
113
- },
114
- "files": [
115
- "dist",
116
- "tsconfig.base.json",
117
- "scripts/sieve-audit.mjs",
118
- "scripts/audit-violations.mjs",
119
- "scripts/verify-entries.mjs",
120
- "scripts/verify-css-build.mjs",
121
- "scripts/smoke-ssr.mjs",
122
- "scripts/smoke-bundle.mjs",
123
- "scripts/smoke-theme.mjs",
124
- "scripts/init-config.mjs",
125
- "scripts/labels-extract.mjs",
126
- "scripts/seed-cli.mjs",
127
- "scripts/seed-cli-loader.mjs"
128
- ],
129
- "bin": {
130
- "appkit-sieve-audit": "./scripts/sieve-audit.mjs",
131
- "appkit-audit": "./scripts/audit-violations.mjs",
132
- "appkit-verify-entries": "./scripts/verify-entries.mjs",
133
- "appkit-verify-css": "./scripts/verify-css-build.mjs",
134
- "appkit-smoke-ssr": "./scripts/smoke-ssr.mjs",
135
- "appkit-smoke-bundle": "./scripts/smoke-bundle.mjs",
136
- "appkit-smoke-theme": "./scripts/smoke-theme.mjs",
137
- "appkit-init-config": "./scripts/init-config.mjs",
138
- "appkit-labels-extract": "./scripts/labels-extract.mjs",
139
- "appkit-seed": "./scripts/seed-cli.mjs",
140
- "appkit-seed-add": "./scripts/seed-cli.mjs",
141
- "appkit-seed-remove": "./scripts/seed-cli.mjs",
142
- "appkit-seed-status": "./scripts/seed-cli.mjs"
143
- },
144
- "scripts": {
145
- "build": "node -e \"const fs=require('fs');try{fs.rmSync('tsconfig.build.tsbuildinfo')}catch(e){}\" && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs && tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --minify && node scripts/verify-css-build.mjs",
146
- "watch": "concurrently --kill-others --restart-tries 0 --names \"ts,css\" \"tsc -p tsconfig.build.json --watch\" \"npm run watch:css\"",
147
- "watch:css": "tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --watch",
148
- "audit": "node scripts/audit-violations.mjs",
149
- "check:types": "tsc --noEmit",
150
- "check:audits": "node scripts/audit-violations.mjs && node scripts/verify-entries.mjs && node scripts/verify-css-build.mjs",
151
- "check": "npm run check:types && npm run check:audits"
152
- },
153
- "dependencies": {
154
- "@mohasinac/sievejs": "^1.0.0",
155
- "tailwind-merge": "^3.3.0",
156
- "zod": "^3.24.0",
157
- "zustand": "^5.0.13"
158
- },
159
- "peerDependencies": {
160
- "@tanstack/react-query": ">=5.0.0",
161
- "@types/react": ">=18",
162
- "@types/react-dom": ">=18",
163
- "firebase-admin": "^10.3.0",
164
- "firebase-functions": ">=6.0.0",
165
- "next": ">=14",
166
- "next-intl": ">=3",
167
- "razorpay": ">=2.9.0",
168
- "react": ">=18",
169
- "react-dom": ">=18",
170
- "recharts": "^3.8.1",
171
- "resend": ">=3.0.0"
172
- },
173
- "peerDependenciesMeta": {
174
- "firebase-admin": {
175
- "optional": true
176
- },
177
- "firebase-functions": {
178
- "optional": true
179
- },
180
- "resend": {
181
- "optional": true
182
- },
183
- "razorpay": {
184
- "optional": true
185
- },
186
- "@tanstack/react-query": {
187
- "optional": true
188
- }
189
- },
190
- "devDependencies": {
191
- "@tanstack/react-query": "^5.0.0",
192
- "@upstash/ratelimit": "^2.0.8",
193
- "@upstash/redis": "^1.37.0",
194
- "concurrently": "^9.2.1",
195
- "eslint": "^9.37.0",
196
- "firebase": "^12.8.0",
197
- "firebase-admin": "^10.3.0",
198
- "firebase-functions": "^6.6.0",
199
- "lucide-react": "^1.7.0",
200
- "next": "^15.0.0",
201
- "next-intl": "^4.9.1",
202
- "razorpay": "^2.9.6",
203
- "react": "^19.2.0",
204
- "react-dom": "^19.2.0",
205
- "resend": "^6.10.0",
206
- "server-only": "^0.0.1",
207
- "tailwindcss": "^3.4.19",
208
- "typescript": "^5.9.3"
209
- }
210
- }
1
+ {
2
+ "name": "@mohasinac/appkit",
3
+ "version": "2.7.0",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "sideEffects": [
9
+ "**/*.css"
10
+ ],
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "react-server": "./dist/server-entry.js",
15
+ "edge-light": "./dist/server-entry.js",
16
+ "worker": "./dist/client-entry.js",
17
+ "browser": "./dist/client-entry.js",
18
+ "types": "./dist/server-entry.d.ts",
19
+ "import": "./dist/server-entry.js",
20
+ "default": "./dist/server-entry.js"
21
+ },
22
+ "./client": {
23
+ "types": "./dist/client.d.ts",
24
+ "import": "./dist/client.js"
25
+ },
26
+ "./next": {
27
+ "types": "./dist/next/index.d.ts",
28
+ "import": "./dist/next/index.js"
29
+ },
30
+ "./features/events": {
31
+ "types": "./dist/features/events/index.d.ts",
32
+ "import": "./dist/features/events/index.js"
33
+ },
34
+ "./ui": {
35
+ "types": "./dist/ui/index.d.ts",
36
+ "import": "./dist/ui/index.js"
37
+ },
38
+ "./server": {
39
+ "types": "./dist/server.d.ts",
40
+ "import": "./dist/server.js"
41
+ },
42
+ "./jobs": {
43
+ "types": "./dist/jobs.d.ts",
44
+ "import": "./dist/jobs.js"
45
+ },
46
+ "./providers": {
47
+ "types": "./dist/providers.d.ts",
48
+ "import": "./dist/providers.js"
49
+ },
50
+ "./instrumentation": {
51
+ "types": "./dist/instrumentation/index.d.ts",
52
+ "import": "./dist/instrumentation/index.js"
53
+ },
54
+ "./contracts/registry": {
55
+ "types": "./dist/contracts/registry.d.ts",
56
+ "import": "./dist/contracts/registry.js"
57
+ },
58
+ "./core/baseline-resolver": {
59
+ "types": "./dist/core/baseline-resolver.d.ts",
60
+ "import": "./dist/core/baseline-resolver.js"
61
+ },
62
+ "./providers/auth-firebase": {
63
+ "types": "./dist/providers/auth-firebase/index.d.ts",
64
+ "import": "./dist/providers/auth-firebase/index.js"
65
+ },
66
+ "./providers/db-firebase": {
67
+ "types": "./dist/providers/db-firebase/index.d.ts",
68
+ "import": "./dist/providers/db-firebase/index.js"
69
+ },
70
+ "./providers/email-resend": {
71
+ "types": "./dist/providers/email-resend/index.d.ts",
72
+ "import": "./dist/providers/email-resend/index.js"
73
+ },
74
+ "./providers/storage-firebase": {
75
+ "types": "./dist/providers/storage-firebase/index.d.ts",
76
+ "import": "./dist/providers/storage-firebase/index.js"
77
+ },
78
+ "./style/tailwind": {
79
+ "types": "./dist/style/tailwind/index.d.ts",
80
+ "import": "./dist/style/tailwind/index.js"
81
+ },
82
+ "./repositories/site-settings": {
83
+ "types": "./dist/features/admin/repository/site-settings.repository.d.ts",
84
+ "import": "./dist/features/admin/repository/site-settings.repository.js"
85
+ },
86
+ "./features/whatsapp-bot/server": {
87
+ "types": "./dist/features/whatsapp-bot/server.d.ts",
88
+ "import": "./dist/features/whatsapp-bot/server.js"
89
+ },
90
+ "./styles": "./dist/styles.css",
91
+ "./tokens": {
92
+ "types": "./dist/tokens/index.d.ts",
93
+ "import": "./dist/tokens/index.js",
94
+ "style": "./dist/tokens/tokens.css"
95
+ },
96
+ "./tokens/*": {
97
+ "types": "./dist/tokens/*.d.ts",
98
+ "import": "./dist/tokens/*.js",
99
+ "require": "./dist/tokens/*.js",
100
+ "style": "./dist/tokens/*.css"
101
+ },
102
+ "./tokens/tokens.css": {
103
+ "style": "./dist/tokens/tokens.css",
104
+ "default": "./dist/tokens/tokens.css"
105
+ },
106
+ "./configs": {
107
+ "types": "./dist/configs/index.d.ts",
108
+ "import": "./dist/configs/index.js",
109
+ "default": "./dist/configs/index.js"
110
+ },
111
+ "./tsconfig.base.json": "./tsconfig.base.json",
112
+ "./messages/*": "./dist/features/*/messages/en.json"
113
+ },
114
+ "files": [
115
+ "dist",
116
+ "tsconfig.base.json",
117
+ "scripts/sieve-audit.mjs",
118
+ "scripts/audit-violations.mjs",
119
+ "scripts/verify-entries.mjs",
120
+ "scripts/verify-css-build.mjs",
121
+ "scripts/smoke-ssr.mjs",
122
+ "scripts/smoke-bundle.mjs",
123
+ "scripts/smoke-theme.mjs",
124
+ "scripts/init-config.mjs",
125
+ "scripts/labels-extract.mjs",
126
+ "scripts/seed-cli.mjs",
127
+ "scripts/seed-cli-loader.mjs"
128
+ ],
129
+ "bin": {
130
+ "appkit-sieve-audit": "./scripts/sieve-audit.mjs",
131
+ "appkit-audit": "./scripts/audit-violations.mjs",
132
+ "appkit-verify-entries": "./scripts/verify-entries.mjs",
133
+ "appkit-verify-css": "./scripts/verify-css-build.mjs",
134
+ "appkit-smoke-ssr": "./scripts/smoke-ssr.mjs",
135
+ "appkit-smoke-bundle": "./scripts/smoke-bundle.mjs",
136
+ "appkit-smoke-theme": "./scripts/smoke-theme.mjs",
137
+ "appkit-init-config": "./scripts/init-config.mjs",
138
+ "appkit-labels-extract": "./scripts/labels-extract.mjs",
139
+ "appkit-seed": "./scripts/seed-cli.mjs",
140
+ "appkit-seed-add": "./scripts/seed-cli.mjs",
141
+ "appkit-seed-remove": "./scripts/seed-cli.mjs",
142
+ "appkit-seed-status": "./scripts/seed-cli.mjs"
143
+ },
144
+ "scripts": {
145
+ "build": "node -e \"const fs=require('fs');try{fs.rmSync('tsconfig.build.tsbuildinfo')}catch(e){}\" && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs && tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --minify && node scripts/verify-css-build.mjs",
146
+ "watch": "concurrently --kill-others --restart-tries 0 --names \"ts,css\" \"tsc -p tsconfig.build.json --watch\" \"npm run watch:css\"",
147
+ "watch:css": "tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --watch",
148
+ "audit": "node scripts/audit-violations.mjs",
149
+ "check:types": "tsc --noEmit",
150
+ "check:audits": "node scripts/audit-violations.mjs && node scripts/verify-entries.mjs && node scripts/verify-css-build.mjs",
151
+ "check": "npm run check:types && npm run check:audits"
152
+ },
153
+ "dependencies": {
154
+ "@mohasinac/sievejs": "^1.0.0",
155
+ "tailwind-merge": "^3.3.0",
156
+ "zod": "^3.24.0",
157
+ "zustand": "^5.0.13"
158
+ },
159
+ "peerDependencies": {
160
+ "@tanstack/react-query": ">=5.0.0",
161
+ "@types/react": ">=18",
162
+ "@types/react-dom": ">=18",
163
+ "firebase-admin": "^10.3.0",
164
+ "firebase-functions": ">=6.0.0",
165
+ "next": ">=14",
166
+ "next-intl": ">=3",
167
+ "razorpay": ">=2.9.0",
168
+ "react": ">=18",
169
+ "react-dom": ">=18",
170
+ "recharts": "^3.8.1",
171
+ "resend": ">=3.0.0"
172
+ },
173
+ "peerDependenciesMeta": {
174
+ "firebase-admin": {
175
+ "optional": true
176
+ },
177
+ "firebase-functions": {
178
+ "optional": true
179
+ },
180
+ "resend": {
181
+ "optional": true
182
+ },
183
+ "razorpay": {
184
+ "optional": true
185
+ },
186
+ "@tanstack/react-query": {
187
+ "optional": true
188
+ }
189
+ },
190
+ "devDependencies": {
191
+ "@tanstack/react-query": "^5.0.0",
192
+ "@upstash/ratelimit": "^2.0.8",
193
+ "@upstash/redis": "^1.37.0",
194
+ "concurrently": "^9.2.1",
195
+ "eslint": "^9.37.0",
196
+ "firebase": "^12.8.0",
197
+ "firebase-admin": "^10.3.0",
198
+ "firebase-functions": "^6.6.0",
199
+ "lucide-react": "^1.7.0",
200
+ "next": "^15.0.0",
201
+ "next-intl": "^4.9.1",
202
+ "razorpay": "^2.9.6",
203
+ "react": "^19.2.0",
204
+ "react-dom": "^19.2.0",
205
+ "resend": "^6.10.0",
206
+ "server-only": "^0.0.1",
207
+ "tailwindcss": "^3.4.19",
208
+ "typescript": "^5.9.3"
209
+ }
210
+ }