@mohasinac/appkit 4.63.4 → 4.64.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/.build-stamp.json +3 -3
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +13 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
- package/dist/_internal/server/features/checkout/actions.js +22 -5
- package/dist/_internal/server/features/events/like-actions.d.ts +25 -0
- package/dist/_internal/server/features/events/like-actions.js +67 -0
- package/dist/_internal/server/features/events/likes.d.ts +65 -0
- package/dist/_internal/server/features/events/likes.js +146 -0
- package/dist/_internal/server/features/orders/adapters.js +3 -1
- package/dist/_internal/server/features/seo/index.d.ts +3 -0
- package/dist/_internal/server/features/seo/index.js +2 -0
- package/dist/_internal/server/features/seo/product-detail-types.d.ts +1 -0
- package/dist/_internal/server/features/seo/product-detail-types.js +26 -0
- package/dist/_internal/server/features/seo/robots.js +20 -1
- package/dist/_internal/server/features/seo/sitemap.js +125 -27
- package/dist/_internal/server/features/seo/static-params.d.ts +107 -0
- package/dist/_internal/server/features/seo/static-params.js +228 -0
- package/dist/_internal/server/features/seo/taxonomy-links.d.ts +44 -0
- package/dist/_internal/server/features/seo/taxonomy-links.js +85 -0
- package/dist/_internal/server/features/site-settings/adapters.js +20 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +88 -1
- package/dist/_internal/server/functions/scheduled.d.ts +16 -1
- package/dist/_internal/server/functions/scheduled.js +23 -1
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
- package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/sellerStandingRollup.d.ts +63 -0
- package/dist/_internal/server/jobs/core/sellerStandingRollup.js +412 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/sellerStandingRollup.d.ts +12 -0
- package/dist/_internal/server/jobs/handlers/sellerStandingRollup.js +14 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
- package/dist/_internal/server/notifications/channel-health.js +2 -2
- package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
- package/dist/_internal/server/notifications/send-guard.js +4 -2
- package/dist/_internal/shared/actions/action-registry.js +89 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
- package/dist/_internal/shared/checkout/order-math.js +1 -1
- package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
- package/dist/_internal/shared/features/checkout/config.js +2 -0
- package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
- package/dist/_internal/shared/features/messaging/config.js +4 -1
- package/dist/_internal/shared/features/seller-standing/config.d.ts +130 -0
- package/dist/_internal/shared/features/seller-standing/config.js +153 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
- package/dist/_internal/shared/features/site-settings/fees.js +2 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
- package/dist/_internal/shared/fees/calculator.d.ts +8 -0
- package/dist/_internal/shared/fees/calculator.js +7 -0
- package/dist/cli/index.js +2 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +39 -0
- package/dist/constants/api-endpoints.js +16 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/constants/notification-types.js +1 -1
- package/dist/core/integration-keys.d.ts +5 -0
- package/dist/core/integration-keys.js +10 -0
- package/dist/features/about/components/FeesView.js +6 -0
- package/dist/features/about/components/HelpPageView.js +9 -1
- package/dist/features/account/components/BecomeSellerView.d.ts +15 -3
- package/dist/features/account/components/BecomeSellerView.js +2 -2
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
- package/dist/features/account/types/index.d.ts +2 -0
- package/dist/features/admin/actions/notification-actions.d.ts +17 -2
- package/dist/features/admin/actions/notification-actions.js +154 -9
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +2 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +12 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +31 -2
- package/dist/features/admin/components/AdminStoresGuideView.js +1 -1
- package/dist/features/admin/components/AdminStoresView.js +59 -2
- package/dist/features/admin/components/AdminTrustGuideView.js +10 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
- package/dist/features/admin/constants/site-settings-tabs.js +12 -6
- package/dist/features/admin/repository/site-settings.repository.js +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +10 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +34 -0
- package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
- package/dist/features/admin/schemas/admin-user-form.js +44 -3
- package/dist/features/admin/schemas/firestore.d.ts +73 -0
- package/dist/features/admin/schemas/firestore.js +8 -3
- package/dist/features/auth/checkout-value-otp.d.ts +1 -1
- package/dist/features/auth/checkout-value-otp.js +1 -1
- package/dist/features/auth/permissions/constants.d.ts +2 -2
- package/dist/features/auth/permissions/constants.js +2 -1
- package/dist/features/blog/components/BlogIndexPageView.js +2 -2
- package/dist/features/blog/components/BlogListView.js +2 -1
- package/dist/features/blog/components/BlogPostView.js +2 -1
- package/dist/features/blog/constants/category-badge.d.ts +33 -0
- package/dist/features/blog/constants/category-badge.js +40 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
- package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
- package/dist/features/cart/repository/cart.repository.js +2 -1
- package/dist/features/cart/schemas/firestore.d.ts +2 -1
- package/dist/features/categories/components/TaxonomyLinkIndex.d.ts +39 -0
- package/dist/features/categories/components/TaxonomyLinkIndex.js +11 -0
- package/dist/features/categories/schemas/firestore.d.ts +25 -0
- package/dist/features/categories/utils/load-category-options.d.ts +33 -0
- package/dist/features/categories/utils/load-category-options.js +50 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
- package/dist/features/events/components/EventCard.js +4 -12
- package/dist/features/events/components/EventInsights.d.ts +27 -0
- package/dist/features/events/components/EventInsights.js +10 -0
- package/dist/features/events/components/EventLikeButton.d.ts +21 -0
- package/dist/features/events/components/EventLikeButton.js +59 -0
- package/dist/features/events/components/EventLikersRail.d.ts +23 -0
- package/dist/features/events/components/EventLikersRail.js +54 -0
- package/dist/features/events/components/EventTypeBadge.d.ts +26 -0
- package/dist/features/events/components/EventTypeBadge.js +7 -0
- package/dist/features/events/components/EventsListPageView.js +2 -2
- package/dist/features/events/components/SpinWheel.d.ts +41 -0
- package/dist/features/events/components/SpinWheel.js +128 -0
- package/dist/features/events/components/SpinWheelView.js +36 -15
- package/dist/features/events/constants/event-type-meta.d.ts +54 -0
- package/dist/features/events/constants/event-type-meta.js +109 -0
- package/dist/features/events/repository/event-likes.repository.d.ts +57 -0
- package/dist/features/events/repository/event-likes.repository.js +151 -0
- package/dist/features/events/schemas/firestore.d.ts +111 -0
- package/dist/features/events/schemas/likes.d.ts +81 -0
- package/dist/features/events/schemas/likes.js +59 -0
- package/dist/features/events/types/index.d.ts +7 -0
- package/dist/features/events/utils/carousel-slide.d.ts +42 -0
- package/dist/features/events/utils/carousel-slide.js +174 -0
- package/dist/features/events/utils/event-charts.d.ts +65 -0
- package/dist/features/events/utils/event-charts.js +172 -0
- package/dist/features/faq/constants/help-guides.js +9 -0
- package/dist/features/faq/schemas/firestore.js +1 -0
- package/dist/features/faq/types/index.d.ts +19 -0
- package/dist/features/homepage/components/BrandsSection.js +25 -1
- package/dist/features/homepage/components/HeroCarousel.js +47 -10
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/homepage/types/index.d.ts +7 -0
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
- package/dist/features/orders/components/OrderAddonBadges.js +4 -3
- package/dist/features/orders/repository/orders.repository.d.ts +8 -0
- package/dist/features/orders/repository/orders.repository.js +2 -0
- package/dist/features/orders/schemas/firestore.d.ts +4 -0
- package/dist/features/orders/types/index.d.ts +5 -2
- package/dist/features/search/action-index/settings-entries.js +9 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
- package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
- package/dist/features/store-extensions/schemas/firestore.js +10 -0
- package/dist/features/stores/components/InteractiveStoreCard.js +2 -1
- package/dist/features/stores/components/SellerStandingPanel.d.ts +31 -0
- package/dist/features/stores/components/SellerStandingPanel.js +25 -0
- package/dist/features/stores/components/SellerTierBadge.d.ts +57 -0
- package/dist/features/stores/components/SellerTierBadge.js +19 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +33 -2
- package/dist/features/stores/components/StoreCreateForm.d.ts +46 -0
- package/dist/features/stores/components/StoreCreateForm.js +107 -0
- package/dist/features/stores/components/StoreGuideHubView.js +18 -5
- package/dist/features/stores/components/StoreHeader.js +2 -1
- package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
- package/dist/features/stores/repository/store.repository.d.ts +23 -1
- package/dist/features/stores/repository/store.repository.js +52 -1
- package/dist/features/stores/schemas/firestore.d.ts +76 -7
- package/dist/features/stores/schemas/firestore.js +21 -6
- package/dist/features/stores/schemas/index.d.ts +88 -2
- package/dist/features/stores/schemas/index.js +14 -0
- package/dist/features/stores/schemas/store-create.d.ts +47 -0
- package/dist/features/stores/schemas/store-create.js +86 -0
- package/dist/features/stores/types/index.d.ts +42 -0
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
- package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/components/index.js +2 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
- package/dist/features/whatsapp-bot/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/index.js +3 -0
- package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
- package/dist/features/whatsapp-bot/manifest.js +13 -0
- package/dist/features/whatsapp-bot/messages/en.json +7 -0
- package/dist/features/whatsapp-bot/server.d.ts +7 -0
- package/dist/features/whatsapp-bot/server.js +7 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
- package/dist/features/whatsapp-bot/types/index.js +1 -0
- package/dist/index.d.ts +23 -6
- package/dist/index.js +38 -12
- package/dist/next/routing/route-map.d.ts +6 -1
- package/dist/next/routing/route-map.js +14 -0
- package/dist/providers/db-firebase/base.repository.d.ts +1 -1
- package/dist/providers/db-firebase/base.repository.js +1 -1
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/schemas/registry.d.ts +115 -0
- package/dist/schemas/registry.js +2 -1
- package/dist/security/pii-encrypt.js +1 -1
- package/dist/server.d.ts +15 -2
- package/dist/server.js +32 -2
- package/dist/styles.css +36 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/HorizontalScroller.js +15 -2
- package/dist/ui/components/NavigationLoader.d.ts +14 -1
- package/dist/ui/components/NavigationLoader.js +79 -30
- package/dist/ui/components/PageHeroHeading.d.ts +40 -0
- package/dist/ui/components/PageHeroHeading.js +15 -0
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +59 -31
- package/dist/ui/components/Typography.style.css +35 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +4 -2
- package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
- package/dist/features/orders/utils/order-announcement-message.js +0 -16
|
@@ -78,6 +78,7 @@ const CAPABILITY_META = {
|
|
|
78
78
|
lower_commission_rate: { group: CAP_GROUP.PLATFORM, label: "Lower Commission Rate" },
|
|
79
79
|
early_access_features: { group: CAP_GROUP.PLATFORM, label: "Early Access Features" },
|
|
80
80
|
advanced_analytics: { group: CAP_GROUP.PLATFORM, label: "Advanced Analytics" },
|
|
81
|
+
whatsapp_catalog_sync: { group: CAP_GROUP.PLATFORM, label: "WhatsApp Catalog Sync" },
|
|
81
82
|
};
|
|
82
83
|
const CAPABILITY_GROUP_ORDER = [
|
|
83
84
|
CAP_GROUP.LISTINGS,
|
|
@@ -110,13 +111,15 @@ function CapabilityPicker({ selected, onToggle, }) {
|
|
|
110
111
|
return (_jsxs(Stack, { gap: "sm", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Capabilities", _jsxs(Span, { size: "xs", weight: "normal", className: "ml-2", color: "muted", children: ["(", selected.size, " active)"] })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)]", rounded: "xl", border: "default", children: CAPABILITY_GROUPS.map((group) => (_jsxs(Details, { className: "group", children: [_jsxs(Summary, { paddingX: "x-sm", paddingY: "y-xs", weight: "semibold", color: "muted", layout: "flex", align: "center", justify: "between", className: "uppercase tracking-wide hover:bg-[var(--appkit-color-bg)] transition-colors", children: [_jsx(Span, { size: "xs", children: group.label }), _jsxs(Span, { size: "xs", weight: "normal", className: "normal-case", color: "faint", children: [group.caps.filter((c) => selected.has(c.key)).length, "/", group.caps.length] })] }), _jsx(Div, { layout: "grid", paddingY: "y-xs-tall", className: "grid-cols-2 gap-x-2 gap-y-1.5", surface: "muted", padding: "x-sm", children: group.caps.map((cap) => (_jsx(Checkbox, { label: cap.label, checked: selected.has(cap.key), onChange: () => onToggle(cap.key), wrapperClassName: "flex items-center gap-[var(--appkit-space-2)] cursor-pointer text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" }, cap.key))) })] }, group.label))) })] }));
|
|
111
112
|
}
|
|
112
113
|
// --- Component ---------------------------------------------------------------
|
|
113
|
-
export function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities, currentAdminNotes, }) {
|
|
114
|
+
export function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentSellerTier, currentTrustedSeller, currentCapabilities, currentAdminNotes, }) {
|
|
114
115
|
const queryClient = useQueryClient();
|
|
115
116
|
const { showToast } = useToast();
|
|
116
117
|
const [values, setValues] = React.useState(() => ({
|
|
117
118
|
storeStatus: currentStatus ?? "pending",
|
|
118
119
|
isVerified: currentIsVerified ?? false,
|
|
119
120
|
isFeatured: currentIsFeatured ?? false,
|
|
121
|
+
sellerTier: currentSellerTier ?? "seller",
|
|
122
|
+
trustedSeller: currentTrustedSeller ?? false,
|
|
120
123
|
suspensionReason: "",
|
|
121
124
|
capabilities: currentCapabilities ?? DEFAULT_CAPABILITIES,
|
|
122
125
|
adminNotes: currentAdminNotes ?? "",
|
|
@@ -131,11 +134,22 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
|
|
|
131
134
|
storeStatus: currentStatus ?? "pending",
|
|
132
135
|
isVerified: currentIsVerified ?? false,
|
|
133
136
|
isFeatured: currentIsFeatured ?? false,
|
|
137
|
+
sellerTier: currentSellerTier ?? "seller",
|
|
138
|
+
trustedSeller: currentTrustedSeller ?? false,
|
|
134
139
|
suspensionReason: "",
|
|
135
140
|
capabilities: currentCapabilities ?? DEFAULT_CAPABILITIES,
|
|
136
141
|
adminNotes: currentAdminNotes ?? "",
|
|
137
142
|
});
|
|
138
|
-
}, [
|
|
143
|
+
}, [
|
|
144
|
+
open,
|
|
145
|
+
currentStatus,
|
|
146
|
+
currentIsVerified,
|
|
147
|
+
currentIsFeatured,
|
|
148
|
+
currentSellerTier,
|
|
149
|
+
currentTrustedSeller,
|
|
150
|
+
currentCapabilities,
|
|
151
|
+
currentAdminNotes,
|
|
152
|
+
]);
|
|
139
153
|
const saveMutation = useApiMutation({
|
|
140
154
|
errorMessage: "Failed to update store.",
|
|
141
155
|
mutationFn: async () => {
|
|
@@ -157,6 +171,21 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
|
|
|
157
171
|
adminNotes: parsed.adminNotes ?? "",
|
|
158
172
|
isFeatured: parsed.isFeatured,
|
|
159
173
|
isVerified: parsed.isVerified,
|
|
174
|
+
/*
|
|
175
|
+
* 🛑 `sellerTier` is sent ONLY when the admin actually moved it away
|
|
176
|
+
* from the stored value, because the route treats any `sellerTier` in
|
|
177
|
+
* the body as a deliberate override and PINS it against the monthly
|
|
178
|
+
* recompute. Sending it unconditionally would lock every store an
|
|
179
|
+
* admin ever opened and saved — quietly freezing the automatic tier
|
|
180
|
+
* for the whole catalogue, one unrelated edit at a time.
|
|
181
|
+
*
|
|
182
|
+
* `trustedSeller` has no such side effect and is sent every time, so
|
|
183
|
+
* it can also be cleared.
|
|
184
|
+
*/
|
|
185
|
+
...(parsed.sellerTier !== undefined && parsed.sellerTier !== (currentSellerTier ?? "seller")
|
|
186
|
+
? { sellerTier: parsed.sellerTier }
|
|
187
|
+
: {}),
|
|
188
|
+
trustedSeller: parsed.trustedSeller ?? false,
|
|
160
189
|
suspensionReason: parsed.suspensionReason || undefined,
|
|
161
190
|
capabilities: parsed.capabilities ?? [],
|
|
162
191
|
});
|
|
@@ -6,7 +6,7 @@ export function AdminStoresGuideView() {
|
|
|
6
6
|
return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(Store, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Stores & Sellers" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Store lifecycle, identity architecture, capabilities, and suspension on LetItRip." })] }), [
|
|
7
7
|
{
|
|
8
8
|
Icon: Store, title: "Store Lifecycle",
|
|
9
|
-
content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Creation" }), ": Seller registers \u2192 completes onboarding wizard \u2192 admin reviews \u2192 store goes ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "active" }), "."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Status values" }), ": ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "active" }), " (visible and selling), ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "suspended" }), " (hidden, not selling), ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "
|
|
9
|
+
content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Creation" }), ": Seller registers \u2192 completes onboarding wizard \u2192 admin reviews \u2192 store goes ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "active" }), "."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Status values" }), ": ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "pending" }), " (awaiting approval, not public), ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "active" }), " (visible and selling), ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "suspended" }), " (hidden, not selling), ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "rejected" }), " (application refused). Only ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "active" }), " sets ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "isPublic" }), ", and leaving it clears any earned tier immediately."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "isVerified badge" }), ": An IDENTITY check, not a performance one \u2014 grant it after reviewing the seller's submitted ID. There is no order-count or rating threshold, and it does not expire. Toggle it in AdminStoreEditorView."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "sellerTier" }), ": The EARNED tier, computed by ", _jsx(Code, { size: "xs", children: "sellerStandingRollup" }), " on the last day of each month. Setting it by hand pins it (", _jsx(Code, { size: "xs", children: "tierLockedByAdmin" }), ") so the job stops moving it \u2014 and it carries early payout with it. See the Trust guide."] })] })),
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
Icon: GitBranch, title: "Store Identity Architecture",
|
|
@@ -37,6 +37,34 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
37
37
|
void queryClient.invalidateQueries({ queryKey: ["admin", "stores", "listing"] });
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
|
+
/*
|
|
41
|
+
* 🛑 Approve/reject — the step that had no affordance anywhere.
|
|
42
|
+
*
|
|
43
|
+
* A store is born `pending` + `isPublic: false`, and this view offered only
|
|
44
|
+
* verify and suspend, so a real applicant's store could never be published.
|
|
45
|
+
* Both mutations go through the same STORE PATCH the rest of this view uses,
|
|
46
|
+
* which dispatches to `storeRepository.setStatus` and is therefore the only
|
|
47
|
+
* path that keeps `status` and `isPublic` in step. Do not reach for
|
|
48
|
+
* `adminUpdateStoreStatus`: it writes the user-level `storeStatus` mirror
|
|
49
|
+
* and never touches the store, so it would report success and publish
|
|
50
|
+
* nothing.
|
|
51
|
+
*/
|
|
52
|
+
const approveStore = useApiMutation({
|
|
53
|
+
errorMessage: "Failed to approve store.",
|
|
54
|
+
mutationFn: (storeId) => apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), { storeStatus: "active" }),
|
|
55
|
+
onSuccess: () => {
|
|
56
|
+
toast.showToast("Store approved — it is now public.", "success");
|
|
57
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "stores", "listing"] });
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const rejectStore = useApiMutation({
|
|
61
|
+
errorMessage: "Failed to reject store.",
|
|
62
|
+
mutationFn: (storeId) => apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), { storeStatus: "rejected" }),
|
|
63
|
+
onSuccess: () => {
|
|
64
|
+
toast.showToast("Store application rejected.", "success");
|
|
65
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "stores", "listing"] });
|
|
66
|
+
},
|
|
67
|
+
});
|
|
40
68
|
if (React.Children.count(children) > 0) {
|
|
41
69
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
42
70
|
}
|
|
@@ -108,7 +136,10 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
108
136
|
},
|
|
109
137
|
})),
|
|
110
138
|
renderRowActions: (row) => {
|
|
111
|
-
const
|
|
139
|
+
const status = row.status?.toLowerCase();
|
|
140
|
+
const isSuspended = status === "suspended";
|
|
141
|
+
const isPending = status === "pending";
|
|
142
|
+
const isActive = status === "active";
|
|
112
143
|
const isVerified = Boolean(row._raw?.isVerified);
|
|
113
144
|
return (_jsx(RowActionMenu, { actions: [
|
|
114
145
|
{
|
|
@@ -119,6 +150,24 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
119
150
|
label: "Open full page",
|
|
120
151
|
onClick: () => router.push(String(ROUTES.ADMIN.STORE_DETAIL(row.id))),
|
|
121
152
|
},
|
|
153
|
+
/*
|
|
154
|
+
* Approve is offered for anything not already active — that
|
|
155
|
+
* deliberately includes `rejected` and `suspended`, which is how a
|
|
156
|
+
* decision gets reversed without hunting for a second control.
|
|
157
|
+
* Reject is offered only while `pending`: rejecting a store that
|
|
158
|
+
* has been live and has orders is a suspension, not a rejection,
|
|
159
|
+
* and conflating them would lose that distinction in the audit log.
|
|
160
|
+
*/
|
|
161
|
+
{
|
|
162
|
+
label: ACTIONS.ADMIN["approve-store"].label,
|
|
163
|
+
onClick: () => approveStore.mutate(row.id),
|
|
164
|
+
disabled: isActive || approveStore.isPending,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: ACTIONS.ADMIN["reject-store"].label,
|
|
168
|
+
onClick: () => rejectStore.mutate(row.id),
|
|
169
|
+
disabled: !isPending || rejectStore.isPending,
|
|
170
|
+
},
|
|
122
171
|
{
|
|
123
172
|
label: ACTIONS.ADMIN["verify-store"].label,
|
|
124
173
|
onClick: () => verifyStore.mutate(row.id),
|
|
@@ -133,7 +182,15 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
133
182
|
},
|
|
134
183
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_STORE_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
|
|
135
184
|
};
|
|
136
|
-
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminStoreEditorView, { open: Boolean(editorRow), onClose: () => setEditorRow(null), storeId: editorRow?.id, storeName: editorRow?.primary, currentStatus: editorRow?.status?.toLowerCase(), currentIsVerified: Boolean(editorRow?._raw?.isVerified), currentIsFeatured: Boolean(editorRow?._raw?.isFeatured),
|
|
185
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminStoreEditorView, { open: Boolean(editorRow), onClose: () => setEditorRow(null), storeId: editorRow?.id, storeName: editorRow?.primary, currentStatus: editorRow?.status?.toLowerCase(), currentIsVerified: Boolean(editorRow?._raw?.isVerified), currentIsFeatured: Boolean(editorRow?._raw?.isFeatured),
|
|
186
|
+
/*
|
|
187
|
+
* Both now travel from the list route's serializer through to the
|
|
188
|
+
* editor. `trustedSeller` was ALREADY emitted by the route and simply
|
|
189
|
+
* never passed down — the API accepted the field, nothing could send
|
|
190
|
+
* it, and the control did not exist. Root Cause #38's shape at the
|
|
191
|
+
* prop, which is where it hides best.
|
|
192
|
+
*/
|
|
193
|
+
currentSellerTier: editorRow?._raw?.sellerTier === "trusted" ? "trusted" : "seller", currentTrustedSeller: Boolean(editorRow?._raw?.trustedSeller), currentCapabilities: Array.isArray(editorRow?._raw?.capabilities)
|
|
137
194
|
? editorRow._raw.capabilities
|
|
138
195
|
: undefined,
|
|
139
196
|
/*
|
|
@@ -3,8 +3,18 @@ import { Shield, Ban, AlertTriangle, Headphones, Flag, ClipboardList } from "luc
|
|
|
3
3
|
import { Alert, Code, Div, Heading, Li, Row, Section, Span, Stack, Table, Tbody, Td, Text, Th, Thead, Tr, Ul } from "../../../ui/index.js";
|
|
4
4
|
import { GC } from "../../_guide-cls.js";
|
|
5
5
|
const STILL_ALLOWED = "✓ Still allowed";
|
|
6
|
+
/** Named because it is used on three rows in the seller-trust table. */
|
|
7
|
+
const ROW_BORDER = "border-b border-[var(--appkit-color-border)]";
|
|
6
8
|
export function AdminTrustGuideView() {
|
|
7
9
|
return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(Shield, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Trust & Safety" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Bans, scam registry, support tickets, moderation, and reports on LetItRip." })] }), [
|
|
10
|
+
{
|
|
11
|
+
Icon: Shield, title: "Seller trust: three fields, two of them visible",
|
|
12
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Div, { overflow: "x-auto", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { children: _jsx(Tr, { className: ROW_BORDER, children: ["Field", "Public?", "What it means", "Who sets it"].map((h) => (_jsx(Th, { align: "left", paddingSide: "pr-md", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: h }, h))) }) }), _jsx(Tbody, { size: "sm", color: "muted", children: [
|
|
13
|
+
["isVerified", "Yes — badge", "We checked WHO they are (ID review). No volume threshold; does not expire.", "Admin, by hand"],
|
|
14
|
+
["sellerTier", "Yes — badge", "EARNED from the last 90 days of trading. Recomputed monthly.", "sellerStandingRollup, or an admin override that pins it"],
|
|
15
|
+
["trustedSeller", "NO — never", "We pay this store at SHIPPING, not at delivery. A risk transfer.", "The tier carries it; an admin can also set it alone"],
|
|
16
|
+
].map((row) => (_jsx(Tr, { className: ROW_BORDER, children: row.map((cell, i) => (_jsx(Td, { paddingSide: "pr-md", padding: "xs-tall", children: i === 0 ? _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: cell }) : cell }, i))) }, row[0]))) })] }) }), _jsxs(Alert, { variant: "warning", title: "trustedSeller moves money", children: ["It means the platform pays before the parcel lands, so we carry a lost parcel and a non-delivery dispute with the money already gone. It is never shown to buyers \u2014 a store does not get to display that we trust it. Every change writes a ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "STORE_TRUST_CHANGE" }), " ", "audit row naming who accepted the risk, including when the monthly job is the one that decided."] }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsxs(Li, { className: GC.listDiscMuted, children: [_jsx(Span, { weight: "bold", children: "Setting sellerTier by hand PINS it." }), " It writes", " ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "tierLockedByAdmin" }), ", and the monthly job then skips that store entirely \u2014 it still refreshes their figures, it just stops moving the tier. Without the pin an override would silently revert at the next month-end."] }), _jsxs(Li, { className: GC.listDiscMuted, children: [_jsx(Span, { weight: "bold", children: "Suspending a store revokes trust immediately" }), ", not at month end. Monthly caching would otherwise keep paying a suspended store early for up to 31 days. Re-activating does NOT re-grant it; the next month-end decides."] }), _jsxs(Li, { className: GC.listDiscMuted, children: [_jsx(Span, { weight: "bold", children: "The criteria live in one place" }), " \u2014", " ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "SELLER_STANDING_DEFAULTS" }), ", admin-tunable under Site Settings. The seller's dashboard panel, the public seller guide and the buyer-facing badge page all render from it, so they cannot state different numbers from the job that grants it."] }), _jsxs(Li, { className: GC.listDiscMuted, children: [_jsx(Span, { weight: "bold", children: "Self-bought and test orders do not count" }), ", and both a distinct-buyer and a distinct-address floor must be cleared \u2014 the tier releases money early, so buying from yourself 25 times is the attack it has to survive."] })] })] })),
|
|
17
|
+
},
|
|
8
18
|
{
|
|
9
19
|
Icon: Ban, title: "Soft Ban vs Hard Ban",
|
|
10
20
|
content: (_jsxs(_Fragment, { children: [_jsx(Div, { overflow: "x-auto", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { children: _jsx(Tr, { className: "border-b border-[var(--appkit-color-border)]", children: ["", "Soft Ban", "Hard Ban"].map((h) => (_jsx(Th, { align: "left", paddingSide: "pr-md", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: h }, h))) }) }), _jsx(Tbody, { size: "sm", color: "muted", children: [
|
|
@@ -76,33 +76,38 @@ export declare const SITE_SETTINGS_TABS: readonly [{
|
|
|
76
76
|
readonly label: "⑫ Legal";
|
|
77
77
|
readonly description: "Terms, privacy policy, refund policy and the GST registration details.";
|
|
78
78
|
readonly keywords: ["terms", "privacy", "policy", "gstin", "refund policy", "compliance"];
|
|
79
|
+
}, {
|
|
80
|
+
readonly id: "whatsapp";
|
|
81
|
+
readonly label: "⑬ WhatsApp";
|
|
82
|
+
readonly description: "WhatsApp Business credentials and which messages go out over it.";
|
|
83
|
+
readonly keywords: ["whatsapp", "meta", "business api", "order updates"];
|
|
79
84
|
}, {
|
|
80
85
|
readonly id: "notifications";
|
|
81
|
-
readonly label: "
|
|
82
|
-
readonly description: "Which channels are on — email
|
|
86
|
+
readonly label: "⑭ Notifications";
|
|
87
|
+
readonly description: "Which channels are on — email, WhatsApp, SMS — and the daily digest.";
|
|
83
88
|
readonly keywords: ["email", "sms", "alerts", "digest", "notify"];
|
|
84
89
|
}, {
|
|
85
90
|
readonly id: "procurement";
|
|
86
|
-
readonly label: "
|
|
91
|
+
readonly label: "⑮ Procurement";
|
|
87
92
|
readonly description: "Labour rate and the defaults used to price inbound shipments.";
|
|
88
93
|
readonly keywords: ["labour rate", "labor", "import", "landed cost", "supplier"];
|
|
89
94
|
}, {
|
|
90
95
|
readonly id: "emi";
|
|
91
|
-
readonly label: "
|
|
96
|
+
readonly label: "⑯ EMI";
|
|
92
97
|
readonly description: "Instalment plans: eligibility threshold, tenures and the surcharge split.";
|
|
93
98
|
readonly keywords: ["instalments", "installments", "emi", "pay monthly", "finance"];
|
|
94
99
|
}, {
|
|
95
100
|
readonly id: "gst";
|
|
96
|
-
readonly label: "
|
|
101
|
+
readonly label: "⑰ GST";
|
|
97
102
|
readonly description: "GST rates, HSN defaults and the registered business details.";
|
|
98
103
|
readonly keywords: ["tax", "gst", "hsn", "vat", "invoice tax"];
|
|
99
104
|
}, {
|
|
100
105
|
readonly id: "listings";
|
|
101
|
-
readonly label: "
|
|
106
|
+
readonly label: "⑱ Listings";
|
|
102
107
|
readonly description: "Which listing types sellers may create, and their defaults.";
|
|
103
108
|
readonly keywords: ["listing types", "auctions on", "enable", "categories"];
|
|
104
109
|
}];
|
|
105
|
-
export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "emi" | "legal" | "appearance" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "procurement")[];
|
|
110
|
+
export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "whatsapp" | "emi" | "legal" | "appearance" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "procurement")[];
|
|
106
111
|
/**
|
|
107
112
|
* The union `?tab=` is validated against.
|
|
108
113
|
*
|
|
@@ -108,33 +108,39 @@ export const SITE_SETTINGS_TABS = [
|
|
|
108
108
|
description: "Terms, privacy policy, refund policy and the GST registration details.",
|
|
109
109
|
keywords: ["terms", "privacy", "policy", "gstin", "refund policy", "compliance"],
|
|
110
110
|
},
|
|
111
|
+
{
|
|
112
|
+
id: "whatsapp",
|
|
113
|
+
label: "⑬ WhatsApp",
|
|
114
|
+
description: "WhatsApp Business credentials and which messages go out over it.",
|
|
115
|
+
keywords: ["whatsapp", "meta", "business api", "order updates"],
|
|
116
|
+
},
|
|
111
117
|
{
|
|
112
118
|
id: "notifications",
|
|
113
|
-
label: "
|
|
114
|
-
description: "Which channels are on — email
|
|
119
|
+
label: "⑭ Notifications",
|
|
120
|
+
description: "Which channels are on — email, WhatsApp, SMS — and the daily digest.",
|
|
115
121
|
keywords: ["email", "sms", "alerts", "digest", "notify"],
|
|
116
122
|
},
|
|
117
123
|
{
|
|
118
124
|
id: "procurement",
|
|
119
|
-
label: "
|
|
125
|
+
label: "⑮ Procurement",
|
|
120
126
|
description: "Labour rate and the defaults used to price inbound shipments.",
|
|
121
127
|
keywords: ["labour rate", "labor", "import", "landed cost", "supplier"],
|
|
122
128
|
},
|
|
123
129
|
{
|
|
124
130
|
id: "emi",
|
|
125
|
-
label: "
|
|
131
|
+
label: "⑯ EMI",
|
|
126
132
|
description: "Instalment plans: eligibility threshold, tenures and the surcharge split.",
|
|
127
133
|
keywords: ["instalments", "installments", "emi", "pay monthly", "finance"],
|
|
128
134
|
},
|
|
129
135
|
{
|
|
130
136
|
id: "gst",
|
|
131
|
-
label: "
|
|
137
|
+
label: "⑰ GST",
|
|
132
138
|
description: "GST rates, HSN defaults and the registered business details.",
|
|
133
139
|
keywords: ["tax", "gst", "hsn", "vat", "invoice tax"],
|
|
134
140
|
},
|
|
135
141
|
{
|
|
136
142
|
id: "listings",
|
|
137
|
-
label: "
|
|
143
|
+
label: "⑱ Listings",
|
|
138
144
|
description: "Which listing types sellers may create, and their defaults.",
|
|
139
145
|
keywords: ["listing types", "auctions on", "enable", "categories"],
|
|
140
146
|
},
|
|
@@ -137,7 +137,7 @@ export class SiteSettingsRepository extends BaseRepository {
|
|
|
137
137
|
encrypted[key] = encryptSecret(value.trim());
|
|
138
138
|
}
|
|
139
139
|
else if (value !== undefined && typeof value !== "string") {
|
|
140
|
-
// Non-string credential fields (e.g.
|
|
140
|
+
// Non-string credential fields (e.g. whatsappTemplates: Record<string,string>)
|
|
141
141
|
// are stored as-is — they are not secret values to encrypt.
|
|
142
142
|
encrypted[key] = value;
|
|
143
143
|
}
|
|
@@ -11,6 +11,8 @@ export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
11
11
|
storeStatus: z.ZodEnum<[StoreStatus, ...StoreStatus[]]>;
|
|
12
12
|
isVerified: z.ZodOptional<z.ZodBoolean>;
|
|
13
13
|
isFeatured: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
sellerTier: z.ZodOptional<z.ZodEnum<["seller", "trusted"]>>;
|
|
15
|
+
trustedSeller: z.ZodOptional<z.ZodBoolean>;
|
|
14
16
|
suspensionReason: z.ZodOptional<z.ZodString>;
|
|
15
17
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
|
|
16
18
|
adminNotes: z.ZodOptional<z.ZodString>;
|
|
@@ -21,6 +23,8 @@ export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
21
23
|
adminNotes?: string | undefined;
|
|
22
24
|
suspensionReason?: string | undefined;
|
|
23
25
|
capabilities?: string[] | undefined;
|
|
26
|
+
sellerTier?: "seller" | "trusted" | undefined;
|
|
27
|
+
trustedSeller?: boolean | undefined;
|
|
24
28
|
}, {
|
|
25
29
|
storeStatus: StoreStatus;
|
|
26
30
|
isVerified?: boolean | undefined;
|
|
@@ -28,6 +32,8 @@ export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
28
32
|
adminNotes?: string | undefined;
|
|
29
33
|
suspensionReason?: string | undefined;
|
|
30
34
|
capabilities?: string[] | undefined;
|
|
35
|
+
sellerTier?: "seller" | "trusted" | undefined;
|
|
36
|
+
trustedSeller?: boolean | undefined;
|
|
31
37
|
}>, {
|
|
32
38
|
storeStatus: StoreStatus;
|
|
33
39
|
isVerified?: boolean | undefined;
|
|
@@ -35,6 +41,8 @@ export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
35
41
|
adminNotes?: string | undefined;
|
|
36
42
|
suspensionReason?: string | undefined;
|
|
37
43
|
capabilities?: string[] | undefined;
|
|
44
|
+
sellerTier?: "seller" | "trusted" | undefined;
|
|
45
|
+
trustedSeller?: boolean | undefined;
|
|
38
46
|
}, {
|
|
39
47
|
storeStatus: StoreStatus;
|
|
40
48
|
isVerified?: boolean | undefined;
|
|
@@ -42,6 +50,8 @@ export declare const adminStoreUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
42
50
|
adminNotes?: string | undefined;
|
|
43
51
|
suspensionReason?: string | undefined;
|
|
44
52
|
capabilities?: string[] | undefined;
|
|
53
|
+
sellerTier?: "seller" | "trusted" | undefined;
|
|
54
|
+
trustedSeller?: boolean | undefined;
|
|
45
55
|
}>;
|
|
46
56
|
export type AdminStoreUpdateValues = z.infer<typeof adminStoreUpdateSchema>;
|
|
47
57
|
/**
|
|
@@ -75,6 +75,40 @@ export const adminStoreUpdateSchema = z
|
|
|
75
75
|
isFeatured: annotate(z.boolean().optional(), {
|
|
76
76
|
section: "standing", order: 3, row: "quarter",
|
|
77
77
|
}),
|
|
78
|
+
/*
|
|
79
|
+
* The EARNED tier, overridable by hand.
|
|
80
|
+
*
|
|
81
|
+
* 🛑 Setting it here PINS it — the monthly `sellerStandingRollup` skips a
|
|
82
|
+
* store whose `standing.tierLockedByAdmin` is true. Without the pin an
|
|
83
|
+
* override would silently revert at the next month-end, which is worse
|
|
84
|
+
* than no override: an admin grants a tier, sees it work, and finds it
|
|
85
|
+
* undone weeks later with nothing saying why.
|
|
86
|
+
*
|
|
87
|
+
* 🛑 It carries EARLY PAYOUT with it (see `trustedSeller` below) — the
|
|
88
|
+
* owner's decision is that reaching the tier grants pay-at-shipping. This
|
|
89
|
+
* control therefore moves money, which is why the help text says so.
|
|
90
|
+
*/
|
|
91
|
+
sellerTier: annotate(z.enum(["seller", "trusted"]).optional(), {
|
|
92
|
+
section: "standing", order: 5, row: "pair", kind: "select",
|
|
93
|
+
help: "Normally earned automatically at each month-end. Setting it here pins it and also switches payouts to start at shipping.",
|
|
94
|
+
}),
|
|
95
|
+
/*
|
|
96
|
+
* 🛑 This field has existed and been accepted by the API since it was
|
|
97
|
+
* added, with NO control anywhere in the admin UI — so the only way to set
|
|
98
|
+
* it was a hand-written PATCH. A flag that moves money and cannot be
|
|
99
|
+
* operated is worse than one that does not exist, because the code reads
|
|
100
|
+
* as though an admin is in control of it.
|
|
101
|
+
*
|
|
102
|
+
* Separate from `sellerTier` on purpose: the tier is the public, earned
|
|
103
|
+
* thing, and this is the private risk posture. An admin occasionally needs
|
|
104
|
+
* one without the other — early payout for a high-volume store that has
|
|
105
|
+
* not met the rating bar, or a badge for the platform's own store without
|
|
106
|
+
* changing its payout timing.
|
|
107
|
+
*/
|
|
108
|
+
trustedSeller: annotate(z.boolean().optional(), {
|
|
109
|
+
section: "standing", order: 6, row: "quarter",
|
|
110
|
+
help: "Pay this store when it ships, not when the parcel is delivered. The platform carries a lost parcel with the money already gone. Never shown to buyers.",
|
|
111
|
+
}),
|
|
78
112
|
/*
|
|
79
113
|
* `when` mirrors the superRefine below, which is what stops the two from
|
|
80
114
|
* disagreeing. The page used to keep this textarea live at all times and
|
|
@@ -65,6 +65,67 @@ export declare function toPublicProfilePayload(v: {
|
|
|
65
65
|
linkedin?: string;
|
|
66
66
|
}): Record<string, JsonValue> | undefined;
|
|
67
67
|
export type AdminUserUpdateValues = z.infer<typeof adminUserUpdateSchema>;
|
|
68
|
+
/**
|
|
69
|
+
* A store's WhatsApp Business connection.
|
|
70
|
+
*
|
|
71
|
+
* `accessToken` is optional because the form shows a masked placeholder and
|
|
72
|
+
* only sends the field when the seller types a NEW value — so "absent" means
|
|
73
|
+
* "unchanged", not "clear it". The `.min(20)` therefore applies only to a real
|
|
74
|
+
* replacement, and is there because a truncated token fails silently at sync
|
|
75
|
+
* time rather than on save.
|
|
76
|
+
*/
|
|
77
|
+
export declare const whatsappSettingsSchema: z.ZodObject<{
|
|
78
|
+
catalogSyncEnabled: z.ZodBoolean;
|
|
79
|
+
phoneNumber: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
80
|
+
wabaId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
81
|
+
catalogId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
82
|
+
accessToken: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
83
|
+
}, "strip", z.ZodTypeAny, {
|
|
84
|
+
catalogSyncEnabled: boolean;
|
|
85
|
+
phoneNumber?: string | undefined;
|
|
86
|
+
accessToken?: string | undefined;
|
|
87
|
+
wabaId?: string | undefined;
|
|
88
|
+
catalogId?: string | undefined;
|
|
89
|
+
}, {
|
|
90
|
+
catalogSyncEnabled: boolean;
|
|
91
|
+
phoneNumber?: string | undefined;
|
|
92
|
+
accessToken?: string | undefined;
|
|
93
|
+
wabaId?: string | undefined;
|
|
94
|
+
catalogId?: string | undefined;
|
|
95
|
+
}>;
|
|
96
|
+
export type WhatsappSettingsValues = z.infer<typeof whatsappSettingsSchema>;
|
|
97
|
+
/**
|
|
98
|
+
* The CONNECTION form's fields — credentials only.
|
|
99
|
+
*
|
|
100
|
+
* `catalogSyncEnabled` is deliberately absent. Its toggle lives in a different
|
|
101
|
+
* card and owns its own write (`syncToggleMutation`), because the credentials
|
|
102
|
+
* form used to send it unconditionally from two cards away: a seller flipped
|
|
103
|
+
* the switch, saw it move, and nothing persisted it. A field in this schema is
|
|
104
|
+
* a field this form's Save button owns.
|
|
105
|
+
*
|
|
106
|
+
* 🛑 `.omit()` rather than a second literal, so the two cannot drift — it
|
|
107
|
+
* carries the SAME field instances into the new shape, which is what keeps the
|
|
108
|
+
* `annotate()` WeakMap entries alive. (A wrapper such as `.superRefine()` is
|
|
109
|
+
* transparent for the same reason; a field-level re-wrap is not.)
|
|
110
|
+
*/
|
|
111
|
+
export declare const whatsappConnectionSchema: z.ZodObject<Omit<{
|
|
112
|
+
catalogSyncEnabled: z.ZodBoolean;
|
|
113
|
+
phoneNumber: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
114
|
+
wabaId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
115
|
+
catalogId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
116
|
+
accessToken: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
117
|
+
}, "catalogSyncEnabled">, "strip", z.ZodTypeAny, {
|
|
118
|
+
phoneNumber?: string | undefined;
|
|
119
|
+
accessToken?: string | undefined;
|
|
120
|
+
wabaId?: string | undefined;
|
|
121
|
+
catalogId?: string | undefined;
|
|
122
|
+
}, {
|
|
123
|
+
phoneNumber?: string | undefined;
|
|
124
|
+
accessToken?: string | undefined;
|
|
125
|
+
wabaId?: string | undefined;
|
|
126
|
+
catalogId?: string | undefined;
|
|
127
|
+
}>;
|
|
128
|
+
export type WhatsappConnectionValues = z.infer<typeof whatsappConnectionSchema>;
|
|
68
129
|
/**
|
|
69
130
|
* A buyer's lottery entry.
|
|
70
131
|
*
|
|
@@ -8,20 +8,24 @@
|
|
|
8
8
|
* false, and saving any unrelated field silently stripped a real
|
|
9
9
|
* tester's access.
|
|
10
10
|
*
|
|
11
|
+
* `SellerWhatsAppSettingsView` writes `accessToken` — a live Meta
|
|
12
|
+
* credential — plus the WABA and catalog ids the sync runs against. A
|
|
13
|
+
* malformed id there fails at sync time, hours later, with no obvious
|
|
14
|
+
* cause.
|
|
11
15
|
*
|
|
12
16
|
* `LotteryPullForm` is a buyer-facing entry: a transaction id, a payment
|
|
13
17
|
* time and an item number, all parsed with a bare `parseInt` and a `new
|
|
14
18
|
* Date(...)` that yields `Invalid Date` on anything unparseable.
|
|
15
19
|
*
|
|
16
20
|
* EXPORTS:
|
|
17
|
-
* adminUserUpdateSchema, lotteryPullSchema,
|
|
21
|
+
* adminUserUpdateSchema, whatsappSettingsSchema, lotteryPullSchema,
|
|
18
22
|
* and their inferred value types
|
|
19
23
|
*
|
|
20
|
-
* @tag domain:admin,lottery
|
|
24
|
+
* @tag domain:admin,whatsapp,lottery
|
|
21
25
|
* @tag layer:schema
|
|
22
26
|
* @tag pattern:none
|
|
23
27
|
* @tag access:isomorphic
|
|
24
|
-
* @tag consumers:AdminUserEditorView,LotteryPullForm
|
|
28
|
+
* @tag consumers:AdminUserEditorView,SellerWhatsAppSettingsView,LotteryPullForm
|
|
25
29
|
* @tag sideEffects:none
|
|
26
30
|
*/
|
|
27
31
|
import { z } from "zod";
|
|
@@ -127,6 +131,43 @@ export function toPublicProfilePayload(v) {
|
|
|
127
131
|
publicProfile.socialLinks = socialLinks;
|
|
128
132
|
return Object.keys(publicProfile).length > 0 ? publicProfile : undefined;
|
|
129
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* A store's WhatsApp Business connection.
|
|
136
|
+
*
|
|
137
|
+
* `accessToken` is optional because the form shows a masked placeholder and
|
|
138
|
+
* only sends the field when the seller types a NEW value — so "absent" means
|
|
139
|
+
* "unchanged", not "clear it". The `.min(20)` therefore applies only to a real
|
|
140
|
+
* replacement, and is there because a truncated token fails silently at sync
|
|
141
|
+
* time rather than on save.
|
|
142
|
+
*/
|
|
143
|
+
export const whatsappSettingsSchema = z.object({
|
|
144
|
+
catalogSyncEnabled: annotate(z.boolean(), {
|
|
145
|
+
section: "sync", sectionLabel: "Catalog sync", sectionRequired: true, quick: true, order: 1, row: "quarter",
|
|
146
|
+
}),
|
|
147
|
+
phoneNumber: annotate(z.string().trim().regex(PHONE_RE, "Enter the 10-digit WhatsApp Business number.").optional().or(z.literal("")), { section: "connection", sectionLabel: "Connection", order: 1, row: "pair" }),
|
|
148
|
+
// Meta ids are numeric strings; a pasted URL or a stray space is the usual
|
|
149
|
+
// mistake and produces a sync that never runs.
|
|
150
|
+
wabaId: annotate(z.string().trim().regex(/^\d{5,32}$/, "A WABA ID is a numeric id, not a URL.").optional().or(z.literal("")), { section: "connection", order: 2, row: "pair" }),
|
|
151
|
+
catalogId: annotate(z.string().trim().regex(/^\d{5,32}$/, "A catalog ID is a numeric id, not a URL.").optional().or(z.literal("")), { section: "connection", order: 3, row: "pair" }),
|
|
152
|
+
accessToken: annotate(z.string().trim().min(20, "That access token looks truncated.").optional().or(z.literal("")), { section: "connection", order: 4, row: "full", help: "Leave blank to keep the current token." }),
|
|
153
|
+
});
|
|
154
|
+
/**
|
|
155
|
+
* The CONNECTION form's fields — credentials only.
|
|
156
|
+
*
|
|
157
|
+
* `catalogSyncEnabled` is deliberately absent. Its toggle lives in a different
|
|
158
|
+
* card and owns its own write (`syncToggleMutation`), because the credentials
|
|
159
|
+
* form used to send it unconditionally from two cards away: a seller flipped
|
|
160
|
+
* the switch, saw it move, and nothing persisted it. A field in this schema is
|
|
161
|
+
* a field this form's Save button owns.
|
|
162
|
+
*
|
|
163
|
+
* 🛑 `.omit()` rather than a second literal, so the two cannot drift — it
|
|
164
|
+
* carries the SAME field instances into the new shape, which is what keeps the
|
|
165
|
+
* `annotate()` WeakMap entries alive. (A wrapper such as `.superRefine()` is
|
|
166
|
+
* transparent for the same reason; a field-level re-wrap is not.)
|
|
167
|
+
*/
|
|
168
|
+
export const whatsappConnectionSchema = whatsappSettingsSchema.omit({
|
|
169
|
+
catalogSyncEnabled: true,
|
|
170
|
+
});
|
|
130
171
|
/**
|
|
131
172
|
* A buyer's lottery entry.
|
|
132
173
|
*
|