@mohasinac/appkit 2.7.49 → 2.7.50

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 (110) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
  3. package/dist/_internal/client/features/layout/index.d.ts +2 -0
  4. package/dist/_internal/client/features/layout/index.js +1 -0
  5. package/dist/_internal/server/features/checkout/actions.js +25 -2
  6. package/dist/_internal/server/features/promotions/actions.js +3 -1
  7. package/dist/_internal/shared/actions/action-registry.js +149 -0
  8. package/dist/client.d.ts +16 -0
  9. package/dist/client.js +11 -0
  10. package/dist/features/account/components/UserSidebar.js +2 -1
  11. package/dist/features/admin/components/AdminBlogEditorView.js +6 -2
  12. package/dist/features/admin/components/AdminSidebar.js +2 -1
  13. package/dist/features/auth/repository/session.repository.js +16 -1
  14. package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
  15. package/dist/features/cart/hooks/useAddToCart.js +2 -0
  16. package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
  17. package/dist/features/cart/utils/guest-cart.d.ts +7 -0
  18. package/dist/features/cart/utils/pending-ops.d.ts +6 -0
  19. package/dist/features/categories/components/CategoryBundlesListing.js +9 -1
  20. package/dist/features/categories/components/CategoryProductsListing.js +6 -10
  21. package/dist/features/categories/schemas/firestore.d.ts +9 -0
  22. package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
  23. package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
  24. package/dist/features/events/components/SpinWheelView.js +3 -3
  25. package/dist/features/layout/BottomNavLayout.d.ts +5 -0
  26. package/dist/features/layout/BottomNavLayout.js +21 -0
  27. package/dist/features/layout/TitleBar.js +7 -1
  28. package/dist/features/layout/TitleBarLayout.d.ts +8 -2
  29. package/dist/features/layout/TitleBarLayout.js +11 -7
  30. package/dist/features/orders/schemas/index.d.ts +2 -2
  31. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +9 -5
  32. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  33. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
  34. package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
  35. package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
  36. package/dist/features/products/components/ProductDetailActions.js +3 -1
  37. package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
  38. package/dist/features/products/components/ProductDetailPageView.js +4 -0
  39. package/dist/features/products/components/ProductGrid.js +3 -3
  40. package/dist/features/products/components/ProductsIndexListing.js +15 -8
  41. package/dist/features/promotions/components/CouponCard.d.ts +11 -1
  42. package/dist/features/promotions/components/CouponCard.js +36 -2
  43. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
  44. package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
  45. package/dist/features/promotions/schemas/firestore.d.ts +34 -0
  46. package/dist/features/promotions/schemas/firestore.js +13 -0
  47. package/dist/features/seller/components/SellerAddressesView.js +3 -2
  48. package/dist/features/seller/components/SellerBidsView.js +33 -3
  49. package/dist/features/seller/components/SellerOrdersView.js +35 -0
  50. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  51. package/dist/features/seller/components/SellerProductShell.js +26 -4
  52. package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
  53. package/dist/features/seller/components/SellerProductsCards.js +19 -0
  54. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
  55. package/dist/features/seller/components/SellerProductsFilterDrawer.js +20 -0
  56. package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
  57. package/dist/features/seller/components/SellerProductsView.js +68 -19
  58. package/dist/features/seller/components/SellerReviewsView.js +67 -4
  59. package/dist/features/seller/components/SellerSidebar.js +2 -1
  60. package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
  61. package/dist/features/seller/components/seller-products-styles.js +14 -0
  62. package/dist/features/shell/FormShell.d.ts +7 -1
  63. package/dist/features/shell/FormShell.js +5 -2
  64. package/dist/features/store-extensions/index.d.ts +4 -0
  65. package/dist/features/store-extensions/index.js +4 -0
  66. package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
  67. package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
  68. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
  69. package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
  70. package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
  71. package/dist/features/store-extensions/schemas/firestore.js +158 -0
  72. package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
  73. package/dist/features/store-extensions/schemas/rbac.js +43 -0
  74. package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
  75. package/dist/features/stores/components/StoreProductsListing.js +6 -10
  76. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  77. package/dist/index.d.ts +6 -0
  78. package/dist/index.js +8 -0
  79. package/dist/next/routing/route-map.d.ts +79 -0
  80. package/dist/next/routing/route-map.js +34 -0
  81. package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
  82. package/dist/react/hooks/useFormStatePreservation.js +62 -0
  83. package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
  84. package/dist/react/hooks/useInlineRowEdit.js +68 -0
  85. package/dist/react/index.d.ts +4 -0
  86. package/dist/react/index.js +4 -0
  87. package/dist/repositories/index.d.ts +4 -0
  88. package/dist/repositories/index.js +5 -0
  89. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  90. package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
  91. package/dist/seed/claimed-coupons-seed-data.js +79 -0
  92. package/dist/seed/index.d.ts +2 -0
  93. package/dist/seed/index.js +3 -0
  94. package/dist/seed/manifest.js +17 -0
  95. package/dist/seed/store-extensions-seed-data.d.ts +19 -0
  96. package/dist/seed/store-extensions-seed-data.js +421 -0
  97. package/dist/tailwind-utilities.css +1 -1
  98. package/dist/ui/components/Button.style.css +20 -12
  99. package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
  100. package/dist/ui/components/ClaimCouponButton.js +89 -0
  101. package/dist/ui/components/QuickCreateModal.d.ts +15 -0
  102. package/dist/ui/components/QuickCreateModal.js +48 -0
  103. package/dist/ui/components/QuickCreateModal.style.css +84 -0
  104. package/dist/ui/components/StickyBottomBar.d.ts +22 -0
  105. package/dist/ui/components/StickyBottomBar.js +20 -0
  106. package/dist/ui/components/VacationBanner.d.ts +11 -0
  107. package/dist/ui/components/VacationBanner.js +16 -0
  108. package/dist/ui/index.d.ts +8 -0
  109. package/dist/ui/index.js +4 -0
  110. package/package.json +1 -1
