@developer_tribe/react-builder 1.0.7 → 1.0.9

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 (217) hide show
  1. package/dist/build-components/BIcon/BIconProps.generated.d.ts +3 -0
  2. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +1 -0
  3. package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -0
  4. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +5 -0
  5. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +1 -0
  6. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +1 -0
  7. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -0
  8. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -0
  9. package/dist/build-components/Image/ImageProps.generated.d.ts +1 -0
  10. package/dist/build-components/Main/MainProps.generated.d.ts +1 -1
  11. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -0
  12. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +1 -0
  13. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +1 -0
  14. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +1 -0
  15. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +3 -0
  16. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -0
  17. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +1 -0
  18. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +3 -0
  19. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +3 -0
  20. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +3 -0
  21. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +1 -1
  22. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +3 -1
  23. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
  24. package/dist/build-components/PaywallProvider/PaywallContext.d.ts +12 -0
  25. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -1
  26. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +1 -0
  27. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +1 -1
  28. package/dist/build-components/Text/TextProps.generated.d.ts +3 -0
  29. package/dist/build-components/View/ViewProps.generated.d.ts +1 -0
  30. package/dist/build-components/patterns.generated.d.ts +372 -374
  31. package/dist/components/BuilderProvider.d.ts +2 -0
  32. package/dist/components/ParamsProvider.d.ts +5 -0
  33. package/dist/components/RenderErrorBoundary.d.ts +28 -0
  34. package/dist/hooks/useSyncHtmlThemeClass.d.ts +7 -0
  35. package/dist/index.cjs.js +5 -5
  36. package/dist/index.cjs.js.map +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.esm.js +3 -3
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/index.native.cjs.js +4 -4
  41. package/dist/index.native.cjs.js.map +1 -1
  42. package/dist/index.native.d.ts +1 -0
  43. package/dist/index.native.esm.js +4 -4
  44. package/dist/index.native.esm.js.map +1 -1
  45. package/dist/migrations/migratePipe.d.ts +14 -0
  46. package/dist/migrations/migrations/1.1.0_normalize_style_attributes.d.ts +2 -0
  47. package/dist/migrations/semver.d.ts +8 -0
  48. package/dist/migrations/types.d.ts +8 -0
  49. package/dist/mockOS/components/SubscriptionModal.d.ts +7 -0
  50. package/dist/mockOS/context/MockOSContextBase.d.ts +1 -0
  51. package/dist/mockOS/hooks/useMockIap.d.ts +3 -0
  52. package/dist/mockOS/index.d.ts +4 -0
  53. package/dist/mockOS/managers/mockOSIapManager.d.ts +6 -0
  54. package/dist/mockOS/managers/subscriptionManager.d.ts +10 -0
  55. package/dist/pages/ProjectDebug.d.ts +14 -0
  56. package/dist/pages/ProjectMigrationPage.d.ts +23 -0
  57. package/dist/pages/ProjectValidationPage.d.ts +15 -0
  58. package/dist/styles.css +1 -1
  59. package/dist/types/Device.d.ts +5 -0
  60. package/dist/utils/__special_exceptions.d.ts +7 -0
  61. package/dist/utils/getImage.d.ts +23 -0
  62. package/dist/utils/pasteNode.d.ts +15 -0
  63. package/dist/utils/patterns.d.ts +1 -2
  64. package/package.json +6 -2
  65. package/scripts/migrate-patterns-to-v2.mjs +131 -0
  66. package/scripts/migrate-samples-to-current.ts +79 -0
  67. package/scripts/prebuild/utils/createGeneratedProps.js +4 -5
  68. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +32 -21
  69. package/scripts/prebuild/utils/validatePatternJson.js +12 -10
  70. package/src/.DS_Store +0 -0
  71. package/src/AttributesEditor.tsx +41 -11
  72. package/src/RenderPage.tsx +55 -0
  73. package/src/assets/.DS_Store +0 -0
  74. package/src/assets/devices.json +91 -0
  75. package/src/assets/samples/carousel-sample.json +141 -29
  76. package/src/assets/samples/getSamples.ts +9 -0
  77. package/src/assets/samples/paywall-1.json +119 -71
  78. package/src/assets/samples/simple-1.json +28 -16
  79. package/src/assets/samples/simple-2.json +157 -82
  80. package/src/assets/samples/unmigrated-builder1.json +42 -0
  81. package/src/assets/samples/unvalidated-builder1.json +49 -0
  82. package/src/assets/samples/unvalidated-crash1.json +19 -0
  83. package/src/assets/samples/unvalidated-crashcomponent1.json +16 -0
  84. package/src/assets/samples/vpn-onboard-1.json +91 -51
  85. package/src/assets/samples/vpn-onboard-2.json +318 -278
  86. package/src/assets/samples/vpn-onboard-3.json +286 -252
  87. package/src/assets/samples/vpn-onboard-4.json +286 -252
  88. package/src/assets/samples/vpn-onboard-5.json +434 -374
  89. package/src/assets/samples/vpn-onboard-6.json +290 -250
  90. package/src/attributes-editor/Field.tsx +1 -1
  91. package/src/attributes-editor/LayoutPreviewPicker.tsx +5 -2
  92. package/src/build-components/BIcon/BIconProps.generated.ts +3 -0
  93. package/src/build-components/BIcon/pattern.json +12 -9
  94. package/src/build-components/BackgroundImage/BackgroundImage.tsx +3 -1
  95. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +1 -0
  96. package/src/build-components/BackgroundImage/pattern.json +25 -16
  97. package/src/build-components/Button/Button.tsx +26 -3
  98. package/src/build-components/Button/ButtonProps.generated.ts +1 -0
  99. package/src/build-components/Button/pattern.json +10 -6
  100. package/src/build-components/Carousel/CarouselProps.generated.ts +5 -0
  101. package/src/build-components/Carousel/pattern.json +19 -8
  102. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +1 -0
  103. package/src/build-components/CarouselButtons/pattern.json +11 -5
  104. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +1 -0
  105. package/src/build-components/CarouselDots/pattern.json +5 -4
  106. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -0
  107. package/src/build-components/CarouselItem/pattern.json +5 -4
  108. package/src/build-components/CarouselProvider/CarouselProvider.tsx +44 -2
  109. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -0
  110. package/src/build-components/Image/Image.tsx +2 -1
  111. package/src/build-components/Image/ImageProps.generated.ts +1 -0
  112. package/src/build-components/Image/pattern.json +11 -5
  113. package/src/build-components/Main/MainProps.generated.ts +1 -1
  114. package/src/build-components/Main/pattern.json +12 -9
  115. package/src/build-components/Onboard/OnboardProps.generated.ts +1 -0
  116. package/src/build-components/Onboard/pattern.json +14 -9
  117. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +1 -0
  118. package/src/build-components/OnboardButton/pattern.json +5 -4
  119. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +1 -0
  120. package/src/build-components/OnboardButtons/pattern.json +5 -4
  121. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +1 -0
  122. package/src/build-components/OnboardDot/pattern.json +5 -4
  123. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +3 -0
  124. package/src/build-components/OnboardFooter/pattern.json +8 -5
  125. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -0
  126. package/src/build-components/OnboardImage/pattern.json +7 -4
  127. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +1 -0
  128. package/src/build-components/OnboardItem/pattern.json +18 -9
  129. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +3 -0
  130. package/src/build-components/OnboardProvider/pattern.json +21 -6
  131. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +3 -0
  132. package/src/build-components/OnboardSubtitle/pattern.json +10 -6
  133. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +3 -0
  134. package/src/build-components/OnboardTitle/pattern.json +11 -7
  135. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +1 -1
  136. package/src/build-components/PaywallBackground/pattern.json +5 -4
  137. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +6 -1
  138. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +3 -1
  139. package/src/build-components/PaywallCloseButton/pattern.json +15 -12
  140. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +0 -1
  141. package/src/build-components/PaywallOptions/PaywallOptions.tsx +3 -2
  142. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
  143. package/src/build-components/PaywallOptions/pattern.json +14 -11
  144. package/src/build-components/PaywallProvider/PaywallContext.ts +25 -0
  145. package/src/build-components/PaywallProvider/PaywallProvider.tsx +102 -5
  146. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -1
  147. package/src/build-components/PaywallProvider/pattern.json +11 -8
  148. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +7 -0
  149. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +1 -0
  150. package/src/build-components/PaywallSubscribeButton/pattern.json +16 -13
  151. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +1 -1
  152. package/src/build-components/RadioButton/pattern.json +5 -4
  153. package/src/build-components/Text/Text.tsx +107 -4
  154. package/src/build-components/Text/TextProps.generated.ts +3 -0
  155. package/src/build-components/Text/pattern.json +19 -4
  156. package/src/build-components/View/ViewProps.generated.ts +1 -0
  157. package/src/build-components/View/pattern.json +28 -13
  158. package/src/build-components/other.tsx +15 -0
  159. package/src/build-components/patterns.generated.ts +340 -235
  160. package/src/build-components/useNode.ts +22 -3
  161. package/src/components/BottomBar.tsx +45 -45
  162. package/src/components/Builder.tsx +20 -6
  163. package/src/components/BuilderButton.tsx +75 -38
  164. package/src/components/BuilderProvider.tsx +22 -2
  165. package/src/components/DeviceButton.tsx +12 -5
  166. package/src/components/EditorHeader.tsx +296 -38
  167. package/src/components/ParamsProvider.tsx +7 -0
  168. package/src/components/RenderErrorBoundary.tsx +200 -0
  169. package/src/hooks/useParams.ts +5 -1
  170. package/src/hooks/useSyncHtmlThemeClass.ts +19 -0
  171. package/src/index.native.ts +7 -0
  172. package/src/index.ts +8 -0
  173. package/src/migrations/migratePipe.ts +59 -0
  174. package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +80 -0
  175. package/src/migrations/semver.ts +24 -0
  176. package/src/migrations/types.ts +9 -0
  177. package/src/mockOS/components/PermissionModal.tsx +3 -2
  178. package/src/mockOS/components/SubscriptionModal.tsx +400 -0
  179. package/src/mockOS/context/MockOSContext.tsx +61 -10
  180. package/src/mockOS/context/MockOSContextBase.ts +1 -0
  181. package/src/mockOS/hooks/useMockIap.ts +11 -0
  182. package/src/mockOS/index.ts +7 -0
  183. package/src/mockOS/managers/mockOSIapManager.ts +10 -0
  184. package/src/mockOS/managers/subscriptionManager.ts +36 -0
  185. package/src/modals/IconPickerModal.tsx +1 -1
  186. package/src/pages/ProjectDebug.tsx +331 -0
  187. package/src/pages/ProjectMigrationPage.tsx +92 -0
  188. package/src/pages/ProjectPage.tsx +318 -166
  189. package/src/pages/ProjectValidationPage.tsx +54 -0
  190. package/src/styles/base/_global.scss +58 -11
  191. package/src/styles/components/_attributes-editor.scss +1 -1
  192. package/src/styles/components/_bottom-bar.scss +7 -4
  193. package/src/styles/components/_editor-shell.scss +126 -4
  194. package/src/styles/components/_mockos-router.scss +3 -2
  195. package/src/styles/components/_ui-components.scss +10 -5
  196. package/src/styles/foundation/_colors.scss +78 -11
  197. package/src/styles/foundation/_mixins.scss +4 -1
  198. package/src/styles/foundation/_sizes.scss +4 -2
  199. package/src/styles/index.scss +1 -0
  200. package/src/styles/layout/_builder.scss +61 -0
  201. package/src/styles/layout/_project-validation.scss +214 -0
  202. package/src/styles/modals/_add-component.scss +4 -2
  203. package/src/styles/modals/_color-modal.scss +4 -2
  204. package/src/styles/modals/_modal-shell.scss +3 -1
  205. package/src/types/Device.ts +5 -0
  206. package/src/utils/__special_exceptions.ts +88 -0
  207. package/src/utils/analyseNode.ts +8 -2
  208. package/src/utils/analyseNodeByPatterns.ts +43 -9
  209. package/src/utils/extractTextStyle.ts +19 -6
  210. package/src/utils/extractViewStyle.ts +68 -59
  211. package/src/utils/getImage.ts +76 -0
  212. package/src/utils/novaToJson.ts +2 -1
  213. package/src/utils/pasteNode.ts +172 -0
  214. package/src/utils/patterns.ts +4 -3
  215. package/dist/android.svg +0 -43
  216. package/dist/apple.svg +0 -16
  217. package/dist/background.jpg +0 -0
