@mohasinac/appkit 2.6.8 → 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 (34) 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/configs/next.js +2 -0
  17. package/dist/features/scams/components/ScamAwarenessModal.d.ts +5 -0
  18. package/dist/features/scams/components/ScamAwarenessModal.js +31 -0
  19. package/dist/features/scams/components/index.d.ts +2 -0
  20. package/dist/features/scams/components/index.js +1 -0
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.js +4 -5
  23. package/dist/jobs.d.ts +1 -1
  24. package/dist/jobs.js +2 -0
  25. package/dist/react/contexts/SessionContext.d.ts +1 -0
  26. package/dist/react/contexts/SessionContext.js +6 -0
  27. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  28. package/dist/seed/index.d.ts +1 -0
  29. package/dist/seed/index.js +1 -0
  30. package/dist/seed/manifest.js +5 -0
  31. package/dist/seed/support-tickets-seed-data.d.ts +9 -0
  32. package/dist/seed/support-tickets-seed-data.js +135 -0
  33. package/dist/tailwind-utilities.css +1 -1
  34. 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";
@@ -97,6 +97,7 @@ export function defineNextConfig(override = {}) {
97
97
  "./node_modules/@grpc/**",
98
98
  // Transitive deps of google-gax / @grpc hoisted to root node_modules
99
99
  "./node_modules/protobufjs/**",
100
+ "./node_modules/@protobufjs/**",
100
101
  "./node_modules/object-hash/**",
101
102
  "./node_modules/proto3-json-serializer/**",
102
103
  "./node_modules/long/**",
@@ -105,6 +106,7 @@ export function defineNextConfig(override = {}) {
105
106
  "./node_modules/retry-request/**",
106
107
  "./node_modules/duplexify/**",
107
108
  "./node_modules/uuid/**",
109
+ "./node_modules/lodash.camelcase/**",
108
110
  ],
109
111
  };
110
112
  const mergedOutputFileTracingIncludes = {
@@ -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";