@frak-labs/core-sdk 1.1.4 → 1.1.5-beta.5b65e64f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/cdn/bundle.js +1 -1
  2. package/dist/actions-Cyn4asew.cjs +2 -0
  3. package/dist/actions-Cyn4asew.cjs.map +1 -0
  4. package/dist/actions-DtPVuk2e.js +2 -0
  5. package/dist/actions-DtPVuk2e.js.map +1 -0
  6. package/dist/actions.cjs +1 -1
  7. package/dist/actions.d.cts +3 -3
  8. package/dist/actions.d.ts +3 -3
  9. package/dist/actions.js +1 -1
  10. package/dist/bundle.cjs +1 -1
  11. package/dist/bundle.d.cts +5 -4
  12. package/dist/bundle.d.ts +5 -4
  13. package/dist/bundle.js +1 -1
  14. package/dist/frakContext-Bgidv-Se.js +2 -0
  15. package/dist/frakContext-Bgidv-Se.js.map +1 -0
  16. package/dist/frakContext-CeVlWFcX.cjs +2 -0
  17. package/dist/frakContext-CeVlWFcX.cjs.map +1 -0
  18. package/dist/getCurrencyAmountKey-DQcwjJiB.js +2 -0
  19. package/dist/getCurrencyAmountKey-DQcwjJiB.js.map +1 -0
  20. package/dist/getCurrencyAmountKey-h2eOiYzv.cjs +2 -0
  21. package/dist/getCurrencyAmountKey-h2eOiYzv.cjs.map +1 -0
  22. package/dist/{index-lcImYQyL.d.ts → index-BCoPwxjq.d.cts} +4 -2
  23. package/dist/{index-Cm1PNC9z.d.ts → index-CiHjUnfc.d.ts} +8 -6
  24. package/dist/{index-Cumn_FNI.d.cts → index-D25z5B9K.d.cts} +8 -6
  25. package/dist/{index-DR4n-QOe.d.cts → index-trhAN7q9.d.ts} +4 -2
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.cts +4 -3
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +1 -1
  30. package/dist/merchantInformation--tLnowAN.d.ts +344 -0
  31. package/dist/merchantInformation-BSuahpjl.d.cts +344 -0
  32. package/dist/{openSso-BErs7_Bg.d.cts → openSso-CENLMWJx.d.ts} +4 -279
  33. package/dist/{openSso-DRLH4D9a.d.ts → openSso-CKvkAGts.d.cts} +4 -279
  34. package/dist/rewards.cjs +2 -0
  35. package/dist/rewards.cjs.map +1 -0
  36. package/dist/rewards.d.cts +101 -0
  37. package/dist/rewards.d.ts +101 -0
  38. package/dist/rewards.js +2 -0
  39. package/dist/rewards.js.map +1 -0
  40. package/dist/src-BXtnEE3a.js +2 -0
  41. package/dist/src-BXtnEE3a.js.map +1 -0
  42. package/dist/src-TpX-7MwJ.cjs +2 -0
  43. package/dist/src-TpX-7MwJ.cjs.map +1 -0
  44. package/package.json +16 -3
  45. package/src/actions/getMerchantInformation.test.ts +2 -0
  46. package/src/actions/referral/processReferral.test.ts +4 -4
  47. package/src/actions/referral/processReferral.ts +4 -4
  48. package/src/clients/createIFrameFrakClient.ts +7 -7
  49. package/src/clients/transports/iframeLifecycleManager.ts +5 -21
  50. package/src/config/sdkConfigStore.ts +6 -3
  51. package/src/index.ts +6 -0
  52. package/src/rewards/conditions.test.ts +60 -0
  53. package/src/rewards/conditions.ts +72 -0
  54. package/src/rewards/example.test.ts +92 -0
  55. package/src/rewards/example.ts +57 -0
  56. package/src/rewards/format.test.ts +143 -0
  57. package/src/rewards/format.ts +81 -0
  58. package/src/rewards/index.ts +27 -0
  59. package/src/rewards/select.test.ts +212 -0
  60. package/src/rewards/select.ts +132 -0
  61. package/src/rewards/value.test.ts +89 -0
  62. package/src/rewards/value.ts +73 -0
  63. package/src/types/index.ts +6 -0
  64. package/src/types/rpc/merchantInformation.ts +120 -13
  65. package/src/utils/analytics/events/lifecycle.ts +1 -1
  66. package/src/utils/analytics/events/referral.ts +3 -3
  67. package/src/utils/browser/deepLinkWithFallback.ts +4 -1
  68. package/src/utils/browser/inAppBrowser.ts +2 -2
  69. package/src/utils/cache/withCache.ts +30 -11
  70. package/src/utils/iframe/iframeHelper.test.ts +39 -0
  71. package/src/utils/iframe/iframeHelper.ts +31 -2
  72. package/dist/actions-D5GbNcvt.cjs +0 -1
  73. package/dist/actions-DE2dHqtp.js +0 -1
  74. package/dist/frakContext-Bl17GGa3.cjs +0 -1
  75. package/dist/frakContext-DmI5CaSw.js +0 -1
  76. package/dist/src-5vpUuhfY.js +0 -1
  77. package/dist/src-BQsjMpvA.cjs +0 -1
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { extractMinPurchaseAmount, extractStartDate } from "./conditions";
3
+
4
+ const unix = (iso: string) => Math.floor(new Date(iso).getTime() / 1000);
5
+
6
+ describe("extractMinPurchaseAmount", () => {
7
+ it("reads a flat purchase.amount gte condition", () => {
8
+ expect(
9
+ extractMinPurchaseAmount([
10
+ { field: "purchase.amount", operator: "gte", value: 50 },
11
+ ])
12
+ ).toBe(50);
13
+ });
14
+
15
+ it("walks nested groups and keeps the lowest threshold", () => {
16
+ expect(
17
+ extractMinPurchaseAmount({
18
+ logic: "all",
19
+ conditions: [
20
+ { field: "purchase.amount", operator: "gte", value: 80 },
21
+ {
22
+ logic: "any",
23
+ conditions: [
24
+ {
25
+ field: "purchase.amount",
26
+ operator: "gt",
27
+ value: 30,
28
+ },
29
+ ],
30
+ },
31
+ ],
32
+ })
33
+ ).toBe(30);
34
+ });
35
+
36
+ it("returns undefined without a purchase gate", () => {
37
+ expect(
38
+ extractMinPurchaseAmount([
39
+ { field: "user.isNew", operator: "eq", value: true },
40
+ ])
41
+ ).toBeUndefined();
42
+ });
43
+ });
44
+
45
+ describe("extractStartDate", () => {
46
+ it("reads a time.timestamp gte condition (unix seconds)", () => {
47
+ const start = extractStartDate([
48
+ {
49
+ field: "time.timestamp",
50
+ operator: "gte",
51
+ value: unix("2025-02-01T00:00:00Z"),
52
+ },
53
+ ]);
54
+ expect(start?.toISOString()).toBe("2025-02-01T00:00:00.000Z");
55
+ });
56
+
57
+ it("returns undefined without a start gate", () => {
58
+ expect(extractStartDate([])).toBeUndefined();
59
+ });
60
+ });
@@ -0,0 +1,72 @@
1
+ import type { ConditionGroup, RuleCondition, RuleConditions } from "../types";
2
+
3
+ type ConditionNode = RuleCondition | ConditionGroup;
4
+
5
+ function conditionNodes(conditions: RuleConditions): ConditionNode[] {
6
+ return Array.isArray(conditions) ? conditions : conditions.conditions;
7
+ }
8
+
9
+ function conditionValueToNumber(
10
+ value: RuleCondition["value"]
11
+ ): number | undefined {
12
+ if (typeof value === "number") return value;
13
+ if (typeof value === "string" && value.trim() !== "") {
14
+ const parsed = Number(value);
15
+ return Number.isFinite(parsed) ? parsed : undefined;
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ // Conditions arrive as a flat list or recursive groups; collect every numeric
21
+ // value whose field + operator match, descending into nested groups.
22
+ function collectConditionValues(
23
+ conditions: RuleConditions,
24
+ field: string,
25
+ operators: ReadonlySet<RuleCondition["operator"]>
26
+ ): number[] {
27
+ const values: number[] = [];
28
+ for (const node of conditionNodes(conditions)) {
29
+ if ("logic" in node) {
30
+ values.push(...collectConditionValues(node, field, operators));
31
+ continue;
32
+ }
33
+ if (node.field === field && operators.has(node.operator)) {
34
+ const value = conditionValueToNumber(node.value);
35
+ if (value != null) values.push(value);
36
+ }
37
+ }
38
+ return values;
39
+ }
40
+
41
+ const MIN_PURCHASE_OPERATORS: ReadonlySet<RuleCondition["operator"]> = new Set([
42
+ "gt",
43
+ "gte",
44
+ "between",
45
+ ]);
46
+ const START_DATE_OPERATORS: ReadonlySet<RuleCondition["operator"]> = new Set([
47
+ "gt",
48
+ "gte",
49
+ ]);
50
+
51
+ export function extractMinPurchaseAmount(
52
+ conditions: RuleConditions
53
+ ): number | undefined {
54
+ const values = collectConditionValues(
55
+ conditions,
56
+ "purchase.amount",
57
+ MIN_PURCHASE_OPERATORS
58
+ );
59
+ return values.length > 0 ? Math.min(...values) : undefined;
60
+ }
61
+
62
+ // The business app encodes a campaign start as a `time.timestamp >= <unix s>`
63
+ // rule condition; the endpoint returns it verbatim inside `conditions`.
64
+ export function extractStartDate(conditions: RuleConditions): Date | undefined {
65
+ const values = collectConditionValues(
66
+ conditions,
67
+ "time.timestamp",
68
+ START_DATE_OPERATORS
69
+ );
70
+ if (values.length === 0) return undefined;
71
+ return new Date(Math.min(...values) * 1000);
72
+ }
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { EstimatedReward } from "../types";
3
+ import {
4
+ buildPercentageExample,
5
+ buildTierExample,
6
+ pickFlatBasket,
7
+ pickTierBasket,
8
+ } from "./example";
9
+
10
+ type PercentageReward = Extract<EstimatedReward, { payoutType: "percentage" }>;
11
+
12
+ function percentage(
13
+ percent: number,
14
+ bounds?: { min?: number; max?: number }
15
+ ): PercentageReward {
16
+ const toAmount = (eur: number) => ({
17
+ amount: eur,
18
+ eurAmount: eur,
19
+ usdAmount: eur,
20
+ gbpAmount: eur,
21
+ });
22
+ return {
23
+ payoutType: "percentage",
24
+ percent,
25
+ percentOf: "purchase_amount",
26
+ minAmount: bounds?.min != null ? toAmount(bounds.min) : undefined,
27
+ maxAmount: bounds?.max != null ? toAmount(bounds.max) : undefined,
28
+ };
29
+ }
30
+
31
+ describe("pickFlatBasket", () => {
32
+ it("defaults to the reference basket of 100", () => {
33
+ expect(pickFlatBasket(undefined)).toBe(100);
34
+ expect(pickFlatBasket(40)).toBe(100);
35
+ });
36
+
37
+ it("uses the minimum purchase when it exceeds the reference", () => {
38
+ expect(pickFlatBasket(150)).toBe(150);
39
+ });
40
+ });
41
+
42
+ describe("pickTierBasket", () => {
43
+ it("uses the reference when it fits the tier range", () => {
44
+ expect(pickTierBasket(80, 200)).toBe(100);
45
+ });
46
+
47
+ it("uses the lower bound for an open-ended tier", () => {
48
+ expect(pickTierBasket(80, undefined)).toBe(80);
49
+ });
50
+
51
+ it("picks a round number inside a bounded tier excluding the reference", () => {
52
+ expect(pickTierBasket(0, 30)).toBe(20);
53
+ expect(pickTierBasket(30, 80)).toBe(60);
54
+ });
55
+ });
56
+
57
+ describe("buildPercentageExample", () => {
58
+ it("computes percent of the basket", () => {
59
+ const example = buildPercentageExample(percentage(8), undefined);
60
+ expect(example?.basket).toBe(100);
61
+ expect(example?.reward).toBeCloseTo(8);
62
+ });
63
+
64
+ it("clamps the reward to the configured max amount", () => {
65
+ const example = buildPercentageExample(
66
+ percentage(8, { max: 4.8 }),
67
+ undefined
68
+ );
69
+ expect(example?.basket).toBe(100);
70
+ expect(example?.reward).toBeCloseTo(4.8);
71
+ });
72
+
73
+ it("uses the minimum purchase as the basket when higher", () => {
74
+ const example = buildPercentageExample(percentage(5), 200);
75
+ expect(example?.basket).toBe(200);
76
+ expect(example?.reward).toBeCloseTo(10);
77
+ });
78
+ });
79
+
80
+ describe("buildTierExample", () => {
81
+ it("computes percent of the open tier lower bound", () => {
82
+ const example = buildTierExample(12, 80, undefined);
83
+ expect(example?.basket).toBe(80);
84
+ expect(example?.reward).toBeCloseTo(9.6);
85
+ });
86
+
87
+ it("computes percent of a round basket inside a bounded tier", () => {
88
+ const example = buildTierExample(5, 30, 80);
89
+ expect(example?.basket).toBe(60);
90
+ expect(example?.reward).toBeCloseTo(3);
91
+ });
92
+ });
@@ -0,0 +1,57 @@
1
+ import type { EstimatedReward } from "../types";
2
+
3
+ type PercentageReward = Extract<EstimatedReward, { payoutType: "percentage" }>;
4
+
5
+ // Reference order total used to turn a "% of basket" reward into a concrete
6
+ // example amount. The campaign's minimum purchase overrides it when higher.
7
+ const REFERENCE_BASKET = 100;
8
+
9
+ export type RewardExample = {
10
+ basket: number;
11
+ reward: number;
12
+ };
13
+
14
+ function roundWithinRange(min: number, max: number): number {
15
+ const rounded = Math.round((min + max) / 2 / 10) * 10;
16
+ return Math.min(max, Math.max(min, rounded));
17
+ }
18
+
19
+ export function pickFlatBasket(minPurchase: number | undefined): number {
20
+ return Math.max(REFERENCE_BASKET, minPurchase ?? 0);
21
+ }
22
+
23
+ // Basket for a percentage tier: the reference when it sits inside the tier
24
+ // range, a round number within the range otherwise, or the lower bound for an
25
+ // open-ended (uncapped) top tier.
26
+ export function pickTierBasket(
27
+ minValue: number,
28
+ maxValue: number | undefined
29
+ ): number {
30
+ if (maxValue == null) return minValue;
31
+ if (minValue <= REFERENCE_BASKET && REFERENCE_BASKET <= maxValue) {
32
+ return REFERENCE_BASKET;
33
+ }
34
+ return roundWithinRange(minValue, maxValue);
35
+ }
36
+
37
+ export function buildPercentageExample(
38
+ reward: PercentageReward,
39
+ minPurchase: number | undefined
40
+ ): RewardExample | undefined {
41
+ const basket = pickFlatBasket(minPurchase);
42
+ if (basket <= 0) return undefined;
43
+ let amount = (reward.percent / 100) * basket;
44
+ if (reward.minAmount) amount = Math.max(amount, reward.minAmount.eurAmount);
45
+ if (reward.maxAmount) amount = Math.min(amount, reward.maxAmount.eurAmount);
46
+ return { basket, reward: amount };
47
+ }
48
+
49
+ export function buildTierExample(
50
+ percent: number,
51
+ minValue: number,
52
+ maxValue: number | undefined
53
+ ): RewardExample | undefined {
54
+ const basket = pickTierBasket(minValue, maxValue);
55
+ if (basket <= 0) return undefined;
56
+ return { basket, reward: (percent / 100) * basket };
57
+ }
@@ -0,0 +1,143 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { EstimatedReward } from "../types";
3
+ import {
4
+ applyRewardPlaceholder,
5
+ formatEstimatedReward,
6
+ formatRewardOrHide,
7
+ } from "./format";
8
+
9
+ const amount = (eur: number) => ({
10
+ amount: eur,
11
+ eurAmount: eur,
12
+ usdAmount: eur,
13
+ gbpAmount: eur,
14
+ });
15
+
16
+ describe("formatEstimatedReward", () => {
17
+ it("formats a fixed reward in the default currency, rounded", () => {
18
+ const reward: EstimatedReward = {
19
+ payoutType: "fixed",
20
+ amount: amount(5.4),
21
+ };
22
+ // Intl renders a narrow no-break space before "€" — assert content.
23
+ const formatted = formatEstimatedReward(reward);
24
+ expect(formatted).toContain("5");
25
+ expect(formatted).not.toContain("6");
26
+ expect(formatted).toContain("€");
27
+ });
28
+
29
+ it("renders a percentage reward as a percent string (never a basket amount)", () => {
30
+ const reward: EstimatedReward = {
31
+ payoutType: "percentage",
32
+ percent: 10,
33
+ percentOf: "purchase_amount",
34
+ maxAmount: amount(50),
35
+ };
36
+ expect(formatEstimatedReward(reward)).toBe("10 %");
37
+ });
38
+
39
+ it("uses the richest token tier for a tiered reward", () => {
40
+ const reward: EstimatedReward = {
41
+ payoutType: "tiered",
42
+ tierField: "purchase.amount",
43
+ tiers: [
44
+ { minValue: 0, maxValue: 50, amount: amount(2) },
45
+ { minValue: 50, amount: amount(8) },
46
+ ],
47
+ };
48
+ const formatted = formatEstimatedReward(reward);
49
+ expect(formatted).toContain("8");
50
+ expect(formatted).toContain("€");
51
+ });
52
+
53
+ it("falls back to the max percent when tiers carry no token amount", () => {
54
+ const reward: EstimatedReward = {
55
+ payoutType: "tiered",
56
+ tierField: "purchase.amount",
57
+ tiers: [
58
+ { minValue: 0, maxValue: 50, percent: 5 },
59
+ { minValue: 50, percent: 12 },
60
+ ],
61
+ };
62
+ expect(formatEstimatedReward(reward)).toBe("12 %");
63
+ });
64
+
65
+ it("respects the requested currency", () => {
66
+ const reward: EstimatedReward = {
67
+ payoutType: "fixed",
68
+ amount: amount(5),
69
+ };
70
+ const formatted = formatEstimatedReward(reward, "usd");
71
+ expect(formatted).toContain("5");
72
+ expect(formatted).toContain("$");
73
+ });
74
+ });
75
+
76
+ describe("formatRewardOrHide", () => {
77
+ it("returns undefined for a missing reward", () => {
78
+ expect(formatRewardOrHide(undefined)).toBeUndefined();
79
+ });
80
+
81
+ it("formats a fixed reward that carries money value", () => {
82
+ const formatted = formatRewardOrHide({
83
+ payoutType: "fixed",
84
+ amount: amount(5),
85
+ });
86
+ expect(formatted).toContain("5");
87
+ expect(formatted).toContain("€");
88
+ });
89
+
90
+ it("hides a fixed reward with no money value", () => {
91
+ expect(
92
+ formatRewardOrHide({ payoutType: "fixed", amount: amount(0) })
93
+ ).toBeUndefined();
94
+ });
95
+
96
+ it("always shows a capped percentage as a percent string", () => {
97
+ expect(
98
+ formatRewardOrHide({
99
+ payoutType: "percentage",
100
+ percent: 10,
101
+ percentOf: "purchase_amount",
102
+ maxAmount: amount(50),
103
+ })
104
+ ).toBe("10 %");
105
+ });
106
+
107
+ it("shows an uncapped percentage as a percent string (no money value)", () => {
108
+ expect(
109
+ formatRewardOrHide({
110
+ payoutType: "percentage",
111
+ percent: 8,
112
+ percentOf: "purchase_amount",
113
+ })
114
+ ).toBe("8 %");
115
+ });
116
+
117
+ it("shows a percent-only tiered reward instead of hiding it", () => {
118
+ expect(
119
+ formatRewardOrHide({
120
+ payoutType: "tiered",
121
+ tierField: "purchase.amount",
122
+ tiers: [
123
+ { minValue: 0, maxValue: 50, percent: 5 },
124
+ { minValue: 50, percent: 12 },
125
+ ],
126
+ })
127
+ ).toBe("12 %");
128
+ });
129
+ });
130
+
131
+ describe("applyRewardPlaceholder", () => {
132
+ it("substitutes the reward into the placeholder", () => {
133
+ expect(applyRewardPlaceholder("Earn {REWARD} now", "5 €")).toBe(
134
+ "Earn 5 € now"
135
+ );
136
+ });
137
+
138
+ it("strips the placeholder when no reward is provided", () => {
139
+ expect(applyRewardPlaceholder("Earn {REWARD} now", undefined)).toBe(
140
+ "Earn now"
141
+ );
142
+ });
143
+ });
@@ -0,0 +1,81 @@
1
+ import type { Currency, EstimatedReward } from "../types";
2
+ import { formatAmount } from "../utils/format/formatAmount";
3
+ import { getCurrencyAmountKey } from "../utils/format/getCurrencyAmountKey";
4
+ import { getSupportedCurrency } from "../utils/format/getSupportedCurrency";
5
+ import { getRewardRank, getRewardValue, maxRewardPercent } from "./value";
6
+
7
+ /**
8
+ * Format an {@link EstimatedReward} into a human-readable string.
9
+ *
10
+ * - `fixed` → e.g. `"5 €"`
11
+ * - `percentage` → e.g. `"10 %"`
12
+ * - `tiered` → the richest tier: max token amount (e.g. `"50 €"`) or, when
13
+ * tiers only carry percentages, the max percent (e.g. `"10 %"`)
14
+ *
15
+ * Percentages are always rendered as a `"X %"` string: the backend never sends
16
+ * a reference basket, so the reward cannot be resolved to a concrete amount
17
+ * here. Callers that need a worked example use the `example` helpers instead.
18
+ */
19
+ export function formatEstimatedReward(
20
+ reward: EstimatedReward,
21
+ currency?: Currency
22
+ ): string {
23
+ const supportedCurrency = getSupportedCurrency(currency);
24
+ const key = getCurrencyAmountKey(supportedCurrency);
25
+
26
+ switch (reward.payoutType) {
27
+ case "fixed":
28
+ return formatAmount(
29
+ Math.round(reward.amount[key]),
30
+ supportedCurrency
31
+ );
32
+
33
+ case "percentage":
34
+ return `${reward.percent} %`;
35
+
36
+ case "tiered": {
37
+ const maxAmount = getRewardValue(reward, key);
38
+ if (maxAmount > 0) {
39
+ return formatAmount(Math.round(maxAmount), supportedCurrency);
40
+ }
41
+ const maxPercent = maxRewardPercent(reward);
42
+ if (maxPercent > 0) {
43
+ return `${maxPercent} %`;
44
+ }
45
+ return formatAmount(0, supportedCurrency);
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Format a reward for display, or return `undefined` when it is not worth
52
+ * advertising. Callers rely on `undefined` to hide a badge / fall back to
53
+ * other copy.
54
+ *
55
+ * A reward is hidden only when it carries no displayable value — a `fixed` or
56
+ * `tiered` reward whose money value is `0` (e.g. an unknown token price). An
57
+ * uncapped percentage always renders as `"X %"`, since the percent itself is
58
+ * meaningful even without a money value.
59
+ */
60
+ export function formatRewardOrHide(
61
+ reward: EstimatedReward | undefined,
62
+ currency?: Currency
63
+ ): string | undefined {
64
+ if (!reward) return undefined;
65
+ const key = getCurrencyAmountKey(getSupportedCurrency(currency));
66
+ if (getRewardRank(reward, key) <= 0) return undefined;
67
+ return formatEstimatedReward(reward, currency);
68
+ }
69
+
70
+ /**
71
+ * Replace the `{REWARD}` placeholder in a text string with a reward value.
72
+ * If no reward is provided, returns the text with `{REWARD}` stripped.
73
+ */
74
+ export function applyRewardPlaceholder(
75
+ text: string,
76
+ reward: string | undefined
77
+ ): string {
78
+ return reward
79
+ ? text.replace("{REWARD}", reward)
80
+ : text.replace("{REWARD}", "");
81
+ }
@@ -0,0 +1,27 @@
1
+ // Framework-agnostic reward display logic shared across the SDK, listener,
2
+ // wallet and wallet-shared. Depends only on `@frak-labs/core-sdk`'s own reward
3
+ // types (`EstimatedReward`, `MerchantReward`, `RuleConditions`) — no backend or
4
+ // framework coupling — so it stays safe to ship inside the published SDK and
5
+ // lets core-sdk consume its own reward logic without a dependency cycle.
6
+
7
+ export { extractMinPurchaseAmount, extractStartDate } from "./conditions";
8
+ export {
9
+ buildPercentageExample,
10
+ buildTierExample,
11
+ pickFlatBasket,
12
+ pickTierBasket,
13
+ type RewardExample,
14
+ } from "./example";
15
+ export {
16
+ applyRewardPlaceholder,
17
+ formatEstimatedReward,
18
+ formatRewardOrHide,
19
+ } from "./format";
20
+ export {
21
+ type DisplayCampaign,
22
+ formatBestReward,
23
+ type RewardAudience,
24
+ type SelectDisplayCampaignOptions,
25
+ selectDisplayCampaign,
26
+ } from "./select";
27
+ export { getRewardValue } from "./value";