@@ -0,0 +1,11 @@
1
+ import { useMemo } from 'react';
2
+ import type { MockOSContextValue } from '../context/MockOSContext';
3
+ import { MockOSIapManager } from '../managers/mockOSIapManager';
4
+
5
+ export function useMockIap(context: MockOSContextValue | null) {
6
+ const iapManager = useMemo(() => {
7
+ return new MockOSIapManager(context);
8
+ }, [context]);
9
+
10
+ return iapManager;
11
+ }
@@ -21,7 +21,14 @@ export type {
21
21
  NavigationDestination,
22
22
  NavigationStackItem,
23
23
  } from './managers/navigationManager';
24
+ export { MockOSIapManager } from './managers/mockOSIapManager';
25
+ export { MockSubscriptionManager } from './managers/subscriptionManager';
26
+ export type {
27
+ SubscriptionPurchaseListener,
28
+ SubscriptionPurchaseResult,
29
+ } from './managers/subscriptionManager';
24
30
 
25
31
  // Hooks
26
32
  export { useMockPermission } from './hooks/useMockPermission';
27
33
  export { useMockNavigation } from './hooks/useMockNavigation';
34
+ export { useMockIap } from './hooks/useMockIap';
@@ -0,0 +1,10 @@
1
+ import type { MockOSContextValue } from '../context/MockOSContext';
2
+ import { MockSubscriptionManager } from './subscriptionManager';
3
+
4
+ export class MockOSIapManager {
5
+ public subscription: MockSubscriptionManager;
6
+
7
+ constructor(context: MockOSContextValue | null) {
8
+ this.subscription = new MockSubscriptionManager(context);
9
+ }
10
+ }
@@ -0,0 +1,36 @@
1
+ import type { MockOSContextValue } from '../context/MockOSContext';
2
+
3
+ export type SubscriptionPurchaseResult = boolean;
4
+ export type SubscriptionPurchaseListener = (
5
+ result: SubscriptionPurchaseResult,
6
+ ) => void;
7
+
8
+ export class MockSubscriptionManager {
9
+ private context: MockOSContextValue | null;
10
+ private listeners = new Set<SubscriptionPurchaseListener>();
11
+
12
+ constructor(context: MockOSContextValue | null) {
13
+ this.context = context;
14
+ }
15
+
16
+ addListener(listener: SubscriptionPurchaseListener): () => void {
17
+ this.listeners.add(listener);
18
+ return () => this.listeners.delete(listener);
19
+ }
20
+
21
+ async buy(productId: string): Promise<boolean> {
22
+ if (this.context === null) {
23
+ const ok =
24
+ typeof window !== 'undefined' &&
25
+ typeof window.confirm === 'function' &&
26
+ window.confirm(`Subscribe to ${productId}?`);
27
+ const result = Boolean(ok);
28
+ this.listeners.forEach((l) => l(result));
29
+ return result;
30
+ }
31
+
32
+ const result = await this.context.requestSubscriptionPurchase(productId);
33
+ this.listeners.forEach((l) => l(result));
34
+ return result;
35
+ }
36
+ }
@@ -80,7 +80,7 @@ export function IconPickerModal({
80
80
  borderRadius: 8,
81
81
  border: isActive ? '2px solid #222' : '1px solid #ddd',
82
82
  padding: '8px 10px',
83
- background: '#fff',
83
+ background: 'hsl(var(--card, var(--rb-card, 0 0% 100%)))',
84
84
  cursor: 'pointer',
85
85
  }}
