@mohasinac/appkit 4.41.0 → 4.41.1
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/features/shell/SectionForm.js +44 -5
- package/package.json +2 -2
- package/dist/_internal/server/features/classified/actions.d.ts +0 -12
- package/dist/_internal/server/features/classified/actions.js +0 -33
- package/dist/_internal/server/features/digital-code/actions.d.ts +0 -12
- package/dist/_internal/server/features/digital-code/actions.js +0 -33
- package/dist/_internal/server/features/homepage/data.d.ts +0 -18
- package/dist/_internal/server/features/homepage/data.js +0 -70
- package/dist/_internal/server/features/live/actions.d.ts +0 -6
- package/dist/_internal/server/features/live/actions.js +0 -27
- package/dist/_internal/server/features/messages/actions.d.ts +0 -6
- package/dist/_internal/server/features/messages/actions.js +0 -6
- package/dist/_internal/server/features/messages/data.d.ts +0 -10
- package/dist/_internal/server/features/messages/data.js +0 -24
- package/dist/_internal/server/features/messages/index.d.ts +0 -2
- package/dist/_internal/server/features/messages/index.js +0 -2
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +0 -12
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +0 -112
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +0 -2
- package/dist/_internal/shared/features/homepage/section-gate.d.ts +0 -14
- package/dist/_internal/shared/features/homepage/section-gate.js +0 -51
- package/dist/_internal/shared/features/lottery/config-form.d.ts +0 -58
- package/dist/_internal/shared/features/lottery/config-form.js +0 -71
- package/dist/client/api/useFeatureFlags.d.ts +0 -22
- package/dist/client/api/useFeatureFlags.js +0 -27
- package/dist/features/account/address-validation.d.ts +0 -20
- package/dist/features/account/address-validation.js +0 -35
- package/dist/features/account/components/ChatList.d.ts +0 -14
- package/dist/features/account/components/ChatList.js +0 -9
- package/dist/features/account/components/ChatWindow.d.ts +0 -17
- package/dist/features/account/components/ChatWindow.js +0 -6
- package/dist/features/account/components/MessagesView.d.ts +0 -16
- package/dist/features/account/components/MessagesView.js +0 -5
- package/dist/features/account/components/UserOffersView.d.ts +0 -10
- package/dist/features/account/components/UserOffersView.js +0 -5
- package/dist/features/account/constants/addresses.d.ts +0 -13
- package/dist/features/account/constants/addresses.js +0 -43
- package/dist/features/account/hooks/useAddressForm.d.ts +0 -24
- package/dist/features/account/hooks/useAddressForm.js +0 -58
- package/dist/features/admin/actions/chat-actions.d.ts +0 -29
- package/dist/features/admin/actions/chat-actions.js +0 -101
- package/dist/features/admin/components/AdminFeatureFlagsView.d.ts +0 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +0 -109
- package/dist/features/admin/components/AdminSiteView.d.ts +0 -10
- package/dist/features/admin/components/AdminSiteView.js +0 -52
- package/dist/features/admin/hooks/useChat.d.ts +0 -24
- package/dist/features/admin/hooks/useChat.js +0 -100
- package/dist/features/admin/permission-map.d.ts +0 -3
- package/dist/features/admin/permission-map.js +0 -26
- package/dist/features/admin/repository/chat.repository.d.ts +0 -50
- package/dist/features/admin/repository/chat.repository.js +0 -223
- package/dist/features/auth/actions/password-change-otp-actions.d.ts +0 -18
- package/dist/features/auth/actions/password-change-otp-actions.js +0 -88
- package/dist/features/auth/actions/realtime-token-actions.d.ts +0 -11
- package/dist/features/auth/actions/realtime-token-actions.js +0 -38
- package/dist/features/auth/password-change-otp.d.ts +0 -34
- package/dist/features/auth/password-change-otp.js +0 -56
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +0 -12
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +0 -49
- package/dist/features/events/api/route.d.ts +0 -12
- package/dist/features/events/api/route.js +0 -90
- package/dist/features/faq/api/route.d.ts +0 -11
- package/dist/features/faq/api/route.js +0 -52
- package/dist/features/faq/hooks/useFAQs.d.ts +0 -22
- package/dist/features/faq/hooks/useFAQs.js +0 -41
- package/dist/features/media/upload/video-poster.test.d.ts +0 -1
- package/dist/features/media/upload/video-poster.test.js +0 -42
- package/dist/features/messages/actions/index.d.ts +0 -2
- package/dist/features/messages/actions/index.js +0 -2
- package/dist/features/messages/actions/messages-actions.d.ts +0 -15
- package/dist/features/messages/actions/messages-actions.js +0 -33
- package/dist/features/messages/actions/ping-rtdb.d.ts +0 -2
- package/dist/features/messages/actions/ping-rtdb.js +0 -26
- package/dist/features/messages/hooks/useConversation.d.ts +0 -22
- package/dist/features/messages/hooks/useConversation.js +0 -155
- package/dist/features/messages/hooks/useConversations.d.ts +0 -15
- package/dist/features/messages/hooks/useConversations.js +0 -104
- package/dist/features/messages/index.d.ts +0 -6
- package/dist/features/messages/index.js +0 -4
- package/dist/features/messages/realtime.d.ts +0 -32
- package/dist/features/messages/realtime.js +0 -39
- package/dist/features/messages/repository/conversations.repository.d.ts +0 -71
- package/dist/features/messages/repository/conversations.repository.js +0 -284
- package/dist/features/messages/schemas/firestore.d.ts +0 -34
- package/dist/features/messages/schemas/firestore.js +0 -16
- package/dist/features/messages/schemas/index.d.ts +0 -177
- package/dist/features/messages/schemas/index.js +0 -35
- package/dist/features/messages/server.d.ts +0 -6
- package/dist/features/messages/server.js +0 -6
- package/dist/features/products/components/MakeOfferForm.d.ts +0 -20
- package/dist/features/products/components/MakeOfferForm.js +0 -9
- package/dist/features/reviews/api/route.d.ts +0 -21
- package/dist/features/reviews/api/route.js +0 -174
- package/dist/features/seller/api/offers/route.d.ts +0 -17
- package/dist/features/seller/api/offers/route.js +0 -63
- package/dist/features/seller/components/SellerAuctionsView.d.ts +0 -9
- package/dist/features/seller/components/SellerAuctionsView.js +0 -113
- package/dist/features/seller/components/SellerOffersPanel.d.ts +0 -21
- package/dist/features/seller/components/SellerOffersPanel.js +0 -160
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +0 -6
- package/dist/features/seller/components/SellerPreOrdersView.js +0 -148
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +0 -6
- package/dist/features/seller/components/SellerPrizeDrawsView.js +0 -152
- package/dist/features/seller/permission-map.d.ts +0 -5
- package/dist/features/seller/permission-map.js +0 -17
- package/dist/features/shell/StepForm.d.ts +0 -80
- package/dist/features/shell/StepForm.js +0 -136
- package/dist/features/stores/api/route.d.ts +0 -12
- package/dist/features/stores/api/route.js +0 -110
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +0 -33
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +0 -138
- package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/bids-tester-seed-data.js +0 -37
- package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/blog-tester-seed-data.js +0 -40
- package/dist/features/tester/seed-data/carts-tester-seed-data.d.ts +0 -3
- package/dist/features/tester/seed-data/carts-tester-seed-data.js +0 -70
- package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/categories-tester-seed-data.js +0 -218
- package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +0 -76
- package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/events-tester-seed-data.js +0 -193
- package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +0 -191
- package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +0 -329
- package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/products-tester-seed-data.js +0 -815
- package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +0 -2
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +0 -79
- package/dist/features/wishlist/components/WishlistToggleButton.d.ts +0 -12
- package/dist/features/wishlist/components/WishlistToggleButton.js +0 -36
- package/dist/monitoring/analytics.d.ts +0 -80
- package/dist/monitoring/analytics.js +0 -176
- package/dist/next/middleware/pii-redactor.d.ts +0 -32
- package/dist/next/middleware/pii-redactor.js +0 -84
- package/dist/providers/db-firebase/realtime.d.ts +0 -42
- package/dist/providers/db-firebase/realtime.js +0 -138
- package/dist/security/rbac/Can.d.ts +0 -31
- package/dist/security/rbac/Can.js +0 -22
- package/dist/security/rbac/default-roles.d.ts +0 -2
- package/dist/security/rbac/default-roles.js +0 -83
- package/dist/security/rbac/hook.d.ts +0 -20
- package/dist/security/rbac/hook.js +0 -30
- package/dist/security/rbac/index.d.ts +0 -8
- package/dist/security/rbac/index.js +0 -6
- package/dist/security/rbac/middleware.d.ts +0 -51
- package/dist/security/rbac/middleware.js +0 -51
- package/dist/security/rbac/resolver.d.ts +0 -5
- package/dist/security/rbac/resolver.js +0 -38
- package/dist/security/rbac/server.d.ts +0 -24
- package/dist/security/rbac/server.js +0 -42
- package/dist/security/rbac/types.d.ts +0 -22
- package/dist/security/rbac/types.js +0 -2
- package/dist/seed/conversations-seed-data.d.ts +0 -2
- package/dist/seed/conversations-seed-data.js +0 -292
- package/dist/utils/filter.helper.d.ts +0 -18
- package/dist/utils/filter.helper.js +0 -21
- package/dist/utils/pagination.helper.d.ts +0 -23
- package/dist/utils/pagination.helper.js +0 -24
- package/dist/utils/search-tokens.d.ts +0 -2
- package/dist/utils/search-tokens.js +0 -21
|
@@ -158,6 +158,20 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
158
158
|
// validator on every render and re-fire the effect below in a loop.
|
|
159
159
|
const onValidationChangeRef = useRef(onValidationChange);
|
|
160
160
|
onValidationChangeRef.current = onValidationChange;
|
|
161
|
+
/**
|
|
162
|
+
* Are two field-error maps the same set of messages?
|
|
163
|
+
*
|
|
164
|
+
* Flat string maps, so a key sweep is exact — no deep compare needed. This is
|
|
165
|
+
* what lets the validation effect depend on `values` (a new object every
|
|
166
|
+
* render) without looping: an unchanged result returns the previous object
|
|
167
|
+
* and React bails out of the update.
|
|
168
|
+
*/
|
|
169
|
+
const sameErrors = (a, b) => {
|
|
170
|
+
const ak = Object.keys(a);
|
|
171
|
+
if (ak.length !== Object.keys(b).length)
|
|
172
|
+
return false;
|
|
173
|
+
return ak.every((k) => a[k] === b[k]);
|
|
174
|
+
};
|
|
161
175
|
/** Section ids holding at least one error, in render order. */
|
|
162
176
|
const erroringSectionIds = useCallback((errs) => {
|
|
163
177
|
const keys = Object.keys(errs);
|
|
@@ -176,8 +190,8 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
176
190
|
// applyZodIssues' incremental setFieldError-per-issue cannot do.
|
|
177
191
|
const parsed = activeSchema.safeParse(nextValues);
|
|
178
192
|
if (parsed.success) {
|
|
179
|
-
setFieldErrors({});
|
|
180
|
-
setForceOpenIds([]);
|
|
193
|
+
setFieldErrors((prev) => (Object.keys(prev).length === 0 ? prev : {}));
|
|
194
|
+
setForceOpenIds((prev) => (prev.length === 0 ? prev : []));
|
|
181
195
|
onValidationChangeRef.current?.({}, fieldToSectionIndex);
|
|
182
196
|
return;
|
|
183
197
|
}
|
|
@@ -187,7 +201,12 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
187
201
|
continue;
|
|
188
202
|
next[issue.path.map(String).join(".")] = issue.message;
|
|
189
203
|
}
|
|
190
|
-
|
|
204
|
+
/*
|
|
205
|
+
* Return the PREVIOUS object when nothing actually changed. `values` is a
|
|
206
|
+
* fresh object on every render, so the effect below re-validates on every
|
|
207
|
+
* render; handing back an equal-but-new errors map would setState in a loop.
|
|
208
|
+
*/
|
|
209
|
+
setFieldErrors((prev) => (sameErrors(prev, next) ? prev : next));
|
|
191
210
|
// Release a force-opened section as soon as ITS OWN errors clear, rather
|
|
192
211
|
// than waiting for the whole form to become valid — otherwise fixing
|
|
193
212
|
// section 1 leaves it pinned open while the user works through section 5.
|
|
@@ -198,10 +217,30 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
198
217
|
});
|
|
199
218
|
onValidationChangeRef.current?.(next, fieldToSectionIndex);
|
|
200
219
|
}, [schema, inheritedSchema, fieldToSectionIndex, erroringSectionIds]);
|
|
220
|
+
/*
|
|
221
|
+
* 🛑 `values` MUST be a dependency, and leaving it out cost the entire admin
|
|
222
|
+
* coupon editor.
|
|
223
|
+
*
|
|
224
|
+
* This effect used to run once on mount with an eslint-disable, so it
|
|
225
|
+
* validated the INITIAL draft. On an edit form that draft is the empty
|
|
226
|
+
* defaults: the record arrives asynchronously (useQuery → useEffect →
|
|
227
|
+
* patch()), and `patch` is not `handleFieldChange`, so the only other caller
|
|
228
|
+
* of runValidation never fired. The mount-time errors therefore outlived the
|
|
229
|
+
* data that fixed them.
|
|
230
|
+
*
|
|
231
|
+
* Measured on production 2026-09-15: opening ARENA25 showed "3 issues" with
|
|
232
|
+
* "A discount value is required." under a field containing 25 and "Give the
|
|
233
|
+
* campaign a name." under a populated name — the three fields that are empty
|
|
234
|
+
* BEFORE the load. Save then refused with "Coupon: This field is required"
|
|
235
|
+
* and issued no PATCH at all, so no coupon could be edited through the UI.
|
|
236
|
+
*
|
|
237
|
+
* Re-validating per render is safe because runValidation returns the previous
|
|
238
|
+
* errors object when nothing changed (see `sameErrors`); without that this
|
|
239
|
+
* would setState in a loop, which is presumably why the dep was dropped.
|
|
240
|
+
*/
|
|
201
241
|
useEffect(() => {
|
|
202
242
|
runValidation(values);
|
|
203
|
-
|
|
204
|
-
}, [schema, inheritedSchema, fieldToSectionIndex]);
|
|
243
|
+
}, [runValidation, values]);
|
|
205
244
|
const handleFieldChange = useCallback((partial) => {
|
|
206
245
|
onChange(partial);
|
|
207
246
|
runValidation({ ...values, ...partial });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohasinac/appkit",
|
|
3
|
-
"version": "4.41.
|
|
3
|
+
"version": "4.41.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
},
|
|
152
152
|
"scripts": {
|
|
153
153
|
"postinstall": "node scripts/dedupe-peer-deps.mjs",
|
|
154
|
-
"build": "node scripts/dedupe-peer-deps.mjs && node -e \"const fs=require('fs');
|
|
154
|
+
"build": "node scripts/dedupe-peer-deps.mjs && node -e \"const fs=require('fs');for(const t of ['tsconfig.build.tsbuildinfo','dist']){try{fs.rmSync(t,{recursive:true})}catch(e){}}\" && tsc -p tsconfig.build.json && node scripts/copy-assets.mjs && tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --minify && node scripts/bundle-css.mjs && node scripts/verify-css-build.mjs",
|
|
155
155
|
"prepublishOnly": "npm run build",
|
|
156
156
|
"watch": "concurrently --kill-others --restart-tries 0 --names \"ts,css\" \"tsc -p tsconfig.build.json --watch\" \"npm run watch:css\"",
|
|
157
157
|
"watch:css": "tailwindcss -i src/tailwind-input.css -o dist/tailwind-utilities.css --watch",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type ActionResult } from "@mohasinac/appkit/server";
|
|
2
|
-
import type { ConversationDocument } from "../../../../features/messages/schemas/firestore";
|
|
3
|
-
export interface StartConversationInput {
|
|
4
|
-
productId: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Open (or resume) a buyer↔seller conversation for a classified listing.
|
|
8
|
-
* Called when the buyer clicks "Contact Seller" on the classified PDP.
|
|
9
|
-
* Idempotent — returns the existing conversation if one already exists for
|
|
10
|
-
* this buyer × store × product tuple.
|
|
11
|
-
*/
|
|
12
|
-
export declare function startClassifiedConversationAction(input: StartConversationInput): Promise<ActionResult<ConversationDocument>>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
import { wrapAction } from "@mohasinac/appkit/server";
|
|
3
|
-
import { conversationsRepository } from "../../../../features/messages/repository/conversations.repository";
|
|
4
|
-
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers";
|
|
5
|
-
import { storeRepository, productRepository } from "../../../../repositories";
|
|
6
|
-
/**
|
|
7
|
-
* Open (or resume) a buyer↔seller conversation for a classified listing.
|
|
8
|
-
* Called when the buyer clicks "Contact Seller" on the classified PDP.
|
|
9
|
-
* Idempotent — returns the existing conversation if one already exists for
|
|
10
|
-
* this buyer × store × product tuple.
|
|
11
|
-
*/
|
|
12
|
-
export async function startClassifiedConversationAction(input) {
|
|
13
|
-
return wrapAction(async () => {
|
|
14
|
-
const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
|
|
15
|
-
const product = await productRepository.findByIdOrSlug(input.productId);
|
|
16
|
-
if (!product || product.listingType !== "classified") {
|
|
17
|
-
throw new Error("Product not found or not a classified listing");
|
|
18
|
-
}
|
|
19
|
-
const store = await storeRepository.findById(product.storeId);
|
|
20
|
-
if (!store) {
|
|
21
|
-
throw new Error("Store not found");
|
|
22
|
-
}
|
|
23
|
-
return conversationsRepository.findOrCreateByContext({
|
|
24
|
-
buyerId: user.uid,
|
|
25
|
-
buyerDisplayName: user.name ?? user.email ?? user.uid,
|
|
26
|
-
storeId: product.storeId,
|
|
27
|
-
storeName: store.storeName,
|
|
28
|
-
sellerDisplayName: store.storeName,
|
|
29
|
-
productId: product.id,
|
|
30
|
-
productTitle: product.title,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type ActionResult } from "@mohasinac/appkit/server";
|
|
2
|
-
import type { ConversationDocument } from "../../../../features/messages/schemas/firestore";
|
|
3
|
-
export interface StartDigitalCodeConversationInput {
|
|
4
|
-
productId: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Open a buyer↔seller conversation for a digital-code listing (questions
|
|
8
|
-
* about platform/region/edition before purchase). Post-purchase code reveal
|
|
9
|
-
* is handled by the existing `/api/orders/[id]/code` endpoint, not a server
|
|
10
|
-
* action — the code reveal panel lives in the order detail view.
|
|
11
|
-
*/
|
|
12
|
-
export declare function startDigitalCodeConversationAction(input: StartDigitalCodeConversationInput): Promise<ActionResult<ConversationDocument>>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
import { wrapAction } from "@mohasinac/appkit/server";
|
|
3
|
-
import { conversationsRepository } from "../../../../features/messages/repository/conversations.repository";
|
|
4
|
-
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers";
|
|
5
|
-
import { storeRepository, productRepository } from "../../../../repositories";
|
|
6
|
-
/**
|
|
7
|
-
* Open a buyer↔seller conversation for a digital-code listing (questions
|
|
8
|
-
* about platform/region/edition before purchase). Post-purchase code reveal
|
|
9
|
-
* is handled by the existing `/api/orders/[id]/code` endpoint, not a server
|
|
10
|
-
* action — the code reveal panel lives in the order detail view.
|
|
11
|
-
*/
|
|
12
|
-
export async function startDigitalCodeConversationAction(input) {
|
|
13
|
-
return wrapAction(async () => {
|
|
14
|
-
const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
|
|
15
|
-
const product = await productRepository.findByIdOrSlug(input.productId);
|
|
16
|
-
if (!product || product.listingType !== "digital-code") {
|
|
17
|
-
throw new Error("Product not found or not a digital-code listing");
|
|
18
|
-
}
|
|
19
|
-
const store = await storeRepository.findById(product.storeId);
|
|
20
|
-
if (!store) {
|
|
21
|
-
throw new Error("Store not found");
|
|
22
|
-
}
|
|
23
|
-
return conversationsRepository.findOrCreateByContext({
|
|
24
|
-
buyerId: user.uid,
|
|
25
|
-
buyerDisplayName: user.name ?? user.email ?? user.uid,
|
|
26
|
-
storeId: product.storeId,
|
|
27
|
-
storeName: store.storeName,
|
|
28
|
-
sellerDisplayName: store.storeName,
|
|
29
|
-
productId: product.id,
|
|
30
|
-
productTitle: product.title,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export declare const getHomepageSections: () => Promise<import("../../../..").HomepageSectionDocument[]>;
|
|
2
|
-
export declare const getHeroCarouselSlides: () => Promise<never[] | import("../../../..").CarouselSlideDocument[]>;
|
|
3
|
-
/**
|
|
4
|
-
* One parallel fetch returning every piece of data the homepage sections need.
|
|
5
|
-
* Page components call this once; React.cache() deduplicates per request.
|
|
6
|
-
*/
|
|
7
|
-
export declare const getHomepageInitial: () => Promise<{
|
|
8
|
-
sections: import("../../../..").HomepageSectionDocument[];
|
|
9
|
-
carouselSlides: never[] | import("../../../..").CarouselSlideDocument[];
|
|
10
|
-
featuredProducts: never[] | import("../../../..").ProductDocument[];
|
|
11
|
-
activeAuctions: never[] | import("../../../..").ProductDocument[];
|
|
12
|
-
activePreOrders: never[] | import("../../../..").ProductDocument[];
|
|
13
|
-
activeBrands: never[] | import("../../../..").CategoryDocument[];
|
|
14
|
-
featuredReviews: never[] | import("../../../..").ReviewDocument[];
|
|
15
|
-
recentBlogPosts: never[] | import("../../../..").BlogPostDocument[];
|
|
16
|
-
activeEvents: never[] | import("../../../..").EventDocument[];
|
|
17
|
-
featuredCategories: never[] | import("../../../..").CategoryDocument[];
|
|
18
|
-
}>;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { cache } from "react";
|
|
2
|
-
import { homepageSectionsRepository, carouselRepository, productRepository, reviewRepository, blogRepository, eventRepository, categoriesRepository, siteSettingsRepository, } from "../../../../repositories";
|
|
3
|
-
import { HOMEPAGE_FEATURED_REVIEWS_LIMIT, HOMEPAGE_RECENT_BLOG_POSTS_LIMIT, } from "../../../shared/features/homepage/config";
|
|
4
|
-
/** Section types that require a feature flag to be enabled to show on the homepage. */
|
|
5
|
-
const SECTION_FEATURE_GATE = {
|
|
6
|
-
auctions: "auctions",
|
|
7
|
-
"pre-orders": "preOrders",
|
|
8
|
-
events: "events",
|
|
9
|
-
"event-raffles": "events",
|
|
10
|
-
"prize-draws": "events",
|
|
11
|
-
"blog-articles": "blog",
|
|
12
|
-
};
|
|
13
|
-
export const getHomepageSections = cache(async () => {
|
|
14
|
-
const [sections, settings] = await Promise.all([
|
|
15
|
-
homepageSectionsRepository.getEnabledSections().catch(() => []),
|
|
16
|
-
siteSettingsRepository.getSingleton().catch(() => null),
|
|
17
|
-
]);
|
|
18
|
-
const flags = settings?.featureFlags;
|
|
19
|
-
if (!flags)
|
|
20
|
-
return sections;
|
|
21
|
-
return sections.filter((s) => {
|
|
22
|
-
const flagKey = SECTION_FEATURE_GATE[s.type];
|
|
23
|
-
if (!flagKey)
|
|
24
|
-
return true;
|
|
25
|
-
return flags[flagKey] !== false;
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
export const getHeroCarouselSlides = cache(async () => {
|
|
29
|
-
return carouselRepository.getActiveSlides().catch(() => []);
|
|
30
|
-
});
|
|
31
|
-
/**
|
|
32
|
-
* One parallel fetch returning every piece of data the homepage sections need.
|
|
33
|
-
* Page components call this once; React.cache() deduplicates per request.
|
|
34
|
-
*/
|
|
35
|
-
export const getHomepageInitial = cache(async () => {
|
|
36
|
-
const [rawSections, settings, carouselSlides, featuredProducts, activeAuctions, activePreOrders, activeBrands, featuredReviews, recentBlogPosts, activeEvents, featuredCategories,] = await Promise.all([
|
|
37
|
-
homepageSectionsRepository.getEnabledSections().catch(() => []),
|
|
38
|
-
siteSettingsRepository.getSingleton().catch(() => null),
|
|
39
|
-
carouselRepository.getActiveSlides().catch(() => []),
|
|
40
|
-
productRepository.findFeatured().catch(() => []),
|
|
41
|
-
productRepository.findActiveAuctions().catch(() => []),
|
|
42
|
-
productRepository.findActivePreOrders().catch(() => []),
|
|
43
|
-
categoriesRepository.findActiveBrands().catch(() => []),
|
|
44
|
-
reviewRepository.findFeatured(HOMEPAGE_FEATURED_REVIEWS_LIMIT).catch(() => []),
|
|
45
|
-
blogRepository.listPublished({}, { page: 1, pageSize: HOMEPAGE_RECENT_BLOG_POSTS_LIMIT, sorts: "-publishedAt" }).then((r) => r.items ?? []).catch(() => []),
|
|
46
|
-
eventRepository.listActive().catch(() => []),
|
|
47
|
-
categoriesRepository.getFeaturedCategories().catch(() => []),
|
|
48
|
-
]);
|
|
49
|
-
const flags = settings?.featureFlags;
|
|
50
|
-
const sections = flags
|
|
51
|
-
? rawSections.filter((s) => {
|
|
52
|
-
const flagKey = SECTION_FEATURE_GATE[s.type];
|
|
53
|
-
if (!flagKey)
|
|
54
|
-
return true;
|
|
55
|
-
return flags[flagKey] !== false;
|
|
56
|
-
})
|
|
57
|
-
: rawSections;
|
|
58
|
-
return {
|
|
59
|
-
sections,
|
|
60
|
-
carouselSlides,
|
|
61
|
-
featuredProducts,
|
|
62
|
-
activeAuctions,
|
|
63
|
-
activePreOrders,
|
|
64
|
-
activeBrands,
|
|
65
|
-
featuredReviews,
|
|
66
|
-
recentBlogPosts,
|
|
67
|
-
activeEvents,
|
|
68
|
-
featuredCategories,
|
|
69
|
-
};
|
|
70
|
-
});
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { type ActionResult } from "@mohasinac/appkit/server";
|
|
2
|
-
import type { ConversationDocument } from "../../../../features/messages/schemas/firestore";
|
|
3
|
-
export interface StartLiveConversationInput {
|
|
4
|
-
productId: string;
|
|
5
|
-
}
|
|
6
|
-
export declare function startLiveConversationAction(input: StartLiveConversationInput): Promise<ActionResult<ConversationDocument>>;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
import { wrapAction } from "@mohasinac/appkit/server";
|
|
3
|
-
import { conversationsRepository } from "../../../../features/messages/repository/conversations.repository";
|
|
4
|
-
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers";
|
|
5
|
-
import { storeRepository, productRepository } from "../../../../repositories";
|
|
6
|
-
export async function startLiveConversationAction(input) {
|
|
7
|
-
return wrapAction(async () => {
|
|
8
|
-
const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
|
|
9
|
-
const product = await productRepository.findByIdOrSlug(input.productId);
|
|
10
|
-
if (!product || product.listingType !== "live") {
|
|
11
|
-
throw new Error("Product not found or not a live-item listing");
|
|
12
|
-
}
|
|
13
|
-
const store = await storeRepository.findById(product.storeId);
|
|
14
|
-
if (!store) {
|
|
15
|
-
throw new Error("Store not found");
|
|
16
|
-
}
|
|
17
|
-
return conversationsRepository.findOrCreateByContext({
|
|
18
|
-
buyerId: user.uid,
|
|
19
|
-
buyerDisplayName: user.name ?? user.email ?? user.uid,
|
|
20
|
-
storeId: product.storeId,
|
|
21
|
-
storeName: store.storeName,
|
|
22
|
-
sellerDisplayName: store.storeName,
|
|
23
|
-
productId: product.id,
|
|
24
|
-
productTitle: product.title,
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages server actions — thin re-exports. Keeps RTDB ping-channel behaviour
|
|
3
|
-
* (firestore canonical + RTDB notification) in the source action so consumers
|
|
4
|
-
* cannot bypass it.
|
|
5
|
-
*/
|
|
6
|
-
export { sendMessage as sendMessageAction, markConversationRead as markConversationReadAction, MESSAGE_MAX_LENGTH, } from "../../../../features/messages/actions/messages-actions";
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages server actions — thin re-exports. Keeps RTDB ping-channel behaviour
|
|
3
|
-
* (firestore canonical + RTDB notification) in the source action so consumers
|
|
4
|
-
* cannot bypass it.
|
|
5
|
-
*/
|
|
6
|
-
export { sendMessage as sendMessageAction, markConversationRead as markConversationReadAction, MESSAGE_MAX_LENGTH, } from "../../../../features/messages/actions/messages-actions";
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages data layer — server-only fetchers with React.cache dedup.
|
|
3
|
-
* RTDB ping channel is preserved (the actions module owns that side effect).
|
|
4
|
-
*/
|
|
5
|
-
export interface MessagesDataOptions {
|
|
6
|
-
_reserved?: never;
|
|
7
|
-
}
|
|
8
|
-
export declare const listConversationsForBuyer: (buyerId: string, _opts?: MessagesDataOptions) => Promise<import("../../../..").ConversationDocument[]>;
|
|
9
|
-
export declare const listConversationsForStore: (storeId: string, _opts?: MessagesDataOptions) => Promise<import("../../../..").ConversationDocument[]>;
|
|
10
|
-
export declare const getConversation: (convId: string, _opts?: MessagesDataOptions) => Promise<import("../../../..").ConversationDocument | null>;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages data layer — server-only fetchers with React.cache dedup.
|
|
3
|
-
* RTDB ping channel is preserved (the actions module owns that side effect).
|
|
4
|
-
*/
|
|
5
|
-
import { cache } from "react";
|
|
6
|
-
import { listConversationsForBuyer as _listConversationsForBuyer, listConversationsForStore as _listConversationsForStore, getConversation as _getConversation, } from "../../../../features/messages/actions/messages-actions";
|
|
7
|
-
export const listConversationsForBuyer = cache(async (buyerId, _opts) => {
|
|
8
|
-
void _opts;
|
|
9
|
-
if (!buyerId)
|
|
10
|
-
return [];
|
|
11
|
-
return _listConversationsForBuyer(buyerId);
|
|
12
|
-
});
|
|
13
|
-
export const listConversationsForStore = cache(async (storeId, _opts) => {
|
|
14
|
-
void _opts;
|
|
15
|
-
if (!storeId)
|
|
16
|
-
return [];
|
|
17
|
-
return _listConversationsForStore(storeId);
|
|
18
|
-
});
|
|
19
|
-
export const getConversation = cache(async (convId, _opts) => {
|
|
20
|
-
void _opts;
|
|
21
|
-
if (!convId)
|
|
22
|
-
return null;
|
|
23
|
-
return _getConversation(convId);
|
|
24
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { JobContext } from "../runtime/types";
|
|
2
|
-
/**
|
|
3
|
-
* Reverts every live tester-sandbox fixture back to its canonical seeded
|
|
4
|
-
* shape (undoing edits made through admin/seller forms) and prunes any
|
|
5
|
-
* isTestData doc that isn't one of the known fixtures (something a tester
|
|
6
|
-
* created during QA, e.g. a cloned product). Runs every 4 hours — distinct
|
|
7
|
-
* from `runTesterSandboxCleanup`, which only deletes docs whose 7-day
|
|
8
|
-
* `testDataExpiresAt` TTL has actually lapsed and never touches still-live
|
|
9
|
-
* docs' fields. Scoped to the tester sandbox only (`isTestData: true`) —
|
|
10
|
-
* the permanent Beyblade catalog real visitors browse is never touched.
|
|
11
|
-
*/
|
|
12
|
-
export declare function runTesterSandboxRefresh(ctx: JobContext): Promise<void>;
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { batchDelete, getTestDataRefs } from "../handlers/_helpers";
|
|
2
|
-
import { BID_FIELDS } from "../../../../constants/field-names";
|
|
3
|
-
import { encryptPiiFields } from "../../../../security/pii-encrypt";
|
|
4
|
-
import { BID_PII_FIELDS, OFFER_PII_FIELDS, } from "../../../../security/pii-schemas";
|
|
5
|
-
import { SANDBOX_COLLECTIONS } from "./testerSandboxCleanup";
|
|
6
|
-
// Direct imports from each defining module (not the seed-data barrel) —
|
|
7
|
-
// this is internal appkit code, not part of the package's public API surface.
|
|
8
|
-
import { categoriesTesterSeedData } from "../../../../features/tester/seed-data/categories-tester-seed-data";
|
|
9
|
-
import { storesTesterSeedData } from "../../../../features/tester/seed-data/stores-tester-seed-data";
|
|
10
|
-
import { productsTesterSeedData } from "../../../../features/tester/seed-data/products-tester-seed-data";
|
|
11
|
-
import { blogTesterSeedData } from "../../../../features/tester/seed-data/blog-tester-seed-data";
|
|
12
|
-
import { eventsTesterSeedData } from "../../../../features/tester/seed-data/events-tester-seed-data";
|
|
13
|
-
import { offersTesterSeedData } from "../../../../features/tester/seed-data/offers-tester-seed-data";
|
|
14
|
-
import { bidsTesterSeedData } from "../../../../features/tester/seed-data/bids-tester-seed-data";
|
|
15
|
-
/**
|
|
16
|
-
* PII per sandbox collection — the same map `seed-cli.mjs` applies.
|
|
17
|
-
*
|
|
18
|
-
* This job wrote every document with a bare `batch.set(...)`, while its own
|
|
19
|
-
* comment claimed to mirror seed-cli's loadGeneric. loadGeneric ENCRYPTS;
|
|
20
|
-
* this did not. So the 4-hourly refresh re-seeded sandbox PII in plaintext and
|
|
21
|
-
* quietly undid the backfill for those fixtures — caught by reading the raw
|
|
22
|
-
* documents after a deploy, where `bids` had 37 encrypted rows and one
|
|
23
|
-
* plaintext one that had not existed at backfill time.
|
|
24
|
-
*
|
|
25
|
-
* A scheduled job writing straight to Firestore bypasses `applyWriteHooks`
|
|
26
|
-
* exactly like the hand-rolled repository overrides did. Same defect, one
|
|
27
|
-
* level up.
|
|
28
|
-
*/
|
|
29
|
-
const SANDBOX_PII = {
|
|
30
|
-
offers: OFFER_PII_FIELDS,
|
|
31
|
-
bids: BID_PII_FIELDS,
|
|
32
|
-
products: ["sellerName", "sellerEmail"],
|
|
33
|
-
};
|
|
34
|
-
function encryptForSandbox(collection, data) {
|
|
35
|
-
const fields = SANDBOX_PII[collection];
|
|
36
|
-
return fields ? encryptPiiFields(data, [...fields]) : data;
|
|
37
|
-
}
|
|
38
|
-
const SEED_BY_COLLECTION = {
|
|
39
|
-
categories: categoriesTesterSeedData,
|
|
40
|
-
stores: storesTesterSeedData,
|
|
41
|
-
products: productsTesterSeedData,
|
|
42
|
-
blogPosts: blogTesterSeedData,
|
|
43
|
-
events: eventsTesterSeedData,
|
|
44
|
-
offers: offersTesterSeedData,
|
|
45
|
-
};
|
|
46
|
-
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
47
|
-
/**
|
|
48
|
-
* Reverts every live tester-sandbox fixture back to its canonical seeded
|
|
49
|
-
* shape (undoing edits made through admin/seller forms) and prunes any
|
|
50
|
-
* isTestData doc that isn't one of the known fixtures (something a tester
|
|
51
|
-
* created during QA, e.g. a cloned product). Runs every 4 hours — distinct
|
|
52
|
-
* from `runTesterSandboxCleanup`, which only deletes docs whose 7-day
|
|
53
|
-
* `testDataExpiresAt` TTL has actually lapsed and never touches still-live
|
|
54
|
-
* docs' fields. Scoped to the tester sandbox only (`isTestData: true`) —
|
|
55
|
-
* the permanent Beyblade catalog real visitors browse is never touched.
|
|
56
|
-
*/
|
|
57
|
-
export async function runTesterSandboxRefresh(ctx) {
|
|
58
|
-
let reverted = 0;
|
|
59
|
-
let deletedExtras = 0;
|
|
60
|
-
for (const collection of SANDBOX_COLLECTIONS) {
|
|
61
|
-
const seedDocs = SEED_BY_COLLECTION[collection] ?? [];
|
|
62
|
-
const validIds = new Set(seedDocs.map((d) => d.id).filter((id) => Boolean(id)));
|
|
63
|
-
if (seedDocs.length > 0) {
|
|
64
|
-
const batch = ctx.db.batch();
|
|
65
|
-
for (const doc of seedDocs) {
|
|
66
|
-
const { id, ...data } = doc;
|
|
67
|
-
if (!id)
|
|
68
|
-
continue;
|
|
69
|
-
// merge:true mirrors seed-cli.mjs's loadGeneric — the seed payload
|
|
70
|
-
// carries every canonical field, so this fully overwrites anything
|
|
71
|
-
// a tester edited through the UI.
|
|
72
|
-
batch.set(ctx.db.collection(collection).doc(id), encryptForSandbox(collection, data), { merge: true });
|
|
73
|
-
reverted++;
|
|
74
|
-
}
|
|
75
|
-
await batch.commit();
|
|
76
|
-
}
|
|
77
|
-
const testDataRefs = await getTestDataRefs(ctx.db, collection, null);
|
|
78
|
-
const extraRefs = testDataRefs.filter((ref) => !validIds.has(ref.id));
|
|
79
|
-
deletedExtras += await batchDelete(ctx, extraRefs);
|
|
80
|
-
}
|
|
81
|
-
// Bids aren't isTestData-flagged themselves — scoped by productId
|
|
82
|
-
// referencing a sandbox product instead, mirroring runTesterSandboxCleanup's
|
|
83
|
-
// cascade convention.
|
|
84
|
-
const sandboxProductIds = (SEED_BY_COLLECTION.products ?? [])
|
|
85
|
-
.map((d) => d.id)
|
|
86
|
-
.filter((id) => Boolean(id));
|
|
87
|
-
const validBidIds = new Set(bidsTesterSeedData.map((d) => d.id).filter((id) => Boolean(id)));
|
|
88
|
-
if (bidsTesterSeedData.length > 0) {
|
|
89
|
-
const bidBatch = ctx.db.batch();
|
|
90
|
-
for (const bid of bidsTesterSeedData) {
|
|
91
|
-
const { id, ...data } = bid;
|
|
92
|
-
if (!id)
|
|
93
|
-
continue;
|
|
94
|
-
bidBatch.set(ctx.db.collection("bids").doc(id), encryptForSandbox("bids", data), { merge: true });
|
|
95
|
-
reverted++;
|
|
96
|
-
}
|
|
97
|
-
await bidBatch.commit();
|
|
98
|
-
}
|
|
99
|
-
const extraBidRefs = [];
|
|
100
|
-
for (let i = 0; i < sandboxProductIds.length; i += BID_CHUNK_SIZE) {
|
|
101
|
-
const chunk = sandboxProductIds.slice(i, i + BID_CHUNK_SIZE);
|
|
102
|
-
if (chunk.length === 0)
|
|
103
|
-
continue;
|
|
104
|
-
const snap = await ctx.db.collection("bids").where(BID_FIELDS.PRODUCT_ID, "in", chunk).get();
|
|
105
|
-
for (const doc of snap.docs) {
|
|
106
|
-
if (!validBidIds.has(doc.id))
|
|
107
|
-
extraBidRefs.push(doc.ref);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
deletedExtras += await batchDelete(ctx, extraBidRefs);
|
|
111
|
-
ctx.logger.info("Tester sandbox refresh complete", { reverted, deletedExtras });
|
|
112
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { SectionType } from "../../../../features/homepage/schemas/firestore";
|
|
2
|
-
import type { FeatureFlagKey } from "../../../../features/admin/schemas/firestore";
|
|
3
|
-
/** Section types that require a feature flag to be enabled to show on the homepage. */
|
|
4
|
-
export declare const SECTION_FEATURE_GATE: Partial<Record<SectionType, FeatureFlagKey>>;
|
|
5
|
-
/**
|
|
6
|
-
* Drop sections whose feature flag is off.
|
|
7
|
-
*
|
|
8
|
-
* A section type with no gate entry always passes, and a MISSING flag value
|
|
9
|
-
* counts as enabled (`!== false`) — the flags map is partial, and a section
|
|
10
|
-
* must not vanish just because an admin has never opened Site Settings.
|
|
11
|
-
*/
|
|
12
|
-
export declare function filterSectionsByFeatureFlags<T extends {
|
|
13
|
-
type: string;
|
|
14
|
-
}>(sections: readonly T[], flags: Partial<Record<FeatureFlagKey, unknown>> | null | undefined): T[];
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* WHY: The only code in the repo that gated homepage sections on
|
|
3
|
-
* `siteSettings.featureFlags` lived inside `getHomepageInitial`, a
|
|
4
|
-
* function with ZERO call sites — the live homepage
|
|
5
|
-
* (`MarketplaceHomepageView`) reads `homepageSectionsRepository`
|
|
6
|
-
* directly. So turning off the `auctions` flag hid auctions everywhere
|
|
7
|
-
* except the homepage, which kept advertising them.
|
|
8
|
-
*
|
|
9
|
-
* `getHomepageInitial` itself was deleted rather than wired up: its other
|
|
10
|
-
* unique calls, `findActiveAuctions()` / `findActivePreOrders()`, are
|
|
11
|
-
* unbounded `.get()` collection scans with no `.limit()`, which Rule #6
|
|
12
|
-
* forbids. The gate is the only part worth keeping, so it moved here.
|
|
13
|
-
*
|
|
14
|
-
* EXPORTS: SECTION_FEATURE_GATE, filterSectionsByFeatureFlags
|
|
15
|
-
*
|
|
16
|
-
* @tag domain:homepage
|
|
17
|
-
* @tag layer:shared
|
|
18
|
-
* @tag pattern:none
|
|
19
|
-
* @tag access:isomorphic
|
|
20
|
-
* @tag consumers:MarketplaceHomepageView
|
|
21
|
-
* @tag sideEffects:none
|
|
22
|
-
*/
|
|
23
|
-
/** Section types that require a feature flag to be enabled to show on the homepage. */
|
|
24
|
-
export const SECTION_FEATURE_GATE = {
|
|
25
|
-
auctions: "auctions",
|
|
26
|
-
"pre-orders": "preOrders",
|
|
27
|
-
events: "events",
|
|
28
|
-
"event-raffles": "events",
|
|
29
|
-
"prize-draws": "events",
|
|
30
|
-
"blog-articles": "blog",
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* Drop sections whose feature flag is off.
|
|
34
|
-
*
|
|
35
|
-
* A section type with no gate entry always passes, and a MISSING flag value
|
|
36
|
-
* counts as enabled (`!== false`) — the flags map is partial, and a section
|
|
37
|
-
* must not vanish just because an admin has never opened Site Settings.
|
|
38
|
-
*/
|
|
39
|
-
export function filterSectionsByFeatureFlags(sections,
|
|
40
|
-
// Not `Partial<Record<FeatureFlagKey, boolean>>`: two of the flag keys
|
|
41
|
-
// (`listingTypes`, `categoryTypes`) hold nested per-type objects rather than
|
|
42
|
-
// booleans. Neither is a gate key, so the `=== false` test below simply
|
|
43
|
-
// never fires for them.
|
|
44
|
-
flags) {
|
|
45
|
-
if (!flags)
|
|
46
|
-
return [...sections];
|
|
47
|
-
return sections.filter((section) => {
|
|
48
|
-
const flagKey = SECTION_FEATURE_GATE[section.type];
|
|
49
|
-
return !flagKey || flags[flagKey] !== false;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export declare const lotteryConfigFormSchema: z.ZodEffects<z.ZodObject<{
|
|
3
|
-
pricingMode: z.ZodEnum<["uniform", "variable"]>;
|
|
4
|
-
uniformPrice: z.ZodOptional<z.ZodNumber>;
|
|
5
|
-
slots: z.ZodArray<z.ZodObject<{
|
|
6
|
-
slotNumber: z.ZodNumber;
|
|
7
|
-
name: z.ZodOptional<z.ZodString>;
|
|
8
|
-
price: z.ZodOptional<z.ZodNumber>;
|
|
9
|
-
image: z.ZodOptional<z.ZodString>;
|
|
10
|
-
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
slotNumber: number;
|
|
12
|
-
image?: string | undefined;
|
|
13
|
-
name?: string | undefined;
|
|
14
|
-
price?: number | undefined;
|
|
15
|
-
}, {
|
|
16
|
-
slotNumber: number;
|
|
17
|
-
image?: string | undefined;
|
|
18
|
-
name?: string | undefined;
|
|
19
|
-
price?: number | undefined;
|
|
20
|
-
}>, "many">;
|
|
21
|
-
}, "strip", z.ZodTypeAny, {
|
|
22
|
-
slots: {
|
|
23
|
-
slotNumber: number;
|
|
24
|
-
image?: string | undefined;
|
|
25
|
-
name?: string | undefined;
|
|
26
|
-
price?: number | undefined;
|
|
27
|
-
}[];
|
|
28
|
-
pricingMode: "uniform" | "variable";
|
|
29
|
-
uniformPrice?: number | undefined;
|
|
30
|
-
}, {
|
|
31
|
-
slots: {
|
|
32
|
-
slotNumber: number;
|
|
33
|
-
image?: string | undefined;
|
|
34
|
-
name?: string | undefined;
|
|
35
|
-
price?: number | undefined;
|
|
36
|
-
}[];
|
|
37
|
-
pricingMode: "uniform" | "variable";
|
|
38
|
-
uniformPrice?: number | undefined;
|
|
39
|
-
}>, {
|
|
40
|
-
slots: {
|
|
41
|
-
slotNumber: number;
|
|
42
|
-
image?: string | undefined;
|
|
43
|
-
name?: string | undefined;
|
|
44
|
-
price?: number | undefined;
|
|
45
|
-
}[];
|
|
46
|
-
pricingMode: "uniform" | "variable";
|
|
47
|
-
uniformPrice?: number | undefined;
|
|
48
|
-
}, {
|
|
49
|
-
slots: {
|
|
50
|
-
slotNumber: number;
|
|
51
|
-
image?: string | undefined;
|
|
52
|
-
name?: string | undefined;
|
|
53
|
-
price?: number | undefined;
|
|
54
|
-
}[];
|
|
55
|
-
pricingMode: "uniform" | "variable";
|
|
56
|
-
uniformPrice?: number | undefined;
|
|
57
|
-
}>;
|
|
58
|
-
export type LotteryConfigFormValues = z.infer<typeof lotteryConfigFormSchema>;
|