@developer_tribe/react-builder 1.2.40 → 1.2.42

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 (71) hide show
  1. package/dist/attributes-editor/FallbackLocalizationField.d.ts +6 -0
  2. package/dist/build-components/PaywallFooter/PaywallFooter.d.ts +5 -0
  3. package/dist/build-components/PaywallFooter/PaywallFooterProps.generated.d.ts +68 -0
  4. package/dist/build-components/index.d.ts +2 -1
  5. package/dist/build-components/patterns.generated.d.ts +495 -3
  6. package/dist/index.cjs.js +1 -1
  7. package/dist/index.cjs.js.map +1 -1
  8. package/dist/index.esm.js +1 -1
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.web.cjs.js +4 -4
  11. package/dist/index.web.cjs.js.map +1 -1
  12. package/dist/index.web.d.ts +8 -0
  13. package/dist/index.web.esm.js +4 -4
  14. package/dist/index.web.esm.js.map +1 -1
  15. package/dist/modals/IconPickerModal.d.ts +1 -1
  16. package/dist/product-base/types.d.ts +3 -0
  17. package/dist/store.d.ts +25 -0
  18. package/dist/styles.css +1 -1
  19. package/dist/types/PreviewConfig.d.ts +1 -1
  20. package/package.json +2 -2
  21. package/scripts/public/bin.js +0 -0
  22. package/src/RenderPage.tsx +1 -1
  23. package/src/assets/meta.json +1 -1
  24. package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
  25. package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
  26. package/src/assets/samples/paywall-1.json +18 -1
  27. package/src/assets/samples/paywall-2.json +2 -2
  28. package/src/assets/samples/paywall-app-delete-offer.json +2 -2
  29. package/src/assets/samples/paywall-app-open-offer.json +2 -2
  30. package/src/assets/samples/paywall-back-offer.json +1 -1
  31. package/src/assets/samples/paywall-notification-offer.json +1 -1
  32. package/src/assets/samples/vpn-onboard-1.json +3 -3
  33. package/src/assets/samples/vpn-onboard-2.json +3 -3
  34. package/src/assets/samples/vpn-onboard-3.json +3 -3
  35. package/src/assets/samples/vpn-onboard-4.json +3 -3
  36. package/src/assets/samples/vpn-onboard-5.json +3 -3
  37. package/src/assets/samples/vpn-onboard-6.json +3 -3
  38. package/src/assets/samples/vpn-onboard-7.json +3 -3
  39. package/src/attributes-editor/AttributesEditorFields.tsx +1 -1
  40. package/src/attributes-editor/AttributesEditorView.tsx +17 -6
  41. package/src/attributes-editor/FallbackLocalizationField.tsx +384 -0
  42. package/src/build-components/BIcon/BIcon.tsx +1 -1
  43. package/src/build-components/OnboardButton/OnboardButton.tsx +1 -1
  44. package/src/build-components/OnboardButton/pattern.json +1 -1
  45. package/src/build-components/OnboardFooter/OnboardFooter.tsx +29 -20
  46. package/src/build-components/OnboardFooter/pattern.json +2 -1
  47. package/src/build-components/OnboardProvider/pattern.json +1 -1
  48. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +1 -1
  49. package/src/build-components/PaywallFooter/PaywallFooter.tsx +242 -0
  50. package/src/build-components/PaywallFooter/PaywallFooterProps.generated.ts +85 -0
  51. package/src/build-components/PaywallFooter/pattern.json +86 -0
  52. package/src/build-components/RenderNode.generated.tsx +5 -0
  53. package/src/build-components/index.ts +5 -0
  54. package/src/build-components/patterns.generated.ts +511 -3
  55. package/src/components/BottomBar.tsx +136 -32
  56. package/src/components/DeviceNavigationBar.tsx +2 -2
  57. package/src/hooks/useLocalize.ts +13 -1
  58. package/src/index.web.ts +19 -0
  59. package/src/mockOS/managers/mockPermissionManager.ts +5 -3
  60. package/src/mockOS/managers/navigationManager.ts +6 -4
  61. package/src/modals/IconPickerModal.tsx +1 -1
  62. package/src/modals/InspectModal.tsx +106 -0
  63. package/src/product-base/buildPaywallLocalizationParams.ts +3 -0
  64. package/src/product-base/types.ts +3 -0
  65. package/src/store.ts +39 -0
  66. package/src/styles/base/_global.scss +1 -1
  67. package/src/types/PreviewConfig.ts +30 -6
  68. package/dist/build-components/index.generated.d.ts +0 -38
  69. package/dist/types/Icons.generated.d.ts +0 -2
  70. package/src/build-components/index.generated.ts +0 -184
  71. package/src/types/Icons.generated.ts +0 -244
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useMemo, useState, useCallback, useRef } from 'react';
2
2
  import { Icon } from './Icon.generated';