86
86
  aria-label={`Select icon ${name}`}
@@ -0,0 +1,331 @@
1
+ import { type ReactNode, useMemo, useState } from 'react';
2
+ import { RenderPage } from '../RenderPage';
3
+ import { BuilderProvider } from '../components/BuilderProvider';
4
+ import { RenderErrorBoundary } from '../components/RenderErrorBoundary';
5
+ import type { Node } from '../types/Node';
6
+ import type { Product } from '../paywall/types/paywall-types';
7
+ import type { PaywallBenefits } from '../paywall/types/benefits';
8
+ import { useSyncHtmlThemeClass } from '../hooks/useSyncHtmlThemeClass';
9
+
10
+ function safeStringify(value: unknown): string {
11
+ try {
12
+ const seen = new WeakSet<object>();
13
+ return JSON.stringify(
14
+ value,
15
+ (_key, v) => {
16
+ if (typeof v === 'object' && v !== null) {
17
+ if (seen.has(v as object)) return '[Circular]';
18
+ seen.add(v as object);
19
+ }
20
+ return v;
21
+ },
22
+ 2,
23
+ );
24
+ } catch (e) {
25
+ return `<< Unable to stringify value: ${String(e)} >>`;
26
+ }
27
+ }
28
+
29
+ async function copyTextToClipboard(text: string): Promise<boolean> {
30
+ try {
31
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
32
+ await navigator.clipboard.writeText(text);
33
+ return true;
34
+ }
35
+ } catch {
36
+ // fall through to legacy method
37
+ }
38
+
39
+ try {
40
+ if (typeof document === 'undefined') return false;
41
+ const el = document.createElement('textarea');
42
+ el.value = text;
43
+ el.setAttribute('readonly', 'true');
44
+ el.style.position = 'fixed';
45
+ el.style.opacity = '0';
46
+ document.body.appendChild(el);
47
+ el.select();
48
+ const ok = document.execCommand('copy');
49
+ document.body.removeChild(el);
50
+ return ok;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function parseValidationPrefix(message: string): {
57
+ path?: string;
58
+ reason: string;
59
+ } {
60
+ const idx = message.indexOf(': ');
61
+ if (idx <= 0) return { reason: message };
62
+ const maybePath = message.slice(0, idx);
63
+ const reason = message.slice(idx + 2);
64
+ if (maybePath.startsWith('root')) return { path: maybePath, reason };
65
+ return { reason: message };
66
+ }
67
+
68
+ function isObject(value: unknown): value is Record<string, unknown> {
69
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
70
+ }
71
+
72
+ function getNodeType(value: unknown): string | null {
73
+ if (!isObject(value)) return null;
74
+ const t = value.type;
75
+ return typeof t === 'string' && t ? t : null;
76
+ }
77
+
78
+ function getFirstComponentFromReactStack(
79
+ componentStack?: string,
80
+ ): string | null {
81
+ if (!componentStack) return null;
82
+ const match = componentStack.match(/\bat\s+([A-Za-z0-9_$]+)/);
83
+ return match?.[1] ?? null;
84
+ }
85
+
86
+ function resolveNodeTypeAtPath(
87
+ root: unknown,
88
+ path?: string,
89
+ ): { nodeType?: string; nodePath?: string } {
90
+ if (!path || !path.startsWith('root')) return {};
91
+ let current: unknown = root;
92
+ let lastNodeType: string | null = getNodeType(current);
93
+ let lastNodePath: string | null = lastNodeType ? 'root' : null;
94
+
95
+ const rest = path === 'root' ? '' : path.replace(/^root\.?/, '');
96
+ const re = /([A-Za-z0-9_$]+)|\[(\d+)\]/g;
97
+ let m: RegExpExecArray | null;
98
+ let cursorPath = 'root';
99
+
100
+ // Traverse and remember the last object node that had a `type` field.
101
+ while ((m = re.exec(rest))) {
102
+ const prop = m[1];
103
+ const idx = m[2];
104
+
105
+ if (prop) {
106
+ cursorPath = `${cursorPath}.${prop}`;
107
+ if (isObject(current)) current = current[prop];
108
+ else current = undefined;
109
+ } else if (idx != null) {
110
+ cursorPath = `${cursorPath}[${idx}]`;
111
+ if (Array.isArray(current)) current = current[Number(idx)];
112
+ else current = undefined;
113
+ }
114
+
115
+ const t = getNodeType(current);
116
+ if (t) {
117
+ lastNodeType = t;
118
+ lastNodePath = cursorPath;
119
+ }
120
+ }
121
+
122
+ return lastNodeType
123
+ ? { nodeType: lastNodeType, nodePath: lastNodePath ?? undefined }
124
+ : {};
125
+ }
126
+
127
+ export type ProjectDebugProps = {
128
+ name: string;
129
+ rawData: unknown;
130
+ validationError: string;
131
+ validationErrorStack?: string;
132
+ products: Product[];
133
+ benefits: PaywallBenefits;
134
+ canvasBg?: string;
135
+ belowName?: ReactNode;
136
+ };
137
+
138
+ export function ProjectDebug({
139
+ name,
140
+ rawData,
141
+ validationError,
142
+ validationErrorStack,
143
+ products,
144
+ benefits,
145
+ canvasBg,
146
+ belowName,
147
+ }: ProjectDebugProps) {
148
+ useSyncHtmlThemeClass();
149
+ const [previewError, setPreviewError] = useState<{
150
+ message: string;
151
+ stack?: string;
152
+ componentStack?: string;
153
+ } | null>(null);
154
+ const [jsonCopyState, setJsonCopyState] = useState<'idle' | 'copied'>('idle');
155
+ const [errorCopyState, setErrorCopyState] = useState<'idle' | 'copied'>(
156
+ 'idle',
157
+ );
158
+
159
+ const json = useMemo(() => safeStringify(rawData), [rawData]);
160
+ const previewData = rawData as Node;
161
+ const rootType = useMemo(() => getNodeType(rawData), [rawData]);
162
+ const parsedValidation = useMemo(
163
+ () => parseValidationPrefix(validationError),
164
+ [validationError],
165
+ );
166
+ const validationContext = useMemo(
167
+ () => resolveNodeTypeAtPath(rawData, parsedValidation.path),
168
+ [rawData, parsedValidation.path],
169
+ );
170
+
171
+ const validationSummary = useMemo(() => {
172
+ const lines: string[] = [];
173
+ lines.push(parsedValidation.reason);
174
+ if (rootType) lines.push(`Root component: ${rootType}`);
175
+ if (parsedValidation.path) lines.push(`Path: ${parsedValidation.path}`);
176
+ if (validationContext.nodeType) {
177
+ lines.push(
178
+ `Component at path: ${validationContext.nodeType}${
179
+ validationContext.nodePath ? ` (${validationContext.nodePath})` : ''
180
+ }`,
181
+ );
182
+ }
183
+ return lines.join('\n');
184
+ }, [
185
+ parsedValidation.reason,
186
+ parsedValidation.path,
187
+ rootType,
188
+ validationContext.nodeType,
189
+ validationContext.nodePath,
190
+ ]);
191
+
192
+ const renderSummary = useMemo(() => {
193
+ if (!previewError) return null;
194
+ const lines: string[] = [];
195
+ lines.push(previewError.message);
196
+ if (rootType) lines.push(`Root component: ${rootType}`);
197
+ if (previewError.componentStack) {
198
+ lines.push(
199
+ `Component (from React stack): ${
200
+ getFirstComponentFromReactStack(previewError.componentStack) ??
201
+ 'unknown'
202
+ }`,
203
+ );
204
+ }
205
+ return lines.join('\n');
206
+ }, [previewError, rootType]);
207
+
208
+ const fullErrorReport = useMemo(() => {
209
+ const parts: string[] = [];
210
+ parts.push('=== Validation ===');
211
+ parts.push(validationSummary);
212
+ if (validationErrorStack) {
213
+ parts.push('');
214
+ parts.push(validationErrorStack);
215
+ }
216
+ if (previewError) {
217
+ parts.push('');
218
+ parts.push('=== Render ===');
219
+ if (renderSummary) parts.push(renderSummary);
220
+ if (previewError.stack) {
221
+ parts.push('');
222
+ parts.push(previewError.stack);
223
+ }
224
+ if (previewError.componentStack) {
225
+ parts.push('');
226
+ parts.push(previewError.componentStack);
227
+ }
228
+ }
229
+ return parts.join('\n');
230
+ }, [previewError, renderSummary, validationErrorStack, validationSummary]);
231
+
232
+ return (
233
+ <div className="rb-project-debug">
234
+ <section className="rb-project-debug__json" aria-label="Debug JSON">
235
+ <div className="rb-project-debug__meta">
236
+ <div>
237
+ <span className="rb-project-debug__meta-label">Name:</span> {name}
238
+ </div>
239
+ </div>
240
+ <div className="rb-project-debug__toolbar">
241
+ <button
242
+ type="button"
243
+ className="editor-button"
244
+ onClick={async () => {
245
+ const ok = await copyTextToClipboard(json);
246
+ if (!ok) return;
247
+ setJsonCopyState('copied');
248
+ window.setTimeout(() => setJsonCopyState('idle'), 900);
249
+ }}
250
+ >
251
+ {jsonCopyState === 'copied' ? 'Copied JSON' : 'Copy JSON'}
252
+ </button>
253
+ </div>
254
+ {belowName && (
255
+ <div className="rb-project-debug__below-name">{belowName}</div>
256
+ )}
257
+ <pre className="rb-project-debug__code" tabIndex={0}>
258
+ {json}
259
+ </pre>
260
+ </section>
261
+
262
+ <section
263
+ className="rb-project-debug__preview"
264
+ aria-label="Preview"
265
+ style={{
266
+ // eslint-disable-next-line @typescript-eslint/naming-convention
267
+ ['--rb-canvas-bg' as any]: canvasBg ?? 'none',
268
+ }}
269
+ >
270
+ <BuilderProvider params={{ products, benefits }}>
271
+ <RenderErrorBoundary
272
+ subtitle="caught by ProjectDebug preview"
273
+ onError={(e, componentStack) =>
274
+ setPreviewError({
275
+ message: e?.message ?? String(e),
276
+ stack: e?.stack,
277
+ componentStack,
278
+ })
279
+ }
280
+ >
281
+ <RenderPage data={previewData} name={name} />
282
+ </RenderErrorBoundary>
283
+ </BuilderProvider>
284
+ </section>
285
+
286
+ <section className="rb-project-debug__error" aria-label="Errors">
287
+ <div className="rb-project-debug__error-head">
288
+ <h2 className="rb-project-debug__title">Errors</h2>
289
+ <button
290
+ type="button"
291
+ className="editor-button"
292
+ onClick={async () => {
293
+ const ok = await copyTextToClipboard(fullErrorReport);
294
+ if (!ok) return;
295
+ setErrorCopyState('copied');
296
+ window.setTimeout(() => setErrorCopyState('idle'), 900);
297
+ }}
298
+ >
299
+ {errorCopyState === 'copied' ? 'Copied' : 'Copy'}
300
+ </button>
301
+ </div>
302
+ <div className="rb-project-debug__error-block">
303
+ <div className="rb-project-debug__error-label">Validation</div>
304
+ <pre className="rb-project-debug__error-pre">{validationSummary}</pre>
305
+ {validationErrorStack && (
306
+ <pre className="rb-project-debug__error-pre">
307
+ {validationErrorStack}
308
+ </pre>
309
+ )}
310
+ </div>
311
+
312
+ {previewError && (
313
+ <div className="rb-project-debug__error-block">
314
+ <div className="rb-project-debug__error-label">Render</div>
315
+ <pre className="rb-project-debug__error-pre">{renderSummary}</pre>
316
+ {previewError.stack && (
317
+ <pre className="rb-project-debug__error-pre">
318
+ {previewError.stack}
319
+ </pre>
320
+ )}
321
+ {previewError.componentStack && (
322
+ <pre className="rb-project-debug__error-pre">
323
+ {previewError.componentStack}
324
+ </pre>
325
+ )}
326
+ </div>
327
+ )}
328
+ </section>
329
+ </div>
330
+ );
331
+ }
@@ -0,0 +1,92 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Product } from '../paywall/types/paywall-types';
3
+ import type { PaywallBenefits } from '../paywall/types/benefits';
4
+ import { ProjectDebug } from './ProjectDebug';
5
+
6
+ export type ProjectMigrationPageProps = {
7
+ name: string;
8
+ rawData: unknown;
9
+
10
+ projectVersion: string;
11
+ requiredVersion: string;
12
+
13
+ pendingMigrations: Array<{
14
+ id: string;
15
+ title: string;
16
+ fromVersion: string;
17
+ toVersion: string;
18
+ }>;
19
+
20
+ products: Product[];
21
+ benefits: PaywallBenefits;
22
+ canvasBg?: string;
23
+ belowName?: ReactNode;
24
+
25
+ migrating?: boolean;
26
+ onContinueWithoutValidation?: () => void;
27
+ onMigrateNow?: () => void;
28
+ };
29
+
30
+ export function ProjectMigrationPage({
31
+ name,
32
+ rawData,
33
+ projectVersion,
34
+ requiredVersion,
35
+ pendingMigrations,
36
+ products,
37
+ benefits,
38
+ canvasBg,
39
+ belowName,
40
+ migrating,
41
+ onContinueWithoutValidation,
42
+ onMigrateNow,
43
+ }: ProjectMigrationPageProps) {
44
+ const message = `Migration required: project version ${projectVersion} is lower than ${requiredVersion}.`;
45
+
46
+ const stack = [
47
+ `Required: ${requiredVersion}`,
48
+ `Current: ${projectVersion}`,
49
+ '',
50
+ 'Pending migrations:',
51
+ ...(pendingMigrations.length
52
+ ? pendingMigrations.map(
53
+ (m) => `- ${m.id} (${m.fromVersion} -> ${m.toVersion}): ${m.title}`,
54
+ )
55
+ : ['- (none)']),
56
+ ].join('\n');
57
+
58
+ return (
59
+ <ProjectDebug
60
+ name={name}
61
+ rawData={rawData}
62
+ validationError={message}
63
+ validationErrorStack={stack}
64
+ products={products}
65
+ benefits={benefits}
66
+ canvasBg={canvasBg}
67
+ belowName={
68
+ <>
69
+ <div className="rb-project-validation__actions">
70
+ <button
71
+ type="button"
72
+ className="editor-button"
73
+ disabled={!!migrating}
74
+ onClick={() => onMigrateNow?.()}
75
+ >
76
+ {migrating ? 'Migrating…' : 'Migrate now'}
77
+ </button>
78
+
79
+ <button
80
+ type="button"
81
+ className="editor-button"
82
+ onClick={() => onContinueWithoutValidation?.()}
83
+ >
84
+ Continue without migration
85
+ </button>
86
+ </div>
87
+ {belowName}
88
+ </>
89
+ }
90
+ />
91
+ );
92
+ }