@developer_tribe/react-builder 1.2.15 → 1.2.17
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/build-components/PaywallCloseButton/PaywallCloseButton.d.ts +1 -1
- package/dist/build-components/PaywallProvider/PaywallContext.d.ts +2 -1
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -0
- package/dist/build-components/patterns.generated.d.ts +11 -1
- package/dist/index.cjs.js +3 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +3 -3
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +3 -3
- package/dist/index.web.esm.js.map +1 -1
- package/dist/mockOS/backHandler.d.ts +4 -0
- package/dist/paywall/hooks/index.d.ts +3 -3
- package/dist/paywall/hooks/useChangeDelayByPaywall.d.ts +4 -0
- package/dist/paywall/hooks/useHandleGoBack.d.ts +1 -0
- package/dist/paywall/hooks/useMockOSBackHandler.d.ts +1 -0
- package/package.json +4 -1
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/paywall-2.json +3 -2
- package/src/build-components/CountDown/CountDown.tsx +9 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +5 -1
- package/src/build-components/PaywallCloseButton/pattern.json +3 -0
- package/src/build-components/PaywallCounter/PaywallCounter.tsx +2 -5
- package/src/build-components/PaywallOptions/PaywallOptions.tsx +2 -2
- package/src/build-components/PaywallProvider/PaywallContext.ts +4 -2
- package/src/build-components/PaywallProvider/PaywallProvider.tsx +45 -7
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -0
- package/src/build-components/PaywallProvider/pattern.json +11 -1
- package/src/build-components/patterns.generated.ts +12 -1
- package/src/components/DeviceNavigationBar.tsx +5 -0
- package/src/index.ts +1 -3
- package/src/mockOS/backHandler.ts +35 -0
- package/src/pages/ProjectMigrationPage.tsx +10 -1
- package/src/pages/ProjectPage.tsx +7 -0
- package/src/paywall/hooks/index.ts +3 -3
- package/src/paywall/hooks/useChangeDelayByPaywall.ts +25 -0
- package/src/paywall/hooks/useHandleGoBack.ts +60 -0
- package/src/paywall/hooks/useMockOSBackHandler.ts +9 -0
- package/dist/paywall/hooks/useCarouselOptionsSeperator.d.ts +0 -6
- package/dist/paywall/hooks/useCloseStatusPaywall.d.ts +0 -4
- package/dist/paywall/hooks/usePaywallCounter.d.ts +0 -4
- package/src/paywall/hooks/useCarouselOptionsSeperator.ts +0 -8
- package/src/paywall/hooks/useCloseStatusPaywall.ts +0 -6
- package/src/paywall/hooks/usePaywallCounter.ts +0 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { usePaywallCounter } from './usePaywallCounter';
|
|
2
|
-
export { useCloseStatusPaywall } from './useCloseStatusPaywall';
|
|
3
|
-
export { useCarouselOptionsSeperator } from './useCarouselOptionsSeperator';
|
|
4
1
|
export { useCalculateLocalizedPrice } from './useCalculateLocalizedPrice';
|
|
5
2
|
export { useDiscountRate } from './useDiscountRate';
|
|
3
|
+
export { useChangeDelayByPaywall } from './useChangeDelayByPaywall';
|
|
4
|
+
export { useHandleGoBack } from './useHandleGoBack';
|
|
5
|
+
export { useMockOSBackHandler } from './useMockOSBackHandler';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useHandleGoBack(handleGoBack: () => boolean | null | undefined): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useMockOSBackHandler(isBackAllowed: boolean): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@developer_tribe/react-builder",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.17",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"restricted": true,
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
35
|
"prebuild": "node ./scripts/prebuild/prebuild.js",
|
|
36
|
+
"prebuild:basics": "yarn builder build --name builder --components=View --components=Text --components=Button --components=Image --components=Carousel --components=CarouselProvider --components=CarouselItem --components=CarouselDots --components=CarouselButtons --components=BIcon --components=BackgroundImage --components=Main --components=RadioButton --components=Counter --components=CountDown",
|
|
37
|
+
"prebuild:onboard": "yarn builder build --name onboard/builder --components=OnboardProvider --components=Onboard --components=OnboardItem --components=OnboardImage --components=OnboardTitle --components=OnboardSubtitle --components=OnboardDot --components=OnboardButtons --components=OnboardButton --components=OnboardFooter",
|
|
38
|
+
"prebuild:paywall": "yarn builder build --name paywall/builder --components=PaywallProvider --components=PaywallBackground --components=PaywallCloseButton --components=PaywallOptions --components=PaywallSubscribeButton --components=PaywallCounter",
|
|
36
39
|
"build": "yarn prebuild && yarn rimraf dist && rollup -c",
|
|
37
40
|
"build:watch": "rollup -c -w",
|
|
38
41
|
"lint": "eslint src",
|
package/src/assets/meta.json
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"backgroundColor": "#EEF2FF"
|
|
38
38
|
},
|
|
39
39
|
"scrollable": true,
|
|
40
|
+
"delay": 5000,
|
|
40
41
|
"description": "Paywall provider container.",
|
|
41
42
|
"title": "Main Paywall"
|
|
42
43
|
},
|
|
@@ -293,14 +294,14 @@
|
|
|
293
294
|
{
|
|
294
295
|
"type": "CountDown",
|
|
295
296
|
"attributes": {
|
|
296
|
-
"count":
|
|
297
|
+
"count": 10000,
|
|
297
298
|
"style": {
|
|
298
299
|
"color": "#FF0000",
|
|
299
300
|
"fontWeight": "700",
|
|
300
301
|
"fontSize": 40,
|
|
301
302
|
"textAlign": "center"
|
|
302
303
|
},
|
|
303
|
-
"description": "Countdown timer (
|
|
304
|
+
"description": "Countdown timer (10 seconds).",
|
|
304
305
|
"title": "Countdown"
|
|
305
306
|
},
|
|
306
307
|
"children": null
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useId, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useId, useRef, useState } from 'react';
|
|
2
2
|
import type { CountDownComponentProps } from './CountDownProps.generated';
|
|
3
3
|
import useNode from '../useNode';
|
|
4
4
|
import { useBuilderParams } from '../../components/BuilderProvider';
|
|
@@ -7,6 +7,7 @@ import { useLogRender } from '../../utils/useLogRender';
|
|
|
7
7
|
import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
|
|
8
8
|
import { useMergedStyle } from '../../utils/useMergedStyle';
|
|
9
9
|
import { formatCountdownTime } from './formatCountdownTime';
|
|
10
|
+
import { usePaywallContext } from '../PaywallProvider/PaywallContext';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_FORMAT = 'm : s';
|
|
12
13
|
|
|
@@ -20,6 +21,8 @@ export function CountDown({ node }: CountDownComponentProps) {
|
|
|
20
21
|
const attributeKey = node.key ?? generatedId;
|
|
21
22
|
|
|
22
23
|
const { previewMode, selectedKey } = useBuilderParams();
|
|
24
|
+
const { onCounterDown } = usePaywallContext();
|
|
25
|
+
const onCounterDownRef = useRef(onCounterDown);
|
|
23
26
|
const baseStyle = useExtractTextStyle(node);
|
|
24
27
|
const isSelected = isNodeSelected({
|
|
25
28
|
previewMode: !!previewMode,
|
|
@@ -39,6 +42,10 @@ export function CountDown({ node }: CountDownComponentProps) {
|
|
|
39
42
|
|
|
40
43
|
const [time, setTime] = useState<string>('');
|
|
41
44
|
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
onCounterDownRef.current = onCounterDown;
|
|
47
|
+
}, [onCounterDown]);
|
|
48
|
+
|
|
42
49
|
useEffect(() => {
|
|
43
50
|
const targetTimeMs = Date.now() + delayMs;
|
|
44
51
|
setTime(formatCountdownTime(targetTimeMs, DEFAULT_FORMAT));
|
|
@@ -53,6 +60,7 @@ export function CountDown({ node }: CountDownComponentProps) {
|
|
|
53
60
|
setTime(formatCountdownTime(targetTimeMs, DEFAULT_FORMAT, nowMs));
|
|
54
61
|
if (diffMs <= 0) {
|
|
55
62
|
clearInterval(intervalId);
|
|
63
|
+
onCounterDownRef.current?.();
|
|
56
64
|
}
|
|
57
65
|
}, 950); // Keep slightly under 1s to reduce visible drift.
|
|
58
66
|
|
|
@@ -14,7 +14,7 @@ import { usePaywallContext } from '../PaywallProvider/PaywallContext';
|
|
|
14
14
|
function PaywallCloseButton({ node }: PaywallCloseButtonComponentProps) {
|
|
15
15
|
useLogRender('PaywallCloseButton');
|
|
16
16
|
node = useNode(node);
|
|
17
|
-
const { onClose } = usePaywallContext();
|
|
17
|
+
const { onClose, isBackAllowed } = usePaywallContext();
|
|
18
18
|
|
|
19
19
|
const generatedId = useId();
|
|
20
20
|
const attributeName = node.sourceType ?? node.type ?? 'PaywallCloseButton';
|
|
@@ -35,6 +35,10 @@ function PaywallCloseButton({ node }: PaywallCloseButtonComponentProps) {
|
|
|
35
35
|
isSelected ? SELECTED_OUTLINE_STYLE : undefined,
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
+
if (!isBackAllowed) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
const attrs = node.attributes;
|
|
39
43
|
const styleBag = attrs?.style;
|
|
40
44
|
const iconType = attrs?.iconType ?? 'close';
|
|
@@ -6,7 +6,6 @@ import { useExtractTextStyle } from '../../attribute-analyser/style/web/useExtra
|
|
|
6
6
|
import { useLogRender } from '../../utils/useLogRender';
|
|
7
7
|
import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
|
|
8
8
|
import { useMergedStyle } from '../../utils/useMergedStyle';
|
|
9
|
-
import { usePaywallCounter } from '../../paywall/hooks';
|
|
10
9
|
|
|
11
10
|
//Optimzation trade off by readability: skip React.memo to keep named exports.
|
|
12
11
|
export function PaywallCounter({ node }: PaywallCounterComponentProps) {
|
|
@@ -29,11 +28,9 @@ export function PaywallCounter({ node }: PaywallCounterComponentProps) {
|
|
|
29
28
|
isSelected ? SELECTED_OUTLINE_STYLE : undefined,
|
|
30
29
|
);
|
|
31
30
|
|
|
32
|
-
const paywallCount = usePaywallCounter();
|
|
33
31
|
const fallbackCount = node.attributes?.count;
|
|
34
|
-
const count =
|
|
35
|
-
|
|
36
|
-
: typeof fallbackCount === 'number' && Number.isFinite(fallbackCount)
|
|
32
|
+
const count =
|
|
33
|
+
typeof fallbackCount === 'number' && Number.isFinite(fallbackCount)
|
|
37
34
|
? fallbackCount
|
|
38
35
|
: 0;
|
|
39
36
|
|
|
@@ -52,7 +52,7 @@ function PaywallOptions({ node }: PaywallOptionsComponentProps) {
|
|
|
52
52
|
useLogRender('PaywallOptions');
|
|
53
53
|
node = useNode(node);
|
|
54
54
|
const { products } = useBuilderParams();
|
|
55
|
-
const {
|
|
55
|
+
const { selectedProduct, setSelectedProductId } = usePaywallContext();
|
|
56
56
|
const getParamsForProduct = usePaywallOptionParamsFactory();
|
|
57
57
|
|
|
58
58
|
const sortedProducts = useMemo(() => {
|
|
@@ -67,7 +67,7 @@ function PaywallOptions({ node }: PaywallOptionsComponentProps) {
|
|
|
67
67
|
<>
|
|
68
68
|
{sortedProducts.map((p, index) => {
|
|
69
69
|
const productId = p.productId || `${index}`;
|
|
70
|
-
const isSelected =
|
|
70
|
+
const isSelected = selectedProduct?.productId === productId;
|
|
71
71
|
const { localizationParams, otherParams } = getParamsForProduct(p, {
|
|
72
72
|
isSelected,
|
|
73
73
|
});
|
|
@@ -3,11 +3,12 @@ import type { Product } from '../../paywall/types/paywall-types';
|
|
|
3
3
|
|
|
4
4
|
export type PaywallContextValue = {
|
|
5
5
|
products: Product[];
|
|
6
|
-
selectedProductId: string;
|
|
7
6
|
setSelectedProductId: (productId: string) => void;
|
|
8
7
|
selectedProduct?: Product;
|
|
9
8
|
onClose?: () => void;
|
|
10
9
|
onSubscribe?: (product?: Product) => void | boolean | Promise<boolean>;
|
|
10
|
+
onCounterDown?: () => void;
|
|
11
|
+
isBackAllowed: boolean;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const PaywallContext = createContext<PaywallContextValue | undefined>(
|
|
@@ -18,8 +19,9 @@ export function usePaywallContext(): PaywallContextValue {
|
|
|
18
19
|
return (
|
|
19
20
|
useContext(PaywallContext) ?? {
|
|
20
21
|
products: [],
|
|
21
|
-
selectedProductId: '',
|
|
22
22
|
setSelectedProductId: () => {},
|
|
23
|
+
onCounterDown: () => {},
|
|
24
|
+
isBackAllowed: true,
|
|
23
25
|
}
|
|
24
26
|
);
|
|
25
27
|
}
|
|
@@ -11,6 +11,10 @@ import { LocalizationParamsProvider } from '../../components/LocalizationParamsP
|
|
|
11
11
|
import { useBuilderParams } from '../../components/BuilderProvider';
|
|
12
12
|
import { PaywallContext } from './PaywallContext';
|
|
13
13
|
import { useMockOSContext } from '../../mockOS/context/MockOSContextBase';
|
|
14
|
+
import type { Product } from '../../paywall/types/paywall-types';
|
|
15
|
+
import { useChangeDelayByPaywall } from '../../paywall/hooks/useChangeDelayByPaywall';
|
|
16
|
+
import { useHandleGoBack } from '../../paywall/hooks/useHandleGoBack';
|
|
17
|
+
import { useMockOSBackHandler } from '../../paywall/hooks/useMockOSBackHandler';
|
|
14
18
|
|
|
15
19
|
function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
16
20
|
useLogRender('PaywallProvider');
|
|
@@ -57,6 +61,7 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
57
61
|
);
|
|
58
62
|
|
|
59
63
|
const [selectedProductId, setSelectedProductId] = useState<string>('');
|
|
64
|
+
const [isBackAllowed, setIsBackAllowed] = useState<boolean>(false);
|
|
60
65
|
useEffect(() => {
|
|
61
66
|
const list = Array.isArray(products) ? products : [];
|
|
62
67
|
if (list.length === 0) {
|
|
@@ -70,6 +75,9 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
70
75
|
}
|
|
71
76
|
}, [products, selectedProductId]);
|
|
72
77
|
|
|
78
|
+
useChangeDelayByPaywall(node, setIsBackAllowed);
|
|
79
|
+
useMockOSBackHandler(isBackAllowed);
|
|
80
|
+
|
|
73
81
|
const selectedProduct = useMemo(() => {
|
|
74
82
|
const list = Array.isArray(products) ? products : [];
|
|
75
83
|
return (
|
|
@@ -79,6 +87,9 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
79
87
|
}, [products, selectedProductId]);
|
|
80
88
|
|
|
81
89
|
const handleClose = useCallback(() => {
|
|
90
|
+
if (!isBackAllowed) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
82
93
|
// Default: in MockOS go back (simulate dismissing paywall screen).
|
|
83
94
|
// TODO: at react native merge it will change
|
|
84
95
|
if (mockOS?.isEnabled) {
|
|
@@ -87,13 +98,22 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
87
98
|
mockOS.navigation('launchscreen');
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
|
-
}, [mockOS]);
|
|
101
|
+
}, [mockOS, isBackAllowed]);
|
|
102
|
+
|
|
103
|
+
const handleGoBack = useCallback(() => {
|
|
104
|
+
if (!isBackAllowed) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}, [isBackAllowed]);
|
|
109
|
+
|
|
110
|
+
useHandleGoBack(handleGoBack);
|
|
91
111
|
|
|
92
112
|
const handleSubscribe = useCallback(
|
|
93
|
-
async (product?:
|
|
113
|
+
async (product?: Product): Promise<boolean> => {
|
|
94
114
|
// Host app override wins.
|
|
95
115
|
if (onPaywallSubscribe) {
|
|
96
|
-
const result = onPaywallSubscribe(product
|
|
116
|
+
const result = onPaywallSubscribe(product);
|
|
97
117
|
const ok =
|
|
98
118
|
result instanceof Promise
|
|
99
119
|
? await result
|
|
@@ -105,7 +125,7 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
105
125
|
if (ok && mockOS?.isEnabled) {
|
|
106
126
|
mockOS.goBack?.();
|
|
107
127
|
}
|
|
108
|
-
return;
|
|
128
|
+
return ok;
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
// Default: in MockOS show a native-like subscription prompt.
|
|
@@ -122,26 +142,44 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
|
|
|
122
142
|
// TODO: at react native merge it will change
|
|
123
143
|
mockOS.goBack?.();
|
|
124
144
|
}
|
|
145
|
+
return ok;
|
|
125
146
|
}
|
|
147
|
+
return false;
|
|
126
148
|
},
|
|
127
149
|
[mockOS, onPaywallSubscribe],
|
|
128
150
|
);
|
|
129
151
|
|
|
152
|
+
const handleCounterDown = useCallback(() => {
|
|
153
|
+
if (!isBackAllowed) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// Default: in MockOS go back (simulate dismissing paywall screen).
|
|
157
|
+
// TODO: at react native merge it will change
|
|
158
|
+
if (mockOS?.isEnabled) {
|
|
159
|
+
const canGoBack = mockOS.goBack();
|
|
160
|
+
if (!canGoBack) {
|
|
161
|
+
mockOS.navigation('launchscreen');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, [isBackAllowed, mockOS]);
|
|
165
|
+
|
|
130
166
|
const paywallContextValue = useMemo(
|
|
131
167
|
() => ({
|
|
132
168
|
products: Array.isArray(products) ? products : [],
|
|
133
|
-
selectedProductId,
|
|
134
169
|
setSelectedProductId,
|
|
135
170
|
selectedProduct,
|
|
136
171
|
onClose: handleClose,
|
|
137
|
-
onSubscribe: handleSubscribe
|
|
172
|
+
onSubscribe: handleSubscribe,
|
|
173
|
+
onCounterDown: handleCounterDown,
|
|
174
|
+
isBackAllowed,
|
|
138
175
|
}),
|
|
139
176
|
[
|
|
140
177
|
products,
|
|
141
|
-
selectedProductId,
|
|
142
178
|
selectedProduct,
|
|
143
179
|
handleClose,
|
|
144
180
|
handleSubscribe,
|
|
181
|
+
handleCounterDown,
|
|
182
|
+
isBackAllowed,
|
|
145
183
|
],
|
|
146
184
|
);
|
|
147
185
|
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"extends": "View",
|
|
7
7
|
"attributes": {
|
|
8
8
|
"title": "title",
|
|
9
|
-
"description": "description"
|
|
9
|
+
"description": "description",
|
|
10
|
+
"delay": "number"
|
|
10
11
|
}
|
|
11
12
|
},
|
|
12
13
|
"defaults": {
|
|
@@ -21,6 +22,15 @@
|
|
|
21
22
|
],
|
|
22
23
|
"label": "Paywall Provider",
|
|
23
24
|
"description": "Provider/wrapper for paywall screen components.",
|
|
25
|
+
"attributes": {
|
|
26
|
+
"delay": {
|
|
27
|
+
"label": "Back Delay (ms)",
|
|
28
|
+
"description": "Milliseconds before the paywall can be dismissed via back/close.",
|
|
29
|
+
"category": "other",
|
|
30
|
+
"specialCategory": null,
|
|
31
|
+
"sort": 1
|
|
32
|
+
}
|
|
33
|
+
},
|
|
24
34
|
"mockableFeatures": {
|
|
25
35
|
"products": true,
|
|
26
36
|
"benefits": true
|
|
@@ -9823,11 +9823,13 @@ export const patterns = [
|
|
|
9823
9823
|
style: {
|
|
9824
9824
|
fontSize: '16',
|
|
9825
9825
|
flexDirection: 'row',
|
|
9826
|
-
position: '
|
|
9826
|
+
position: 'absolute',
|
|
9827
9827
|
zIndex: 1,
|
|
9828
9828
|
alignSelf: 'flex-start',
|
|
9829
9829
|
flexGrow: 0,
|
|
9830
9830
|
flexShrink: 0,
|
|
9831
|
+
top: '35@vs',
|
|
9832
|
+
left: '24@s',
|
|
9831
9833
|
justifyContent: 'center',
|
|
9832
9834
|
alignItems: 'center',
|
|
9833
9835
|
},
|
|
@@ -10705,6 +10707,7 @@ export const patterns = [
|
|
|
10705
10707
|
right: 'size',
|
|
10706
10708
|
zIndex: 'number',
|
|
10707
10709
|
},
|
|
10710
|
+
delay: 'number',
|
|
10708
10711
|
},
|
|
10709
10712
|
},
|
|
10710
10713
|
meta: {
|
|
@@ -11004,6 +11007,14 @@ export const patterns = [
|
|
|
11004
11007
|
specialCategory: null,
|
|
11005
11008
|
sort: 26,
|
|
11006
11009
|
},
|
|
11010
|
+
delay: {
|
|
11011
|
+
label: 'Back Delay (ms)',
|
|
11012
|
+
description:
|
|
11013
|
+
'Milliseconds before the paywall can be dismissed via back/close.',
|
|
11014
|
+
category: 'other',
|
|
11015
|
+
specialCategory: null,
|
|
11016
|
+
sort: 1,
|
|
11017
|
+
},
|
|
11007
11018
|
},
|
|
11008
11019
|
mockableFeatures: { products: true, benefits: true },
|
|
11009
11020
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Device } from '../types/Device';
|
|
3
|
+
import { emitMockOSBackPress } from '../mockOS/backHandler';
|
|
3
4
|
import { useMockOSContext } from '../mockOS/context/MockOSContextBase';
|
|
4
5
|
|
|
5
6
|
type DeviceNavigationBarProps = {
|
|
@@ -37,6 +38,10 @@ export function DeviceNavigationBar({
|
|
|
37
38
|
: 'rgba(0, 0, 0, 0.4)';
|
|
38
39
|
|
|
39
40
|
function handleBackButton() {
|
|
41
|
+
if (!emitMockOSBackPress()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
if (context) {
|
|
41
46
|
const canGoBack = context.goBack();
|
|
42
47
|
// If can't go back, go to launchscreen
|
package/src/index.ts
CHANGED
|
@@ -27,11 +27,9 @@ export type {
|
|
|
27
27
|
|
|
28
28
|
// Paywall hooks (RN-safe placeholders)
|
|
29
29
|
export {
|
|
30
|
-
usePaywallCounter,
|
|
31
|
-
useCloseStatusPaywall,
|
|
32
|
-
useCarouselOptionsSeperator,
|
|
33
30
|
useCalculateLocalizedPrice,
|
|
34
31
|
useDiscountRate,
|
|
32
|
+
useChangeDelayByPaywall,
|
|
35
33
|
} from './paywall/hooks';
|
|
36
34
|
|
|
37
35
|
// Context (RN-safe). In React Native, `products` should come from an IAP wrapper
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type MockOSBackHandler = () => boolean;
|
|
2
|
+
|
|
3
|
+
const backHandlers: MockOSBackHandler[] = [];
|
|
4
|
+
|
|
5
|
+
export function registerMockOSBackHandler(
|
|
6
|
+
handler: MockOSBackHandler,
|
|
7
|
+
): () => void {
|
|
8
|
+
backHandlers.push(handler);
|
|
9
|
+
|
|
10
|
+
return () => {
|
|
11
|
+
const index = backHandlers.lastIndexOf(handler);
|
|
12
|
+
if (index >= 0) {
|
|
13
|
+
backHandlers.splice(index, 1);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function emitMockOSBackPress(): boolean {
|
|
19
|
+
if (backHandlers.length === 0) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let i = backHandlers.length - 1; i >= 0; i -= 1) {
|
|
24
|
+
const allow = backHandlers[i]?.();
|
|
25
|
+
if (!allow) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function clearMockOSBackHandlers(): void {
|
|
34
|
+
backHandlers.length = 0;
|
|
35
|
+
}
|
|
@@ -71,7 +71,16 @@ export function ProjectMigrationPage({
|
|
|
71
71
|
type="button"
|
|
72
72
|
className="editor-button"
|
|
73
73
|
disabled={!!migrating}
|
|
74
|
-
onClick={() =>
|
|
74
|
+
onClick={() => {
|
|
75
|
+
console.info('[ProjectMigrationPage] onMigrateNow clicked', {
|
|
76
|
+
name,
|
|
77
|
+
projectVersion,
|
|
78
|
+
requiredVersion,
|
|
79
|
+
pendingMigrationsCount: pendingMigrations.length,
|
|
80
|
+
migrating: !!migrating,
|
|
81
|
+
});
|
|
82
|
+
onMigrateNow?.();
|
|
83
|
+
}}
|
|
75
84
|
>
|
|
76
85
|
{migrating ? 'Migrating…' : 'Migrate now'}
|
|
77
86
|
</button>
|
|
@@ -289,6 +289,13 @@ export function ProjectPage({
|
|
|
289
289
|
<EditorHeader
|
|
290
290
|
onSaveProject={() => {
|
|
291
291
|
try {
|
|
292
|
+
console.info('[ProjectPage] onSaveProject clicked', {
|
|
293
|
+
name: project.name,
|
|
294
|
+
hasOverrideProject: !!overrideProject,
|
|
295
|
+
hasEditorData: !!editorData,
|
|
296
|
+
hasResolvedProjectColors: !!resolvedProjectColors,
|
|
297
|
+
bypassValidation,
|
|
298
|
+
});
|
|
292
299
|
logger.info('ProjectPage', 'save project', { name: project.name });
|
|
293
300
|
if (onSaveProjectColors && resolvedProjectColors) {
|
|
294
301
|
onSaveProjectColors(resolvedProjectColors);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { usePaywallCounter } from './usePaywallCounter';
|
|
2
|
-
export { useCloseStatusPaywall } from './useCloseStatusPaywall';
|
|
3
|
-
export { useCarouselOptionsSeperator } from './useCarouselOptionsSeperator';
|
|
4
1
|
export { useCalculateLocalizedPrice } from './useCalculateLocalizedPrice';
|
|
5
2
|
export { useDiscountRate } from './useDiscountRate';
|
|
3
|
+
export { useChangeDelayByPaywall } from './useChangeDelayByPaywall';
|
|
4
|
+
export { useHandleGoBack } from './useHandleGoBack';
|
|
5
|
+
export { useMockOSBackHandler } from './useMockOSBackHandler';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import type { NodeData } from '../../types/Node';
|
|
3
|
+
|
|
4
|
+
export function useChangeDelayByPaywall(
|
|
5
|
+
node: NodeData<{ delay?: number }> | null | undefined,
|
|
6
|
+
setIsBackAllowed: (value: boolean) => void,
|
|
7
|
+
) {
|
|
8
|
+
const delay = useMemo(() => {
|
|
9
|
+
const delayAttribute = node?.attributes?.delay
|
|
10
|
+
? parseInt(String(node?.attributes?.delay), 10)
|
|
11
|
+
: 1000;
|
|
12
|
+
return delayAttribute;
|
|
13
|
+
}, [node?.attributes?.delay]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!delay || delay <= 0) {
|
|
17
|
+
setIsBackAllowed(true);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const timeout = setTimeout(() => {
|
|
21
|
+
setIsBackAllowed(true);
|
|
22
|
+
}, delay);
|
|
23
|
+
return () => clearTimeout(timeout);
|
|
24
|
+
}, [delay, setIsBackAllowed]);
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useMockOSContext } from '../../mockOS/context/MockOSContextBase';
|
|
3
|
+
|
|
4
|
+
export function useHandleGoBack(
|
|
5
|
+
handleGoBack: () => boolean | null | undefined,
|
|
6
|
+
) {
|
|
7
|
+
const mockOS = useMockOSContext();
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!mockOS?.isEnabled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const handleBackPress = (event?: KeyboardEvent | PopStateEvent) => {
|
|
15
|
+
const result = handleGoBack();
|
|
16
|
+
|
|
17
|
+
// Only allow navigation if result is explicitly true
|
|
18
|
+
if (result !== true) {
|
|
19
|
+
// Prevent navigation
|
|
20
|
+
if (event instanceof KeyboardEvent) {
|
|
21
|
+
event.preventDefault();
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
} else if (event instanceof PopStateEvent) {
|
|
24
|
+
// Prevent browser navigation by pushing state back
|
|
25
|
+
window.history.pushState(null, '', window.location.href);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Allow navigation - execute MockOS goBack
|
|
31
|
+
const canGoBack = mockOS.goBack();
|
|
32
|
+
if (!canGoBack) {
|
|
33
|
+
mockOS.navigation('launchscreen');
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Handle Escape key
|
|
38
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
39
|
+
if (e.key === 'Escape') {
|
|
40
|
+
handleBackPress(e);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Handle browser back button
|
|
45
|
+
const handlePopState = (e: PopStateEvent) => {
|
|
46
|
+
handleBackPress(e);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Push a state to track back navigation
|
|
50
|
+
window.history.pushState(null, '', window.location.href);
|
|
51
|
+
|
|
52
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
53
|
+
window.addEventListener('popstate', handlePopState);
|
|
54
|
+
|
|
55
|
+
return () => {
|
|
56
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
57
|
+
window.removeEventListener('popstate', handlePopState);
|
|
58
|
+
};
|
|
59
|
+
}, [handleGoBack, mockOS]);
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { registerMockOSBackHandler } from '../../mockOS/backHandler';
|
|
3
|
+
|
|
4
|
+
export function useMockOSBackHandler(isBackAllowed: boolean) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const unsubscribe = registerMockOSBackHandler(() => isBackAllowed);
|
|
7
|
+
return unsubscribe;
|
|
8
|
+
}, [isBackAllowed]);
|
|
9
|
+
}
|