3
- import type { IconsType } from '../types/Icons.generated';
3
+ import type { IconsType } from '../types/Icons';
4
4
  import { useRenderStore } from '../store';
5
5
  import type { Localication } from '../types/PreviewConfig';
6
6
  import {
@@ -29,7 +29,6 @@ export function BottomBar({
29
29
  setData,
30
30
  project,
31
31
  }: BottomBarProps) {
32
- const rtlIcon: IconsType = 'translate';
33
32
  const magicCursorIcon: IconsType = 'magicpen';
34
33
  const debugIcon: IconsType = 'speedometer-03';
35
34
  const localizationIcon: IconsType = 'globe-01';
@@ -45,8 +44,8 @@ export function BottomBar({
45
44
  setDefaultLanguage,
46
45
  previewMode,
47
46
  setPreviewMode,
48
- isRtl,
49
47
  setIsRtl,
48
+ languageColumns,
50
49
  } = useRenderStore((s) => ({
51
50
  localization: s.localization,
52
51
  setLocalization: s.setLocalization,
@@ -56,24 +55,55 @@ export function BottomBar({
56
55
  setDefaultLanguage: s.setDefaultLanguage,
57
56
  previewMode: s.previewMode,
58
57
  setPreviewMode: s.setPreviewMode,
59
- isRtl: s.isRtl,
60
58
  setIsRtl: s.setIsRtl,
59
+ languageColumns: s.languageColumns,
61
60
  }));
62
61
 
63
62
  const [isDebugOpen, setIsDebugOpen] = useState(false);
64
63
  const [isLocalizationOpen, setIsLocalizationOpen] = useState(false);
65
64
  const [isInspectOpen, setIsInspectOpen] = useState(false);
66
65
  const [isPromptManagerOpen, setIsPromptManagerOpen] = useState(false);
66
+ const [langDropdownOpen, setLangDropdownOpen] = useState(false);
67
+ const langTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
68
+
69
+ // Use languageColumns from API, fallback to localization object keys
70
+ const availableLanguages = useMemo(() => {
71
+ if (languageColumns.length > 0) return languageColumns;
72
+ const locKeys = Object.keys(localization ?? {});
73
+ return locKeys.map((code) => ({
74
+ id: 0,
75
+ code: code.toUpperCase(),
76
+ title: code,
77
+ iso_code: code.toUpperCase(),
78
+ is_right_alignment: false,
79
+ }));
80
+ }, [languageColumns, localization]);
67
81
 
68
- const languages = useMemo(() => ['en', 'tr', 'ar'], []);
69
82
  const activeLanguage = defaultLanguage;
70
83
 
84
+ const handleSelectLanguage = useCallback(
85
+ (lang: { code: string; is_right_alignment: boolean }) => {
86
+ const code = lang.code.toLowerCase();
87
+ setDefaultLanguage(code);
88
+ setIsRtl(lang.is_right_alignment);
89
+ setLangDropdownOpen(false);
90
+ },
91
+ [setDefaultLanguage, setIsRtl],
92
+ );
93
+
94
+ const handleLangMouseEnter = () => {
95
+ if (langTimeoutRef.current) clearTimeout(langTimeoutRef.current);
96
+ setLangDropdownOpen(true);
97
+ };
98
+ const handleLangMouseLeave = () => {
99
+ langTimeoutRef.current = setTimeout(() => setLangDropdownOpen(false), 200);
100
+ };
101
+
71
102
  const handleLocalicationChange = (next: Localication) => {
72
103
  setLocalization(next);
73
104
  };
74
105
 
75
106
  const themeIsActive = theme === 'dark';
76
- const rtlIsActive = isRtl;
77
107
  const previewIsActive = previewMode;
78
108
  const themeIcon: IconsType = themeIsActive ? 'moon-bold' : 'sun';
79
109
 
@@ -90,19 +120,6 @@ export function BottomBar({
90
120
  <Icon iconType={themeIcon} size={20} color="currentColor" alt="" />
91
121
  </button>
92
122
 
93
- <button
94
- type="button"
95
- className={`rb-bottom-bar__button rb-bottom-bar__button--rtl${
96
- rtlIsActive ? ' is-active' : ''
97
- }`}
98
- aria-label="RTL"
99
- aria-pressed={rtlIsActive}
100
- onClick={() => setIsRtl(!isRtl)}
101
- >
102
- <Icon iconType={rtlIcon} size={18} color="currentColor" alt="" />
103
- <span className="rb-bottom-bar__rtl-text">RTL</span>
104
- </button>
105
-
106
123
  <button
107
124
  type="button"
108
125
  className={`rb-bottom-bar__button rb-bottom-bar__button--preview${
@@ -171,19 +188,106 @@ export function BottomBar({
171
188
 
172
189
  <div className="rb-bottom-bar__spacer" />
173
190
 
174
- <div className="rb-bottom-bar__langs" aria-label="Language">
175
- {languages.map((language) => (
176
- <button
177
- key={language}
178
- type="button"
179
- className={`rb-bottom-bar__lang${
180
- activeLanguage === language ? ' is-active' : ''
181
- }`}
182
- onClick={() => setDefaultLanguage(language)}
191
+ {/* Language selector with hover-up dropdown */}
192
+ <div
193
+ className="rb-bottom-bar__lang-selector"
194
+ onMouseEnter={handleLangMouseEnter}
195
+ onMouseLeave={handleLangMouseLeave}
196
+ style={{ position: 'relative' }}
197
+ >
198
+ <button
199
+ type="button"
200
+ className="rb-bottom-bar__lang is-active"
201
+ style={{
202
+ display: 'flex',
203
+ alignItems: 'center',
204
+ gap: 4,
205
+ }}
206
+ >
207
+ {activeLanguage.toUpperCase()}
208
+ <span style={{ fontSize: 8, opacity: 0.6 }}>▲</span>
209
+ </button>
210
+
211
+ {langDropdownOpen && availableLanguages.length > 0 && (
212
+ <div
213
+ className="rb-bottom-bar__lang-dropdown"
214
+ style={{
215
+ position: 'absolute',
216
+ bottom: '100%',
217
+ right: 0,
218
+ marginBottom: 4,
219
+ background: 'var(--rb-bg-secondary, #2a2a2a)',
220
+ border: '1px solid var(--rb-border, #444)',
221
+ borderRadius: 6,
222
+ padding: '4px 0',
223
+ minWidth: 150,
224
+ maxHeight: 260,
225
+ overflowY: 'auto',
226
+ boxShadow: '0 -4px 16px rgba(0,0,0,0.3)',
227
+ zIndex: 100,
228
+ }}
183
229
  >
184
- {language}
185
- </button>
186
- ))}
230
+ {availableLanguages.map((lang) => {
231
+ const isActive =
232
+ lang.code.toLowerCase() === activeLanguage.toLowerCase();
233
+ return (
234
+ <button
235
+ key={lang.code}
236
+ type="button"
237
+ onClick={() => handleSelectLanguage(lang)}
238
+ style={{
239
+ display: 'flex',
240
+ alignItems: 'center',
241
+ gap: 6,
242
+ width: '100%',
243
+ padding: '5px 10px',
244
+ border: 'none',
245
+ background: isActive
246
+ ? 'var(--rb-accent, #6366f1)'
247
+ : 'transparent',
248
+ color: isActive
249
+ ? '#fff'
250
+ : 'var(--rb-text-primary, #e0e0e0)',
251
+ cursor: 'pointer',
252
+ fontSize: 12,
253
+ textAlign: 'left',
254
+ }}
255
+ onMouseEnter={(e) => {
256
+ if (!isActive)
257
+ e.currentTarget.style.background =
258
+ 'var(--rb-bg-hover, #333)';
259
+ }}
260
+ onMouseLeave={(e) => {
261
+ if (!isActive)
262
+ e.currentTarget.style.background = 'transparent';
263
+ }}
264
+ >
265
+ <span
266
+ style={{
267
+ fontWeight: 600,
268
+ width: 24,
269
+ flexShrink: 0,
270
+ }}
271
+ >
272
+ {lang.code}
273
+ </span>
274
+ <span style={{ opacity: 0.7, flex: 1 }}>{lang.title}</span>
275
+ {lang.is_right_alignment && (
276
+ <span
277
+ style={{
278
+ fontSize: 9,
279
+ opacity: 0.5,
280
+ flexShrink: 0,
281
+ }}
282
+ >
283
+ RTL
284
+ </span>
285
+ )}
286
+ </button>
287
+ );
288
+ })}
289
+ </div>
290
+ )}
187
291
  </div>
188
292
  </div>
189
293
 
@@ -49,7 +49,7 @@ export function DeviceNavigationBar({
49
49
  context.navigation('launchscreen');
50
50
  }
51
51
  } else {
52
- alert('Navigation: Back\n(Mock OS context not available)');
52
+ console.warn('Navigation: Back\n(Mock OS context not available)');
53
53
  }
54
54
  }
55
55
 
@@ -57,7 +57,7 @@ export function DeviceNavigationBar({
57
57
  if (context) {
58
58
  context.navigation('launchscreen');
59
59
  } else {
60
- alert('Navigation: Home\n(Mock OS context not available)');
60
+ console.warn('Navigation: Home\n(Mock OS context not available)');
61
61
  }
62
62
  }
63
63
 
@@ -23,7 +23,19 @@ export function useLocalize(options?: {
23
23
 
24
24
  return useCallback(
25
25
  (keyOrText: string) => {
26
- const raw = localization?.[defaultLanguage]?.[keyOrText] ?? keyOrText;
26
+ const langMap = localization?.[defaultLanguage];
27
+ const raw = langMap?.[keyOrText] ?? keyOrText;
28
+ // DEBUG: trace localization lookup
29
+ if (keyOrText.includes('.')) {
30
+ console.log('[useLocalize]', {
31
+ keyOrText,
32
+ defaultLanguage,
33
+ hasLangMap: !!langMap,
34
+ langMapKeyCount: langMap ? Object.keys(langMap).length : 0,
35
+ resolved: raw !== keyOrText ? raw : '❌ NOT FOUND',
36
+ allLangs: Object.keys(localization ?? {}),
37
+ });
38
+ }
27
39
  return replaceLocalizationParams(raw, params);
28
40
  },
29
41
  [defaultLanguage, localization, params],
package/src/index.web.ts CHANGED
@@ -11,3 +11,22 @@ export * from './index';
11
11
  export * from './build-components';
12
12
  export { default as useNode } from './build-components/useNode';
13
13
  export type { EventObjectGenerated } from './build-components/OnboardButton/OnboardButtonProps.generated';
14
+
15
+ // Host-app API: let parent apps inject a custom "Text" field renderer.
16
+ import { useRenderStore } from './store';
17
+ import type { ComponentType } from 'react';
18
+ import type { LocalizationApiConfig, LanguageColumn } from './store';
19
+
20
+ export function setBuilderStringChildrenField(
21
+ comp?: ComponentType<{ value: string; onChange: (v: string) => void }>,
22
+ ) {
23
+ useRenderStore.getState().setRenderStringChildrenField(comp);
24
+ }
25
+
26
+ export function setBuilderLocalizationConfig(config?: LocalizationApiConfig) {
27
+ useRenderStore.getState().setLocalizationApiConfig(config);
28
+ }
29
+
30
+ export function setBuilderLanguageColumns(cols: LanguageColumn[]) {
31
+ useRenderStore.getState().setLanguageColumns(cols);
32
+ }
@@ -22,7 +22,7 @@ export class MockPermissionManager {
22
22
  permission: PermissionType | string,
23
23
  ): Promise<PermissionStatus> {
24
24
  if (this.context === null) {
25
- alert(
25
+ console.warn(
26
26
  `Permission requested: ${permission}\n(Mock OS context not available)`,
27
27
  );
28
28
  return 'not-determined';
@@ -40,7 +40,9 @@ export class MockPermissionManager {
40
40
 
41
41
  checkPermission(permission: PermissionType | string): PermissionStatus {
42
42
  if (this.context === null) {
43
- alert(`Permission check: ${permission}\n(Mock OS context not available)`);
43
+ console.warn(
44
+ `Permission check: ${permission}\n(Mock OS context not available)`,
45
+ );
44
46
  return 'not-determined';
45
47
  }
46
48
 
@@ -49,7 +51,7 @@ export class MockPermissionManager {
49
51
 
50
52
  openSettings(): void {
51
53
  if (this.context === null) {
52
- alert('Opening Settings\n(Mock OS context not available)');
54
+ console.warn('Opening Settings\n(Mock OS context not available)');
53
55
  return;
54
56
  }
55
57
  }
@@ -22,7 +22,7 @@ export class MockNavigationManager {
22
22
 
23
23
  goToHome(): void {
24
24
  if (this.context === null) {
25
- alert('Navigate to Home\n(Mock OS context not available)');
25
+ console.warn('Navigate to Home\n(Mock OS context not available)');
26
26
  return;
27
27
  }
28
28
 
@@ -35,7 +35,9 @@ export class MockNavigationManager {
35
35
 
36
36
  goToSubscriptions(): void {
37
37
  if (this.context === null) {
38
- alert('Navigate to Subscriptions\n(Mock OS context not available)');
38
+ console.warn(
39
+ 'Navigate to Subscriptions\n(Mock OS context not available)',
40
+ );
39
41
  return;
40
42
  }
41
43
 
@@ -48,7 +50,7 @@ export class MockNavigationManager {
48
50
 
49
51
  goToLaunchApp(): void {
50
52
  if (this.context === null) {
51
- alert('Navigate to Launch App\n(Mock OS context not available)');
53
+ console.warn('Navigate to Launch App\n(Mock OS context not available)');
52
54
  return;
53
55
  }
54
56
 
@@ -65,7 +67,7 @@ export class MockNavigationManager {
65
67
  }
66
68
 
67
69
  if (this.context === null) {
68
- alert('Go Back\n(Mock OS context not available)');
70
+ console.warn('Go Back\n(Mock OS context not available)');
69
71
  return false;
70
72
  }
71
73
 
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo, useState } from 'react';
2
2
  import Modal from './Modal';
3
- import { Icons, type IconsType } from '../types/Icons.generated';
3
+ import { Icons, type IconsType } from '../types/Icons';
4
4
  import { Icon } from '../components/Icon.generated';
5
5
 
6
6
  type IconPickerModalProps = {
@@ -113,8 +113,41 @@ function LocalizationsTab({
113
113
  );
114
114
  }
115
115
 
116
+ const handleDownloadCSV = () => {
117
+ let csvContent = 'Key,Value,Source\n';
118
+ allKeys.forEach((key) => {
119
+ const hasCustom = key in langKeys;
120
+ const customValue = langKeys[key];
121
+ const defaultValue = defaultLangKeys[key];
122
+
123
+ const isCustom =
124
+ hasCustom && customValue !== defaultValue && customValue !== undefined;
125
+ const value = isCustom ? customValue : (defaultValue ?? '');
126
+
127
+ const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
128
+ csvContent += `${escape(key)},${escape(String(value))},${isCustom ? 'custom' : 'fallback'}\n`;
129
+ });
130
+
131
+ downloadCSV(`localizations-${language}.csv`, csvContent);
132
+ };
133
+
116
134
  return (
117
135
  <div className="inspect-modal__table-wrap">
136
+ <div
137
+ style={{
138
+ display: 'flex',
139
+ justifyContent: 'flex-end',
140
+ marginBottom: '8px',
141
+ }}
142
+ >
143
+ <button
144
+ type="button"
145
+ className="editor-button"
146
+ onClick={handleDownloadCSV}
147
+ >
148
+ Download CSV
149
+ </button>
150
+ </div>
118
151
  <table className="inspect-modal__table">
119
152
  <thead>
120
153
  <tr>
@@ -184,8 +217,38 @@ function ParamsTab({
184
217
  );
185
218
  }
186
219
 
220
+ const handleDownloadCSV = () => {
221
+ let csvContent = 'Param,Value,Type\n';
222
+ const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
223
+
224
+ flatEntries.forEach(([key, value]) => {
225
+ csvContent += `${escape(key)},${escape(formatValue(value))},Flat\n`;
226
+ });
227
+
228
+ nestedEntries.forEach(([key, value]) => {
229
+ csvContent += `${escape(key)},${escape(formatValue(value))},Nested\n`;
230
+ });
231
+
232
+ downloadCSV('params.csv', csvContent);
233
+ };
234
+
187
235
  return (
188
236
  <div className="inspect-modal__table-wrap">
237
+ <div
238
+ style={{
239
+ display: 'flex',
240
+ justifyContent: 'flex-end',
241
+ marginBottom: '8px',
242
+ }}
243
+ >
244
+ <button
245
+ type="button"
246
+ className="editor-button"
247
+ onClick={handleDownloadCSV}
248
+ >
249
+ Download CSV
250
+ </button>
251
+ </div>
189
252
  <table className="inspect-modal__table">
190
253
  <thead>
191
254
  <tr>
@@ -249,8 +312,40 @@ function ColorsTab({
249
312
  return <p className="inspect-modal__empty">No project colors defined.</p>;
250
313
  }
251
314
 
315
+ const handleDownloadCSV = () => {
316
+ let csvContent = 'Type,Token,Value,Source\n';
317
+ const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
318
+
319
+ staticEntries.forEach(([token, hex]) => {
320
+ const isDefault = defaultStatic[token] === hex;
321
+ csvContent += `Static,${escape(token)},${escape(String(hex))},${isDefault ? 'fallback' : 'custom'}\n`;
322
+ });
323
+
324
+ themeEntries.forEach(([token, hex]) => {
325
+ const isDefault = defaultThemeMap[token] === hex;
326
+ csvContent += `Theme (${theme}),${escape(token)},${escape(String(hex))},${isDefault ? 'fallback' : 'custom'}\n`;
327
+ });
328
+
329
+ downloadCSV('colors.csv', csvContent);
330
+ };
331
+
252
332
  return (
253
333
  <div className="inspect-modal__table-wrap">
334
+ <div
335
+ style={{
336
+ display: 'flex',
337
+ justifyContent: 'flex-end',
338
+ marginBottom: '8px',
339
+ }}
340
+ >
341
+ <button
342
+ type="button"
343
+ className="editor-button"
344
+ onClick={handleDownloadCSV}
345
+ >
346
+ Download CSV
347
+ </button>
348
+ </div>
254
349
  {staticEntries.length > 0 && (
255
350
  <>
256
351
  <h4 className="inspect-modal__section-title">Static Colors</h4>
@@ -357,3 +452,14 @@ function formatValue(v: unknown): string {
357
452
  if (typeof v === 'number' || typeof v === 'boolean') return String(v);
358
453
  return JSON.stringify(v);
359
454
  }
455
+
456
+ function downloadCSV(filename: string, csvContent: string) {
457
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
458
+ const url = URL.createObjectURL(blob);
459
+ const link = document.createElement('a');
460
+ link.setAttribute('href', url);
461
+ link.setAttribute('download', filename);
462
+ document.body.appendChild(link);
463
+ link.click();
464
+ document.body.removeChild(link);
465
+ }
@@ -105,5 +105,8 @@ export function buildPaywallLocalizationParams(
105
105
  productCurreny: extractedParams.currency,
106
106
  productId: String(product.productId ?? ''),
107
107
  productSelected: 'true',
108
+ subscribe: hasTrial
109
+ ? resolve('base.builder.paywall.button.subscribe-free')
110
+ : resolve('base.builder.paywall.button.subscribe'),
108
111
  };
109
112
  }
@@ -116,6 +116,8 @@ export interface Product {
116
116
  displayPrice?: string;
117
117
  localizedPrice?: string;
118
118
  price?: string;
119
+ promoPrice?: string;
120
+ localizedPromoPrice?: string;
119
121
  currency?: string;
120
122
  currencyCode?: string;
121
123
  platform?: 'android' | 'ios';
@@ -169,6 +171,7 @@ export interface ProductParams {
169
171
  productCurreny: string;
170
172
  productId: string;
171
173
  productSelected: string;
174
+ subscribe: string;
172
175
  }
173
176
 
174
177
  /** Keys excluded from single (per-option) params — these are global/context-level. */
package/src/store.ts CHANGED
@@ -1,3 +1,18 @@
1
+ import type { ComponentType } from 'react';
2
+
3
+ export type LocalizationApiConfig = {
4
+ forgeUrl: string;
5
+ forgeToken: string;
6
+ forgeAppId?: string | number | null;
7
+ };
8
+
9
+ export type LanguageColumn = {
10
+ id: number;
11
+ code: string;
12
+ title: string;
13
+ iso_code: string;
14
+ is_right_alignment: boolean;
15
+ };
1
16
  import { createWithEqualityFn } from 'zustand/traditional';
2
17
  import { shallow } from 'zustand/shallow';
3
18
  import type { Device } from './types/Device';
@@ -43,6 +58,8 @@ type RenderStore = {
43
58
  setLocalization: (localization: Localication) => void;
44
59
  isRtl: boolean;
45
60
  setIsRtl: (isRtl: boolean) => void;
61
+ languageColumns: LanguageColumn[];
62
+ setLanguageColumns: (cols: LanguageColumn[]) => void;
46
63
  previewMode: boolean;
47
64
  setPreviewMode: (previewMode: boolean) => void;
48
65
  // Mockable: products (persisted)
@@ -101,6 +118,18 @@ type RenderStore = {
101
118
 
102
119
  favoriteDevices: string[];
103
120
  toggleFavoriteDevice: (name: string) => void;
121
+
122
+ // Host-provided custom renderer for the "Text" (string children) field
123
+ renderStringChildrenField?: ComponentType<{
124
+ value: string;
125
+ onChange: (v: string) => void;
126
+ }>;
127
+ setRenderStringChildrenField: (
128
+ comp?: ComponentType<{ value: string; onChange: (v: string) => void }>,
129
+ ) => void;
130
+
131
+ localizationApiConfig?: LocalizationApiConfig;
132
+ setLocalizationApiConfig: (config?: LocalizationApiConfig) => void;
104
133
  };
105
134
 
106
135
  export const useRenderStore = createWithEqualityFn<RenderStore>()(
@@ -129,6 +158,8 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
129
158
  setLocalization: (localization) => set({ localization }),
130
159
  isRtl: false,
131
160
  setIsRtl: (isRtl) => set({ isRtl }),
161
+ languageColumns: [],
162
+ setLanguageColumns: (cols) => set({ languageColumns: cols }),
132
163
  previewMode: false,
133
164
  setPreviewMode: (previewMode) => set({ previewMode }),
134
165
  products: [],
@@ -296,6 +327,14 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
296
327
  : [...devs, name],
297
328
  };
298
329
  }),
330
+
331
+ renderStringChildrenField: undefined,
332
+ setRenderStringChildrenField: (comp) =>
333
+ set({ renderStringChildrenField: comp }),
334
+
335
+ localizationApiConfig: undefined,
336
+ setLocalizationApiConfig: (config) =>
337
+ set({ localizationApiConfig: config }),
299
338
  }),
300
339
  {
301
340
  name: 'render-store',
@@ -80,6 +80,7 @@ body,
80
80
  background-position: center;
81
81
  background-repeat: repeat;
82
82
  background-size: 500px auto;
83
+ padding-bottom: 120px;
83
84
  > * {
84
85
  position: relative;
85
86
  z-index: 1;
@@ -369,7 +370,6 @@ body,
369
370
  min-width: 0;
370
371
  min-height: calc(100vh - 120px);
371
372
  padding: sizes.$spaceComfy;
372
- padding-bottom: 120px;
373
373
  position: relative;
374
374
  }
375
375