@mohasinac/appkit 4.63.3 → 4.63.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
- package/dist/_internal/server/features/checkout/actions.js +22 -5
- package/dist/_internal/server/features/orders/adapters.js +3 -1
- package/dist/_internal/server/features/promotions/adapters.d.ts +25 -0
- package/dist/_internal/server/features/promotions/adapters.js +43 -0
- package/dist/_internal/server/features/promotions/data.d.ts +26 -0
- package/dist/_internal/server/features/promotions/data.js +45 -0
- package/dist/_internal/server/features/promotions/index.d.ts +2 -1
- package/dist/_internal/server/features/promotions/index.js +2 -1
- package/dist/_internal/server/features/site-settings/adapters.js +20 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +1 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
- package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +45 -14
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
- package/dist/_internal/server/notifications/channel-health.js +2 -2
- package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
- package/dist/_internal/server/notifications/send-guard.js +4 -2
- package/dist/_internal/shared/actions/action-registry.js +28 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
- package/dist/_internal/shared/checkout/order-math.js +1 -1
- package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
- package/dist/_internal/shared/features/checkout/config.js +2 -0
- package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
- package/dist/_internal/shared/features/messaging/config.js +4 -1
- package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
- package/dist/_internal/shared/features/site-settings/fees.js +2 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
- package/dist/_internal/shared/fees/calculator.d.ts +8 -0
- package/dist/_internal/shared/fees/calculator.js +7 -0
- package/dist/cli/index.js +2 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +15 -0
- package/dist/constants/api-endpoints.js +8 -0
- package/dist/constants/field-names.d.ts +4 -0
- package/dist/constants/field-names.js +4 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/constants/notification-types.js +1 -1
- package/dist/core/integration-keys.d.ts +5 -0
- package/dist/core/integration-keys.js +10 -0
- package/dist/features/about/components/FeesView.js +6 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/types/index.d.ts +2 -0
- package/dist/features/admin/actions/notification-actions.d.ts +17 -2
- package/dist/features/admin/actions/notification-actions.js +154 -9
- package/dist/features/admin/components/AdminBlogEditorView.js +10 -2
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +2 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
- package/dist/features/admin/components/AdminStoreEditorView.js +1 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
- package/dist/features/admin/constants/site-settings-tabs.js +12 -6
- package/dist/features/admin/repository/site-settings.repository.js +1 -1
- package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
- package/dist/features/admin/schemas/admin-user-form.js +44 -3
- package/dist/features/admin/schemas/firestore.d.ts +73 -0
- package/dist/features/admin/schemas/firestore.js +8 -3
- package/dist/features/auth/checkout-value-otp.d.ts +1 -1
- package/dist/features/auth/checkout-value-otp.js +1 -1
- package/dist/features/auth/permissions/constants.d.ts +2 -2
- package/dist/features/auth/permissions/constants.js +2 -1
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +14 -0
- package/dist/features/blog/schemas/firestore.d.ts +17 -0
- package/dist/features/blog/schemas/firestore.js +1 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +3 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
- package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
- package/dist/features/cart/repository/cart.repository.js +2 -1
- package/dist/features/cart/schemas/firestore.d.ts +2 -1
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
- package/dist/features/events/components/AdminEventEditorView.js +64 -9
- package/dist/features/events/components/EventOfferCard.js +3 -18
- package/dist/features/events/components/EventRaffleWinnerView.d.ts +6 -0
- package/dist/features/events/components/EventRaffleWinnerView.js +11 -1
- package/dist/features/events/schemas/event-form.d.ts +10 -0
- package/dist/features/events/schemas/event-form.js +19 -0
- package/dist/features/events/schemas/firestore.d.ts +68 -0
- package/dist/features/events/schemas/firestore.js +25 -0
- package/dist/features/events/schemas/index.d.ts +7 -0
- package/dist/features/events/schemas/index.js +5 -0
- package/dist/features/events/types/index.d.ts +4 -0
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
- package/dist/features/orders/components/OrderAddonBadges.js +4 -3
- package/dist/features/orders/repository/orders.repository.d.ts +8 -0
- package/dist/features/orders/repository/orders.repository.js +2 -0
- package/dist/features/orders/schemas/firestore.d.ts +4 -0
- package/dist/features/orders/types/index.d.ts +5 -2
- package/dist/features/promotions/components/AttachedCouponsSection.d.ts +30 -0
- package/dist/features/promotions/components/AttachedCouponsSection.js +99 -0
- package/dist/features/promotions/components/CouponCodeCopyRow.d.ts +19 -0
- package/dist/features/promotions/components/CouponCodeCopyRow.js +39 -0
- package/dist/features/promotions/components/index.d.ts +4 -0
- package/dist/features/promotions/components/index.js +2 -0
- package/dist/features/promotions/constants/attached-coupons.d.ts +55 -0
- package/dist/features/promotions/constants/attached-coupons.js +24 -0
- package/dist/features/promotions/repository/coupons.repository.d.ts +20 -0
- package/dist/features/promotions/repository/coupons.repository.js +39 -0
- package/dist/features/promotions/schemas/attached-coupons.d.ts +39 -0
- package/dist/features/promotions/schemas/attached-coupons.js +66 -0
- package/dist/features/search/action-index/settings-entries.js +9 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
- package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
- package/dist/features/store-extensions/schemas/firestore.js +10 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +1 -0
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
- package/dist/features/stores/repository/store.repository.d.ts +23 -1
- package/dist/features/stores/repository/store.repository.js +29 -1
- package/dist/features/stores/schemas/firestore.d.ts +16 -6
- package/dist/features/stores/schemas/firestore.js +3 -6
- package/dist/features/stores/schemas/index.d.ts +86 -0
- package/dist/features/stores/schemas/index.js +14 -0
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
- package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/components/index.js +2 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
- package/dist/features/whatsapp-bot/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/index.js +3 -0
- package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
- package/dist/features/whatsapp-bot/manifest.js +13 -0
- package/dist/features/whatsapp-bot/messages/en.json +7 -0
- package/dist/features/whatsapp-bot/server.d.ts +7 -0
- package/dist/features/whatsapp-bot/server.js +7 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
- package/dist/features/whatsapp-bot/types/index.js +1 -0
- package/dist/index.d.ts +23 -6
- package/dist/index.js +38 -12
- package/dist/next/routing/route-map.d.ts +3 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/providers/db-firebase/base.repository.d.ts +1 -1
- package/dist/providers/db-firebase/base.repository.js +1 -1
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/schemas/registry.d.ts +121 -0
- package/dist/schemas/registry.js +2 -1
- package/dist/security/pii-encrypt.js +1 -1
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +2 -0
- package/dist/server.d.ts +11 -2
- package/dist/server.js +26 -2
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/NavigationLoader.d.ts +14 -1
- package/dist/ui/components/NavigationLoader.js +79 -30
- package/package.json +1 -1
- package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
- package/dist/features/orders/utils/order-announcement-message.js +0 -16
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ATTACHED_COUPON_MAX } from "../constants/attached-coupons.js";
|
|
3
|
+
/**
|
|
4
|
+
* The write shape for any `couponIds`-style field — one definition, imported
|
|
5
|
+
* by the event schema, the blog schema and both admin API routes.
|
|
6
|
+
*
|
|
7
|
+
* 🛑 ONE definition on purpose. Four hand-written `z.array(z.string())`s would
|
|
8
|
+
* be four chances for the cap to drift, and a cap enforced on three of four
|
|
9
|
+
* write paths is not a cap — the fourth is simply the way in. This is the same
|
|
10
|
+
* failure as the ten hand-maintained copies of the `ListingType` union in
|
|
11
|
+
* Root Cause #61.
|
|
12
|
+
*
|
|
13
|
+
* `.max()` carries a message because this one is reachable by a human in an
|
|
14
|
+
* admin form, and "Too big: expected array to have <=10 items" is not an
|
|
15
|
+
* instruction.
|
|
16
|
+
*/
|
|
17
|
+
/*
|
|
18
|
+
* 🛑 THE EXPLICIT ANNOTATION IS LOAD-BEARING — do not delete it as redundant.
|
|
19
|
+
*
|
|
20
|
+
* This repo runs TWO zod majors: appkit is pinned to 3.25.76 (peer `^3.24.0`)
|
|
21
|
+
* and the consumer app resolves 4.4.3. Inferred, this export's `.d.ts` reads
|
|
22
|
+
* `z.ZodOptional<z.ZodArray<z.ZodString, "many">>` — and zod 4's `ZodArray`
|
|
23
|
+
* takes ONE type parameter, not two. The consumer cannot resolve that shape,
|
|
24
|
+
* the inference collapses to `unknown`, and four admin routes fail with
|
|
25
|
+
* `Type 'unknown' is not assignable to type 'string[] | undefined'`.
|
|
26
|
+
*
|
|
27
|
+
* `z.ZodType<T>` is spelled the same in both majors and `z.infer` yields `T`
|
|
28
|
+
* from it either way, so the emitted declaration stays resolvable on both
|
|
29
|
+
* sides of the boundary.
|
|
30
|
+
*
|
|
31
|
+
* The real defect is the version split — appkit's declared peer range and the
|
|
32
|
+
* app's installed major disagree — and aligning them is a migration, not a
|
|
33
|
+
* drive-by. Until that happens, any appkit zod schema a consumer infers from
|
|
34
|
+
* needs this treatment.
|
|
35
|
+
*/
|
|
36
|
+
export const attachedCouponIdsSchema = z
|
|
37
|
+
.array(z.string().min(1))
|
|
38
|
+
.max(ATTACHED_COUPON_MAX, `At most ${ATTACHED_COUPON_MAX} coupons can be attached.`)
|
|
39
|
+
.optional();
|
|
40
|
+
/**
|
|
41
|
+
* Dedupe and drop blanks. Still ONE definition — it moved out of the schema,
|
|
42
|
+
* it did not become four copies.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 WHY IT IS NOT A `.transform()` ANY MORE, which is where it started:
|
|
45
|
+
*
|
|
46
|
+
* `.transform()` wraps the schema in `z.ZodEffects`, and **`ZodEffects` is a
|
|
47
|
+
* zod 3 type that does not exist in zod 4**. This repo runs both — appkit is
|
|
48
|
+
* pinned to zod 3.25.76 (peer `^3.24.0`) while the consumer app resolves zod
|
|
49
|
+
* 4.4.3 — so appkit's emitted `.d.ts` referenced a type the consumer's zod
|
|
50
|
+
* cannot resolve, the inference collapsed to `unknown`, and four admin routes
|
|
51
|
+
* failed to typecheck with `Type 'unknown' is not assignable to type
|
|
52
|
+
* 'string[] | undefined'`.
|
|
53
|
+
*
|
|
54
|
+
* The declared peer range is the thing that is actually wrong here, and
|
|
55
|
+
* aligning the two majors is a real migration rather than a drive-by. Until
|
|
56
|
+
* then: no schema crossing the package boundary may use `.transform()`,
|
|
57
|
+
* `.refine()` or `.preprocess()`, because all three produce `ZodEffects`.
|
|
58
|
+
*
|
|
59
|
+
* Callers apply this after parsing, which is the same boundary the dedupe
|
|
60
|
+
* always ran at — the route, not the client.
|
|
61
|
+
*/
|
|
62
|
+
export function dedupeAttachedCouponIds(ids) {
|
|
63
|
+
if (!ids)
|
|
64
|
+
return undefined;
|
|
65
|
+
return Array.from(new Set(ids.filter(Boolean)));
|
|
66
|
+
}
|
|
@@ -169,6 +169,15 @@ export const SITE_SETTINGS_ENTRIES = [
|
|
|
169
169
|
tab: "notifications",
|
|
170
170
|
kind: "toggle",
|
|
171
171
|
},
|
|
172
|
+
{
|
|
173
|
+
slug: "whatsapp-notifications",
|
|
174
|
+
anchored: true,
|
|
175
|
+
label: "WhatsApp notifications",
|
|
176
|
+
description: "Send order updates over WhatsApp.",
|
|
177
|
+
keywords: ["whatsapp", "order updates", "meta"],
|
|
178
|
+
tab: "notifications",
|
|
179
|
+
kind: "toggle",
|
|
180
|
+
},
|
|
172
181
|
{
|
|
173
182
|
slug: "daily-digest",
|
|
174
183
|
anchored: true,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { BaseRepository } from "../../../providers/db-firebase";
|
|
16
16
|
import type { DocumentSnapshot } from "firebase-admin/firestore";
|
|
17
|
-
import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument } from "../schemas/firestore";
|
|
17
|
+
import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument, type StoreWhatsAppConfigDocument } from "../schemas/firestore";
|
|
18
18
|
export declare class PayoutMethodsRepository extends BaseRepository<PayoutMethodDocument> {
|
|
19
19
|
/**
|
|
20
20
|
* Bank/UPI details, encrypted at rest by BaseRepository's write hooks.
|
|
@@ -103,6 +103,10 @@ export declare class ItemRequestsRepository extends BaseRepository<ItemRequestDo
|
|
|
103
103
|
*/
|
|
104
104
|
appendReply(id: string, reply: ItemRequestReply): Promise<void>;
|
|
105
105
|
}
|
|
106
|
+
export declare class StoreWhatsAppConfigRepository extends BaseRepository<StoreWhatsAppConfigDocument> {
|
|
107
|
+
constructor();
|
|
108
|
+
getByStore(storeId: string): Promise<StoreWhatsAppConfigDocument | null>;
|
|
109
|
+
}
|
|
106
110
|
export declare class StoreGoogleConfigRepository extends BaseRepository<StoreGoogleConfigDocument> {
|
|
107
111
|
constructor();
|
|
108
112
|
getByStore(storeId: string): Promise<StoreGoogleConfigDocument | null>;
|
|
@@ -116,4 +120,5 @@ export declare const listingTemplatesRepository: ListingTemplatesRepository;
|
|
|
116
120
|
export declare const moderationQueueRepository: ModerationQueueRepository;
|
|
117
121
|
export declare const reportsRepository: ReportsRepository;
|
|
118
122
|
export declare const itemRequestsRepository: ItemRequestsRepository;
|
|
123
|
+
export declare const storeWhatsAppConfigRepository: StoreWhatsAppConfigRepository;
|
|
119
124
|
export declare const storeGoogleConfigRepository: StoreGoogleConfigRepository;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { BaseRepository } from "../../../providers/db-firebase/index.js";
|
|
16
16
|
import { arrayUnion, increment } from "../../../contracts/field-ops.js";
|
|
17
17
|
import { decryptPiiFields } from "../../../security/pii-encrypt.js";
|
|
18
|
-
import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, } from "../schemas/firestore.js";
|
|
18
|
+
import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_COLLECTION, } from "../schemas/firestore.js";
|
|
19
19
|
export class PayoutMethodsRepository extends BaseRepository {
|
|
20
20
|
constructor() {
|
|
21
21
|
super(PAYOUT_METHODS_COLLECTION);
|
|
@@ -149,6 +149,14 @@ export class ItemRequestsRepository extends BaseRepository {
|
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
export class StoreWhatsAppConfigRepository extends BaseRepository {
|
|
153
|
+
constructor() {
|
|
154
|
+
super(STORE_WHATSAPP_CONFIG_COLLECTION);
|
|
155
|
+
}
|
|
156
|
+
async getByStore(storeId) {
|
|
157
|
+
return this.findOneBy("storeId", storeId);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
152
160
|
export class StoreGoogleConfigRepository extends BaseRepository {
|
|
153
161
|
constructor() {
|
|
154
162
|
super(STORE_GOOGLE_CONFIG_COLLECTION);
|
|
@@ -166,4 +174,5 @@ export const listingTemplatesRepository = new ListingTemplatesRepository();
|
|
|
166
174
|
export const moderationQueueRepository = new ModerationQueueRepository();
|
|
167
175
|
export const reportsRepository = new ReportsRepository();
|
|
168
176
|
export const itemRequestsRepository = new ItemRequestsRepository();
|
|
177
|
+
export const storeWhatsAppConfigRepository = new StoreWhatsAppConfigRepository();
|
|
169
178
|
export const storeGoogleConfigRepository = new StoreGoogleConfigRepository();
|
|
@@ -18,6 +18,7 @@ import type { BaseDocument } from "../../../_internal/shared/types/base-document
|
|
|
18
18
|
* 7. moderationQueue — video / media moderation queue items
|
|
19
19
|
* 8. reports — buyer-submitted reports against listings/stores
|
|
20
20
|
* 9. itemRequests — community item request bulletin board
|
|
21
|
+
* 10. storeWhatsAppConfig — per-store WhatsApp business catalogue + auto-replies
|
|
21
22
|
* 11. storeGoogleConfig — per-store Google Business reviews integration
|
|
22
23
|
*/
|
|
23
24
|
export type PayoutMethodType = "upi" | "bank" | "card" | "other";
|
|
@@ -88,7 +89,7 @@ export interface AnalyticsAlertDocument extends BaseDocument {
|
|
|
88
89
|
threshold: number;
|
|
89
90
|
windowHours: number;
|
|
90
91
|
isActive: boolean;
|
|
91
|
-
notifyChannels: Array<"in-app" | "email">;
|
|
92
|
+
notifyChannels: Array<"in-app" | "email" | "whatsapp">;
|
|
92
93
|
/**
|
|
93
94
|
* When this alert last fired.
|
|
94
95
|
*
|
|
@@ -220,6 +221,21 @@ export interface ItemRequestDocument extends BaseDocument {
|
|
|
220
221
|
export declare const ITEM_REQUESTS_COLLECTION: "itemRequests";
|
|
221
222
|
export declare const ITEM_REQUEST_INDEXED_FIELDS: readonly ["opUserId", "status", "category", "createdAt"];
|
|
222
223
|
export declare const DEFAULT_ITEM_REQUEST_DATA: Partial<ItemRequestDocument>;
|
|
224
|
+
export interface StoreWhatsAppConfigDocument extends BaseDocument {
|
|
225
|
+
storeId: string;
|
|
226
|
+
isConnected: boolean;
|
|
227
|
+
isPaid: boolean;
|
|
228
|
+
phoneNumber?: string;
|
|
229
|
+
businessProfileName?: string;
|
|
230
|
+
catalogUrl?: string;
|
|
231
|
+
autoReply?: string;
|
|
232
|
+
welcomeMessage?: string;
|
|
233
|
+
/** Status of pending WhatsApp Business onboarding (Meta Cloud API). */
|
|
234
|
+
onboardingStatus?: "pending" | "approved" | "rejected";
|
|
235
|
+
}
|
|
236
|
+
export declare const STORE_WHATSAPP_CONFIG_COLLECTION: "storeWhatsAppConfig";
|
|
237
|
+
export declare const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected", "isPaid"];
|
|
238
|
+
export declare const DEFAULT_STORE_WHATSAPP_CONFIG_DATA: Partial<StoreWhatsAppConfigDocument>;
|
|
223
239
|
export interface StoreGoogleConfigDocument extends BaseDocument {
|
|
224
240
|
storeId: string;
|
|
225
241
|
isConnected: boolean;
|
|
@@ -182,6 +182,16 @@ export const DEFAULT_ITEM_REQUEST_DATA = {
|
|
|
182
182
|
replies: [],
|
|
183
183
|
replyCount: 0,
|
|
184
184
|
};
|
|
185
|
+
export const STORE_WHATSAPP_CONFIG_COLLECTION = "storeWhatsAppConfig";
|
|
186
|
+
export const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS = [
|
|
187
|
+
"storeId",
|
|
188
|
+
"isConnected",
|
|
189
|
+
"isPaid",
|
|
190
|
+
];
|
|
191
|
+
export const DEFAULT_STORE_WHATSAPP_CONFIG_DATA = {
|
|
192
|
+
isConnected: false,
|
|
193
|
+
isPaid: false,
|
|
194
|
+
};
|
|
185
195
|
export const STORE_GOOGLE_CONFIG_COLLECTION = "storeGoogleConfig";
|
|
186
196
|
export const STORE_GOOGLE_CONFIG_INDEXED_FIELDS = [
|
|
187
197
|
"storeId",
|
|
@@ -45,6 +45,7 @@ const CAPABILITY_GROUPS = [
|
|
|
45
45
|
{ cap: "lower_commission_rate", unlocks: "Negotiated reduced platform fee. Requires a signed agreement and minimum volume.", defaultOn: false },
|
|
46
46
|
{ cap: "early_access_features", unlocks: "Access to beta features before they are publicly available.", defaultOn: false },
|
|
47
47
|
{ cap: "advanced_analytics", unlocks: "Cohort retention, basket analysis, and top-buyer breakdown (beyond summary stats).", defaultOn: false },
|
|
48
|
+
{ cap: "whatsapp_catalog_sync", unlocks: "Sync your store products to your WhatsApp Business Catalog automatically.", defaultOn: false },
|
|
48
49
|
],
|
|
49
50
|
},
|
|
50
51
|
];
|
|
@@ -27,7 +27,7 @@ const GUIDE_CARDS = [
|
|
|
27
27
|
{
|
|
28
28
|
Icon: Settings,
|
|
29
29
|
title: "Settings",
|
|
30
|
-
description: "Store profile, shipping config, return policy, and
|
|
30
|
+
description: "Store profile, shipping config, return policy, vacation mode, and WhatsApp integration.",
|
|
31
31
|
href: String(ROUTES.STORE.GUIDE_SETTINGS),
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -26,7 +26,11 @@ function ReturnPolicySection() {
|
|
|
26
26
|
function VacationModeSection() {
|
|
27
27
|
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "What vacation mode does" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsx(Li, { className: LIST_DISC, children: "All your published listings are hidden from search results and browse pages." }), _jsx(Li, { className: LIST_DISC, children: "Your store profile page shows a \"On Vacation\" banner." }), _jsx(Li, { className: LIST_DISC, children: "Buyers cannot add your items to cart or checkout while on vacation." }), _jsx(Li, { className: LIST_DISC, children: "Existing orders that were placed before vacation mode must still be fulfilled." })] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "How to enable" }), _jsx(Text, { className: "leading-relaxed", children: "Go to your Store Dashboard \u2192 Settings \u2192 Storefront tab. Toggle \"Vacation mode\" on. Your listings re-appear instantly when you toggle it off." })] }), _jsx(Alert, { variant: "warning", title: "Ongoing orders are not paused", children: "Enabling vacation mode does not pause or cancel any existing orders. You are still responsible for fulfilling orders placed before vacation mode was enabled." })] }));
|
|
28
28
|
}
|
|
29
|
-
// -- Section 5:
|
|
29
|
+
// -- Section 5: WhatsApp integration ------------------------------------------
|
|
30
|
+
function WhatsAppSection() {
|
|
31
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Text, { className: "leading-relaxed", children: ["The WhatsApp integration allows your customers to contact you via WhatsApp from your store profile and lets you sync your product catalog to your WhatsApp Business Catalog (requires the", _jsx(Code, { className: "mx-1", size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_catalog_sync" }), " capability)."] }), _jsxs(Text, { className: "leading-relaxed", children: ["Full settings and the catalog sync toggle are at", " ", _jsx(TextLink, { variant: "bare", href: String(ROUTES.STORE.WHATSAPP), className: "underline text-[var(--appkit-color-primary)] hover:opacity-80", children: "Settings \u2192 WhatsApp" }), ". See the WhatsApp settings page for step-by-step setup instructions."] })] }));
|
|
32
|
+
}
|
|
33
|
+
// -- Section 6: Visibility states ---------------------------------------------
|
|
30
34
|
function VisibilityStatesSection() {
|
|
31
35
|
const STATES = [
|
|
32
36
|
{
|
|
@@ -49,5 +53,5 @@ function VisibilityStatesSection() {
|
|
|
49
53
|
return (_jsx(Stack, { gap: "3", children: STATES.map(({ state, note }) => (_jsxs(Row, { textSize: "sm", gap: "3", children: [_jsx(Text, { className: "shrink-0 w-32 text-[var(--appkit-color-text)]", weight: "semibold", children: state }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", children: note })] }, state))) }));
|
|
50
54
|
}
|
|
51
55
|
export function StoreSettingsGuideView(_props) {
|
|
52
|
-
return (_jsxs(Stack, { gap: "lg", padding: "b-2xl", children: [_jsxs(TextLink, { variant: "bare", href: String(ROUTES.STORE.GUIDE), size: "sm", layout: "inline-flex", align: "center", gap: "sm", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Seller Guide"] }), _jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "2xl", weight: "bold", children: "Settings Guide" }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Store profile, shipping, returns, vacation mode, and visibility." })] }), _jsx(GuideSection, { title: "1. Store profile", children: _jsx(StoreProfileSection, {}) }), _jsx(GuideSection, { title: "2. Shipping configuration", children: _jsx(ShippingSection, {}) }), _jsx(GuideSection, { title: "3. Return policy", children: _jsx(ReturnPolicySection, {}) }), _jsx(GuideSection, { title: "4. Vacation mode", children: _jsx(VacationModeSection, {}) }), _jsx(GuideSection, { title: "5. Visibility states", children: _jsx(VisibilityStatesSection, {}) })] }));
|
|
56
|
+
return (_jsxs(Stack, { gap: "lg", padding: "b-2xl", children: [_jsxs(TextLink, { variant: "bare", href: String(ROUTES.STORE.GUIDE), size: "sm", layout: "inline-flex", align: "center", gap: "sm", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Seller Guide"] }), _jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "2xl", weight: "bold", children: "Settings Guide" }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Store profile, shipping, returns, vacation mode, WhatsApp, and visibility." })] }), _jsx(GuideSection, { title: "1. Store profile", children: _jsx(StoreProfileSection, {}) }), _jsx(GuideSection, { title: "2. Shipping configuration", children: _jsx(ShippingSection, {}) }), _jsx(GuideSection, { title: "3. Return policy", children: _jsx(ReturnPolicySection, {}) }), _jsx(GuideSection, { title: "4. Vacation mode", children: _jsx(VacationModeSection, {}) }), _jsx(GuideSection, { title: "5. WhatsApp integration", children: _jsx(WhatsAppSection, {}) }), _jsx(GuideSection, { title: "6. Visibility states", children: _jsx(VisibilityStatesSection, {}) })] }));
|
|
53
57
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Data access layer for the `stores` Firestore collection.
|
|
5
5
|
* One store per seller; identified by storeSlug (used as document ID).
|
|
6
6
|
*/
|
|
7
|
-
import { BaseRepository, type SieveModel, type FirebaseSieveResult } from "../../../providers/db-firebase";
|
|
7
|
+
import { BaseRepository, type SieveModel, type FirebaseSieveResult, type DocumentSnapshot } from "../../../providers/db-firebase";
|
|
8
8
|
import { StoreDocument } from "../schemas";
|
|
9
9
|
import { type HistoryActor } from "../../../_internal/shared/history/index";
|
|
10
10
|
import type { JsonValue } from "../../../schemas/types";
|
|
@@ -17,6 +17,28 @@ export interface StoreWriteContext {
|
|
|
17
17
|
}
|
|
18
18
|
export declare class StoreRepository extends BaseRepository<StoreDocument> {
|
|
19
19
|
constructor();
|
|
20
|
+
protected mapDoc<D = StoreDocument>(snap: DocumentSnapshot): D;
|
|
21
|
+
/**
|
|
22
|
+
* LIST reads never carry the WhatsApp token — not even encrypted.
|
|
23
|
+
*
|
|
24
|
+
* `mapDoc` above decrypts it, and `listStores(model, activeOnly)` backs the
|
|
25
|
+
* PUBLIC `/stores` page. Once `sieveQuery` started routing list rows through
|
|
26
|
+
* `mapDoc` (it previously mapped documents itself and skipped every
|
|
27
|
+
* override), that decrypt would have put a plaintext Meta OAuth bearer token
|
|
28
|
+
* on a public listing — the exact shape of Root Cause #70.
|
|
29
|
+
*
|
|
30
|
+
* So the list path deliberately does NOT call `decryptSecrets`, and drops
|
|
31
|
+
* `accessToken` outright: a list has no legitimate use for it, and a value
|
|
32
|
+
* that is never in the payload cannot be leaked by a projection someone
|
|
33
|
+
* forgets to write.
|
|
34
|
+
*
|
|
35
|
+
* Detail reads are unchanged — `findBySlug`/`findById` still decrypt, so the
|
|
36
|
+
* WhatsApp settings and catalog-sync routes keep working exactly as before.
|
|
37
|
+
* Removing the decrypt from `mapDoc` itself is a separate, riskier change
|
|
38
|
+
* that CLAUDE.md explicitly defers; this closes the public-exposure half
|
|
39
|
+
* without touching the encryption round-trip at all.
|
|
40
|
+
*/
|
|
41
|
+
update(id: string, data: Partial<StoreDocument>): Promise<StoreDocument>;
|
|
20
42
|
/**
|
|
21
43
|
* Create a new store.
|
|
22
44
|
* The document ID is set to storeSlug for easy URL-based lookups.
|
|
@@ -16,6 +16,32 @@ export class StoreRepository extends BaseRepository {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
super(STORE_COLLECTION);
|
|
18
18
|
}
|
|
19
|
+
mapDoc(snap) {
|
|
20
|
+
return super.mapDoc(snap);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* LIST reads never carry the WhatsApp token — not even encrypted.
|
|
24
|
+
*
|
|
25
|
+
* `mapDoc` above decrypts it, and `listStores(model, activeOnly)` backs the
|
|
26
|
+
* PUBLIC `/stores` page. Once `sieveQuery` started routing list rows through
|
|
27
|
+
* `mapDoc` (it previously mapped documents itself and skipped every
|
|
28
|
+
* override), that decrypt would have put a plaintext Meta OAuth bearer token
|
|
29
|
+
* on a public listing — the exact shape of Root Cause #70.
|
|
30
|
+
*
|
|
31
|
+
* So the list path deliberately does NOT call `decryptSecrets`, and drops
|
|
32
|
+
* `accessToken` outright: a list has no legitimate use for it, and a value
|
|
33
|
+
* that is never in the payload cannot be leaked by a projection someone
|
|
34
|
+
* forgets to write.
|
|
35
|
+
*
|
|
36
|
+
* Detail reads are unchanged — `findBySlug`/`findById` still decrypt, so the
|
|
37
|
+
* WhatsApp settings and catalog-sync routes keep working exactly as before.
|
|
38
|
+
* Removing the decrypt from `mapDoc` itself is a separate, riskier change
|
|
39
|
+
* that CLAUDE.md explicitly defers; this closes the public-exposure half
|
|
40
|
+
* without touching the encryption round-trip at all.
|
|
41
|
+
*/
|
|
42
|
+
async update(id, data) {
|
|
43
|
+
return super.update(id, data);
|
|
44
|
+
}
|
|
19
45
|
/**
|
|
20
46
|
* Create a new store.
|
|
21
47
|
* The document ID is set to storeSlug for easy URL-based lookups.
|
|
@@ -206,7 +232,9 @@ export class StoreRepository extends BaseRepository {
|
|
|
206
232
|
options: { maxPageSize: win.maxPageSize(100) },
|
|
207
233
|
fields: sieveFields,
|
|
208
234
|
// Called DIRECTLY, not through `sieveQuery`, so the mapper has to be
|
|
209
|
-
// passed by hand
|
|
235
|
+
// passed by hand — and it is the list mapper, never `mapDoc`. With
|
|
236
|
+
// `activeOnly` this is the public /stores listing; `mapDoc` decrypts the
|
|
237
|
+
// WhatsApp bearer token and a list has no use for it.
|
|
210
238
|
mapDoc: (snap) => this.mapDocForList(snap),
|
|
211
239
|
});
|
|
212
240
|
const searched = win.finish(result);
|
|
@@ -107,6 +107,19 @@ export interface StoreDocument extends BaseDocument {
|
|
|
107
107
|
adminNotes?: string;
|
|
108
108
|
/** Admin "verified store" badge — distinct from `status`/`isPublic`. */
|
|
109
109
|
isVerified?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Does this store subscribe to WhatsApp order notifications?
|
|
112
|
+
*
|
|
113
|
+
* A monthly add-on priced by the admin (`commissions.whatsappNotifyMonthlyFee`),
|
|
114
|
+
* not a per-order charge: the platform owns one WhatsApp number, so the cost
|
|
115
|
+
* is standing and borne by the seller who benefits, rather than a line item
|
|
116
|
+
* a buyer opts into per order.
|
|
117
|
+
*
|
|
118
|
+
* 🛑 Absent means NOT subscribed, and the gate fails closed on it. Defaulting
|
|
119
|
+
* a missing flag to `true` would message customers on behalf of every seller
|
|
120
|
+
* who never signed up, and bill the platform for each template.
|
|
121
|
+
*/
|
|
122
|
+
whatsappNotifySubscribed?: boolean;
|
|
110
123
|
/**
|
|
111
124
|
* Paid at SHIPPING rather than at delivery.
|
|
112
125
|
*
|
|
@@ -370,12 +383,9 @@ export interface StoreDocument extends BaseDocument {
|
|
|
370
383
|
*/
|
|
371
384
|
export declare const STORE_TRACKED_FIELDS: readonly ["status", "isPublic", "isVerified", "isFeatured", "suspensionReason", "capabilities"];
|
|
372
385
|
/**
|
|
373
|
-
* PII on this document, for
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* one was removed 2026-10-20: a scrub list is cheap, and a future secret
|
|
377
|
-
* reaching this document under that name should be scrubbed by default
|
|
378
|
-
* rather than by someone remembering to add it.
|
|
386
|
+
* PII on this document, for `withHistory`'s scrub. `whatsappConfig.accessToken`
|
|
387
|
+
* is a live Meta credential; `mapDoc` decrypts it on EVERY read, so it is
|
|
388
|
+
* present in memory whenever a diff runs.
|
|
379
389
|
*/
|
|
380
390
|
export declare const STORE_HISTORY_PII_FIELDS: readonly ["accessToken", "payoutDetails", "upiVpa", "accountNumber"];
|
|
381
391
|
export type { StoreCapability } from "../../auth/permissions/constants";
|
|
@@ -33,12 +33,9 @@ export const STORE_TRACKED_FIELDS = [
|
|
|
33
33
|
"capabilities",
|
|
34
34
|
];
|
|
35
35
|
/**
|
|
36
|
-
* PII on this document, for
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* one was removed 2026-10-20: a scrub list is cheap, and a future secret
|
|
40
|
-
* reaching this document under that name should be scrubbed by default
|
|
41
|
-
* rather than by someone remembering to add it.
|
|
36
|
+
* PII on this document, for `withHistory`'s scrub. `whatsappConfig.accessToken`
|
|
37
|
+
* is a live Meta credential; `mapDoc` decrypts it on EVERY read, so it is
|
|
38
|
+
* present in memory whenever a diff runs.
|
|
42
39
|
*/
|
|
43
40
|
export const STORE_HISTORY_PII_FIELDS = [
|
|
44
41
|
"accessToken",
|
|
@@ -347,6 +347,60 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
347
347
|
defaultProviderId: string;
|
|
348
348
|
}>>;
|
|
349
349
|
emiEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
350
|
+
whatsappConfig: z.ZodOptional<z.ZodObject<{
|
|
351
|
+
phoneNumber: z.ZodOptional<z.ZodString>;
|
|
352
|
+
wabaId: z.ZodOptional<z.ZodString>;
|
|
353
|
+
catalogId: z.ZodOptional<z.ZodString>;
|
|
354
|
+
accessToken: z.ZodOptional<z.ZodString>;
|
|
355
|
+
catalogSyncEnabled: z.ZodBoolean;
|
|
356
|
+
lastCatalogSyncAt: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
357
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
358
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
359
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
360
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
361
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
362
|
+
}, z.ZodTypeAny, "passthrough">>]>>;
|
|
363
|
+
lastSyncCount: z.ZodOptional<z.ZodNumber>;
|
|
364
|
+
lastSyncStatus: z.ZodOptional<z.ZodEnum<["success", "partial", "failed"]>>;
|
|
365
|
+
connected: z.ZodBoolean;
|
|
366
|
+
connectedAt: z.ZodOptional<z.ZodUnion<[z.ZodDate, z.ZodString, z.ZodObject<{
|
|
367
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
368
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
369
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
370
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
371
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
372
|
+
}, z.ZodTypeAny, "passthrough">>]>>;
|
|
373
|
+
}, "strip", z.ZodTypeAny, {
|
|
374
|
+
catalogSyncEnabled: boolean;
|
|
375
|
+
connected: boolean;
|
|
376
|
+
phoneNumber?: string | undefined;
|
|
377
|
+
accessToken?: string | undefined;
|
|
378
|
+
wabaId?: string | undefined;
|
|
379
|
+
catalogId?: string | undefined;
|
|
380
|
+
lastCatalogSyncAt?: string | Date | z.objectOutputType<{
|
|
381
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
382
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
383
|
+
lastSyncCount?: number | undefined;
|
|
384
|
+
lastSyncStatus?: "success" | "failed" | "partial" | undefined;
|
|
385
|
+
connectedAt?: string | Date | z.objectOutputType<{
|
|
386
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
387
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
388
|
+
}, {
|
|
389
|
+
catalogSyncEnabled: boolean;
|
|
390
|
+
connected: boolean;
|
|
391
|
+
phoneNumber?: string | undefined;
|
|
392
|
+
accessToken?: string | undefined;
|
|
393
|
+
wabaId?: string | undefined;
|
|
394
|
+
catalogId?: string | undefined;
|
|
395
|
+
lastCatalogSyncAt?: string | Date | z.objectInputType<{
|
|
396
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
397
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
398
|
+
lastSyncCount?: number | undefined;
|
|
399
|
+
lastSyncStatus?: "success" | "failed" | "partial" | undefined;
|
|
400
|
+
connectedAt?: string | Date | z.objectInputType<{
|
|
401
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
402
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
403
|
+
}>>;
|
|
350
404
|
}, "strip", z.ZodTypeAny, {
|
|
351
405
|
status: "pending" | "active" | "rejected" | "suspended";
|
|
352
406
|
id: string;
|
|
@@ -416,6 +470,22 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
416
470
|
maxReviews?: number | undefined;
|
|
417
471
|
minRating?: number | undefined;
|
|
418
472
|
} | undefined;
|
|
473
|
+
whatsappConfig?: {
|
|
474
|
+
catalogSyncEnabled: boolean;
|
|
475
|
+
connected: boolean;
|
|
476
|
+
phoneNumber?: string | undefined;
|
|
477
|
+
accessToken?: string | undefined;
|
|
478
|
+
wabaId?: string | undefined;
|
|
479
|
+
catalogId?: string | undefined;
|
|
480
|
+
lastCatalogSyncAt?: string | Date | z.objectOutputType<{
|
|
481
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
482
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
483
|
+
lastSyncCount?: number | undefined;
|
|
484
|
+
lastSyncStatus?: "success" | "failed" | "partial" | undefined;
|
|
485
|
+
connectedAt?: string | Date | z.objectOutputType<{
|
|
486
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
487
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
488
|
+
} | undefined;
|
|
419
489
|
}, {
|
|
420
490
|
status: "pending" | "active" | "rejected" | "suspended";
|
|
421
491
|
id: string;
|
|
@@ -485,6 +555,22 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
485
555
|
maxReviews?: number | undefined;
|
|
486
556
|
minRating?: number | undefined;
|
|
487
557
|
} | undefined;
|
|
558
|
+
whatsappConfig?: {
|
|
559
|
+
catalogSyncEnabled: boolean;
|
|
560
|
+
connected: boolean;
|
|
561
|
+
phoneNumber?: string | undefined;
|
|
562
|
+
accessToken?: string | undefined;
|
|
563
|
+
wabaId?: string | undefined;
|
|
564
|
+
catalogId?: string | undefined;
|
|
565
|
+
lastCatalogSyncAt?: string | Date | z.objectInputType<{
|
|
566
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
567
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
568
|
+
lastSyncCount?: number | undefined;
|
|
569
|
+
lastSyncStatus?: "success" | "failed" | "partial" | undefined;
|
|
570
|
+
connectedAt?: string | Date | z.objectInputType<{
|
|
571
|
+
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
572
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
573
|
+
} | undefined;
|
|
488
574
|
}>;
|
|
489
575
|
export type StoreFromSchema = z.infer<typeof storeFirestoreSchema>;
|
|
490
576
|
/** Zod schema for store status — use instead of inline `z.enum(["pending",...])`. */
|
|
@@ -92,6 +92,20 @@ export const storeFirestoreSchema = z.object({
|
|
|
92
92
|
.optional(),
|
|
93
93
|
shippingConfig: storeShippingConfigSchema.optional(),
|
|
94
94
|
emiEnabled: z.boolean().optional(),
|
|
95
|
+
whatsappConfig: z
|
|
96
|
+
.object({
|
|
97
|
+
phoneNumber: z.string().optional(),
|
|
98
|
+
wabaId: z.string().optional(),
|
|
99
|
+
catalogId: z.string().optional(),
|
|
100
|
+
accessToken: z.string().optional(),
|
|
101
|
+
catalogSyncEnabled: z.boolean(),
|
|
102
|
+
lastCatalogSyncAt: firestoreDateSchema.optional(),
|
|
103
|
+
lastSyncCount: z.number().int().optional(),
|
|
104
|
+
lastSyncStatus: z.enum(["success", "partial", "failed"]).optional(),
|
|
105
|
+
connected: z.boolean(),
|
|
106
|
+
connectedAt: firestoreDateSchema.optional(),
|
|
107
|
+
})
|
|
108
|
+
.optional(),
|
|
95
109
|
...auditTimestampsShape,
|
|
96
110
|
});
|
|
97
111
|
/** Zod schema for store status — use instead of inline `z.enum(["pending",...])`. */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-whatsapp-bot — POST /api/whatsapp/send-status
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp message delivery status update.
|
|
5
|
+
*
|
|
6
|
+
* Consumer stub:
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { whatsappSendStatusPOST as POST } from "@mohasinac/feat-whatsapp-bot";
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { NextResponse } from "next/server.js";
|
|
12
|
+
export declare function POST(request: Request): Promise<NextResponse>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-whatsapp-bot — POST /api/whatsapp/send-status
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp message delivery status update.
|
|
5
|
+
*
|
|
6
|
+
* Consumer stub:
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { whatsappSendStatusPOST as POST } from "@mohasinac/feat-whatsapp-bot";
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { NextResponse } from "next/server.js";
|
|
12
|
+
import { getProviders } from "../../../../contracts/index.js";
|
|
13
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
14
|
+
export async function POST(request) {
|
|
15
|
+
try {
|
|
16
|
+
const body = await request.json();
|
|
17
|
+
const { db } = getProviders();
|
|
18
|
+
if (!db)
|
|
19
|
+
return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
|
|
20
|
+
// Update message status in database
|
|
21
|
+
const repo = db.getRepository("whatsapp_message_status");
|
|
22
|
+
const updated = await repo.create({
|
|
23
|
+
...body,
|
|
24
|
+
updatedAt: new Date(),
|
|
25
|
+
});
|
|
26
|
+
return NextResponse.json({
|
|
27
|
+
success: true,
|
|
28
|
+
data: updated,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
void normalizeError(error);
|
|
33
|
+
return NextResponse.json({
|
|
34
|
+
success: false,
|
|
35
|
+
error: error instanceof Error ? error.message : "Internal server error",
|
|
36
|
+
}, { status: 500 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-whatsapp-bot — POST /api/whatsapp/webhook
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp webhook receiver for incoming messages.
|
|
5
|
+
*
|
|
6
|
+
* Consumer stub:
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { whatsappWebhookPOST as POST } from "@mohasinac/feat-whatsapp-bot";
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { NextResponse } from "next/server.js";
|
|
12
|
+
export declare function POST(request: Request): Promise<NextResponse>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-whatsapp-bot — POST /api/whatsapp/webhook
|
|
3
|
+
*
|
|
4
|
+
* WhatsApp webhook receiver for incoming messages.
|
|
5
|
+
*
|
|
6
|
+
* Consumer stub:
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { whatsappWebhookPOST as POST } from "@mohasinac/feat-whatsapp-bot";
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { NextResponse } from "next/server.js";
|
|
12
|
+
import { getProviders } from "../../../../contracts/index.js";
|
|
13
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
14
|
+
export async function POST(request) {
|
|
15
|
+
try {
|
|
16
|
+
const body = await request.json();
|
|
17
|
+
const { db, eventBus } = getProviders();
|
|
18
|
+
if (!db)
|
|
19
|
+
return NextResponse.json({ success: false, error: "DB not configured" }, { status: 503 });
|
|
20
|
+
// Store webhook payload for processing
|
|
21
|
+
const repo = db.getRepository("whatsapp_messages");
|
|
22
|
+
const created = await repo.create({
|
|
23
|
+
...body,
|
|
24
|
+
receivedAt: new Date(),
|
|
25
|
+
processed: false,
|
|
26
|
+
});
|
|
27
|
+
// Emit event for async processing if eventBus available
|
|
28
|
+
if (eventBus) {
|
|
29
|
+
await eventBus.emit("whatsapp.message.received", created);
|
|
30
|
+
}
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
success: true,
|
|
33
|
+
data: created,
|
|
34
|
+
}, { status: 201 });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
void normalizeError(error);
|
|
38
|
+
return NextResponse.json({
|
|
39
|
+
success: false,
|
|
40
|
+
error: error instanceof Error ? error.message : "Internal server error",
|
|
41
|
+
}, { status: 500 });
|
|
42
|
+
}
|
|
43
|
+
}
|