@revenuecat/purchases-ui-js 0.0.10 → 0.0.12

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.
@@ -44,6 +44,11 @@
44
44
  },
45
45
  });
46
46
 
47
+ const purchaseState = {
48
+ locale: "en_US",
49
+ defaultLocale: "en_US",
50
+ };
51
+
47
52
  export const labelsData = {
48
53
  en_US: {
49
54
  id1: "Restore purchases",
@@ -53,7 +58,7 @@
53
58
  },
54
59
  };
55
60
 
56
- const textProps = (id: number): TextProps => ({
61
+ const textProps = (id: number) => ({
57
62
  type: "text",
58
63
  name: "Button Text",
59
64
  components: [],
@@ -114,6 +119,7 @@
114
119
  ...baseStackProps,
115
120
  components: [textProps(1)],
116
121
  },
122
+ purchaseState: purchaseState,
117
123
  onAction,
118
124
  }}
119
125
  />
@@ -143,6 +149,7 @@
143
149
  },
144
150
  components: [textProps(2)],
145
151
  },
152
+ purchaseState: purchaseState,
146
153
  }}
147
154
  />
148
155
 
@@ -174,6 +181,7 @@
174
181
  },
175
182
  components: [textProps(3)],
176
183
  },
184
+ purchaseState: purchaseState,
177
185
  }}
178
186
  />
179
187
 
@@ -200,5 +208,6 @@
200
208
  },
201
209
  components: [textProps(4)],
202
210
  },
211
+ purchaseState: purchaseState,
203
212
  }}
204
213
  />
@@ -3,12 +3,18 @@
3
3
  import Footer from "./Footer.svelte";
4
4
  import { colorModeStoryMeta } from "../../stories/meta-templates";
5
5
 
