@mohasinac/appkit 4.1.1 → 4.2.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.
- package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
- package/dist/_internal/client/features/maintenance/index.js +1 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
- package/dist/_internal/server/features/auctions/index.d.ts +1 -1
- package/dist/_internal/server/features/auctions/index.js +1 -1
- package/dist/_internal/server/features/auctions/service.js +2 -2
- package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
- package/dist/_internal/server/features/checkout/actions.js +245 -182
- package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
- package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +9 -1
- package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
- package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
- package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
- package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
- 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/onProductWrite.js +14 -2
- 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/revenueRollup.d.ts +13 -0
- package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
- package/dist/_internal/shared/features/auctions/config.js +1 -1
- package/dist/_internal/shared/features/auctions/schema.js +1 -1
- package/dist/_internal/shared/features/cart/schema.js +2 -2
- package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
- package/dist/_internal/shared/features/checkout/config.js +7 -0
- package/dist/_internal/shared/features/events/schema.js +1 -1
- package/dist/_internal/shared/features/orders/schema.js +1 -1
- package/dist/_internal/shared/features/products/schema.js +3 -3
- package/dist/_internal/shared/features/promotions/schema.js +3 -3
- package/dist/_internal/shared/fees/calculator.d.ts +32 -7
- package/dist/_internal/shared/fees/calculator.js +25 -0
- package/dist/client.d.ts +4 -4
- package/dist/client.js +4 -2
- package/dist/constants/api-endpoints.d.ts +9 -6
- package/dist/constants/api-endpoints.js +3 -2
- package/dist/contracts/client-payment-gateway.d.ts +1 -2
- package/dist/features/about/components/AboutView.d.ts +6 -16
- package/dist/features/about/components/AboutView.js +3 -3
- package/dist/features/about/components/FeesView.js +18 -0
- package/dist/features/about/index.d.ts +2 -1
- package/dist/features/about/schemas/firestore.d.ts +47 -0
- package/dist/features/about/schemas/firestore.js +6 -0
- package/dist/features/about/schemas/index.d.ts +210 -0
- package/dist/features/about/schemas/index.js +45 -0
- package/dist/features/account/hooks/useProfile.js +3 -0
- package/dist/features/admin/actions/notification-actions.d.ts +4 -1
- package/dist/features/admin/actions/notification-actions.js +56 -10
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
- package/dist/features/admin/components/AdminCartsView.js +16 -3
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +14 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
- package/dist/features/admin/components/AdminMediaView.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +42 -11
- package/dist/features/admin/components/AdminNotificationsView.js +31 -19
- package/dist/features/admin/components/AdminOrdersView.js +7 -0
- package/dist/features/admin/components/AdminPayoutsView.js +18 -3
- package/dist/features/admin/components/AdminProductsView.js +17 -2
- package/dist/features/admin/components/AdminReviewsView.js +16 -2
- package/dist/features/admin/components/AdminScammersView.js +7 -0
- package/dist/features/admin/components/AdminSectionsView.js +4 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
- package/dist/features/admin/components/AdminStoresView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
- package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
- package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
- package/dist/features/admin/constants/filter-tabs.js +55 -27
- package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
- package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
- package/dist/features/admin/repository/site-settings.repository.js +8 -1
- package/dist/features/admin/schemas/firestore.d.ts +46 -0
- package/dist/features/admin/schemas/firestore.js +62 -24
- package/dist/features/auctions/actions/bid-actions.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/AuctionsListView.js +8 -1
- package/dist/features/auth/components/LoginForm.js +2 -2
- package/dist/features/auth/components/RegisterForm.js +2 -2
- package/dist/features/auth/permissions/constants.d.ts +16 -0
- package/dist/features/auth/permissions/constants.js +53 -0
- package/dist/features/auth/schemas/firestore.js +1 -0
- package/dist/features/blog/components/BlogFilters.js +1 -1
- package/dist/features/cart/schemas/firestore.d.ts +1 -1
- package/dist/features/categories/schemas/firestore.d.ts +1 -1
- package/dist/features/consultation/components/ConsultationForm.js +1 -1
- package/dist/features/events/components/EventFilters.js +2 -0
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/layout/BackToTop.d.ts +9 -5
- package/dist/features/layout/BackToTop.js +17 -21
- package/dist/features/layout/TitleBarLayout.d.ts +2 -2
- package/dist/features/layout/TitleBarLayout.js +4 -4
- package/dist/features/media/AvatarUpload.js +7 -3
- package/dist/features/media/MediaVideo.js +1 -1
- package/dist/features/media/finalize.d.ts +9 -13
- package/dist/features/media/finalize.js +23 -74
- package/dist/features/media/server.d.ts +1 -1
- package/dist/features/media/server.js +1 -1
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/hooks/useOrders.js +16 -4
- package/dist/features/orders/schemas/firestore.d.ts +19 -5
- package/dist/features/payments/schemas/firestore.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
- package/dist/features/products/components/ArtStickersListView.js +5 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
- package/dist/features/products/components/ProductForm.js +14 -1
- package/dist/features/products/components/ProductsIndexPageView.js +7 -0
- package/dist/features/products/constants/action-defs.d.ts +29 -3
- package/dist/features/products/constants/action-defs.js +77 -1
- package/dist/features/products/schemas/firestore.d.ts +1 -1
- package/dist/features/products/schemas/product-templates.d.ts +1 -1
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/scams/schemas/index.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/seller/components/SellerAuctionsView.js +27 -2
- package/dist/features/seller/components/SellerOrdersView.js +2 -2
- package/dist/features/seller/components/SellerPayoutsView.js +24 -10
- package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +1 -1
- package/dist/features/seller/components/SellerProductsView.js +8 -6
- package/dist/features/seller/components/SellerReviewsView.js +27 -6
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreProductsView.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +11 -0
- package/dist/features/stores/schemas/firestore.js +5 -0
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
- package/dist/features/tester/actions/checklist-item-actions.js +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
- package/dist/features/tester/components/TesterHubView.js +34 -21
- package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
- package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
- package/dist/features/tester/schemas/firestore.d.ts +8 -3
- package/dist/features/tester/schemas/firestore.js +6 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
- package/dist/features/tester/utils/phases.d.ts +31 -0
- package/dist/features/tester/utils/phases.js +46 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
- package/dist/features/wishlist/types/index.d.ts +3 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +19 -9
- package/dist/next/components/NotFoundView.js +2 -1
- package/dist/next/routing/route-map.d.ts +6 -12
- package/dist/next/routing/route-map.js +3 -4
- package/dist/providers/db-firebase/admin.d.ts +1 -0
- package/dist/providers/db-firebase/admin.js +1 -1
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seed/addresses-seed-data.js +18 -18
- package/dist/seed/bids-seed-data.js +4 -4
- package/dist/seed/blog-posts-seed-data.js +82 -82
- package/dist/seed/carousel-slides-seed-data.js +14 -10
- package/dist/seed/carousels-seed-data.js +3 -3
- package/dist/seed/cart-seed-data.js +92 -92
- package/dist/seed/claimed-coupons-seed-data.js +10 -10
- package/dist/seed/conversations-seed-data.js +75 -75
- package/dist/seed/coupon-usage-seed-data.js +12 -12
- package/dist/seed/coupons-seed-data.js +41 -41
- package/dist/seed/events-seed-data.js +105 -105
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
- package/dist/seed/grouped-listings-seed-data.js +79 -143
- package/dist/seed/history-seed-data.js +38 -38
- package/dist/seed/homepage-sections-seed-data.js +9 -9
- package/dist/seed/notifications-seed-data.js +56 -56
- package/dist/seed/offers-seed-data.js +91 -91
- package/dist/seed/orders-seed-data.js +121 -106
- package/dist/seed/payouts-seed-data.js +42 -42
- package/dist/seed/products-auctions-seed-data.js +10 -2
- package/dist/seed/products-standard-seed-data.js +40 -8
- package/dist/seed/reviews-seed-data.js +42 -23
- package/dist/seed/scammers-seed-data.js +22 -22
- package/dist/seed/sessions-seed-data.js +7 -7
- package/dist/seed/shipments-seed-data.js +10 -10
- package/dist/seed/site-settings-seed-data.js +89 -0
- package/dist/seed/store-addresses-seed-data.js +2 -2
- package/dist/seed/store-extensions-seed-data.js +93 -93
- package/dist/seed/stores-seed-data.js +2 -0
- package/dist/seed/support-tickets-seed-data.js +47 -47
- package/dist/seed/users-seed-data.js +55 -89
- package/dist/seed/wishlists-seed-data.js +22 -22
- package/dist/server.d.ts +4 -3
- package/dist/server.js +7 -7
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PageLoader.js +8 -3
- package/dist/ui/components/Select.d.ts +8 -1
- package/dist/ui/components/Select.js +2 -2
- package/dist/ui/components/SiteMark.d.ts +23 -0
- package/dist/ui/components/SiteMark.js +76 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/validation/schemas.d.ts +0 -2
- package/dist/validation/schemas.js +0 -2
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +17 -15
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export * from "./firestore";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const aboutHowItemSchema: z.ZodObject<{
|
|
4
|
+
title: z.ZodString;
|
|
5
|
+
text: z.ZodString;
|
|
6
|
+
icon: z.ZodString;
|
|
7
|
+
tone: z.ZodOptional<z.ZodEnum<["indigo", "teal", "amber", "rose"]>>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
title: string;
|
|
10
|
+
text: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
title: string;
|
|
15
|
+
text: string;
|
|
16
|
+
icon: string;
|
|
17
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const aboutValueItemSchema: z.ZodObject<{
|
|
20
|
+
title: z.ZodString;
|
|
21
|
+
text: z.ZodString;
|
|
22
|
+
icon: z.ZodString;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
title: string;
|
|
25
|
+
text: string;
|
|
26
|
+
icon: string;
|
|
27
|
+
}, {
|
|
28
|
+
title: string;
|
|
29
|
+
text: string;
|
|
30
|
+
icon: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const aboutMilestoneSchema: z.ZodObject<{
|
|
33
|
+
year: z.ZodString;
|
|
34
|
+
text: z.ZodString;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
text: string;
|
|
37
|
+
year: string;
|
|
38
|
+
}, {
|
|
39
|
+
text: string;
|
|
40
|
+
year: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare const aboutTeamMemberSchema: z.ZodObject<{
|
|
43
|
+
name: z.ZodString;
|
|
44
|
+
role: z.ZodString;
|
|
45
|
+
bio: z.ZodString;
|
|
46
|
+
photoUrl: z.ZodOptional<z.ZodString>;
|
|
47
|
+
isFounder: z.ZodOptional<z.ZodBoolean>;
|
|
48
|
+
isDeveloper: z.ZodOptional<z.ZodBoolean>;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
role: string;
|
|
51
|
+
name: string;
|
|
52
|
+
bio: string;
|
|
53
|
+
photoUrl?: string | undefined;
|
|
54
|
+
isFounder?: boolean | undefined;
|
|
55
|
+
isDeveloper?: boolean | undefined;
|
|
56
|
+
}, {
|
|
57
|
+
role: string;
|
|
58
|
+
name: string;
|
|
59
|
+
bio: string;
|
|
60
|
+
photoUrl?: string | undefined;
|
|
61
|
+
isFounder?: boolean | undefined;
|
|
62
|
+
isDeveloper?: boolean | undefined;
|
|
63
|
+
}>;
|
|
64
|
+
export declare const aboutContentSchema: z.ZodObject<{
|
|
65
|
+
title: z.ZodString;
|
|
66
|
+
subtitle: z.ZodString;
|
|
67
|
+
missionTitle: z.ZodString;
|
|
68
|
+
missionText: z.ZodString;
|
|
69
|
+
howItWorksTitle: z.ZodString;
|
|
70
|
+
howItems: z.ZodArray<z.ZodObject<{
|
|
71
|
+
title: z.ZodString;
|
|
72
|
+
text: z.ZodString;
|
|
73
|
+
icon: z.ZodString;
|
|
74
|
+
tone: z.ZodOptional<z.ZodEnum<["indigo", "teal", "amber", "rose"]>>;
|
|
75
|
+
}, "strip", z.ZodTypeAny, {
|
|
76
|
+
title: string;
|
|
77
|
+
text: string;
|
|
78
|
+
icon: string;
|
|
79
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
80
|
+
}, {
|
|
81
|
+
title: string;
|
|
82
|
+
text: string;
|
|
83
|
+
icon: string;
|
|
84
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
85
|
+
}>, "many">;
|
|
86
|
+
valuesTitle: z.ZodString;
|
|
87
|
+
valueItems: z.ZodArray<z.ZodObject<{
|
|
88
|
+
title: z.ZodString;
|
|
89
|
+
text: z.ZodString;
|
|
90
|
+
icon: z.ZodString;
|
|
91
|
+
}, "strip", z.ZodTypeAny, {
|
|
92
|
+
title: string;
|
|
93
|
+
text: string;
|
|
94
|
+
icon: string;
|
|
95
|
+
}, {
|
|
96
|
+
title: string;
|
|
97
|
+
text: string;
|
|
98
|
+
icon: string;
|
|
99
|
+
}>, "many">;
|
|
100
|
+
milestonesTitle: z.ZodString;
|
|
101
|
+
milestones: z.ZodArray<z.ZodObject<{
|
|
102
|
+
year: z.ZodString;
|
|
103
|
+
text: z.ZodString;
|
|
104
|
+
}, "strip", z.ZodTypeAny, {
|
|
105
|
+
text: string;
|
|
106
|
+
year: string;
|
|
107
|
+
}, {
|
|
108
|
+
text: string;
|
|
109
|
+
year: string;
|
|
110
|
+
}>, "many">;
|
|
111
|
+
teamTitle: z.ZodString;
|
|
112
|
+
teamSubtitle: z.ZodString;
|
|
113
|
+
teamMembers: z.ZodArray<z.ZodObject<{
|
|
114
|
+
name: z.ZodString;
|
|
115
|
+
role: z.ZodString;
|
|
116
|
+
bio: z.ZodString;
|
|
117
|
+
photoUrl: z.ZodOptional<z.ZodString>;
|
|
118
|
+
isFounder: z.ZodOptional<z.ZodBoolean>;
|
|
119
|
+
isDeveloper: z.ZodOptional<z.ZodBoolean>;
|
|
120
|
+
}, "strip", z.ZodTypeAny, {
|
|
121
|
+
role: string;
|
|
122
|
+
name: string;
|
|
123
|
+
bio: string;
|
|
124
|
+
photoUrl?: string | undefined;
|
|
125
|
+
isFounder?: boolean | undefined;
|
|
126
|
+
isDeveloper?: boolean | undefined;
|
|
127
|
+
}, {
|
|
128
|
+
role: string;
|
|
129
|
+
name: string;
|
|
130
|
+
bio: string;
|
|
131
|
+
photoUrl?: string | undefined;
|
|
132
|
+
isFounder?: boolean | undefined;
|
|
133
|
+
isDeveloper?: boolean | undefined;
|
|
134
|
+
}>, "many">;
|
|
135
|
+
ctaTitle: z.ZodString;
|
|
136
|
+
ctaSell: z.ZodString;
|
|
137
|
+
ctaShop: z.ZodString;
|
|
138
|
+
}, "strip", z.ZodTypeAny, {
|
|
139
|
+
title: string;
|
|
140
|
+
subtitle: string;
|
|
141
|
+
missionTitle: string;
|
|
142
|
+
missionText: string;
|
|
143
|
+
howItWorksTitle: string;
|
|
144
|
+
valuesTitle: string;
|
|
145
|
+
milestonesTitle: string;
|
|
146
|
+
teamTitle: string;
|
|
147
|
+
teamSubtitle: string;
|
|
148
|
+
ctaTitle: string;
|
|
149
|
+
ctaSell: string;
|
|
150
|
+
ctaShop: string;
|
|
151
|
+
howItems: {
|
|
152
|
+
title: string;
|
|
153
|
+
text: string;
|
|
154
|
+
icon: string;
|
|
155
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
156
|
+
}[];
|
|
157
|
+
valueItems: {
|
|
158
|
+
title: string;
|
|
159
|
+
text: string;
|
|
160
|
+
icon: string;
|
|
161
|
+
}[];
|
|
162
|
+
milestones: {
|
|
163
|
+
text: string;
|
|
164
|
+
year: string;
|
|
165
|
+
}[];
|
|
166
|
+
teamMembers: {
|
|
167
|
+
role: string;
|
|
168
|
+
name: string;
|
|
169
|
+
bio: string;
|
|
170
|
+
photoUrl?: string | undefined;
|
|
171
|
+
isFounder?: boolean | undefined;
|
|
172
|
+
isDeveloper?: boolean | undefined;
|
|
173
|
+
}[];
|
|
174
|
+
}, {
|
|
175
|
+
title: string;
|
|
176
|
+
subtitle: string;
|
|
177
|
+
missionTitle: string;
|
|
178
|
+
missionText: string;
|
|
179
|
+
howItWorksTitle: string;
|
|
180
|
+
valuesTitle: string;
|
|
181
|
+
milestonesTitle: string;
|
|
182
|
+
teamTitle: string;
|
|
183
|
+
teamSubtitle: string;
|
|
184
|
+
ctaTitle: string;
|
|
185
|
+
ctaSell: string;
|
|
186
|
+
ctaShop: string;
|
|
187
|
+
howItems: {
|
|
188
|
+
title: string;
|
|
189
|
+
text: string;
|
|
190
|
+
icon: string;
|
|
191
|
+
tone?: "indigo" | "teal" | "amber" | "rose" | undefined;
|
|
192
|
+
}[];
|
|
193
|
+
valueItems: {
|
|
194
|
+
title: string;
|
|
195
|
+
text: string;
|
|
196
|
+
icon: string;
|
|
197
|
+
}[];
|
|
198
|
+
milestones: {
|
|
199
|
+
text: string;
|
|
200
|
+
year: string;
|
|
201
|
+
}[];
|
|
202
|
+
teamMembers: {
|
|
203
|
+
role: string;
|
|
204
|
+
name: string;
|
|
205
|
+
bio: string;
|
|
206
|
+
photoUrl?: string | undefined;
|
|
207
|
+
isFounder?: boolean | undefined;
|
|
208
|
+
isDeveloper?: boolean | undefined;
|
|
209
|
+
}[];
|
|
210
|
+
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export * from "./firestore";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const REQUIRED_TITLE = "Title is required";
|
|
4
|
+
const REQUIRED_TEXT = "Text is required";
|
|
5
|
+
export const aboutHowItemSchema = z.object({
|
|
6
|
+
title: z.string().min(1, REQUIRED_TITLE),
|
|
7
|
+
text: z.string().min(1, REQUIRED_TEXT),
|
|
8
|
+
icon: z.string().min(1, "Icon is required"),
|
|
9
|
+
tone: z.enum(["indigo", "teal", "amber", "rose"]).optional(),
|
|
10
|
+
});
|
|
11
|
+
export const aboutValueItemSchema = z.object({
|
|
12
|
+
title: z.string().min(1, REQUIRED_TITLE),
|
|
13
|
+
text: z.string().min(1, REQUIRED_TEXT),
|
|
14
|
+
icon: z.string().min(1, "Icon is required"),
|
|
15
|
+
});
|
|
16
|
+
export const aboutMilestoneSchema = z.object({
|
|
17
|
+
year: z.string().min(1, "Year is required"),
|
|
18
|
+
text: z.string().min(1, REQUIRED_TEXT),
|
|
19
|
+
});
|
|
20
|
+
export const aboutTeamMemberSchema = z.object({
|
|
21
|
+
name: z.string().min(1, "Name is required"),
|
|
22
|
+
role: z.string().min(1, "Role is required"),
|
|
23
|
+
bio: z.string().min(1, "Bio is required"),
|
|
24
|
+
photoUrl: z.string().optional(),
|
|
25
|
+
isFounder: z.boolean().optional(),
|
|
26
|
+
isDeveloper: z.boolean().optional(),
|
|
27
|
+
});
|
|
28
|
+
export const aboutContentSchema = z.object({
|
|
29
|
+
title: z.string().min(1, REQUIRED_TITLE),
|
|
30
|
+
subtitle: z.string().min(1, "Subtitle is required"),
|
|
31
|
+
missionTitle: z.string().min(1, "Mission title is required"),
|
|
32
|
+
missionText: z.string().min(1, "Mission text is required"),
|
|
33
|
+
howItWorksTitle: z.string().min(1, "How it works title is required"),
|
|
34
|
+
howItems: z.array(aboutHowItemSchema),
|
|
35
|
+
valuesTitle: z.string().min(1, "Values title is required"),
|
|
36
|
+
valueItems: z.array(aboutValueItemSchema),
|
|
37
|
+
milestonesTitle: z.string().min(1, "Milestones title is required"),
|
|
38
|
+
milestones: z.array(aboutMilestoneSchema),
|
|
39
|
+
teamTitle: z.string().min(1, "Team title is required"),
|
|
40
|
+
teamSubtitle: z.string().min(1, "Team subtitle is required"),
|
|
41
|
+
teamMembers: z.array(aboutTeamMemberSchema),
|
|
42
|
+
ctaTitle: z.string().min(1, "CTA title is required"),
|
|
43
|
+
ctaSell: z.string().min(1, "Sell CTA copy is required"),
|
|
44
|
+
ctaShop: z.string().min(1, "Shop CTA copy is required"),
|
|
45
|
+
});
|
|
@@ -17,6 +17,9 @@ export function useUpdateCurrentProfile(options) {
|
|
|
17
17
|
mutationFn: (data) => apiClient.patch(endpoint, data),
|
|
18
18
|
onSuccess: async (data) => {
|
|
19
19
|
await queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
20
|
+
// useCurrentUser()/useRBAC() read identity via a separate ["auth","me"]
|
|
21
|
+
// query — without this, displayName/photoURL stay stale for up to 5min.
|
|
22
|
+
await queryClient.invalidateQueries({ queryKey: ["auth", "me"] });
|
|
20
23
|
options?.onSuccess?.(data);
|
|
21
24
|
},
|
|
22
25
|
onError: options?.onError,
|
|
@@ -14,11 +14,14 @@ export interface SendNotificationInput extends NotificationCreateInput {
|
|
|
14
14
|
userPhone?: string;
|
|
15
15
|
/** Pre-rendered email HTML — falls back to a plain-text <p> when absent. */
|
|
16
16
|
emailHtml?: string;
|
|
17
|
+
/** Buyer paid the ₹10 WhatsApp order-updates addon — enables WhatsApp channel for this notification even when the user has no standing WhatsApp subscription. */
|
|
18
|
+
orderWhatsappAddonPaid?: boolean;
|
|
17
19
|
}
|
|
18
20
|
export interface SendNotificationResult {
|
|
19
21
|
notification: NotificationDocument;
|
|
20
22
|
email: "sent" | "skipped" | "failed";
|
|
21
|
-
|
|
23
|
+
/** WhatsApp dispatch is now async (enqueued as a `whatsappNotify` job) — "queued" replaces "sent" as the immediate outcome; the real delivery result lands on `notification.whatsappStatus`. */
|
|
24
|
+
whatsapp: "queued" | "skipped" | "failed";
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Create an in-app notification and fan out to enabled external channels.
|
|
@@ -3,10 +3,39 @@ import { notificationRepository } from "../repository/notification.repository";
|
|
|
3
3
|
import { siteSettingsRepository } from "../repository/site-settings.repository";
|
|
4
4
|
import { userRepository } from "../../auth/repository/user.repository";
|
|
5
5
|
import { createResendProvider } from "../../../providers/email-resend/provider";
|
|
6
|
-
import {
|
|
6
|
+
import { enqueueJob } from "../../jobs/actions/enqueue-job";
|
|
7
7
|
import { serverLogger } from "../../../monitoring";
|
|
8
8
|
import { decryptPii } from "../../../security/index";
|
|
9
9
|
import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore";
|
|
10
|
+
/**
|
|
11
|
+
* Notification types whose WhatsApp send is gated behind the buyer's
|
|
12
|
+
* per-order ₹10 WhatsApp-updates addon (`OrderDocument.whatsappNotifyAddon`)
|
|
13
|
+
* — see `orderWhatsappAddonPaid` on `SendNotificationInput`. Every other
|
|
14
|
+
* type (bid_won, offers, scam reports, support tickets, admin alerts, …)
|
|
15
|
+
* keeps the original two-gate behavior (admin channel enabled + user hasn't
|
|
16
|
+
* opted out) unchanged.
|
|
17
|
+
*/
|
|
18
|
+
const ORDER_GATED_TYPES = new Set([
|
|
19
|
+
"order_placed",
|
|
20
|
+
"order_confirmed",
|
|
21
|
+
"order_shipped",
|
|
22
|
+
"order_delivered",
|
|
23
|
+
"order_cancelled",
|
|
24
|
+
"refund_initiated",
|
|
25
|
+
]);
|
|
26
|
+
/** Notification type → the flat `SiteSettingsCredentials` field holding its approved Meta template name. */
|
|
27
|
+
const WHATSAPP_TEMPLATE_FIELD = {
|
|
28
|
+
order_placed: "whatsappTemplateOrderPlaced",
|
|
29
|
+
order_confirmed: "whatsappTemplateOrderConfirmed",
|
|
30
|
+
order_shipped: "whatsappTemplateOrderShipped",
|
|
31
|
+
order_delivered: "whatsappTemplateOrderDelivered",
|
|
32
|
+
order_cancelled: "whatsappTemplateOrderCancelled",
|
|
33
|
+
refund_initiated: "whatsappTemplateRefundInitiated",
|
|
34
|
+
};
|
|
35
|
+
function resolveWhatsAppTemplateName(type, creds) {
|
|
36
|
+
const field = WHATSAPP_TEMPLATE_FIELD[type];
|
|
37
|
+
return (field ? creds[field] : undefined)?.trim() ?? "";
|
|
38
|
+
}
|
|
10
39
|
export async function markNotificationRead(id) {
|
|
11
40
|
await notificationRepository.markAsRead(id);
|
|
12
41
|
}
|
|
@@ -121,28 +150,45 @@ export async function sendNotification(input) {
|
|
|
121
150
|
}
|
|
122
151
|
}
|
|
123
152
|
}
|
|
124
|
-
// WhatsApp channel — admin enabled AND user hasn't opted out
|
|
153
|
+
// WhatsApp channel — admin enabled AND user hasn't opted out AND (for
|
|
154
|
+
// order-lifecycle types) the buyer paid the per-order WhatsApp addon.
|
|
125
155
|
let whatsappStatus = "skipped";
|
|
126
156
|
if (channels.whatsapp.enabled &&
|
|
127
157
|
userChannelPrefs.whatsapp !== false &&
|
|
128
158
|
resolvedPhone &&
|
|
129
159
|
meetsMinPriority(priority, channels.whatsapp.minPriority) &&
|
|
130
|
-
(!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type))
|
|
160
|
+
(!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type)) &&
|
|
161
|
+
(!ORDER_GATED_TYPES.has(type) || input.orderWhatsappAddonPaid === true)) {
|
|
131
162
|
const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
|
|
132
163
|
const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
|
|
133
164
|
if (phoneNumberId && accessToken) {
|
|
134
165
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
166
|
+
const { jobId } = await enqueueJob({
|
|
167
|
+
jobType: "whatsappNotify",
|
|
168
|
+
payload: {
|
|
169
|
+
toPhone: resolvedPhone,
|
|
170
|
+
title,
|
|
171
|
+
message,
|
|
172
|
+
type,
|
|
173
|
+
templateName: resolveWhatsAppTemplateName(type, creds),
|
|
174
|
+
templateLanguage: creds.whatsappTemplateLanguage ?? "en",
|
|
175
|
+
notificationId: notification.id,
|
|
176
|
+
phoneNumberId,
|
|
177
|
+
accessToken,
|
|
178
|
+
},
|
|
179
|
+
requestedBy: input.userId,
|
|
180
|
+
});
|
|
181
|
+
whatsappStatus = "queued";
|
|
182
|
+
await notificationRepository
|
|
183
|
+
.update(notification.id, { whatsappStatus: "queued", whatsappJobId: jobId })
|
|
184
|
+
.catch((err) => {
|
|
185
|
+
void normalizeError(err);
|
|
186
|
+
serverLogger.warn("sendNotification: failed to write whatsappJobId onto notification doc", { notificationId: notification.id });
|
|
140
187
|
});
|
|
141
|
-
whatsappStatus = ok ? "sent" : "failed";
|
|
142
188
|
}
|
|
143
189
|
catch (err) {
|
|
144
190
|
void normalizeError(err);
|
|
145
|
-
serverLogger.error("sendNotification: WhatsApp
|
|
191
|
+
serverLogger.error("sendNotification: WhatsApp job enqueue failed", { userId: input.userId, err });
|
|
146
192
|
whatsappStatus = "failed";
|
|
147
193
|
}
|
|
148
194
|
}
|
|
@@ -156,7 +156,7 @@ export function AdminAdsView({ endpoint = ADMIN_ENDPOINTS.ADS, labels = {}, crea
|
|
|
156
156
|
settingsMutation.mutate(payload);
|
|
157
157
|
};
|
|
158
158
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? "Ad Inventory", sections: [
|
|
159
|
-
_jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-
|
|
159
|
+
_jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)]", children: "New ad" })] }),
|
|
160
160
|
adsQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ads", children: adsQuery.error instanceof Error ? adsQuery.error.message : "Unknown error" })) : null,
|
|
161
161
|
_jsx(AdsSettingsPanel, { adsenseClientId: adsenseClientId, setAdsenseClientId: setAdsenseClientId, thirdPartyScriptUrl: thirdPartyScriptUrl, setThirdPartyScriptUrl: setThirdPartyScriptUrl, consentRequired: consentRequired, setConsentRequired: setConsentRequired, serverCredentialIssues: serverCredentialIssues, localCredentialIssues: localCredentialIssues, credentialStatus: credentialStatus, providerCredentialsMasked: adsQuery.data?.providerCredentialsMasked, settingsMutation: settingsMutation, hasPendingCredentialInput: hasPendingCredentialInput, currentConsentRequired: Boolean(adsQuery.data?.consentRequired), settingsMessage: settingsMessage, onSave: saveSettings }),
|
|
162
162
|
_jsx(AdsFilterRow, { q: q, setQ: setQ, status: status, setStatus: setStatus, provider: provider, setProvider: setProvider, placement: placement, setPlacement: setPlacement, placements: placements, onPageReset: () => setPage(1) }),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { StackedViewShellProps } from "../../../ui";
|
|
3
|
+
export interface AdminCarouselGroupEditorViewProps extends Omit<StackedViewShellProps, "sections"> {
|
|
4
|
+
onCreated?: (id: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function AdminCarouselGroupEditorView({ onCreated, ...rest }: AdminCarouselGroupEditorViewProps): React.JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
import { Button, Form, Row, StackedViewShell, useToast } from "../../../ui";
|
|
7
|
+
import { FieldInput, FieldSelect } from "../../../ui/forms";
|
|
8
|
+
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
9
|
+
import { apiClient } from "../../../http";
|
|
10
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
12
|
+
const carouselGroupFormSchema = z.object({
|
|
13
|
+
name: z.string().min(1, "Name is required").max(200),
|
|
14
|
+
status: z.enum(["active", "draft"]),
|
|
15
|
+
});
|
|
16
|
+
export function AdminCarouselGroupEditorView({ onCreated, ...rest }) {
|
|
17
|
+
const { showToast } = useToast();
|
|
18
|
+
const router = useRouter();
|
|
19
|
+
const [name, setName] = React.useState("");
|
|
20
|
+
const [status, setStatus] = React.useState("draft");
|
|
21
|
+
const createMutation = useApiMutation({
|
|
22
|
+
mutationFn: () => apiClient.post(ADMIN_ENDPOINTS.CAROUSELS, { name, status }),
|
|
23
|
+
onSuccess: (res) => {
|
|
24
|
+
const id = res?.data?.id;
|
|
25
|
+
showToast("Carousel created.", "success");
|
|
26
|
+
if (id) {
|
|
27
|
+
if (onCreated)
|
|
28
|
+
onCreated(id);
|
|
29
|
+
else
|
|
30
|
+
router.push(String(ROUTES.ADMIN.CAROUSEL_DETAIL(id)));
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
onError: (err) => {
|
|
34
|
+
showToast(err?.message ?? "Failed to create carousel.", "error");
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: "New Named Carousel", sections: [
|
|
38
|
+
_jsx(Form, { schema: carouselGroupFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "name", label: "Carousel name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Homepage Hero" }), _jsx(FieldSelect, { name: "status", label: "Status", value: status, onChange: (v) => setStatus(v), options: [
|
|
39
|
+
{ value: "draft", label: "Draft" },
|
|
40
|
+
{ value: "active", label: "Active" },
|
|
41
|
+
] }), _jsx(Row, { gap: "3", padding: "t-xs", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, disabled: !name || createMutation.isPending, onClick: () => {
|
|
42
|
+
clearErrors();
|
|
43
|
+
if (!name.trim()) {
|
|
44
|
+
setFieldError("name", "Carousel name is required");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
createMutation.mutate();
|
|
48
|
+
}, children: "Create carousel" }) })] })) }, "carousel-group-form"),
|
|
49
|
+
] }));
|
|
50
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { SIEVE_OP, sieveFilter } from "@mohasinac/appkit";
|
|
3
4
|
import { sortBy } from "@mohasinac/appkit";
|
|
4
5
|
import React from "react";
|
|
5
|
-
import { ListingLayout } from "../../../ui";
|
|
6
|
+
import { FilterChipGroup, ListingLayout } from "../../../ui";
|
|
6
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
8
|
import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
|
|
8
9
|
import { DataListingView } from "./DataListingView";
|
|
@@ -11,7 +12,7 @@ const ADMIN_CARTS_CONFIG = {
|
|
|
11
12
|
title: "Carts",
|
|
12
13
|
searchPlaceholder: "Search by user ID or session",
|
|
13
14
|
emptyLabel: "No carts found",
|
|
14
|
-
filterKeys: [],
|
|
15
|
+
filterKeys: ["ownership"],
|
|
15
16
|
defaultSort: sortBy("updatedAt", "DESC"),
|
|
16
17
|
queryKey: ["admin", "carts", "listing"],
|
|
17
18
|
endpoint: ADMIN_ENDPOINTS.ADMIN_CARTS,
|
|
@@ -34,7 +35,19 @@ const ADMIN_CARTS_CONFIG = {
|
|
|
34
35
|
};
|
|
35
36
|
}),
|
|
36
37
|
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
37
|
-
buildFilters: () =>
|
|
38
|
+
buildFilters: (state) =>
|
|
39
|
+
// Guest carts store userId as an empty string (CartDocument.userId is a
|
|
40
|
+
// required string, not optional, see mapRows below). Only the guest-only
|
|
41
|
+
// direction is offered: an authenticated-only filter would need a NEQ
|
|
42
|
+
// on userId combined with the default updatedAt sort, which Firestore
|
|
43
|
+
// rejects (inequality filters require orderBy to match the same
|
|
44
|
+
// field), and CartRepository Sieve config marks userId as not
|
|
45
|
+
// sortable, so no safe workaround sort exists.
|
|
46
|
+
state.ownership === "guest" ? sieveFilter("userId", SIEVE_OP.EQ, "") : undefined,
|
|
47
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Ownership", tabs: [
|
|
48
|
+
{ id: "", label: "All" },
|
|
49
|
+
{ id: "guest", label: "Guest only" },
|
|
50
|
+
], value: pendingFilters.ownership ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, ownership: id })), allId: "" })),
|
|
38
51
|
};
|
|
39
52
|
export function AdminCartsView({ children, ...props }) {
|
|
40
53
|
if (React.Children.count(children) > 0) {
|
|
@@ -50,7 +50,7 @@ function toDateInputValue(val) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
function CouponDiscountFields({ type, discountValue, setDiscountValue, maxDiscount, setMaxDiscount, minPurchase, setMinPurchase, buyQty, setBuyQty, getQty, setGetQty, discountLabel, }) {
|
|
53
|
-
return (_jsxs(_Fragment, { children: [type !== "free_shipping" && type !== "buy_x_get_y" && (_jsx(Input, { label: discountLabel, value: discountValue, onChange: (e) => setDiscountValue(e.target.value), type: "number", min: 0, required: true, placeholder: type === "percentage" ? "e.g. 20" : "e.g. 5000" })), type === "percentage" && (_jsx(Input, { label: "Max discount cap (
|
|
53
|
+
return (_jsxs(_Fragment, { children: [type !== "free_shipping" && type !== "buy_x_get_y" && (_jsx(Input, { label: discountLabel, value: discountValue, onChange: (e) => setDiscountValue(e.target.value), type: "number", min: 0, required: true, placeholder: type === "percentage" ? "e.g. 20" : "e.g. 5000" })), type === "percentage" && (_jsx(Input, { label: "Max discount cap (\u20B9, optional)", value: maxDiscount, onChange: (e) => setMaxDiscount(e.target.value), type: "number", min: 0, placeholder: "e.g. 200", helperText: "Leave blank for no cap." })), (type === "percentage" || type === "fixed") && (_jsx(Input, { label: "Min order value (\u20B9, optional)", value: minPurchase, onChange: (e) => setMinPurchase(e.target.value), type: "number", min: 0, placeholder: "e.g. 500", helperText: "Leave blank for no minimum." })), type === "buy_x_get_y" && (_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Buy quantity", value: buyQty, onChange: (e) => setBuyQty(e.target.value), type: "number", min: 1, required: true }), _jsx(Input, { label: "Get quantity", value: getQty, onChange: (e) => setGetQty(e.target.value), type: "number", min: 1, required: true })] }))] }));
|
|
54
54
|
}
|
|
55
55
|
function CouponValidityFields({ startDate, setStartDate, endDate, setEndDate, isActive, setIsActive, totalLimit, setTotalLimit, perUserLimit, setPerUserLimit, isEdit, currentUsage, }) {
|
|
56
56
|
return (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Total usage limit (optional)", value: totalLimit, onChange: (e) => setTotalLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" }), _jsx(Input, { label: "Per-user limit (optional)", value: perUserLimit, onChange: (e) => setPerUserLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" })] }), isEdit && (_jsx(Input, { label: "Current usage", value: String(currentUsage), disabled: true, helperText: "Read-only \u2014 updated by orders." })), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Start date", value: startDate, onChange: (e) => setStartDate(e.target.value), type: "date", required: true }), _jsx(Input, { label: "End date (optional)", value: endDate, onChange: (e) => setEndDate(e.target.value), type: "date", helperText: "Leave blank for no expiry." })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive })] }));
|
|
@@ -203,7 +203,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
203
203
|
const discountLabel = type === "percentage"
|
|
204
204
|
? "Discount percentage (%)"
|
|
205
205
|
: type === "fixed"
|
|
206
|
-
? "Discount amount (
|
|
206
|
+
? "Discount amount (₹)"
|
|
207
207
|
: "Discount value";
|
|
208
208
|
const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: couponFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Coupon type", options: TYPE_OPTIONS, value: type, onValueChange: (v) => setType(v), required: true }), _jsx(FieldInput, { name: "name", label: "Campaign name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Summer Sale 20%" }), !isEdit && (_jsx(Input, { label: "Coupon code", value: code, onChange: (e) => {
|
|
209
209
|
setCode(toCouponCode(e.target.value));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
4
|
import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
|
|
5
5
|
import { sortBy } from "@mohasinac/appkit";
|
|
@@ -54,7 +54,7 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
54
54
|
title: "Coupons",
|
|
55
55
|
searchPlaceholder: "Search codes, campaigns, or seller scopes",
|
|
56
56
|
emptyLabel: "No coupons found",
|
|
57
|
-
filterKeys: ["type"],
|
|
57
|
+
filterKeys: ["type", "showExpired"],
|
|
58
58
|
defaultSort: sortBy("createdAt", "DESC"),
|
|
59
59
|
queryKey: ["admin", "coupons", "listing"],
|
|
60
60
|
endpoint: ADMIN_ENDPOINTS.COUPONS,
|
|
@@ -69,7 +69,14 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
69
69
|
raw: item,
|
|
70
70
|
})),
|
|
71
71
|
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
72
|
-
buildFilters: (state) =>
|
|
72
|
+
buildFilters: (state) => {
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (state.type && state.type !== "All")
|
|
75
|
+
parts.push(sieveFilter("type", SIEVE_OP.EQ, state.type));
|
|
76
|
+
if (state.showExpired !== "true")
|
|
77
|
+
parts.push(sieveFilter("validity.isActive", SIEVE_OP.EQ, true));
|
|
78
|
+
return parts.join(",") || undefined;
|
|
79
|
+
},
|
|
73
80
|
primaryAction: {
|
|
74
81
|
label: "Add Coupon",
|
|
75
82
|
onClick: ({ openCreatePanel }) => openCreatePanel(),
|
|
@@ -95,7 +102,10 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
95
102
|
onClick: handlers[id],
|
|
96
103
|
}));
|
|
97
104
|
},
|
|
98
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })
|
|
105
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Expired coupons", tabs: [
|
|
106
|
+
{ id: "", label: "Hide expired" },
|
|
107
|
+
{ id: "true", label: "Show all" },
|
|
108
|
+
], value: pendingFilters.showExpired ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showExpired: id })), allId: "" })] })),
|
|
99
109
|
renderCards: (rows, _view, selectionCtx, isLoading) => {
|
|
100
110
|
rowsRef.current = rows;
|
|
101
111
|
return isLoading ? (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { border: "skeleton", className: "animate-pulse", gap: "3", rounded: "xl", padding: "md", children: [_jsx(Div, { className: "h-6 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-4xl", children: _jsx(Text, { color: "faint", children: "No coupons found" }) })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: selectionCtx.openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }));
|
|
@@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
6
6
|
import { Button, Checkbox, ConfirmDeleteModal, Details, Div, Form, FormActions, Input, Label, Select, SideDrawer, Span, Stack, Summary, Text, useToast } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
-
import { PERMISSION_GROUPS, } from "../../auth/permissions/constants";
|
|
9
|
+
import { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatPermLabel, } from "../../auth/permissions/constants";
|
|
10
10
|
const __O = {
|
|
11
11
|
yAuto: "overflow-y-auto",
|
|
12
12
|
};
|
|
@@ -32,47 +32,6 @@ const GROUP_OPTIONS = [
|
|
|
32
32
|
{ label: "Scam Moderator", value: "scam_moderator" },
|
|
33
33
|
{ label: "Custom", value: "custom" },
|
|
34
34
|
];
|
|
35
|
-
const PERMISSION_DOMAINS = [
|
|
36
|
-
{ label: "Dashboard", prefix: "admin:dashboard:" },
|
|
37
|
-
{ label: "Users & Bans", prefix: "admin:users:|admin:user-bans:" },
|
|
38
|
-
{ label: "Products", prefix: "admin:products:" },
|
|
39
|
-
{ label: "Orders & Returns", prefix: "admin:orders:|admin:returns:" },
|
|
40
|
-
{ label: "Stores", prefix: "admin:stores:|admin:store-addresses:" },
|
|
41
|
-
{ label: "Finance", prefix: "admin:analytics:|admin:payouts:" },
|
|
42
|
-
{ label: "Catalog", prefix: "admin:categories:|admin:brands:|admin:coupons:|admin:deals:|admin:featured:" },
|
|
43
|
-
{
|
|
44
|
-
label: "Content",
|
|
45
|
-
prefix: "admin:reviews:|admin:blog:|admin:bids:|admin:media:",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
label: "Site / CMS",
|
|
49
|
-
prefix: "admin:site:|admin:navigation:|admin:sections:|admin:carousel:|admin:ads:|admin:faqs:|admin:newsletter:|admin:contact:",
|
|
50
|
-
},
|
|
51
|
-
{ label: "Events", prefix: "admin:events:|admin:event-entries:" },
|
|
52
|
-
{ label: "Support & Safety", prefix: "admin:support-tickets:|admin:scammers:" },
|
|
53
|
-
{ label: "System", prefix: "admin:sessions:|admin:notifications:|admin:carts:|admin:wishlists:|admin:feature-flags:|admin:copilot:|admin:team:" },
|
|
54
|
-
];
|
|
55
|
-
function matchesDomain(perm, prefix) {
|
|
56
|
-
return prefix.split("|").some((p) => perm.startsWith(p));
|
|
57
|
-
}
|
|
58
|
-
function getPermissionsForDomain(prefix) {
|
|
59
|
-
const allPerms = Object.values(PERMISSION_GROUPS).flat();
|
|
60
|
-
const seen = new Set();
|
|
61
|
-
const result = [];
|
|
62
|
-
for (const p of allPerms) {
|
|
63
|
-
if (matchesDomain(p, prefix) && !seen.has(p)) {
|
|
64
|
-
seen.add(p);
|
|
65
|
-
result.push(p);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
function formatPermLabel(perm) {
|
|
71
|
-
const parts = perm.split(":");
|
|
72
|
-
return parts[parts.length - 1]
|
|
73
|
-
.replace(/-/g, " ")
|
|
74
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
75
|
-
}
|
|
76
35
|
function PermissionDomainsPanel({ permissions, togglePerm }) {
|
|
77
36
|
return (_jsxs(Stack, { gap: "sm", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Permissions", _jsxs(Span, { size: "xs", weight: "normal", className: "ml-2", color: "muted", children: ["(", permissions.size, " selected)"] })] }), _jsx(Div, { className: `divide-y divide-zinc-100 divide-[var(--appkit-color-border)] max-h-[42vh] ${__O.yAuto}`, rounded: "xl", border: "default", children: PERMISSION_DOMAINS.map((domain) => {
|
|
78
37
|
const domainPerms = getPermissionsForDomain(domain.prefix);
|
|
@@ -64,7 +64,7 @@ function MediaBrowser({ onCopy }) {
|
|
|
64
64
|
return files;
|
|
65
65
|
return files.filter((f) => f.name.toLowerCase().includes(q));
|
|
66
66
|
}, [files, search]);
|
|
67
|
-
return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter",
|
|
67
|
+
return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", wrapperClassName: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6", children: [filtered.map((f) => {
|
|
68
68
|
const isImage = (f.contentType ?? "").startsWith("image/");
|
|
69
69
|
return (_jsxs(Div, { className: "group overflow-hidden", surface: "muted", rounded: "lg", border: "default", children: [_jsx(Row, { color: "muted", textSize: "xs", className: "relative aspect-square", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { align: "center", paddingX: "x-xs", className: "break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-[0.375rem]", children: [_jsx(Text, { className: "truncate text-[10px] font-mono", color: "muted", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", textSize: "xs", className: "mt-1 w-full", onClick: () => onCopy(f.downloadURL), children: ACTIONS.MEDIA["copy-url"].label })] })] }, f.name));
|
|
70
70
|
}), filtered.length === 0 && !isLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", color: "muted", size: "sm", align: "center", children: "No files found." }))] }), _jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { size: "xs", color: "muted", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));
|