@wix/himalaya-cli 0.1631.0 → 0.1633.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +4 -14
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-wire.d.ts +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments.d.ts +1 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +26 -13
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-core.d.ts +0 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/checkout-source.d.ts +3 -10
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-core.d.ts +0 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-order-backend.d.ts +0 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/iap-verticals/stores.d.ts +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/index.d.ts +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/index.d.ts +64 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +13 -45
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +2 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +7 -11
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +39 -0
- package/dist/cli.mjs +258 -150
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +3 -0
- package/guides/getting-started.md +2 -1
- package/guides/himi-json-output.md +6 -0
- package/guides/himi-skill.md +2 -1
- package/guides/iap-screen-adoption.md +4 -4
- package/guides/importable-sdks.md +1 -1
- package/guides/one-app-section-mappings.md +3 -3
- package/guides/sdk-reference.json +8829 -4386
- package/guides/skill.md +11 -1
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/branded-lite/dev/index.ts +2924 -2110
- package/templates/branded-lite/l10n/en.json +5 -3
- package/templates/branded-lite/tier5-src/_shared/loyalty-offerings.ts +73 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/earning.ts +148 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +57 -31
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty-rewards/index.ts +208 -0
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +1 -1
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$source": "en",
|
|
3
|
-
"$generated": "2026-11-
|
|
3
|
+
"$generated": "2026-11-09T18:25:39.914Z",
|
|
4
4
|
"messages": {
|
|
5
5
|
"h_042c862e4467": {
|
|
6
6
|
"source": "Try again",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"himi_coupons#payload.body.props.children[4].body.props.children[8].body.props.children[2].body.props.title",
|
|
32
32
|
"himi_notifications#payload.body.props.children[6].body.props.children[8].body.props.children[2].body.props.title",
|
|
33
33
|
"himi_loyalty#payload.body.props.children[5].body.props.children[1].body.props.title",
|
|
34
|
+
"himi_loyalty_reward#payload.body.props.children[3].body.props.children[1].body.props.title",
|
|
34
35
|
"himi_reviews#payload.body.props.children[4].body.props.children[8].body.props.children[2].body.props.title",
|
|
35
36
|
"himi_portfolio#payload.body.props.children[2].body.props.children[8].body.props.children[2].body.props.title",
|
|
36
37
|
"himi_portfolio_project#payload.body.props.children[1].body.props.children[1].body.props.title",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"himi_donations#payload.body.props.children[4].body.props.children[8].body.props.children[2].body.props.title",
|
|
51
52
|
"himi_gift_cards#payload.body.props.children[4].body.props.children[8].body.props.children[2].body.props.title",
|
|
52
53
|
"himi_videos#payload.body.props.children[7].body.props.children[8].body.props.children[2].body.props.title",
|
|
54
|
+
"himi_video#payload.body.props.children[7].body.props.title",
|
|
53
55
|
"himi_podcasts#payload.body.props.children[2].body.props.children[8].body.props.children[2].body.props.title",
|
|
54
56
|
"himi_forms#payload.body.props.children[5].body.props.ctaLabel",
|
|
55
57
|
"himi_form#payload.body.props.children[6].body.props.ctaLabel",
|
|
@@ -221,7 +223,6 @@
|
|
|
221
223
|
"himi_member#payload.body.props.children[3].body.props.children[0].body.props.text",
|
|
222
224
|
"himi_chat_thread#payload.body.props.children[2].body.props.children[0].body.props.text",
|
|
223
225
|
"himi_coupon#payload.body.props.children[1].body.props.children[0].body.props.text",
|
|
224
|
-
"himi_loyalty_reward#payload.body.props.children[1].body.props.children[0].body.props.text",
|
|
225
226
|
"himi_review#payload.body.props.children[1].body.props.children[0].body.props.text",
|
|
226
227
|
"himi_invoice#payload.body.props.children[1].body.props.children[0].body.props.text",
|
|
227
228
|
"himi_badge#payload.body.props.children[1].body.props.children[0].body.props.text",
|
|
@@ -413,6 +414,7 @@
|
|
|
413
414
|
"himi_coupons#payload.body.props.children[4].body.props.children[8].body.props.children[3].body.props.title",
|
|
414
415
|
"himi_notifications#payload.body.props.children[6].body.props.children[8].body.props.children[3].body.props.title",
|
|
415
416
|
"himi_loyalty#payload.body.props.children[7].body.props.children[1].body.props.children[3].body.props.title",
|
|
417
|
+
"himi_loyalty_reward#payload.body.props.children[2].body.props.children[3].body.props.title",
|
|
416
418
|
"himi_reviews#payload.body.props.children[4].body.props.children[8].body.props.children[3].body.props.title",
|
|
417
419
|
"himi_portfolio#payload.body.props.children[2].body.props.children[8].body.props.children[3].body.props.title",
|
|
418
420
|
"himi_reservations#payload.body.props.children[6].body.props.children[2].body.props.title",
|
|
@@ -427,7 +429,7 @@
|
|
|
427
429
|
"himi_gift_cards#payload.body.props.children[4].body.props.children[8].body.props.children[3].body.props.title",
|
|
428
430
|
"himi_videos#payload.body.props.children[7].body.props.children[8].body.props.children[3].body.props.title",
|
|
429
431
|
"himi_video_channel#payload.body.props.children[8].body.props.title",
|
|
430
|
-
"himi_video#payload.body.props.children[
|
|
432
|
+
"himi_video#payload.body.props.children[13].body.props.title",
|
|
431
433
|
"himi_podcasts#payload.body.props.children[2].body.props.children[8].body.props.children[3].body.props.title",
|
|
432
434
|
"himi_forms#payload.body.props.children[7].body.props.ctaLabel",
|
|
433
435
|
"himi_galleries#payload.body.props.children[4].body.props.children[8].body.props.children[3].body.props.title",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Raw REST tier configurations are intentionally matched exactly: a tier without
|
|
2
|
+
// a configuration does not inherit an offering from a different tier.
|
|
3
|
+
import type { WixLoyaltyReward, WixLoyaltyRewardConfig, WixLoyaltyEarningRule, WixLoyaltyTiersProgram } from "@himalaya/wix-loyalty";
|
|
4
|
+
import { copyParam, copyTemplate } from "./himi-copy.js";
|
|
5
|
+
import { isSingular } from "./app-locale.js";
|
|
6
|
+
import { formatCurrency, formatNumber } from "./engine-intl.js";
|
|
7
|
+
|
|
8
|
+
export interface OfferingRow { id: string; name: string; subtitle: string }
|
|
9
|
+
export interface OfferingGroup {
|
|
10
|
+
id: string; name: string; description: string; threshold: string;
|
|
11
|
+
earningRules: OfferingRow[]; rewards: OfferingRow[];
|
|
12
|
+
hideEarning: boolean; hideRewards: boolean; hideTitle: boolean;
|
|
13
|
+
}
|
|
14
|
+
export const OFFERING_TIER_LIMIT = 3; // wix-loyalty-mobile constants/common.ts
|
|
15
|
+
const amount = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
16
|
+
export function pointLabel(value: number, unit: string): string {
|
|
17
|
+
return copyTemplate("loyalty.pointsLabel", "{points} {unit}", { points: formatNumber(value), unit: unit || copyParam(isSingular(value) ? "loyalty.pointOne" : "loyalty.pointMany", isSingular(value) ? "point" : "points") });
|
|
18
|
+
}
|
|
19
|
+
export function rewardConfigs(reward: WixLoyaltyReward): WixLoyaltyRewardConfig[] | undefined {
|
|
20
|
+
return reward.discountAmount?.configsByTier ?? reward.couponReward?.fixedAmount?.configsByTier
|
|
21
|
+
?? reward.couponReward?.percentage?.configsByTier ?? reward.couponReward?.freeShipping?.configsByTier;
|
|
22
|
+
}
|
|
23
|
+
/** Current-member pricing never inherits a different tier's configuration. */
|
|
24
|
+
export function memberRewardCost(reward: WixLoyaltyReward, tierId?: string): number | undefined {
|
|
25
|
+
const configs = rewardConfigs(reward);
|
|
26
|
+
if (!configs?.length) return amount(reward.requiredPoints) ? reward.requiredPoints : undefined;
|
|
27
|
+
const config = configFor(configs, tierId);
|
|
28
|
+
return amount(config?.costInPoints) ? config.costInPoints : undefined;
|
|
29
|
+
}
|
|
30
|
+
function configFor<T extends { tierId?: string }>(configs: T[] | undefined, tierId?: string): T | undefined {
|
|
31
|
+
const rows = configs?.filter(c => (c.tierId || undefined) === tierId);
|
|
32
|
+
return rows?.length === 1 ? rows[0] : undefined;
|
|
33
|
+
}
|
|
34
|
+
export function offeringRewardLabel(reward: WixLoyaltyReward, tierId: string | undefined, unit: string, currency: string): string {
|
|
35
|
+
if (reward.type === "SPI_DISCOUNT_AMOUNT") return reward.spiDiscountAmount?.description || copyParam("loyalty.checkoutReward", "Use this reward at checkout.");
|
|
36
|
+
const config = configFor(rewardConfigs(reward), tierId);
|
|
37
|
+
const cost = rewardConfigs(reward)?.length ? config?.costInPoints : reward.requiredPoints;
|
|
38
|
+
const price = !amount(cost) ? copyParam("loyalty.costUnavailable", "Points details unavailable")
|
|
39
|
+
: cost === 0 ? copyParam("loyalty.free", "Free") : pointLabel(cost, unit);
|
|
40
|
+
let benefit = "";
|
|
41
|
+
const money = Number(config?.amount);
|
|
42
|
+
if (config?.amount !== undefined && amount(money) && currency) benefit = formatCurrency(money, currency);
|
|
43
|
+
else if (amount(config?.percentage)) benefit = copyTemplate("loyalty.percentOff", "{percent}% off", { percent: formatNumber(config.percentage) });
|
|
44
|
+
else if (reward.couponReward?.freeShipping) benefit = copyParam("loyalty.freeShipping", "Free shipping");
|
|
45
|
+
return benefit ? copyTemplate("loyalty.benefitCost", "{benefit} · {cost}", { benefit, cost: price }) : price;
|
|
46
|
+
}
|
|
47
|
+
function earningLabel(rule: WixLoyaltyEarningRule, tierId: string | undefined, unit: string, currency: string): string {
|
|
48
|
+
if (rule.fixedAmount) {
|
|
49
|
+
const value = configFor(rule.fixedAmount.configs, tierId)?.points;
|
|
50
|
+
return amount(value) ? pointLabel(value, unit) : copyParam("loyalty.earningUnavailable", "Points details unavailable");
|
|
51
|
+
}
|
|
52
|
+
const rate = configFor(rule.conversionRate?.configs, tierId);
|
|
53
|
+
if (!rate || !amount(rate.points) || !amount(rate.moneyAmount) || rate.moneyAmount === 0 || !currency) return copyParam("loyalty.earningUnavailable", "Points details unavailable");
|
|
54
|
+
return copyTemplate("loyalty.pointsPerSpend", "{points} per {amount}", {
|
|
55
|
+
points: pointLabel(rate.points, unit), amount: formatCurrency(rate.moneyAmount, currency, undefined, Number.isInteger(rate.moneyAmount) ? 0 : undefined),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function buildOfferings(program: WixLoyaltyTiersProgram | undefined, rewards: WixLoyaltyReward[], rules: WixLoyaltyEarningRule[], unit: string, currency: string): OfferingGroup[] {
|
|
59
|
+
const active = program?.programSettings?.status === "ACTIVE";
|
|
60
|
+
const tiers = [{ id: undefined as string | undefined, tierDefinition: program?.programSettings?.baseTierDefinition, requiredPoints: undefined as number | undefined }, ...(active ? (program?.tiers ?? []).filter(t => !!t.id) : [])];
|
|
61
|
+
return tiers.map(tier => {
|
|
62
|
+
const earningRules = rules.filter(rule => rule.status === "ACTIVE" && !!configFor(rule.fixedAmount?.configs ?? rule.conversionRate?.configs, tier.id))
|
|
63
|
+
.filter(rule => !!rule.id).map(rule => ({ id: rule.id!, name: rule.title || copyParam("loyalty.earnFallback", "Earn points"), subtitle: earningLabel(rule, tier.id, unit, currency) }));
|
|
64
|
+
const tierRewards = rewards.filter(reward => reward.active === true && !!reward.id && (reward.type === "SPI_DISCOUNT_AMOUNT" || (rewardConfigs(reward)?.length ? !!configFor(rewardConfigs(reward), tier.id) : !tier.id)))
|
|
65
|
+
.map(reward => ({ id: reward.id, name: reward.name || copyParam("loyalty.rewardFallback", "Reward"), subtitle: offeringRewardLabel(reward, tier.id, unit, currency) }));
|
|
66
|
+
return { id: tier.id || "base", name: active ? tier.tierDefinition?.name || copyParam("loyalty.baseTier", "Base tier") : "",
|
|
67
|
+
description: active ? tier.tierDefinition?.description || "" : "", threshold: active && amount(tier.requiredPoints) ? copyTemplate("loyalty.tierThreshold", "From {points}", { points: pointLabel(tier.requiredPoints, unit) }) : "",
|
|
68
|
+
earningRules, rewards: tierRewards, hideEarning: !earningRules.length, hideRewards: !tierRewards.length, hideTitle: !active };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function visibleOfferings(groups: OfferingGroup[], expanded: boolean): OfferingGroup[] {
|
|
72
|
+
return expanded ? groups : groups.slice(0, OFFERING_TIER_LIMIT);
|
|
73
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getMyMember, updateMember } from "@himalaya/wix-members";
|
|
2
|
+
import { listMyLoyaltyFollowedChannels, followMyLoyaltyChannel, type WixLoyaltyProgram, type WixLoyaltyEarningRule } from "@himalaya/wix-loyalty";
|
|
3
|
+
import { open } from "@himalaya/open-url";
|
|
4
|
+
import { navigate } from "@himalaya/state";
|
|
5
|
+
import { memberAccess, assertMemberAccess, type MemberAccess } from "../_shared/member-access.js";
|
|
6
|
+
import { copyParam } from "../_shared/himi-copy.js";
|
|
7
|
+
|
|
8
|
+
export interface SocialRow { id: string; name: string; label: string; followed: boolean }
|
|
9
|
+
export interface EarningState {
|
|
10
|
+
hideBirthday: boolean; birthdayLabel: string; birthdayDraft: number; birthdayPicked: boolean; birthdayOpen: boolean;
|
|
11
|
+
socialChannels: SocialRow[]; earnBusy: boolean; earnMessage: string;
|
|
12
|
+
}
|
|
13
|
+
export const EARNING_EMPTY: EarningState = { hideBirthday: true, birthdayLabel: "", birthdayDraft: 0, birthdayPicked: false, birthdayOpen: false, socialChannels: [], earnBusy: false, earnMessage: "" };
|
|
14
|
+
const business = () => (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.__himiPreferenceBusiness;
|
|
15
|
+
const obj = (v: unknown): Record<string, unknown> => v && typeof v === "object" ? v as Record<string, unknown> : {};
|
|
16
|
+
const HOSTS: Record<string, string> = { FACEBOOK: "facebook.com", INSTAGRAM: "instagram.com", LINKEDIN: "linkedin.com", TIKTOK: "tiktok.com", X: "x.com" };
|
|
17
|
+
/** Engine-safe URL check; never hand a program-supplied scheme or credentials to open_url. */
|
|
18
|
+
export function validSocialUrl(type: string, url: string): boolean {
|
|
19
|
+
const host = /^https:\/\/([^/?#]+)(?:[/?#]|$)/i.exec(url)?.[1]?.toLowerCase();
|
|
20
|
+
return !!host && !!HOSTS[type] && (host === HOSTS[type] || host === `www.${HOSTS[type]}` || host === `m.${HOSTS[type]}`);
|
|
21
|
+
}
|
|
22
|
+
export function birthdayRule(rule: WixLoyaltyEarningRule): boolean {
|
|
23
|
+
return rule.status === "ACTIVE" && rule.triggerActivityType === "segments-contact_enters_segment" && rule.metadata?.ruleTypeTag?.ruleType === "BIRTHDAY";
|
|
24
|
+
}
|
|
25
|
+
export function birthdateAt(value: number): string | undefined {
|
|
26
|
+
if (!Number.isFinite(value)) return undefined;
|
|
27
|
+
const date = new Date(value * 1000), today = new Date();
|
|
28
|
+
if (!Number.isFinite(date.getTime()) || date.getFullYear() < 1900 || date > today) return undefined;
|
|
29
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Private controller state never hydrates. Every network completion rechecks the
|
|
33
|
+
// authenticated member AND business before publishing, including background writes.
|
|
34
|
+
export function createEarningController<S extends EarningState>(publish: () => Promise<unknown>) {
|
|
35
|
+
let revision = 0, busy = false;
|
|
36
|
+
let context: { member: MemberAccess; site: unknown; id: string; birthday: boolean; date: string; channels: { type: string; url: string; followed: boolean }[] } | undefined;
|
|
37
|
+
let outcome: { version: number; message: string; date?: string } | undefined;
|
|
38
|
+
const check = async (c: NonNullable<typeof context>) => {
|
|
39
|
+
await assertMemberAccess(c.member);
|
|
40
|
+
if (business() !== c.site || context !== c) throw new Error("Member access changed");
|
|
41
|
+
};
|
|
42
|
+
const view = (): EarningState => ({ ...EARNING_EMPTY, earnBusy: busy, hideBirthday: !context?.birthday || (!!context.member.email && !context.id),
|
|
43
|
+
birthdayLabel: copyParam(context?.date ? "loyalty.editBirthday" : "loyalty.addBirthday", context?.date ? "Edit birthday" : "Add birthday"),
|
|
44
|
+
socialChannels: context?.channels.map(c => ({ id: c.type, name: c.type, followed: c.followed,
|
|
45
|
+
label: copyParam(c.followed ? "loyalty.followed" : "loyalty.follow", c.followed ? "Followed" : "Follow") })) ?? [] });
|
|
46
|
+
const signIn = (s: S) => navigate(s, { screenId: "himi_sign_in", args: { returnTo: "himi_loyalty" } });
|
|
47
|
+
async function write(c: NonNullable<typeof context>, version: number, task: () => Promise<string | undefined>) {
|
|
48
|
+
let message = copyParam("loyalty.earnSaved", "Saved. Your points will update when the earning rule runs."), date: string | undefined;
|
|
49
|
+
try { await check(c); date = await task(); await check(c); }
|
|
50
|
+
catch { message = copyParam("loyalty.earnFailed", "Couldn't confirm this change. Refresh before trying again."); }
|
|
51
|
+
finally { busy = false; }
|
|
52
|
+
if (context !== c || version !== revision) { await publish(); return; }
|
|
53
|
+
try { await check(c); outcome = { version, message, date }; await publish(); }
|
|
54
|
+
catch { outcome = undefined; await publish(); }
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
clear() { revision++; context = undefined; outcome = undefined; },
|
|
58
|
+
async load(member: MemberAccess, program: WixLoyaltyProgram, rules: WixLoyaltyEarningRule[]): Promise<EarningState> {
|
|
59
|
+
const version = ++revision;
|
|
60
|
+
const birthday = rules.some(birthdayRule);
|
|
61
|
+
const channels = rules.some(r => r.status === "ACTIVE" && r.triggerActivityType === "wix_loyalty-social_media_channel_followed")
|
|
62
|
+
? (program.socialMediaSettings?.channels ?? []).filter(c => c.enabled === true && validSocialUrl(c.type ?? "", c.url ?? ""))
|
|
63
|
+
.map(c => ({ type: c.type!, url: c.url!, followed: false })) : [];
|
|
64
|
+
const c = { member, site: business(), id: "", birthday, date: "", channels };
|
|
65
|
+
context = c; outcome = undefined;
|
|
66
|
+
if (!member.email || (!birthday && !channels.length)) return view();
|
|
67
|
+
try {
|
|
68
|
+
const [profile, followed] = await Promise.allSettled([
|
|
69
|
+
birthday ? getMyMember({ fieldsets: ["FULL"] }) : Promise.resolve(undefined),
|
|
70
|
+
channels.length ? listMyLoyaltyFollowedChannels() : Promise.resolve(undefined),
|
|
71
|
+
]);
|
|
72
|
+
await check(c);
|
|
73
|
+
if (version !== revision) return { ...EARNING_EMPTY };
|
|
74
|
+
let failed = false;
|
|
75
|
+
if (birthday) {
|
|
76
|
+
if (profile.status === "fulfilled" && profile.value?.member?.id) {
|
|
77
|
+
c.id = profile.value.member.id;
|
|
78
|
+
c.date = profile.value.member.contact?.birthdate ?? "";
|
|
79
|
+
} else { c.id = ""; failed = true; }
|
|
80
|
+
}
|
|
81
|
+
if (channels.length) {
|
|
82
|
+
const rows = followed.status === "fulfilled" ? followed.value?.followedChannels : undefined;
|
|
83
|
+
if (followed.status === "fulfilled" && (rows === undefined || Array.isArray(rows))) {
|
|
84
|
+
const known = new Set(rows?.map(row => row.channel));
|
|
85
|
+
c.channels = channels.map(channel => ({ ...channel, followed: known.has(channel.type) }));
|
|
86
|
+
} else { c.channels = []; failed = true; }
|
|
87
|
+
}
|
|
88
|
+
return { ...view(), earnMessage: failed ? copyParam("loyalty.earnLoadError", "Your earning actions couldn't be loaded. Refresh to try again.") : "" };
|
|
89
|
+
} catch {
|
|
90
|
+
if (context !== c || version !== revision) return { ...EARNING_EMPTY };
|
|
91
|
+
// A failed private read must not pretend the member has never followed.
|
|
92
|
+
c.channels = []; c.id = "";
|
|
93
|
+
return { ...view(), earnMessage: copyParam("loyalty.earnLoadError", "Your earning actions couldn't be loaded. Refresh to try again.") };
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
async openBirthday(s: S): Promise<S> {
|
|
97
|
+
if (!(await memberAccess()).email) return signIn(s);
|
|
98
|
+
const c = context;
|
|
99
|
+
if (!c?.birthday || !c.id || busy) return s;
|
|
100
|
+
await check(c);
|
|
101
|
+
const parsed = c.date ? new Date(`${c.date}T12:00:00`).getTime() / 1000 : Date.now() / 1000;
|
|
102
|
+
return { ...s, birthdayOpen: true, birthdayPicked: false, birthdayDraft: Number.isFinite(parsed) ? parsed : 0, earnMessage: "" };
|
|
103
|
+
},
|
|
104
|
+
draftBirthday(s: S, arg: unknown): S { const value = Number(obj(arg).value); return busy || !birthdateAt(value) ? s : { ...s, birthdayDraft: value, birthdayPicked: true }; },
|
|
105
|
+
closeBirthday(s: S): S { return busy ? s : { ...s, birthdayOpen: false }; },
|
|
106
|
+
saveBirthday(s: S): S {
|
|
107
|
+
const c = context, date = birthdateAt(s.birthdayDraft);
|
|
108
|
+
if (busy || !s.birthdayOpen || !c?.id || !c.birthday) return s;
|
|
109
|
+
if (!date || !s.birthdayPicked) return { ...s, earnMessage: copyParam("loyalty.birthdayInvalid", "Choose a birthday in the past.") };
|
|
110
|
+
busy = true;
|
|
111
|
+
void write(c, revision, async () => {
|
|
112
|
+
const result = await updateMember(c.id, { member: { contact: { birthdate: date } } });
|
|
113
|
+
if (result.member?.contact?.birthdate !== date) throw new Error("Birthday update was not confirmed");
|
|
114
|
+
return date;
|
|
115
|
+
});
|
|
116
|
+
return { ...s, earnBusy: true, earnMessage: copyParam("loyalty.saving", "Saving…") };
|
|
117
|
+
},
|
|
118
|
+
async followChannel(s: S, arg: unknown): Promise<S> {
|
|
119
|
+
if (!(await memberAccess()).email) return signIn(s);
|
|
120
|
+
const c = context, channel = c?.channels.find(row => row.type === obj(arg).id);
|
|
121
|
+
if (!c || !channel || busy) return s;
|
|
122
|
+
await check(c);
|
|
123
|
+
if (busy) return s;
|
|
124
|
+
busy = true;
|
|
125
|
+
void write(c, revision, async () => {
|
|
126
|
+
if (!channel.followed) {
|
|
127
|
+
const result = await followMyLoyaltyChannel(channel.type);
|
|
128
|
+
if (result.followedChannel?.channel !== channel.type) throw new Error("Follow was not confirmed");
|
|
129
|
+
await check(c); channel.followed = true;
|
|
130
|
+
}
|
|
131
|
+
const opened = await open(channel.url);
|
|
132
|
+
if (!opened.opened) throw new Error("Could not open channel");
|
|
133
|
+
return undefined;
|
|
134
|
+
});
|
|
135
|
+
return { ...s, earnBusy: true, earnMessage: copyParam("loyalty.saving", "Saving…") };
|
|
136
|
+
},
|
|
137
|
+
async finishEarning(s: S): Promise<S> {
|
|
138
|
+
if (context) {
|
|
139
|
+
try { await check(context); }
|
|
140
|
+
catch { context = undefined; outcome = undefined; return { ...s, ...EARNING_EMPTY }; }
|
|
141
|
+
}
|
|
142
|
+
if (!outcome || outcome.version !== revision || !context) return { ...s, earnBusy: busy };
|
|
143
|
+
if (outcome.date) context.date = outcome.date;
|
|
144
|
+
const message = outcome.message; outcome = undefined;
|
|
145
|
+
return { ...s, ...view(), earnMessage: message };
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -9,13 +9,20 @@ import { calendarDay, formatCurrency, formatDateMedium, formatNumber } from "../
|
|
|
9
9
|
import { appLocale, isSingular } from "../_shared/app-locale.js";
|
|
10
10
|
import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
|
|
11
11
|
import {
|
|
12
|
-
getLoyaltyProgram, listLoyaltyRewards, listLoyaltyEarningRules, getCurrentMemberLoyaltyAccount, getLoyaltyTiersProgram, listMyLoyaltyCoupons,
|
|
12
|
+
getLoyaltyProgram, listLoyaltyRewards, listLoyaltyEarningRules, getCurrentMemberLoyaltyAccount, getLoyaltyTiersProgram, listMyLoyaltyCoupons, listMyReferralRewards,
|
|
13
13
|
type WixLoyaltyReward, type WixLoyaltyEarningRule, type WixLoyaltyProgram, type WixLoyaltyAccount, type WixLoyaltyTiersProgram,
|
|
14
|
-
type WixLoyaltyCoupon, type
|
|
14
|
+
type WixLoyaltyCoupon, type WixReferralReward,
|
|
15
15
|
} from "@himalaya/wix-loyalty";
|
|
16
16
|
|
|
17
|
+
import { buildOfferings, visibleOfferings, offeringRewardLabel, memberRewardCost, OFFERING_TIER_LIMIT, type OfferingGroup } from "../_shared/loyalty-offerings.js";
|
|
18
|
+
|
|
19
|
+
import { createEarningController, EARNING_EMPTY, type EarningState } from "./earning.js";
|
|
20
|
+
import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
21
|
+
|
|
17
22
|
interface Row { id: string; name: string; subtitle: string }
|
|
18
|
-
interface S extends LoadFlags {
|
|
23
|
+
interface S extends LoadFlags, EarningState {
|
|
24
|
+
offeringGroups: OfferingGroup[]; offeringsExpanded: boolean;
|
|
25
|
+
hideTierOfferings: boolean; hideBaseOffering: boolean; hideShowAllTiers: boolean; tierError: string;
|
|
19
26
|
programName: string; unit: string; currency: string;
|
|
20
27
|
rewards: Row[]; earningRules: Row[]; emptyText: string; earningEmptyText: string;
|
|
21
28
|
hideCatalog: boolean; hideEarningHeading: boolean; inactiveText: string;
|
|
@@ -29,12 +36,13 @@ interface S extends LoadFlags {
|
|
|
29
36
|
/** "Rewards available (N)"; hidden when N is 0 or cannot be counted honestly. */
|
|
30
37
|
memberRewards: string; hideMemberRewards: boolean;
|
|
31
38
|
}
|
|
32
|
-
const PRIVATE_EMPTY = {
|
|
39
|
+
const PRIVATE_EMPTY = { ...EARNING_EMPTY,
|
|
33
40
|
memberPoints: "", memberMessage: "", hideMemberCard: true, hideMemberSignIn: true, hideMemberRetry: true,
|
|
34
41
|
memberTier: "", memberExpiry: "", memberExpiryUrgent: "", memberRewards: "", hideMemberRewards: true,
|
|
35
42
|
};
|
|
36
43
|
const PRIVATE_KEYS = Object.keys(PRIVATE_EMPTY) as (keyof typeof PRIVATE_EMPTY)[];
|
|
37
|
-
const
|
|
44
|
+
const OFFERING_EMPTY = { offeringGroups: [] as OfferingGroup[], offeringsExpanded: false, hideTierOfferings: true, hideBaseOffering: false, hideShowAllTiers: true, tierError: "" };
|
|
45
|
+
const INITIAL: S = { ...OFFERING_EMPTY, ...LOADING, ...PRIVATE_EMPTY, programName: "", unit: "", currency: "", rewards: [], earningRules: [], emptyText: "", earningEmptyText: "", hideCatalog: false, hideEarningHeading: true, inactiveText: "", rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: false };
|
|
38
46
|
|
|
39
47
|
function amount(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
|
|
40
48
|
function baseConfig<T extends { tierId?: string }>(configs: T[] | undefined): T | undefined {
|
|
@@ -73,22 +81,13 @@ function currencyFrom(s: S, arg: unknown): string {
|
|
|
73
81
|
const raw = (arg as { currency?: unknown }).currency;
|
|
74
82
|
return typeof raw === "string" && /^[A-Z]{3}$/.test(raw) ? raw : "";
|
|
75
83
|
}
|
|
76
|
-
/** What `reward` costs a member on `tierId` (undefined = the base tier): its row for that tier,
|
|
77
|
-
* else the base row, else the legacy flat `requiredPoints`. Undefined when nothing prices it. */
|
|
78
|
-
function memberCost(reward: WixLoyaltyReward, tierId: string | undefined): number | undefined {
|
|
79
|
-
const configs: WixLoyaltyRewardConfig[] | undefined = reward.discountAmount?.configsByTier ?? reward.couponReward?.fixedAmount?.configsByTier
|
|
80
|
-
?? reward.couponReward?.percentage?.configsByTier ?? reward.couponReward?.freeShipping?.configsByTier;
|
|
81
|
-
if (!configs?.length) return amount(reward.requiredPoints) ? reward.requiredPoints : undefined;
|
|
82
|
-
const row = (tierId ? configs.find(config => config.tierId === tierId) : undefined) ?? baseConfig(configs);
|
|
83
|
-
return amount(row?.costInPoints) ? row.costInPoints : undefined;
|
|
84
|
-
}
|
|
85
84
|
/** The one-app `calculateAvailableRewardsToClaim`: active rewards the balance covers on the
|
|
86
85
|
* member's tier (an SPI discount prices itself in text, not points, so it is left out), plus
|
|
87
86
|
* claimed coupons that are still ACTIVE and not deleted. */
|
|
88
87
|
export function countAvailableRewards(rewards: WixLoyaltyReward[], coupons: WixLoyaltyCoupon[], balance: number, tierId: string | undefined): number {
|
|
89
88
|
const claimable = rewards.filter(reward => {
|
|
90
89
|
if (reward.active !== true || reward.type === "SPI_DISCOUNT_AMOUNT") return false;
|
|
91
|
-
const cost =
|
|
90
|
+
const cost = memberRewardCost(reward, tierId);
|
|
92
91
|
return cost !== undefined && cost <= balance;
|
|
93
92
|
}).length;
|
|
94
93
|
return claimable + coupons.filter(coupon => coupon.status === "ACTIVE" && !coupon.couponReference?.deleted).length;
|
|
@@ -128,11 +127,12 @@ const settle = <T>(read: Promise<T>): Promise<Settled<T>> => read.then(value =>
|
|
|
128
127
|
/** The status lines for a member whose balance is known. A failed tiers read only drops the tier;
|
|
129
128
|
* a failed coupon or rewards read drops the count, because a partial total would understate it. */
|
|
130
129
|
function statusLines(program: WixLoyaltyProgram, account: WixLoyaltyAccount, balance: number, tiers: Settled<WixLoyaltyTiersProgram>,
|
|
131
|
-
coupons: Settled<{ loyaltyCoupons?: WixLoyaltyCoupon[] }>, rewards: WixLoyaltyReward[] | undefined) {
|
|
130
|
+
coupons: Settled<{ loyaltyCoupons?: WixLoyaltyCoupon[] }>, referrals: Settled<{ referralRewards: WixReferralReward[] }>, rewards: WixLoyaltyReward[] | undefined) {
|
|
132
131
|
const tierId = account.tier?.id || undefined;
|
|
133
132
|
const tier = tiers.failed ? "" : currentTierName(tiers.value, tierId);
|
|
134
133
|
const [expiry, urgent] = expirySentence(program, account);
|
|
135
|
-
const
|
|
134
|
+
const referralCount = referrals.value?.referralRewards.filter(r => r.rewardType === "COUPON" && r.coupon?.status === "ACTIVE").length ?? 0;
|
|
135
|
+
const count = coupons.failed || referrals.failed || !rewards ? 0 : countAvailableRewards(rewards, coupons.value?.loyaltyCoupons ?? [], balance, tierId) + referralCount;
|
|
136
136
|
return {
|
|
137
137
|
memberTier: tier ? copyTemplate("loyalty.memberTier", "{tier} tier", { tier }) : "",
|
|
138
138
|
memberExpiry: urgent ? "" : expiry, memberExpiryUrgent: urgent ? expiry : "",
|
|
@@ -145,13 +145,26 @@ const COPY: LoadCopy<Row> = {
|
|
|
145
145
|
sample: () => [{ id: "sample-reward", name: copyParam("loyalty.sampleReward", "5% off your next order"), subtitle: points(500, "") }],
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
let loadRevision = 0;
|
|
149
|
+
let allOfferings: OfferingGroup[] = [];
|
|
150
|
+
const earning: ReturnType<typeof createEarningController<S>> = createEarningController<S>((): Promise<unknown> => svc.finishEarning());
|
|
151
|
+
const register = withOnlineWrites(defineService, ["saveBirthday", "followChannel"], { messageFields: ["earnMessage"] });
|
|
152
|
+
const svc = register<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown) => Promise<S>; showAllTiers: (s: S) => S;
|
|
153
|
+
openBirthday: (s: S) => Promise<S>; draftBirthday: (s: S, arg?: unknown) => S; closeBirthday: (s: S) => S;
|
|
154
|
+
saveBirthday: (s: S) => S; followChannel: (s: S, arg?: unknown) => Promise<S>; finishEarning: (s: S) => Promise<S>;
|
|
155
|
+
}>("himiBrandedLoyalty", {
|
|
149
156
|
state: INITIAL,
|
|
150
157
|
notHydrated: PRIVATE_KEYS,
|
|
151
158
|
notPersisted: PRIVATE_KEYS,
|
|
152
159
|
actions: {
|
|
153
|
-
|
|
160
|
+
showAllTiers: s => allOfferings.length ? ({ ...s, offeringsExpanded: true, offeringGroups: allOfferings, hideShowAllTiers: true }) : s,
|
|
161
|
+
openBirthday: s => earning.openBirthday(s), draftBirthday: (s, arg) => earning.draftBirthday(s, arg),
|
|
162
|
+
closeBirthday: s => earning.closeBirthday(s), saveBirthday: s => earning.saveBirthday(s),
|
|
163
|
+
followChannel: (s, arg) => earning.followChannel(s, arg), finishEarning: s => earning.finishEarning(s),
|
|
164
|
+
prepare: s => { loadRevision++; allOfferings = []; earning.clear(); return { ...s, ...PRIVATE_EMPTY }; },
|
|
154
165
|
load: async (s, arg) => {
|
|
166
|
+
const revision = ++loadRevision;
|
|
167
|
+
earning.clear(); allOfferings = [];
|
|
155
168
|
const currency = currencyFrom(s, arg);
|
|
156
169
|
const business = businessStamp();
|
|
157
170
|
try {
|
|
@@ -159,8 +172,8 @@ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown)
|
|
|
159
172
|
const program = response.loyaltyProgram ?? response.program;
|
|
160
173
|
if (!program || !["ACTIVE", "DRAFT", "PAUSED"].includes(program.status ?? "")) throw new Error("Loyalty program response is unavailable");
|
|
161
174
|
const customUnit = (program.pointDefinition?.customName ?? "").trim();
|
|
162
|
-
const common = { ...s, ...PRIVATE_EMPTY, currency, programName: program.name || copyParam("loyalty.programFallback", "Rewards"), unit: customUnit || copyParam("loyalty.pointMany", "points"), inactiveText: "", rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: true, hideEarningHeading: true };
|
|
163
|
-
if (program.status !== "ACTIVE") {
|
|
175
|
+
const common = { ...s, ...PRIVATE_EMPTY, ...OFFERING_EMPTY, currency, programName: program.name || copyParam("loyalty.programFallback", "Rewards"), unit: customUnit || copyParam("loyalty.pointMany", "points"), inactiveText: "", rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: true, hideEarningHeading: true };
|
|
176
|
+
if (program.status !== "ACTIVE" || program.premiumFeatures?.loyaltyProgram === false) {
|
|
164
177
|
return { ...common, ...loaded("rewards", [], COPY), earningRules: [], earningEmptyText: "", hideCatalog: true,
|
|
165
178
|
inactiveText: copyParam("loyalty.inactive", "The rewards program isn't available right now.") };
|
|
166
179
|
}
|
|
@@ -169,23 +182,36 @@ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown)
|
|
|
169
182
|
const accountRead = member.email && privateStateSupported
|
|
170
183
|
? settle(getCurrentMemberLoyaltyAccount())
|
|
171
184
|
: Promise.resolve({ value: undefined, failed: false });
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
// Public tier metadata is shared by the visitor offering and the member status.
|
|
186
|
+
// Coupons and account details remain member-only.
|
|
187
|
+
const tiersRead = settle(getLoyaltyTiersProgram());
|
|
188
|
+
const couponsRead = member.email && privateStateSupported ? settle(listMyLoyaltyCoupons())
|
|
189
|
+
: Promise.resolve({ failed: true } as Settled<{ loyaltyCoupons?: WixLoyaltyCoupon[] }>);
|
|
190
|
+
const referralsRead = member.email && privateStateSupported ? settle(listMyReferralRewards())
|
|
191
|
+
: Promise.resolve({ failed: true } as Settled<{ referralRewards: WixReferralReward[] }>);
|
|
192
|
+
const [[rewardResult, earningResult], account, tiers, coupons, referrals] = await Promise.all([
|
|
193
|
+
Promise.allSettled([listLoyaltyRewards(), listLoyaltyEarningRules()]), accountRead, tiersRead, couponsRead, referralsRead,
|
|
178
194
|
]);
|
|
179
195
|
const rewardResponse = rewardResult.status === "fulfilled" ? rewardResult.value : undefined;
|
|
180
196
|
const earningResponse = earningResult.status === "fulfilled" ? earningResult.value : undefined;
|
|
181
197
|
if (businessStamp() !== business) throw new Error("Business changed while Loyalty loaded");
|
|
182
198
|
if (rewardResult.status === "rejected" && earningResult.status === "rejected") throw new Error("Loyalty catalog is unavailable");
|
|
183
199
|
const rewards = (rewardResponse?.rewards ?? []).filter(reward => reward.active === true).map((reward, i) => ({
|
|
184
|
-
id: reward.id || `reward-${i}`, name: reward.name || copyParam("loyalty.rewardFallback", "Reward"), subtitle: rewardLabel(reward, customUnit),
|
|
200
|
+
id: reward.id || `reward-${i}`, name: reward.name || copyParam("loyalty.rewardFallback", "Reward"), subtitle: reward.type === "SPI_DISCOUNT_AMOUNT" ? offeringRewardLabel(reward, undefined, customUnit, currency) : rewardLabel(reward, customUnit),
|
|
185
201
|
}));
|
|
186
202
|
const earningRules = (earningResponse?.earningRules ?? []).filter(rule => rule.status === "ACTIVE").map((rule, i) => ({
|
|
187
203
|
id: rule.id || `earning-${i}`, name: rule.title || copyParam("loyalty.earnFallback", "Earn points"), subtitle: earningLabel(rule, customUnit, currency),
|
|
188
204
|
}));
|
|
205
|
+
const tiered = !tiers.failed && tiers.value?.programSettings?.status === "ACTIVE" && program.premiumFeatures?.tiers !== false;
|
|
206
|
+
const allOfferingGroups = tiered ? buildOfferings(tiers.value, rewardResponse?.rewards ?? [], earningResponse?.earningRules ?? [], customUnit, currency) : [];
|
|
207
|
+
const offeringsExpanded = s.offeringsExpanded;
|
|
208
|
+
const offeringState = { offeringGroups: visibleOfferings(allOfferingGroups, offeringsExpanded), offeringsExpanded,
|
|
209
|
+
hideTierOfferings: !tiered, hideBaseOffering: tiered, hideShowAllTiers: !tiered || offeringsExpanded || allOfferingGroups.length <= OFFERING_TIER_LIMIT,
|
|
210
|
+
tierError: tiers.failed ? copyParam("loyalty.tiersError", "Tier benefits couldn't be loaded. Showing the base offering.") : "" };
|
|
211
|
+
if (revision !== loadRevision) return s;
|
|
212
|
+
allOfferings = allOfferingGroups;
|
|
213
|
+
const earningState = member.email && !privateStateSupported ? { ...EARNING_EMPTY } : await earning.load(member, program, earningResponse?.earningRules ?? []);
|
|
214
|
+
if (businessStamp() !== business) throw new Error("Business changed while Loyalty loaded");
|
|
189
215
|
let privateState = { ...PRIVATE_EMPTY };
|
|
190
216
|
if (member.email) {
|
|
191
217
|
try {
|
|
@@ -194,7 +220,7 @@ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown)
|
|
|
194
220
|
const known = typeof balance === "number" && Number.isFinite(balance);
|
|
195
221
|
privateState = { ...PRIVATE_EMPTY, hideMemberCard: false,
|
|
196
222
|
...(known && privateStateSupported && !account.failed
|
|
197
|
-
? statusLines(program, account.value?.account ?? {}, balance, tiers, coupons, rewardResult.status === "fulfilled" ? (rewardResponse?.rewards ?? []) : undefined) : {}),
|
|
223
|
+
? statusLines(program, account.value?.account ?? {}, balance, tiers, coupons, referrals, rewardResult.status === "fulfilled" ? (rewardResponse?.rewards ?? []) : undefined) : {}),
|
|
198
224
|
memberPoints: typeof balance === "number" && Number.isFinite(balance) ? points(balance, customUnit) : "",
|
|
199
225
|
memberMessage: !privateStateSupported ? copyParam("loyalty.updateApp", "Update the app to see your points securely.") : account.failed ? copyParam("loyalty.pointsError", "Your points couldn't be loaded.")
|
|
200
226
|
: typeof balance !== "number" || !Number.isFinite(balance) ? copyParam("loyalty.pointsUnavailable", "Your points aren't available yet.") : "",
|
|
@@ -207,7 +233,7 @@ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown)
|
|
|
207
233
|
} else {
|
|
208
234
|
privateState.hideMemberSignIn = false;
|
|
209
235
|
}
|
|
210
|
-
return { ...common, ...loaded("rewards", rewards, COPY), ...privateState, earningRules, hideCatalog: false, hideEarningHeading: false,
|
|
236
|
+
return { ...common, ...loaded("rewards", rewards, COPY), ...privateState, ...offeringState, ...earningState, earningRules, hideCatalog: false, hideEarningHeading: false,
|
|
211
237
|
rewardError: rewardResult.status === "rejected" ? copyParam("loyalty.rewardsError", "Rewards couldn't be loaded.") : "",
|
|
212
238
|
earningError: earningResult.status === "rejected" ? copyParam("loyalty.earningError", "Ways to earn points couldn't be loaded.") : "",
|
|
213
239
|
hideRewardError: rewardResult.status === "fulfilled", hideEarningError: earningResult.status === "fulfilled", hideRewardRows: rewardResult.status === "rejected",
|
|
@@ -215,7 +241,7 @@ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown)
|
|
|
215
241
|
earningEmptyText: earningResult.status === "rejected" || earningRules.length ? "" : copyParam("loyalty.earningEmpty", "No ways to earn points yet.") };
|
|
216
242
|
} catch (error) {
|
|
217
243
|
const failure = loadFailed("rewards", error, COPY);
|
|
218
|
-
return { ...s, ...PRIVATE_EMPTY, currency, earningRules: [], earningEmptyText: "", inactiveText: "", hideCatalog: !failure.isMock, hideEarningHeading: true, rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: !failure.isMock,
|
|
244
|
+
return { ...s, ...PRIVATE_EMPTY, ...OFFERING_EMPTY, currency, earningRules: [], earningEmptyText: "", inactiveText: "", hideCatalog: !failure.isMock, hideEarningHeading: true, rewardError: "", earningError: "", hideRewardError: true, hideEarningError: true, hideRewardRows: !failure.isMock,
|
|
219
245
|
...failure, signInRequired: false, noSignIn: true };
|
|
220
246
|
}
|
|
221
247
|
},
|