@@ -0,0 +1,127 @@
1
+ /**
2
+ * S-STORE Extension Repositories
3
+ *
4
+ * 11 lightweight BaseRepository subclasses. None of these collections store
5
+ * PII directly so no `createWithId` override is required (Pattern #9). Uses
6
+ * the BaseRepository public API only: `findById`, `findBy`, `findAll`,
7
+ * `create`, `update`, `delete`.
8
+ */
9
+ import { BaseRepository } from "../../../providers/db-firebase";
10
+ 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";
11
+ export class PayoutMethodsRepository extends BaseRepository {
12
+ constructor() {
13
+ super(PAYOUT_METHODS_COLLECTION);
14
+ }
15
+ async listByStore(storeId) {
16
+ const items = await this.findBy("storeId", storeId);
17
+ return { items };
18
+ }
19
+ }
20
+ export class ShippingConfigsRepository extends BaseRepository {
21
+ constructor() {
22
+ super(SHIPPING_CONFIGS_COLLECTION);
23
+ }
24
+ async listByStore(storeId) {
25
+ const items = await this.findBy("storeId", storeId);
26
+ return { items };
27
+ }
28
+ }
29
+ export class AnalyticsCardsRepository extends BaseRepository {
30
+ constructor() {
31
+ super(ANALYTICS_CARDS_COLLECTION);
32
+ }
33
+ async listForOwner(scope, ownerId) {
34
+ const all = await this.findBy("ownerId", ownerId);
35
+ return { items: all.filter((d) => d.scope === scope && d.isVisible !== false) };
36
+ }
37
+ }
38
+ export class AnalyticsAlertsRepository extends BaseRepository {
39
+ constructor() {
40
+ super(ANALYTICS_ALERTS_COLLECTION);
41
+ }
42
+ async listForOwner(scope, ownerId) {
43
+ const all = await this.findBy("ownerId", ownerId);
44
+ return { items: all.filter((d) => d.scope === scope) };
45
+ }
46
+ }
47
+ export class StoreCategoriesRepository extends BaseRepository {
48
+ constructor() {
49
+ super(STORE_CATEGORIES_COLLECTION);
50
+ }
51
+ async listByStore(storeId) {
52
+ const items = await this.findBy("storeId", storeId);
53
+ return { items };
54
+ }
55
+ }
56
+ export class ListingTemplatesRepository extends BaseRepository {
57
+ constructor() {
58
+ super(LISTING_TEMPLATES_COLLECTION);
59
+ }
60
+ async listByStore(storeId) {
61
+ const all = await this.findBy("storeId", storeId);
62
+ return { items: all.filter((d) => d.isActive !== false) };
63
+ }
64
+ }
65
+ export class ModerationQueueRepository extends BaseRepository {
66
+ constructor() {
67
+ super(MODERATION_QUEUE_COLLECTION);
68
+ }
69
+ async listPending() {
70
+ const items = await this.findBy("status", "pending");
71
+ return { items };
72
+ }
73
+ }
74
+ export class ReportsRepository extends BaseRepository {
75
+ constructor() {
76
+ super(REPORTS_COLLECTION);
77
+ }
78
+ async listForEntity(entityType, entityId) {
79
+ const all = await this.findBy("entityId", entityId);
80
+ return { items: all.filter((d) => d.entityType === entityType) };
81
+ }
82
+ async listPending() {
83
+ const items = await this.findBy("status", "pending");
84
+ return { items };
85
+ }
86
+ }
87
+ export class ItemRequestsRepository extends BaseRepository {
88
+ constructor() {
89
+ super(ITEM_REQUESTS_COLLECTION);
90
+ }
91
+ async listOpen(opts) {
92
+ const all = await this.findBy("status", "open");
93
+ const limit = opts?.limit ?? 50;
94
+ return { items: all.slice(0, limit) };
95
+ }
96
+ async listByOwner(opUserId) {
97
+ const items = await this.findBy("opUserId", opUserId);
98
+ return { items };
99
+ }
100
+ }
101
+ export class StoreWhatsAppConfigRepository extends BaseRepository {
102
+ constructor() {
103
+ super(STORE_WHATSAPP_CONFIG_COLLECTION);
104
+ }
105
+ async getByStore(storeId) {
106
+ return this.findOneBy("storeId", storeId);
107
+ }
108
+ }
109
+ export class StoreGoogleConfigRepository extends BaseRepository {
110
+ constructor() {
111
+ super(STORE_GOOGLE_CONFIG_COLLECTION);
112
+ }
113
+ async getByStore(storeId) {
114
+ return this.findOneBy("storeId", storeId);
115
+ }
116
+ }
117
+ export const payoutMethodsRepository = new PayoutMethodsRepository();
118
+ export const shippingConfigsRepository = new ShippingConfigsRepository();
119
+ export const analyticsCardsRepository = new AnalyticsCardsRepository();
120
+ export const analyticsAlertsRepository = new AnalyticsAlertsRepository();
121
+ export const storeCategoriesRepository = new StoreCategoriesRepository();
122
+ export const listingTemplatesRepository = new ListingTemplatesRepository();
123
+ export const moderationQueueRepository = new ModerationQueueRepository();
124
+ export const reportsRepository = new ReportsRepository();
125
+ export const itemRequestsRepository = new ItemRequestsRepository();
126
+ export const storeWhatsAppConfigRepository = new StoreWhatsAppConfigRepository();
127
+ export const storeGoogleConfigRepository = new StoreGoogleConfigRepository();
@@ -0,0 +1,244 @@
1
+ /**
2
+ * S-STORE Extensions — Firestore Document Types
3
+ *
4
+ * Foundational schemas for the 11 new collections introduced in Tier S-STORE.
5
+ * Each collection includes COLLECTION constant, document interface,
6
+ * INDEXED_FIELDS tuple, and DEFAULT_DATA partial.
7
+ *
8
+ * Collections covered:
9
+ * 1. payoutMethods — seller payout destinations (UPI/bank/card/other)
10
+ * 2. shippingConfigs — store shipping rules (free/flat/weight/express)
11
+ * 3. analyticsCards — seller-defined dashboard cards
12
+ * 4. analyticsAlerts — seller-defined alert thresholds
13
+ * 5. storeCategories — storefront-level catalogue groupings
14
+ * 6. listingTemplates — seller-defined listing templates
15
+ * 7. moderationQueue — video / media moderation queue items
16
+ * 8. reports — buyer-submitted reports against listings/stores
17
+ * 9. itemRequests — community item request bulletin board
18
+ * 10. storeWhatsAppConfig — per-store WhatsApp business catalogue + auto-replies
19
+ * 11. storeGoogleConfig — per-store Google Business reviews integration
20
+ */
21
+ export type PayoutMethodType = "upi" | "bank" | "card" | "other";
22
+ export interface PayoutMethodDocument {
23
+ id: string;
24
+ sellerId: string;
25
+ storeId: string;
26
+ type: PayoutMethodType;
27
+ label: string;
28
+ isDefault: boolean;
29
+ isActive: boolean;
30
+ upiVpa?: string;
31
+ accountNumber?: string;
32
+ ifscCode?: string;
33
+ accountHolderName?: string;
34
+ bankName?: string;
35
+ details?: Record<string, string>;
36
+ createdAt: Date;
37
+ updatedAt: Date;
38
+ }
39
+ export declare const PAYOUT_METHODS_COLLECTION: "payoutMethods";
40
+ export declare const PAYOUT_METHOD_INDEXED_FIELDS: readonly ["sellerId", "storeId", "type", "isDefault", "isActive", "createdAt"];
41
+ export declare const DEFAULT_PAYOUT_METHOD_DATA: Partial<PayoutMethodDocument>;
42
+ export type ShippingMethod = "free" | "flat" | "weight" | "express" | "pickup" | "custom";
43
+ export interface ShippingConfigDocument {
44
+ id: string;
45
+ storeId: string;
46
+ label: string;
47
+ method: ShippingMethod;
48
+ isDefault: boolean;
49
+ isActive: boolean;
50
+ flatRateInPaise?: number;
51
+ pricePerKgInPaise?: number;
52
+ freeAbovePaise?: number;
53
+ expressSurchargeInPaise?: number;
54
+ estimatedDays?: number;
55
+ zones?: string[];
56
+ createdAt: Date;
57
+ updatedAt: Date;
58
+ }
59
+ export declare const SHIPPING_CONFIGS_COLLECTION: "shippingConfigs";
60
+ export declare const SHIPPING_CONFIG_INDEXED_FIELDS: readonly ["storeId", "method", "isDefault", "isActive", "createdAt"];
61
+ export declare const DEFAULT_SHIPPING_CONFIG_DATA: Partial<ShippingConfigDocument>;
62
+ export type AnalyticsScope = "seller" | "admin";
63
+ export type AnalyticsCardType = "metric" | "line" | "bar" | "pie" | "table" | "custom";
64
+ export interface AnalyticsCardDocument {
65
+ id: string;
66
+ scope: AnalyticsScope;
67
+ ownerId: string;
68
+ title: string;
69
+ type: AnalyticsCardType;
70
+ metric: string;
71
+ filters: Record<string, unknown>;
72
+ position: number;
73
+ isBuiltIn: boolean;
74
+ isVisible: boolean;
75
+ createdAt: Date;
76
+ updatedAt: Date;
77
+ }
78
+ export declare const ANALYTICS_CARDS_COLLECTION: "analyticsCards";
79
+ export declare const ANALYTICS_CARD_INDEXED_FIELDS: readonly ["scope", "ownerId", "isVisible", "position", "createdAt"];
80
+ export declare const DEFAULT_ANALYTICS_CARD_DATA: Partial<AnalyticsCardDocument>;
81
+ export type AlertOperator = ">" | "<" | ">=" | "<=" | "==" | "!=";
82
+ export interface AnalyticsAlertDocument {
83
+ id: string;
84
+ scope: AnalyticsScope;
85
+ ownerId: string;
86
+ label: string;
87
+ metric: string;
88
+ operator: AlertOperator;
89
+ threshold: number;
90
+ windowHours: number;
91
+ isActive: boolean;
92
+ notifyChannels: Array<"in-app" | "email" | "whatsapp">;
93
+ lastTriggeredAt?: Date;
94
+ createdAt: Date;
95
+ updatedAt: Date;
96
+ }
97
+ export declare const ANALYTICS_ALERTS_COLLECTION: "analyticsAlerts";
98
+ export declare const ANALYTICS_ALERT_INDEXED_FIELDS: readonly ["scope", "ownerId", "isActive", "metric", "createdAt"];
99
+ export declare const DEFAULT_ANALYTICS_ALERT_DATA: Partial<AnalyticsAlertDocument>;
100
+ export interface StoreCategoryDocument {
101
+ id: string;
102
+ storeId: string;
103
+ label: string;
104
+ slug: string;
105
+ displayOrder: number;
106
+ productIds: string[];
107
+ isActive: boolean;
108
+ description?: string;
109
+ coverImageUrl?: string;
110
+ createdAt: Date;
111
+ updatedAt: Date;
112
+ }
113
+ export declare const STORE_CATEGORIES_COLLECTION: "storeCategories";
114
+ export declare const STORE_CATEGORY_INDEXED_FIELDS: readonly ["storeId", "slug", "isActive", "displayOrder", "createdAt"];
115
+ export declare const DEFAULT_STORE_CATEGORY_DATA: Partial<StoreCategoryDocument>;
116
+ export type ListingTemplateType = "standard" | "auction" | "pre-order" | "prize-draw" | "bundle" | "classified" | "digital-code" | "live";
117
+ export interface ListingTemplateDocument {
118
+ id: string;
119
+ storeId: string;
120
+ ownerId: string;
121
+ name: string;
122
+ description?: string;
123
+ listingType: ListingTemplateType;
124
+ /** JSON-shaped default fields applied when the template is selected. */
125
+ defaults: Record<string, unknown>;
126
+ isShared: boolean;
127
+ isActive: boolean;
128
+ usageCount: number;
129
+ createdAt: Date;
130
+ updatedAt: Date;
131
+ }
132
+ export declare const LISTING_TEMPLATES_COLLECTION: "listingTemplates";
133
+ export declare const LISTING_TEMPLATE_INDEXED_FIELDS: readonly ["storeId", "ownerId", "listingType", "isShared", "isActive", "createdAt"];
134
+ export declare const DEFAULT_LISTING_TEMPLATE_DATA: Partial<ListingTemplateDocument>;
135
+ export type ModerationStatus = "pending" | "approved" | "rejected" | "auto-approved";
136
+ export type ModerationMediaType = "video" | "image" | "rich-text";
137
+ export interface ModerationQueueDocument {
138
+ id: string;
139
+ mediaType: ModerationMediaType;
140
+ mediaUrl?: string;
141
+ thumbnailUrl?: string;
142
+ entityType: "product" | "review" | "event" | "blog" | "storefront";
143
+ entityId: string;
144
+ ownerId: string;
145
+ storeId?: string;
146
+ status: ModerationStatus;
147
+ reason?: string;
148
+ reviewerId?: string;
149
+ reviewedAt?: Date;
150
+ submittedAt: Date;
151
+ createdAt: Date;
152
+ updatedAt: Date;
153
+ }
154
+ export declare const MODERATION_QUEUE_COLLECTION: "moderationQueue";
155
+ export declare const MODERATION_QUEUE_INDEXED_FIELDS: readonly ["status", "mediaType", "entityType", "entityId", "storeId", "ownerId", "submittedAt", "createdAt"];
156
+ export declare const DEFAULT_MODERATION_QUEUE_DATA: Partial<ModerationQueueDocument>;
157
+ export type ReportEntityType = "product" | "store" | "review" | "event" | "user" | "blog" | "comment";
158
+ export type ReportStatus = "pending" | "under-review" | "actioned" | "dismissed";
159
+ export type ReportReason = "scam" | "counterfeit" | "prohibited" | "inappropriate" | "harassment" | "spam" | "ip-violation" | "other";
160
+ export interface ReportDocument {
161
+ id: string;
162
+ entityType: ReportEntityType;
163
+ entityId: string;
164
+ reporterId: string;
165
+ reporterEmail?: string;
166
+ reason: ReportReason;
167
+ detail: string;
168
+ evidenceUrls: string[];
169
+ status: ReportStatus;
170
+ assignedTo?: string;
171
+ resolution?: string;
172
+ resolvedAt?: Date;
173
+ createdAt: Date;
174
+ updatedAt: Date;
175
+ }
176
+ export declare const REPORTS_COLLECTION: "reports";
177
+ export declare const REPORT_INDEXED_FIELDS: readonly ["entityType", "entityId", "reporterId", "reason", "status", "createdAt"];
178
+ export declare const DEFAULT_REPORT_DATA: Partial<ReportDocument>;
179
+ export type ItemRequestStatus = "pending-approval" | "open" | "fulfilled" | "closed" | "rejected";
180
+ export interface ItemRequestReply {
181
+ id: string;
182
+ authorId: string;
183
+ authorName?: string;
184
+ body: string;
185
+ isOpInitiatedThread: boolean;
186
+ createdAt: Date;
187
+ }
188
+ export interface ItemRequestDocument {
189
+ id: string;
190
+ opUserId: string;
191
+ opDisplayName: string;
192
+ title: string;
193
+ description: string;
194
+ category?: string;
195
+ brand?: string;
196
+ maxBudgetInPaise?: number;
197
+ imageUrls: string[];
198
+ status: ItemRequestStatus;
199
+ replyCount: number;
200
+ /** Replies kept inline up to a cap; spillover lives in a sub-collection. */
201
+ replies: ItemRequestReply[];
202
+ approvedAt?: Date;
203
+ approvedBy?: string;
204
+ closedAt?: Date;
205
+ createdAt: Date;
206
+ updatedAt: Date;
207
+ }
208
+ export declare const ITEM_REQUESTS_COLLECTION: "itemRequests";
209
+ export declare const ITEM_REQUEST_INDEXED_FIELDS: readonly ["opUserId", "status", "category", "createdAt"];
210
+ export declare const DEFAULT_ITEM_REQUEST_DATA: Partial<ItemRequestDocument>;
211
+ export interface StoreWhatsAppConfigDocument {
212
+ id: string;
213
+ storeId: string;
214
+ isConnected: boolean;
215
+ isPaid: boolean;
216
+ phoneNumber?: string;
217
+ businessProfileName?: string;
218
+ catalogUrl?: string;
219
+ autoReply?: string;
220
+ welcomeMessage?: string;
221
+ /** Status of pending WhatsApp Business onboarding (Meta Cloud API). */
222
+ onboardingStatus?: "pending" | "approved" | "rejected";
223
+ createdAt: Date;
224
+ updatedAt: Date;
225
+ }
226
+ export declare const STORE_WHATSAPP_CONFIG_COLLECTION: "storeWhatsAppConfig";
227
+ export declare const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected", "isPaid"];
228
+ export declare const DEFAULT_STORE_WHATSAPP_CONFIG_DATA: Partial<StoreWhatsAppConfigDocument>;
229
+ export interface StoreGoogleConfigDocument {
230
+ id: string;
231
+ storeId: string;
232
+ isConnected: boolean;
233
+ placeId?: string;
234
+ businessName?: string;
235
+ averageRating?: number;
236
+ totalReviews?: number;
237
+ lastSyncedAt?: Date;
238
+ oauthRefreshToken?: string;
239
+ createdAt: Date;
240
+ updatedAt: Date;
241
+ }
242
+ export declare const STORE_GOOGLE_CONFIG_COLLECTION: "storeGoogleConfig";
243
+ export declare const STORE_GOOGLE_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected"];
244
+ export declare const DEFAULT_STORE_GOOGLE_CONFIG_DATA: Partial<StoreGoogleConfigDocument>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * S-STORE Extensions — Firestore Document Types
3
+ *
4
+ * Foundational schemas for the 11 new collections introduced in Tier S-STORE.
5
+ * Each collection includes COLLECTION constant, document interface,
6
+ * INDEXED_FIELDS tuple, and DEFAULT_DATA partial.
7
+ *
8
+ * Collections covered:
9
+ * 1. payoutMethods — seller payout destinations (UPI/bank/card/other)
10
+ * 2. shippingConfigs — store shipping rules (free/flat/weight/express)
11
+ * 3. analyticsCards — seller-defined dashboard cards
12
+ * 4. analyticsAlerts — seller-defined alert thresholds
13
+ * 5. storeCategories — storefront-level catalogue groupings
14
+ * 6. listingTemplates — seller-defined listing templates
15
+ * 7. moderationQueue — video / media moderation queue items
16
+ * 8. reports — buyer-submitted reports against listings/stores
17
+ * 9. itemRequests — community item request bulletin board
18
+ * 10. storeWhatsAppConfig — per-store WhatsApp business catalogue + auto-replies
19
+ * 11. storeGoogleConfig — per-store Google Business reviews integration
20
+ */
21
+ export const PAYOUT_METHODS_COLLECTION = "payoutMethods";
22
+ export const PAYOUT_METHOD_INDEXED_FIELDS = [
23
+ "sellerId",
24
+ "storeId",
25
+ "type",
26
+ "isDefault",
27
+ "isActive",
28
+ "createdAt",
29
+ ];
30
+ export const DEFAULT_PAYOUT_METHOD_DATA = {
31
+ isDefault: false,
32
+ isActive: true,
33
+ };
34
+ export const SHIPPING_CONFIGS_COLLECTION = "shippingConfigs";
35
+ export const SHIPPING_CONFIG_INDEXED_FIELDS = [
36
+ "storeId",
37
+ "method",
38
+ "isDefault",
39
+ "isActive",
40
+ "createdAt",
41
+ ];
42
+ export const DEFAULT_SHIPPING_CONFIG_DATA = {
43
+ isDefault: false,
44
+ isActive: true,
45
+ };
46
+ export const ANALYTICS_CARDS_COLLECTION = "analyticsCards";
47
+ export const ANALYTICS_CARD_INDEXED_FIELDS = [
48
+ "scope",
49
+ "ownerId",
50
+ "isVisible",
51
+ "position",
52
+ "createdAt",
53
+ ];
54
+ export const DEFAULT_ANALYTICS_CARD_DATA = {
55
+ isBuiltIn: false,
56
+ isVisible: true,
57
+ position: 0,
58
+ filters: {},
59
+ };
60
+ export const ANALYTICS_ALERTS_COLLECTION = "analyticsAlerts";
61
+ export const ANALYTICS_ALERT_INDEXED_FIELDS = [
62
+ "scope",
63
+ "ownerId",
64
+ "isActive",
65
+ "metric",
66
+ "createdAt",
67
+ ];
68
+ export const DEFAULT_ANALYTICS_ALERT_DATA = {
69
+ isActive: true,
70
+ windowHours: 24,
71
+ notifyChannels: ["in-app"],
72
+ };
73
+ export const STORE_CATEGORIES_COLLECTION = "storeCategories";
74
+ export const STORE_CATEGORY_INDEXED_FIELDS = [
75
+ "storeId",
76
+ "slug",
77
+ "isActive",
78
+ "displayOrder",
79
+ "createdAt",
80
+ ];
81
+ export const DEFAULT_STORE_CATEGORY_DATA = {
82
+ isActive: true,
83
+ displayOrder: 0,
84
+ productIds: [],
85
+ };
86
+ export const LISTING_TEMPLATES_COLLECTION = "listingTemplates";
87
+ export const LISTING_TEMPLATE_INDEXED_FIELDS = [
88
+ "storeId",
89
+ "ownerId",
90
+ "listingType",
91
+ "isShared",
92
+ "isActive",
93
+ "createdAt",
94
+ ];
95
+ export const DEFAULT_LISTING_TEMPLATE_DATA = {
96
+ isShared: false,
97
+ isActive: true,
98
+ usageCount: 0,
99
+ defaults: {},
100
+ };
101
+ export const MODERATION_QUEUE_COLLECTION = "moderationQueue";
102
+ export const MODERATION_QUEUE_INDEXED_FIELDS = [
103
+ "status",
104
+ "mediaType",
105
+ "entityType",
106
+ "entityId",
107
+ "storeId",
108
+ "ownerId",
109
+ "submittedAt",
110
+ "createdAt",
111
+ ];
112
+ export const DEFAULT_MODERATION_QUEUE_DATA = {
113
+ status: "pending",
114
+ };
115
+ export const REPORTS_COLLECTION = "reports";
116
+ export const REPORT_INDEXED_FIELDS = [
117
+ "entityType",
118
+ "entityId",
119
+ "reporterId",
120
+ "reason",
121
+ "status",
122
+ "createdAt",
123
+ ];
124
+ export const DEFAULT_REPORT_DATA = {
125
+ status: "pending",
126
+ evidenceUrls: [],
127
+ };
128
+ export const ITEM_REQUESTS_COLLECTION = "itemRequests";
129
+ export const ITEM_REQUEST_INDEXED_FIELDS = [
130
+ "opUserId",
131
+ "status",
132
+ "category",
133
+ "createdAt",
134
+ ];
135
+ export const DEFAULT_ITEM_REQUEST_DATA = {
136
+ status: "pending-approval",
137
+ imageUrls: [],
138
+ replies: [],
139
+ replyCount: 0,
140
+ };
141
+ export const STORE_WHATSAPP_CONFIG_COLLECTION = "storeWhatsAppConfig";
142
+ export const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS = [
143
+ "storeId",
144
+ "isConnected",
145
+ "isPaid",
146
+ ];
147
+ export const DEFAULT_STORE_WHATSAPP_CONFIG_DATA = {
148
+ isConnected: false,
149
+ isPaid: false,
150
+ };
151
+ export const STORE_GOOGLE_CONFIG_COLLECTION = "storeGoogleConfig";
152
+ export const STORE_GOOGLE_CONFIG_INDEXED_FIELDS = [
153
+ "storeId",
154
+ "isConnected",
155
+ ];
156
+ export const DEFAULT_STORE_GOOGLE_CONFIG_DATA = {
157
+ isConnected: false,
158
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * RBAC — roleOverrides + customRoles
3
+ *
4
+ * S-STORE-9B-G foundation. Stores per-user permission patches and
5
+ * admin-defined custom roles. Used by the existing `hasPermission()`
6
+ * predicate via a layered lookup: builtin role → custom role → override.
7
+ */
8
+ export interface RoleOverrideDocument {
9
+ id: string;
10
+ /** Target user UID. */
11
+ userId: string;
12
+ /** Optional store-scoped override (null = global). */
13
+ storeId?: string | null;
14
+ /** Permission keys granted in addition to the user's base role. */
15
+ grantedPermissions: string[];
16
+ /** Permission keys revoked from the user's base role. */
17
+ revokedPermissions: string[];
18
+ /** Optional custom role IDs the user inherits from. */
19
+ customRoleIds: string[];
20
+ reason?: string;
21
+ createdBy: string;
22
+ createdAt: Date;
23
+ updatedAt: Date;
24
+ expiresAt?: Date;
25
+ }
26
+ export declare const ROLE_OVERRIDES_COLLECTION: "roleOverrides";
27
+ export declare const ROLE_OVERRIDE_INDEXED_FIELDS: readonly ["userId", "storeId", "expiresAt", "createdAt"];
28
+ export declare const DEFAULT_ROLE_OVERRIDE_DATA: Partial<RoleOverrideDocument>;
29
+ export interface CustomRoleDocument {
30
+ id: string;
31
+ name: string;
32
+ slug: string;
33
+ description?: string;
34
+ /** Permission keys this custom role confers. */
35
+ permissions: string[];
36
+ /** Optional parent role to inherit from. */
37
+ inheritsFrom?: string;
38
+ /** Where this role can be assigned. */
39
+ scope: "global" | "store";
40
+ isActive: boolean;
41
+ createdBy: string;
42
+ createdAt: Date;
43
+ updatedAt: Date;
44
+ }
45
+ export declare const CUSTOM_ROLES_COLLECTION: "customRoles";
46
+ export declare const CUSTOM_ROLE_INDEXED_FIELDS: readonly ["slug", "scope", "isActive", "createdAt"];
47
+ export declare const DEFAULT_CUSTOM_ROLE_DATA: Partial<CustomRoleDocument>;
48
+ export type AdminNotificationCategory = "system" | "security" | "moderation" | "payouts" | "fraud" | "growth";
49
+ export interface AdminNotificationDocument {
50
+ id: string;
51
+ category: AdminNotificationCategory;
52
+ title: string;
53
+ body: string;
54
+ severity: "info" | "warning" | "error";
55
+ isRead: boolean;
56
+ entityType?: string;
57
+ entityId?: string;
58
+ /** Admins this notification targets (empty = all admins). */
59
+ audienceUserIds: string[];
60
+ createdAt: Date;
61
+ readAt?: Date;
62
+ }
63
+ export declare const ADMIN_NOTIFICATIONS_COLLECTION: "adminNotifications";
64
+ export declare const ADMIN_NOTIFICATION_INDEXED_FIELDS: readonly ["category", "severity", "isRead", "createdAt"];
65
+ export declare const DEFAULT_ADMIN_NOTIFICATION_DATA: Partial<AdminNotificationDocument>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * RBAC — roleOverrides + customRoles
3
+ *
4
+ * S-STORE-9B-G foundation. Stores per-user permission patches and
5
+ * admin-defined custom roles. Used by the existing `hasPermission()`
6
+ * predicate via a layered lookup: builtin role → custom role → override.
7
+ */
8
+ export const ROLE_OVERRIDES_COLLECTION = "roleOverrides";
9
+ export const ROLE_OVERRIDE_INDEXED_FIELDS = [
10
+ "userId",
11
+ "storeId",
12
+ "expiresAt",
13
+ "createdAt",
14
+ ];
15
+ export const DEFAULT_ROLE_OVERRIDE_DATA = {
16
+ grantedPermissions: [],
17
+ revokedPermissions: [],
18
+ customRoleIds: [],
19
+ };
20
+ export const CUSTOM_ROLES_COLLECTION = "customRoles";
21
+ export const CUSTOM_ROLE_INDEXED_FIELDS = [
22
+ "slug",
23
+ "scope",
24
+ "isActive",
25
+ "createdAt",
26
+ ];
27
+ export const DEFAULT_CUSTOM_ROLE_DATA = {
28
+ permissions: [],
29
+ isActive: true,
30
+ scope: "global",
31
+ };
32
+ export const ADMIN_NOTIFICATIONS_COLLECTION = "adminNotifications";
33
+ export const ADMIN_NOTIFICATION_INDEXED_FIELDS = [
34
+ "category",
35
+ "severity",
36
+ "isRead",
37
+ "createdAt",
38
+ ];
39
+ export const DEFAULT_ADMIN_NOTIFICATION_DATA = {
40
+ isRead: false,
41
+ severity: "info",
42
+ audienceUserIds: [],
43
+ };
@@ -1,4 +1,6 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
2
4
  import Link from "next/link";
3
5
  import { BaseListingCard, Heading, Span, Row, RichText, Div } from "../../../ui";
4
6
  import { MediaImage } from "../../media/MediaImage";
@@ -7,8 +9,10 @@ import { normalizeRichTextHtml } from "../../../utils";
7
9
  import { useLongPress } from "../../../react/hooks/useLongPress";
8
10
  export function InteractiveStoreCard({ store, href, selectable, isSelected = false, onSelect, labels = {}, className = "", }) {
9
11
  const initial = store.storeName[0]?.toUpperCase() ?? "S";
10
- const hasLogo = Boolean(store.storeLogoURL);
11
- const hasBanner = Boolean(store.storeBannerURL);
12
+ const [logoBroken, setLogoBroken] = useState(false);
13
+ const [bannerBroken, setBannerBroken] = useState(false);
14
+ const hasLogo = Boolean(store.storeLogoURL) && !logoBroken;
15
+ const hasBanner = Boolean(store.storeBannerURL) && !bannerBroken;
12
16
  const longPress = useLongPress(() => onSelect?.(store.id, !isSelected));
13
17
  return (_jsxs(Div, { className: [
14
18
  "group relative flex flex-col h-full overflow-hidden rounded-xl border bg-white dark:bg-zinc-900 shadow-sm transition-shadow hover:shadow-lg",
@@ -19,5 +23,5 @@ export function InteractiveStoreCard({ store, href, selectable, isSelected = fal
19
23
  ].join(" "), onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => {
20
24
  e.preventDefault();
21
25
  onSelect(store.id, !isSelected);
22
- }, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { className: "relative aspect-video w-full overflow-hidden bg-gradient-to-br from-zinc-100 via-zinc-200 to-zinc-300 dark:from-zinc-800 dark:via-zinc-700 dark:to-zinc-900 flex-shrink-0", children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Span, { className: "text-5xl opacity-20 select-none", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Div, { className: "absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/40 to-transparent pointer-events-none" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: "absolute top-2 right-2 flex items-center gap-1 rounded-full bg-black/50 backdrop-blur-sm px-2 py-0.5 text-xs font-semibold text-yellow-300", children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Div, { className: "flex flex-col flex-1 px-4 pb-4", children: [_jsx(Div, { className: "-mt-5 mb-2 flex items-end justify-between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx(MediaImage, { src: store.storeLogoURL, alt: store.storeName, size: "avatar", className: "h-10 w-10 rounded-lg border-2 border-white dark:border-zinc-800 shadow-md object-cover" })) : (_jsx(Div, { className: "flex h-10 w-10 items-center justify-center rounded-lg border-2 border-white dark:border-zinc-800 bg-primary/10 dark:bg-primary/20 text-base font-bold text-primary shadow-md", children: initial })) }) }), _jsx(Heading, { level: 3, className: `${THEME_CONSTANTS.utilities.textClamp1} text-sm font-bold text-zinc-900 dark:text-zinc-100 group-hover:text-primary transition-colors`, children: store.storeName }), store.storeDescription ? (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: `mt-1 ${THEME_CONSTANTS.utilities.textClamp2} text-xs text-zinc-500 dark:text-zinc-400 flex-1` })) : (_jsx(Div, { className: "flex-1" })), _jsxs(Row, { gap: "sm", className: "mt-2.5 flex-wrap text-xs text-zinc-500 dark:text-zinc-400", children: [store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { className: "flex items-center gap-0.5", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), " ", store.totalProducts, " ", labels.products ?? "products"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { className: "flex items-center gap-0.5", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDECD\uFE0F" }), " ", store.itemsSold, " ", labels.sold ?? "sold"] }))] }), _jsxs(Div, { className: "mt-3 pt-2.5 border-t border-zinc-100 dark:border-zinc-800 flex items-center justify-between", children: [_jsxs(Span, { className: "text-xs font-semibold text-primary group-hover:underline transition-colors", children: [labels.visitStore ?? "Visit store", " \u2192"] }), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [store.totalReviews, " ", labels.reviews ?? "reviews"] }))] })] })] })] }));
26
+ }, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { className: "relative aspect-video w-full overflow-hidden bg-gradient-to-br from-zinc-100 via-zinc-200 to-zinc-300 dark:from-zinc-800 dark:via-zinc-700 dark:to-zinc-900 flex-shrink-0", children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Span, { className: "text-5xl opacity-20 select-none", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Div, { className: "absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/40 to-transparent pointer-events-none" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: "absolute top-2 right-2 flex items-center gap-1 rounded-full bg-black/50 backdrop-blur-sm px-2 py-0.5 text-xs font-semibold text-yellow-300", children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Div, { className: "flex flex-col flex-1 px-4 pb-4", children: [_jsx(Div, { className: "-mt-5 mb-2 flex items-end justify-between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx("img", { src: store.storeLogoURL, alt: store.storeName, className: "h-10 w-10 rounded-lg border-2 border-white dark:border-zinc-800 shadow-md object-cover bg-white dark:bg-zinc-800", onError: () => setLogoBroken(true), loading: "lazy" })) : (_jsx(Div, { className: "flex h-10 w-10 items-center justify-center rounded-lg border-2 border-white dark:border-zinc-800 bg-primary/10 dark:bg-primary/20 text-base font-bold text-primary shadow-md", children: initial })) }) }), _jsx(Heading, { level: 3, className: `${THEME_CONSTANTS.utilities.textClamp1} text-sm font-bold text-zinc-900 dark:text-zinc-100 group-hover:text-primary transition-colors`, children: store.storeName }), store.storeDescription ? (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: `mt-1 ${THEME_CONSTANTS.utilities.textClamp2} text-xs text-zinc-500 dark:text-zinc-400 flex-1` })) : (_jsx(Div, { className: "flex-1" })), _jsxs(Row, { gap: "sm", className: "mt-2.5 flex-wrap text-xs text-zinc-500 dark:text-zinc-400", children: [store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { className: "flex items-center gap-0.5", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), " ", store.totalProducts, " ", labels.products ?? "products"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { className: "flex items-center gap-0.5", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDECD\uFE0F" }), " ", store.itemsSold, " ", labels.sold ?? "sold"] }))] }), _jsxs(Div, { className: "mt-3 pt-2.5 border-t border-zinc-100 dark:border-zinc-800 flex items-center justify-between", children: [_jsxs(Span, { className: "text-xs font-semibold text-primary group-hover:underline transition-colors", children: [labels.visitStore ?? "Visit store", " \u2192"] }), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: [store.totalReviews, " ", labels.reviews ?? "reviews"] }))] })] })] })] }));
23
27
  }