6
+ const purchaseState = {
7
+ locale: "en_US",
8
+ defaultLocale: "en_US",
9
+ };
10
+
6
11
  const { Story } = defineMeta({
7
12
  title: "Components/Footer",
8
13
  component: Footer,
9
14
  tags: ["autodocs"],
10
15
  argTypes: {
11
16
  colorMode: colorModeStoryMeta,
17
+
12
18
  stack: {
13
19
  control: { type: "object" },
14
20
  description: "Stack configuration for package content",
@@ -38,7 +44,9 @@
38
44
  is_selected_by_default: false,
39
45
  name: "Package - Monthly",
40
46
  package_id: "$rc_annual",
47
+ purchaseState: purchaseState,
41
48
  stack: {
49
+ purchaseState: purchaseState,
42
50
  background_color: {
43
51
  light: {
44
52
  type: "hex",
@@ -33,6 +33,10 @@
33
33
  QZ4ZmYsqjN: "$1.99",
34
34
  },
35
35
  };
36
+ const purchaseState = {
37
+ locale: "en_US",
38
+ defaultLocale: "en_US",
39
+ };
36
40
  </script>
37
41
 
38
42
  <!-- Default -->
@@ -43,7 +47,9 @@
43
47
  is_selected_by_default: false,
44
48
  name: "Package - Monthly",
45
49
  package_id: "$rc_annual",
50
+ purchaseState,
46
51
  stack: {
52
+ purchaseState,
47
53
  background_color: {
48
54
  light: {
49
55
  type: "hex",
@@ -3,6 +3,7 @@
3
3
 
4
4
  import Paywall from "./Paywall.svelte";
5
5
  import { paywallData } from "../../stories/fixtures";
6
+ import { fn } from "@storybook/test";
6
7
 
7
8
  const { Story } = defineMeta({
8
9
  title: "Example/Paywall",
@@ -11,14 +12,28 @@
11
12
  argTypes: {},
12
13
  args: {},
13
14
  });
14
-
15
- const onPurchaseClicked = (selectedPackageId: string) => {
16
- alert(`Purchasing: ${selectedPackageId}`);
17
- };
18
15
  </script>
19
16
 
20
17
  <!-- Default story -->
21
18
  <Story
22
19
  name="Primary"
23
- args={{ data: paywallData, onPurchaseClicked: onPurchaseClicked }}
20
+ args={{
21
+ paywallData: paywallData,
22
+ onPurchaseClicked: fn(),
23
+ onBackClicked: fn(),
24
+ onNavigateToClicked: fn(),
25
+ onRestorePurchasesClicked: fn(),
26
+ }}
27
+ />
28
+
29
+ <Story
30
+ name="Italian Missing Labels"
31
+ args={{
32
+ paywallData: paywallData,
33
+ selectedLocale: "it_IT",
34
+ onPurchaseClicked: fn(),
35
+ onBackClicked: fn(),
36
+ onNavigateToClicked: fn(),
37
+ onRestorePurchasesClicked: fn(),
38
+ }}
24
39
  />
@@ -1,36 +1,80 @@
1
1
  <script lang="ts">
2
2
  import type {
3
3
  Extra,
4
+ NavigateToAction,
4
5
  PaywallData,
5
6
  SupportedActions,
6
7
  } from "../../data/entities";
7
8
 
8
9
  import Node from "./Node.svelte";
9
10
  import type { PurchaseState } from "../../data/state";
11
+ import { findSelectedPackageId, getLabelById } from "../../utils";
10
12
 
11
13
  interface Props {
12
- data: PaywallData;
13
- onPurchaseClicked: (selectedPackageId: string) => void;
14
+ paywallData: PaywallData;
15
+ onPurchaseClicked?: (selectedPackageId: string) => void;
16
+ onBackClicked?: () => void;
17
+ onVisitCustomerCenterClicked?: () => void;
18
+ onRestorePurchasesClicked?: () => void;
19
+ onNavigateToUrlClicked?: (url: string) => void;
20
+ selectedLocale?: string;
14
21
  }
15
22
 
16
- const findSelectedPackage = (data: PaywallData) => {
17
- return undefined;
18
- };
19
- const { data, onPurchaseClicked }: Props = $props();
23
+ const {
24
+ paywallData,
25
+ onPurchaseClicked,
26
+ onRestorePurchasesClicked,
27
+ onNavigateToUrlClicked,
28
+ onBackClicked,
29
+ onVisitCustomerCenterClicked,
30
+ selectedLocale,
31
+ }: Props = $props();
20
32
 
21
33
  let purchaseState: PurchaseState = $state({
22
- selectedPackageId: findSelectedPackage(data),
34
+ selectedPackageId: findSelectedPackageId(paywallData),
35
+ locale: selectedLocale || paywallData.default_locale,
36
+ defaultLocale: paywallData.default_locale,
23
37
  });
24
38
 
25
- const onSelectPackageAction = (action: SupportedActions, data?: Extra) => {
39
+ const onRestorePurchasesAction = (_: SupportedActions, _2?: Extra) => {
40
+ onRestorePurchasesClicked?.();
41
+ };
42
+
43
+ const onNavigateBackAction = (_: SupportedActions, _2?: Extra) => {
44
+ onBackClicked?.();
45
+ };
46
+
47
+ const onNavigateToUrl = (action: NavigateToAction, _?: Extra) => {
48
+ if (action.destination === "customer_center") {
49
+ onVisitCustomerCenterClicked?.();
50
+ }
51
+
52
+ if (!action.url) {
53
+ return;
54
+ }
55
+
56
+ const localizedURL = getLabelById(
57
+ action.url.url_lid,
58
+ purchaseState.locale,
59
+ purchaseState.defaultLocale,
60
+ paywallData.components_localizations,
61
+ );
62
+ if (!localizedURL) {
63
+ return;
64
+ }
65
+
66
+ onNavigateToUrlClicked?.(localizedURL);
67
+ };
68
+
69
+ const onSelectPackageAction = (_: SupportedActions, data?: Extra) => {
26
70
  purchaseState.selectedPackageId = data?.packageId;
27
71
  };
28
72
 
29
- const onPurchaseAction = (action: SupportedActions, data?: Extra) => {
73
+ const onPurchaseAction = (_: SupportedActions, _2?: Extra) => {
30
74
  if (purchaseState.selectedPackageId === undefined) {
31
75
  return;
32
76
  }
33
- onPurchaseClicked(purchaseState.selectedPackageId);
77
+ onPurchaseClicked?.(purchaseState.selectedPackageId);
34
78
  };
35
79
 
36
80
  const onAction = (action: SupportedActions, data?: Extra) => {
@@ -38,12 +82,18 @@
38
82
  case "select_package":
39
83
  onSelectPackageAction(action, data);
40
84
  break;
85
+ case "navigate_to":
86
+ onNavigateToUrl(action, data);
87
+ break;
88
+ case "navigate_back":
89
+ onNavigateBackAction(action, data);
90
+ break;
91
+ case "restore_purchases":
92
+ onRestorePurchasesAction(action, data);
93
+ break;
41
94
  case "purchase":
42
95
  onPurchaseAction(action, data);
43
96
  break;
44
- default:
45
- alert(action.type);
46
- break;
47
97
  }
48
98
  };
49
99
  </script>
@@ -51,8 +101,8 @@
51
101
  <div>
52
102
  <Node
53
103
  {onAction}
54
- nodeData={data.components_config.base.stack}
55
- labels={data.components_localizations}
104
+ nodeData={paywallData.components_config.base.stack}
105
+ labels={paywallData.components_localizations}
56
106
  {purchaseState}
57
107
  />
58
108
  </div>
@@ -64,16 +114,19 @@
64
114
  *::after {
65
115
  box-sizing: border-box;
66
116
  }
117
+
67
118
  /* 2. Remove default margin */
68
119
  * {
69
120
  margin: 0;
70
121
  }
122
+
71
123
  body {
72
124
  /* 3. Add accessible line-height */
73
125
  line-height: 1.5;
74
126
  /* 4. Improve text rendering */
75
127
  -webkit-font-smoothing: antialiased;
76
128
  }
129
+
77
130
  /* 5. Improve media defaults */
78
131
  img,
79
132
  picture,
@@ -83,6 +136,7 @@
83
136
  display: block;
84
137
  max-width: 100%;
85
138
  }
139
+
86
140
  /* 6. Inherit fonts for form controls */
87
141
  input,
88
142
  button,
@@ -90,6 +144,7 @@
90
144
  select {
91
145
  font: inherit;
92
146
  }
147
+
93
148
  /* 7. Avoid text overflows */
94
149
  p,
95
150
  h1,
@@ -100,10 +155,12 @@
100
155
  h6 {
101
156
  overflow-wrap: break-word;
102
157
  }
158
+
103
159
  /* 8. Improve line wrapping */
104
160
  p {
105
161
  text-wrap: pretty;
106
162
  }
163
+
107
164
  h1,
108
165
  h2,
109
166
  h3,
@@ -112,9 +169,10 @@
112
169
  h6 {
113
170
  text-wrap: balance;
114
171
  }
172
+
115
173
  /*
116
- 9. Create a root stacking context
117
- */
174
+ 9. Create a root stacking context
175
+ */
118
176
  #root,
119
177
  #__next {
120
178
  isolation: isolate;
@@ -1,6 +1,11 @@
1
1
  import type { PaywallData } from "../../data/entities";
2
2
  declare const Paywall: import("svelte").Component<{
3
- data: PaywallData;
4
- onPurchaseClicked: (selectedPackageId: string) => void;
3
+ paywallData: PaywallData;
4
+ onPurchaseClicked?: (selectedPackageId: string) => void;
5
+ onBackClicked?: () => void;
6
+ onVisitCustomerCenterClicked?: () => void;
7
+ onRestorePurchasesClicked?: () => void;
8
+ onNavigateToUrlClicked?: (url: string) => void;
9
+ selectedLocale?: string;
5
10
  }, {}, "">;
6
11
  export default Paywall;
@@ -27,6 +27,10 @@
27
27
  "6gpcQ4q6T8": "Continue",
28
28
  },
29
29
  };
30
+ const purchaseState = {
31
+ locale: "en_US",
32
+ defaultLocale: "en_US",
33
+ };
30
34
  </script>
31
35
 
32
36
  <!-- Default button -->
@@ -35,7 +39,9 @@
35
39
  args={{
36
40
  id: "mjaE9fOv5z",
37
41
  name: "Purchase_button",
42
+ purchaseState,
38
43
  stack: {
44
+ purchaseState,
39
45
  background_color: {
40
46
  light: {
41
47
  type: "hex",
@@ -45,6 +51,7 @@
45
51
  border: null,
46
52
  components: [
47
53
  {
54
+ purchaseState,
48
55
  background_color: null,
49
56
  color: {
50
57
  light: {
@@ -32,6 +32,11 @@
32
32
  },
33
33
  });
34
34
 
35
+ const purchaseState = {
36
+ locale: "en_US",
37
+ defaultLocale: "en_US",
38
+ };
39
+
35
40
  const components = [
36
41
  {
37
42
  type: "text",
@@ -48,6 +53,7 @@
48
53
  dark: { type: "alias", value: "transparent" },
49
54
  light: { type: "alias", value: "transparent" },
50
55
  },
56
+ purchaseState,
51
57
  },
52
58
  {
53
59
  type: "text",
@@ -64,6 +70,7 @@
64
70
  dark: { type: "alias", value: "transparent" },
65
71
  light: { type: "alias", value: "transparent" },
66
72
  },
73
+ purchaseState,
67
74
  },
68
75
  {
69
76
  type: "text",
@@ -80,6 +87,7 @@
80
87
  dark: { type: "alias", value: "transparent" },
81
88
  light: { type: "alias", value: "transparent" },
82
89
  },
90
+ purchaseState,
83
91
  },
84
92
  ];
85
93
  </script>
@@ -89,7 +97,7 @@
89
97
  name="Vertical Stack"
90
98
  args={{
91
99
  type: "stack",
92
-
100
+ purchaseState,
93
101
  size: { width: { type: "fill" }, height: { type: "fill" } },
94
102
  labels,
95
103
  dimension: {
@@ -111,6 +119,7 @@
111
119
  name="Horizontal Stack"
112
120
  args={{
113
121
  type: "stack",
122
+ purchaseState,
114
123
  dimension: {
115
124
  type: "horizontal",
116
125
  alignment: "center",
@@ -132,6 +141,7 @@
132
141
  name="Space Around"
133
142
  args={{
134
143
  type: "stack",
144
+ purchaseState,
135
145
  dimension: {
136
146
  type: "horizontal",
137
147
  alignment: "center",
@@ -153,6 +163,7 @@
153
163
  name="Background Colors"
154
164
  args={{
155
165
  type: "stack",
166
+ purchaseState,
156
167
  dimension: {
157
168
  type: "vertical",
158
169
  alignment: "center",
@@ -175,6 +186,7 @@
175
186
  name="Border Styles"
176
187
  args={{
177
188
  type: "stack",
189
+ purchaseState,
178
190
  dimension: {
179
191
  type: "vertical",
180
192
  alignment: "center",
@@ -210,6 +222,7 @@
210
222
  name="Rounded Corners"
211
223
  args={{
212
224
  type: "stack",
225
+ purchaseState,
213
226
  dimension: {
214
227
  type: "vertical",
215
228
  alignment: "center",
@@ -241,6 +254,7 @@
241
254
  name="Fixed Size"
242
255
  args={{
243
256
  type: "stack",
257
+ purchaseState,
244
258
  dimension: {
245
259
  type: "vertical",
246
260
  alignment: "center",
@@ -281,6 +295,7 @@
281
295
  distribution: "start",
282
296
  },
283
297
  components,
298
+ purchaseState,
284
299
  labels,
285
300
  border: {
286
301
  width: 2,
@@ -40,6 +40,10 @@
40
40
  width: { type: "fill" },
41
41
  height: { type: "fill" },
42
42
  },
43
+ purchaseState: {
44
+ locale: "en_US",
45
+ defaultLocale: "en_US",
46
+ },
43
47
  },
44
48
  });
45
49
  </script>
@@ -1,17 +1,30 @@
1
1
  <script lang="ts">
2
2
  import { getTextComponentStyles } from "./text-utils";
3
3
  import type { TextProps } from "../../data/entities";
4
- import { stringifyStyles } from "../../utils";
4
+ import { getLabelById, stringifyStyles } from "../../utils";
5
5
 
6
- const { id, labels, text_lid, ...restProps }: TextProps = $props();
6
+ const { id, labels, text_lid, purchaseState, ...restProps }: TextProps =
7
+ $props();
7
8
 
8
9
  const { tagToRender, textStyles } = $derived(
9
- getTextComponentStyles({ id, labels, text_lid, ...restProps }),
10
+ getTextComponentStyles({
11
+ id,
12
+ labels,
13
+ text_lid,
14
+ purchaseState,
15
+ ...restProps,
16
+ }),
10
17
  );
11
18
  const stylesString = $derived(stringifyStyles(textStyles));
12
19
 
13
- // TODO: drill down the selected locale.
14
- const label = $derived(labels["en_US"][text_lid]);
20
+ const label = $derived(
21
+ getLabelById(
22
+ text_lid,
23
+ purchaseState.locale,
24
+ purchaseState.defaultLocale,
25
+ labels,
26
+ ),
27
+ );
15
28
  </script>
16
29
 
17
30
  <svelte:element this={tagToRender} {id} style={stylesString} class="text-block">
@@ -1,4 +1,4 @@
1
- import type { BorderType, ColorMode, ColorType, CornerRadiusType, DimensionType, FitTypes, FontWeights, ShadowType, ShapeType, SizeType, Spacing, TextAlignment, FontSizeTags } from "../types";
1
+ import type { BorderType, ColorMode, ColorType, CornerRadiusType, DimensionType, FitTypes, FontSizeTags, FontWeights, ShadowType, ShapeType, SizeType, Spacing, TextAlignment } from "../types";
2
2
  import type { PurchaseState } from "./state";
3
3
  export interface Extra {
4
4
  [key: string]: any;
@@ -34,12 +34,13 @@ export interface ComponentLocalizations extends Extra {
34
34
  export interface PaywallData extends Extra {
35
35
  components_config: ComponentConfig;
36
36
  components_localizations: ComponentLocalizations;
37
+ default_locale: string;
37
38
  }
38
39
  export interface ActionsProps {
39
40
  onAction?: (action: SupportedActions, data?: Extra) => void;
40
41
  }
41
42
  export interface PurchaseStateProps {
42
- purchaseState?: PurchaseState;
43
+ purchaseState: PurchaseState;
43
44
  }
44
45
  interface SharedComponentProps extends PaywallComponent, ActionsProps, PurchaseStateProps {
45
46
  labels: ComponentLocalizations;
@@ -1,3 +1,5 @@
1
1
  export interface PurchaseState {
2
2
  selectedPackageId?: string;
3
+ locale: string;
4
+ defaultLocale: string;
3
5
  }
@@ -426,7 +426,7 @@ export const paywallData = {
426
426
  {
427
427
  components: [],
428
428
  id: "lPNvA6W9st",
429
- is_selected_by_default: false,
429
+ is_selected_by_default: true,
430
430
  name: "Package - Annual",
431
431
  package_id: "$rc_annual",
432
432
  stack: {
@@ -2222,6 +2222,12 @@ export const paywallData = {
2222
2222
  wxvc6e6xE3: "Switching phones? Restore a previously exported backup",
2223
2223
  zwOsIynvC9: "Annual",
2224
2224
  },
2225
+ it_IT: {
2226
+ "62QzV4Le26": "Genera un file a partire dalle tue abitudini",
2227
+ dkKKOS047e: "Sblocca",
2228
+ "BcX-6YwhoV": "Mensile",
2229
+ Pu686q0Mya: "A vita",
2230
+ },
2225
2231
  },
2226
2232
  config: {},
2227
2233
  created_at: "2024-11-07T04:36:37Z",
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { ImageMaskShapeType, TextProps } from "./data/entities.js";
2
- import { FontSizeTags, type BorderType, type ColorMode, type ColorType, type DimensionType, type ShadowType, type ShapeType, type SizeType, type Spacing } from "./types.js";
1
+ import type { ComponentLocalizations, ImageMaskShapeType, PaywallData, TextProps } from "./data/entities.js";
2
+ import { type BorderType, type ColorMode, type ColorType, type DimensionType, FontSizeTags, type ShadowType, type ShapeType, type SizeType, type Spacing } from "./types.js";
3
3
  export type TextComponentTags = "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
4
4
  /**
5
5
  * Maps font size to appropriate HTML heading tag
@@ -91,3 +91,20 @@ export declare function getTextStyles(props: TextProps): {
91
91
  * @returns CSS string
92
92
  */
93
93
  export declare function stringifyStyles(styles: Record<string, string | number>): string;
94
+ /**
95
+ * Given a ComponentLocalizations object and a locale returns the label with label_id in the chosen locale, if any. Falls
96
+ * back to the label with the same label_id in the fallbackLocale, if any. Finally returns undefined if no label can be
97
+ * found for the requested label_id.
98
+ * @param label_id - The id of the label to be returned
99
+ * @param locale - The preferred locale to return the label
100
+ * @param fallbackLocale - The locale to fall back to in case no label is found in the preferred one
101
+ * @param labels - A ComponentLocalizations instance
102
+ * @returns The label in the preferred or fallback locale, or undefined.
103
+ */
104
+ export declare function getLabelById(label_id: string, locale: string, fallbackLocale: string, labels: ComponentLocalizations): string | undefined;
105
+ /**
106
+ * Given an instance of PaywallData, returns the id of the first package marked as `is_selected_by_default` if any.
107
+ * @param paywallData
108
+ * @returns the id of the first package marked as `is_selected_by_default` or undefined
109
+ */
110
+ export declare function findSelectedPackageId(paywallData: PaywallData): string | undefined;
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { FontSizes, FontWeights, StackAlignment, StackDirection, StackDistribution, FontSizeTags, } from "./types.js";
1
+ import { FontSizes, FontSizeTags, FontWeights, StackAlignment, StackDirection, StackDistribution, } from "./types.js";
2
2
  /**
3
3
  * Generates CSS spacing styles for margin or padding
4
4
  * @param spacing - The spacing object containing top, trailing, bottom, and leading values
@@ -225,3 +225,53 @@ export function stringifyStyles(styles) {
225
225
  .map(([key, value]) => `${key}: ${value};`)
226
226
  .join("; ");
227
227
  }
228
+ /**
229
+ * Given a ComponentLocalizations object and a locale returns the label with label_id in the chosen locale, if any. Falls
230
+ * back to the label with the same label_id in the fallbackLocale, if any. Finally returns undefined if no label can be
231
+ * found for the requested label_id.
232
+ * @param label_id - The id of the label to be returned
233
+ * @param locale - The preferred locale to return the label
234
+ * @param fallbackLocale - The locale to fall back to in case no label is found in the preferred one
235
+ * @param labels - A ComponentLocalizations instance
236
+ * @returns The label in the preferred or fallback locale, or undefined.
237
+ */
238
+ export function getLabelById(label_id, locale, fallbackLocale, labels) {
239
+ const fallback = (labels[fallbackLocale] || {})[label_id];
240
+ if (!(labels[locale] || {})[label_id]) {
241
+ return fallback;
242
+ }
243
+ return labels[locale][label_id];
244
+ }
245
+ /**
246
+ * Given an instance of PaywallData, returns the id of the first package marked as `is_selected_by_default` if any.
247
+ * @param paywallData
248
+ * @returns the id of the first package marked as `is_selected_by_default` or undefined
249
+ */
250
+ export function findSelectedPackageId(paywallData) {
251
+ const traverseNode = (node) => {
252
+ if (node.type === "package" &&
253
+ node.is_selected_by_default) {
254
+ return node;
255
+ }
256
+ if (node.components) {
257
+ for (let c of node.components) {
258
+ const pkg = traverseNode(c);
259
+ if (pkg) {
260
+ return pkg;
261
+ }
262
+ }
263
+ }
264
+ if (node.stack !== undefined) {
265
+ const pkg = traverseNode(node.stack);
266
+ if (pkg) {
267
+ return pkg;
268
+ }
269
+ }
270
+ return undefined;
271
+ };
272
+ const p = traverseNode(paywallData.components_config.base.stack);
273
+ if (p === undefined) {
274
+ return undefined;
275
+ }
276
+ return p.package_id;
277
+ }
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": "0.0.10",
5
+ "version": "0.0.12",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },