@revenuecat/purchases-ui-js 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/paywall/Node.svelte +2 -0
- package/dist/components/paywall/Paywall.svelte +15 -4
- package/dist/components/paywall/Paywall.svelte.d.ts +7 -0
- package/dist/components/redemption-button/RedemptionButton.stories.svelte +90 -0
- package/dist/components/redemption-button/RedemptionButton.stories.svelte.d.ts +19 -0
- package/dist/components/redemption-button/RedemptionButton.svelte +88 -0
- package/dist/components/redemption-button/RedemptionButton.svelte.d.ts +4 -0
- package/dist/components/workflows/Screen.svelte +4 -0
- package/dist/components/workflows/Screen.svelte.d.ts +2 -0
- package/dist/types/component.d.ts +2 -1
- package/dist/types/components/redemption-button.d.ts +7 -0
- package/dist/types/components/redemption-button.js +1 -0
- package/dist/types/variables.d.ts +2 -2
- package/package.json +6 -2
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import InputMultipleChoice from "../options/InputMultipleChoice.svelte";
|
|
14
14
|
import InputOption from "../options/InputOption.svelte";
|
|
15
15
|
import InputSingleChoice from "../options/InputSingleChoice.svelte";
|
|
16
|
+
import RedemptionButton from "../redemption-button/RedemptionButton.svelte";
|
|
16
17
|
import TextNode from "../text/TextNode.svelte";
|
|
17
18
|
import type { Component } from "../../types/component";
|
|
18
19
|
import type { Component as SvelteComponent } from "svelte";
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
input_single_choice: InputSingleChoice,
|
|
40
41
|
package: Package,
|
|
41
42
|
purchase_button: PurchaseButton,
|
|
43
|
+
redemption_button: RedemptionButton,
|
|
42
44
|
stack: Stack,
|
|
43
45
|
tab_control_button: TabControlButton,
|
|
44
46
|
tab_control_toggle: TabControlToggle,
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
paywallData: PaywallData;
|
|
31
31
|
selectedLocale?: string;
|
|
32
32
|
variablesPerPackage?: Record<string, VariableDictionary>;
|
|
33
|
+
/**
|
|
34
|
+
* Global variables that are available independent of package selection.
|
|
35
|
+
* Useful for success screens, confirmation flows, and other contexts where variables
|
|
36
|
+
* aren't tied to a specific package. Package-specific variables from variablesPerPackage
|
|
37
|
+
* will override global variables when both are present.
|
|
38
|
+
*/
|
|
39
|
+
globalVariables?: VariableDictionary;
|
|
33
40
|
infoPerPackage?: Record<string, PackageInfo>;
|
|
34
41
|
uiConfig: UIConfig;
|
|
35
42
|
preferredColorMode?: ColorMode;
|
|
@@ -56,6 +63,7 @@
|
|
|
56
63
|
paywallData,
|
|
57
64
|
selectedLocale,
|
|
58
65
|
variablesPerPackage = {},
|
|
66
|
+
globalVariables = {},
|
|
59
67
|
infoPerPackage = {},
|
|
60
68
|
preferredColorMode,
|
|
61
69
|
onPurchaseClicked,
|
|
@@ -142,10 +150,13 @@
|
|
|
142
150
|
uiConfig,
|
|
143
151
|
});
|
|
144
152
|
|
|
145
|
-
const variables: VariablesStore = derived(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
const variables: VariablesStore = derived(selectedPackageId, (packageId) => {
|
|
154
|
+
const packageVars = variablesPerPackage[packageId || ""];
|
|
155
|
+
return {
|
|
156
|
+
...globalVariables,
|
|
157
|
+
...packageVars,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
149
160
|
|
|
150
161
|
setVariablesContext(variables);
|
|
151
162
|
|
|
@@ -6,6 +6,13 @@ interface Props {
|
|
|
6
6
|
paywallData: PaywallData;
|
|
7
7
|
selectedLocale?: string;
|
|
8
8
|
variablesPerPackage?: Record<string, VariableDictionary>;
|
|
9
|
+
/**
|
|
10
|
+
* Global variables that are available independent of package selection.
|
|
11
|
+
* Useful for success screens, confirmation flows, and other contexts where variables
|
|
12
|
+
* aren't tied to a specific package. Package-specific variables from variablesPerPackage
|
|
13
|
+
* will override global variables when both are present.
|
|
14
|
+
*/
|
|
15
|
+
globalVariables?: VariableDictionary;
|
|
9
16
|
infoPerPackage?: Record<string, PackageInfo>;
|
|
10
17
|
uiConfig: UIConfig;
|
|
11
18
|
preferredColorMode?: ColorMode;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import RedemptionButton from "./RedemptionButton.svelte";
|
|
3
|
+
import { componentDecorator } from "../../stories/component-decorator";
|
|
4
|
+
import { localizationDecorator } from "../../stories/localization-decorator";
|
|
5
|
+
import { variablesDecorator } from "../../stories/variables-decorator";
|
|
6
|
+
import type { RedemptionButtonProps } from "../../types/components/redemption-button";
|
|
7
|
+
import type { StackProps } from "../../types/components/stack";
|
|
8
|
+
import type { TextNodeProps } from "../../types/components/text";
|
|
9
|
+
import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
|
|
10
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
11
|
+
|
|
12
|
+
const defaultLocale = "en_US";
|
|
13
|
+
|
|
14
|
+
const textProps: TextNodeProps = {
|
|
15
|
+
type: "text",
|
|
16
|
+
id: "redeem-text",
|
|
17
|
+
name: "Redeem text",
|
|
18
|
+
text_lid: "redeem_text",
|
|
19
|
+
font_size: "body_m",
|
|
20
|
+
font_weight: "medium",
|
|
21
|
+
horizontal_alignment: "center",
|
|
22
|
+
size: {
|
|
23
|
+
width: { type: "fit" },
|
|
24
|
+
height: { type: "fit" },
|
|
25
|
+
},
|
|
26
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
27
|
+
padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
28
|
+
color: {
|
|
29
|
+
dark: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
30
|
+
light: { type: "hex", value: "#ffffff" },
|
|
31
|
+
},
|
|
32
|
+
background_color: null,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const stackProps: StackProps = {
|
|
36
|
+
type: "stack",
|
|
37
|
+
id: "redemption-stack",
|
|
38
|
+
name: "Redemption Stack",
|
|
39
|
+
components: [textProps],
|
|
40
|
+
size: { width: { type: "fill" }, height: { type: "fit" } },
|
|
41
|
+
dimension: {
|
|
42
|
+
type: "horizontal",
|
|
43
|
+
alignment: "center",
|
|
44
|
+
distribution: "center",
|
|
45
|
+
},
|
|
46
|
+
spacing: 8,
|
|
47
|
+
margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
|
|
48
|
+
padding: { top: 12, trailing: 16, bottom: 12, leading: 16 },
|
|
49
|
+
background_color: null,
|
|
50
|
+
background: {
|
|
51
|
+
type: "color",
|
|
52
|
+
value: {
|
|
53
|
+
dark: { type: "hex", value: "#000000" },
|
|
54
|
+
light: { type: "hex", value: "#000000" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
border: null,
|
|
58
|
+
shape: { type: "pill" },
|
|
59
|
+
shadow: null,
|
|
60
|
+
badge: null,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const { Story } = defineMeta({
|
|
64
|
+
title: "Components/RedemptionButton",
|
|
65
|
+
component: RedemptionButton,
|
|
66
|
+
decorators: [
|
|
67
|
+
componentDecorator(),
|
|
68
|
+
localizationDecorator({
|
|
69
|
+
defaultLocale,
|
|
70
|
+
localizations: {
|
|
71
|
+
[defaultLocale]: {
|
|
72
|
+
redeem_text: "Redeem your trial",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
variablesDecorator({
|
|
77
|
+
"success.redemption_url": "myapp://redeem?token=abc123xyz",
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
args: {
|
|
81
|
+
type: "redemption_button",
|
|
82
|
+
id: "redemption-button",
|
|
83
|
+
name: "Redemption Button",
|
|
84
|
+
stack: stackProps,
|
|
85
|
+
transition: null,
|
|
86
|
+
} satisfies RedemptionButtonProps,
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<Story name="Default" />
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import RedemptionButton from "./RedemptionButton.svelte";
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const RedemptionButton: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type RedemptionButton = InstanceType<typeof RedemptionButton>;
|
|
19
|
+
export default RedemptionButton;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Stack from "../stack/Stack.svelte";
|
|
3
|
+
import { getVariablesContext } from "../../stores/variables";
|
|
4
|
+
import type { RedemptionButtonProps } from "../../types/components/redemption-button";
|
|
5
|
+
import QRCode from "qrcode";
|
|
6
|
+
import { onMount } from "svelte";
|
|
7
|
+
|
|
8
|
+
const props: RedemptionButtonProps = $props();
|
|
9
|
+
|
|
10
|
+
const variables = getVariablesContext();
|
|
11
|
+
const redemptionUrl = $derived($variables?.["success.redemption_url"]);
|
|
12
|
+
|
|
13
|
+
let isMobile = $state(true);
|
|
14
|
+
let qrCodeDataUrl = $state("");
|
|
15
|
+
let qrFailed = $state(false);
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
const mediaQuery = window.matchMedia("(max-width: 767px)");
|
|
19
|
+
isMobile = mediaQuery.matches;
|
|
20
|
+
|
|
21
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
22
|
+
isMobile = e.matches;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
mediaQuery.addEventListener("change", handler);
|
|
26
|
+
|
|
27
|
+
return () => {
|
|
28
|
+
mediaQuery.removeEventListener("change", handler);
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
$effect(() => {
|
|
33
|
+
if (!isMobile && redemptionUrl) {
|
|
34
|
+
qrFailed = false;
|
|
35
|
+
QRCode.toDataURL(redemptionUrl, {
|
|
36
|
+
width: 512,
|
|
37
|
+
margin: 2,
|
|
38
|
+
errorCorrectionLevel: "M",
|
|
39
|
+
color: {
|
|
40
|
+
dark: "#000000",
|
|
41
|
+
light: "#ffffff",
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.then((url) => {
|
|
45
|
+
qrCodeDataUrl = url;
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
console.warn("Failed to generate QR code:", err);
|
|
49
|
+
qrFailed = true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if (isMobile || qrFailed) && redemptionUrl}
|
|
56
|
+
<Stack
|
|
57
|
+
{...props.stack}
|
|
58
|
+
onclick={() => {
|
|
59
|
+
window.location.href = redemptionUrl;
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
{:else if qrCodeDataUrl}
|
|
63
|
+
<div class="qr-wrapper">
|
|
64
|
+
<img src={qrCodeDataUrl} alt="Redemption QR Code" class="qr-code" />
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
.qr-wrapper {
|
|
70
|
+
display: flex;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
align-items: center;
|
|
73
|
+
align-self: center;
|
|
74
|
+
width: fit-content;
|
|
75
|
+
margin: 0 auto;
|
|
76
|
+
background-color: #ffffff;
|
|
77
|
+
border-radius: 8px;
|
|
78
|
+
padding: 16px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.qr-code {
|
|
82
|
+
display: block;
|
|
83
|
+
width: 128px;
|
|
84
|
+
height: 128px;
|
|
85
|
+
image-rendering: -webkit-optimize-contrast;
|
|
86
|
+
image-rendering: crisp-edges;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RedemptionButtonProps } from "../../types/components/redemption-button";
|
|
2
|
+
declare const RedemptionButton: import("svelte").Component<RedemptionButtonProps, {}, "">;
|
|
3
|
+
type RedemptionButton = ReturnType<typeof RedemptionButton>;
|
|
4
|
+
export default RedemptionButton;
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
type UIConfig,
|
|
4
4
|
} from "../paywall/Paywall.svelte";
|
|
5
5
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
6
|
+
import type { VariableDictionary } from "../../types/variables";
|
|
6
7
|
import { VARIABLES } from "../paywall/fixtures/variables";
|
|
7
8
|
interface Props {
|
|
8
9
|
paywallComponents: WorkflowScreen | null | undefined;
|
|
9
10
|
selectedLocale?: string;
|
|
10
11
|
uiConfig: UIConfig;
|
|
12
|
+
globalVariables?: VariableDictionary;
|
|
11
13
|
onActionTriggered?: (actionId: string) => void;
|
|
12
14
|
onPurchaseClicked?: (
|
|
13
15
|
packageId: string,
|
|
@@ -19,6 +21,7 @@
|
|
|
19
21
|
paywallComponents,
|
|
20
22
|
selectedLocale = "en_US",
|
|
21
23
|
uiConfig,
|
|
24
|
+
globalVariables = {},
|
|
22
25
|
onActionTriggered,
|
|
23
26
|
onPurchaseClicked,
|
|
24
27
|
containerId = "screen-container",
|
|
@@ -32,6 +35,7 @@
|
|
|
32
35
|
{selectedLocale}
|
|
33
36
|
{uiConfig}
|
|
34
37
|
variablesPerPackage={VARIABLES}
|
|
38
|
+
{globalVariables}
|
|
35
39
|
onNavigateToUrlClicked={() => {}}
|
|
36
40
|
onVisitCustomerCenterClicked={() => {}}
|
|
37
41
|
onBackClicked={() => {}}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type UIConfig } from "../paywall/Paywall.svelte";
|
|
2
2
|
import type { WorkflowScreen } from "../../types/workflow";
|
|
3
|
+
import type { VariableDictionary } from "../../types/variables";
|
|
3
4
|
interface Props {
|
|
4
5
|
paywallComponents: WorkflowScreen | null | undefined;
|
|
5
6
|
selectedLocale?: string;
|
|
6
7
|
uiConfig: UIConfig;
|
|
8
|
+
globalVariables?: VariableDictionary;
|
|
7
9
|
onActionTriggered?: (actionId: string) => void;
|
|
8
10
|
onPurchaseClicked?: (packageId: string, actionId: string) => void | Promise<void>;
|
|
9
11
|
containerId?: string;
|
|
@@ -7,9 +7,10 @@ import type { ImageProps } from "./components/image";
|
|
|
7
7
|
import type { InputMultipleChoiceProps, InputOptionProps, InputSingleChoiceProps } from "./components/options";
|
|
8
8
|
import type { PackageProps } from "./components/package";
|
|
9
9
|
import type { PurchaseButtonProps } from "./components/purchase-button";
|
|
10
|
+
import type { RedemptionButtonProps } from "./components/redemption-button";
|
|
10
11
|
import type { StackProps } from "./components/stack";
|
|
11
12
|
import type { TabControlButtonProps, TabControlProps, TabControlToggleProps, TabsProps } from "./components/tabs";
|
|
12
13
|
import type { TextNodeProps } from "./components/text";
|
|
13
14
|
import type { TimelineProps } from "./components/timeline";
|
|
14
15
|
import type { VideoProps } from "./components/video";
|
|
15
|
-
export type Component = ButtonProps | CarouselProps | CountdownProps | FooterProps | IconProps | ImageProps | InputMultipleChoiceProps | InputOptionProps | InputSingleChoiceProps | PackageProps | PurchaseButtonProps | StackProps | TabControlButtonProps | TabControlToggleProps | TabControlProps | TabsProps | TextNodeProps | TimelineProps | VideoProps;
|
|
16
|
+
export type Component = ButtonProps | CarouselProps | CountdownProps | FooterProps | IconProps | ImageProps | InputMultipleChoiceProps | InputOptionProps | InputSingleChoiceProps | PackageProps | PurchaseButtonProps | RedemptionButtonProps | StackProps | TabControlButtonProps | TabControlToggleProps | TabControlProps | TabsProps | TextNodeProps | TimelineProps | VideoProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,8 +7,8 @@ export declare enum PackageIdentifier {
|
|
|
7
7
|
annual = "$rc_annual",
|
|
8
8
|
lifetime = "$rc_lifetime"
|
|
9
9
|
}
|
|
10
|
-
type VariableName = "product.price" | "product.price_per_period" | "product.price_per_period_abbreviated" | "product.price_per_day" | "product.price_per_week" | "product.price_per_month" | "product.price_per_year" | "product.period" | "product.period_abbreviated" | "product.periodly" | "product.period_in_days" | "product.period_in_weeks" | "product.period_in_months" | "product.period_in_years" | "product.period_with_unit" | "product.currency_code" | "product.currency_symbol" | "product.offer_price" | "product.offer_price_per_day" | "product.offer_price_per_week" | "product.offer_price_per_month" | "product.offer_price_per_year" | "product.offer_period" | "product.offer_period_abbreviated" | "product.offer_period_in_days" | "product.offer_period_in_weeks" | "product.offer_period_in_months" | "product.offer_period_in_years" | "product.offer_period_with_unit" | "product.offer_end_date" | "product.secondary_offer_price" | "product.secondary_offer_period" | "product.secondary_offer_period_abbreviated" | "product.relative_discount" | "product.store_product_name";
|
|
11
|
-
export type VariableDictionary = Record<VariableName, string
|
|
10
|
+
type VariableName = "product.price" | "product.price_per_period" | "product.price_per_period_abbreviated" | "product.price_per_day" | "product.price_per_week" | "product.price_per_month" | "product.price_per_year" | "product.period" | "product.period_abbreviated" | "product.periodly" | "product.period_in_days" | "product.period_in_weeks" | "product.period_in_months" | "product.period_in_years" | "product.period_with_unit" | "product.currency_code" | "product.currency_symbol" | "product.offer_price" | "product.offer_price_per_day" | "product.offer_price_per_week" | "product.offer_price_per_month" | "product.offer_price_per_year" | "product.offer_period" | "product.offer_period_abbreviated" | "product.offer_period_in_days" | "product.offer_period_in_weeks" | "product.offer_period_in_months" | "product.offer_period_in_years" | "product.offer_period_with_unit" | "product.offer_end_date" | "product.secondary_offer_price" | "product.secondary_offer_period" | "product.secondary_offer_period_abbreviated" | "product.relative_discount" | "product.store_product_name" | "success.redemption_url" | "success.package_identifier" | "success.product_identifier" | "success.product_title" | "success.product_description" | "success.price" | "success.currency_code" | "success.customer_email" | "success.is_subscription" | "success.is_trial";
|
|
11
|
+
export type VariableDictionary = Partial<Record<VariableName, string>>;
|
|
12
12
|
export type VariablesDictionary = Record<PackageIdentifier, VariableDictionary>;
|
|
13
13
|
export interface PackageInfo {
|
|
14
14
|
hasIntroOffer?: boolean;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@revenuecat/purchases-ui-js",
|
|
3
3
|
"description": "Web components for Paywalls. Powered by RevenueCat",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.2.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "RevenueCat, Inc."
|
|
8
8
|
},
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@sveltejs/package": "2.5.4",
|
|
84
84
|
"@sveltejs/vite-plugin-svelte": "6.2.1",
|
|
85
85
|
"@types/node": "24.9.2",
|
|
86
|
+
"@types/qrcode": "^1.5.6",
|
|
86
87
|
"@typescript-eslint/parser": "8.46.2",
|
|
87
88
|
"chromatic": "13.3.2",
|
|
88
89
|
"eslint": "9.38.0",
|
|
@@ -115,5 +116,8 @@
|
|
|
115
116
|
"engines": {
|
|
116
117
|
"node": "^22.18"
|
|
117
118
|
},
|
|
118
|
-
"packageManager": "npm@11.5.2+sha512.aac1241cfc3f41dc38780d64295c6c6b917a41e24288b33519a7b11adfc5a54a5f881c642d7557215b6c70e01e55655ed7ba666300fd0238bc75fb17478afaf3"
|
|
119
|
+
"packageManager": "npm@11.5.2+sha512.aac1241cfc3f41dc38780d64295c6c6b917a41e24288b33519a7b11adfc5a54a5f881c642d7557215b6c70e01e55655ed7ba666300fd0238bc75fb17478afaf3",
|
|
120
|
+
"dependencies": {
|
|
121
|
+
"qrcode": "^1.5.4"
|
|
122
|
+
}
|
|
119
123
|
}
|