@developer_tribe/react-builder 1.2.44-test.2 → 1.2.45
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/attributes-editor/Field.d.ts +1 -3
- package/dist/attributes-editor/attributesEditorModelTypes.d.ts +0 -3
- package/dist/attributes-editor/useAttributesEditorModel.d.ts +1 -1
- package/dist/build-components/FormSubmitButton/FormSubmitButtonProps.generated.d.ts +3 -8
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +3 -8
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +0 -2
- package/dist/build-components/SystemButton/SystemButtonProps.generated.d.ts +3 -8
- package/dist/build-components/SystemButton/usePlacementButtonEvents.d.ts +2 -15
- package/dist/build-components/index.d.ts +1 -3
- package/dist/build-components/patterns.generated.d.ts +1334 -2129
- package/dist/components/DeviceButton.d.ts +2 -1
- package/dist/index.cjs.js +28 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +28 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +4 -4
- 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/modals/CreateDeviceModal.d.ts +8 -0
- package/dist/product-base/periodLocalizationKeys.d.ts +16 -0
- package/dist/store/customDeviceStore.d.ts +21 -0
- package/dist/store.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/nodeTree.d.ts +0 -18
- package/package.json +1 -1
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +4 -68
- package/src/.DS_Store +0 -0
- package/src/assets/.DS_Store +0 -0
- package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
- package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
- package/src/assets/samples/getSamples.ts +0 -2
- package/src/assets/samples/terms-and-privacy-no-form.json +1 -1
- package/src/assets/samples/terms-and-privacy.json +1 -1
- package/src/attributes-editor/AttributesEditorView.tsx +0 -3
- package/src/attributes-editor/Field.tsx +2 -91
- package/src/attributes-editor/attributesEditorModelTypes.ts +0 -3
- package/src/attributes-editor/useAttributesEditorModel.ts +0 -8
- package/src/build-components/FormCheckbox/FormCheckbox.tsx +3 -3
- package/src/build-components/FormSubmitButton/FormSubmitButton.tsx +0 -6
- package/src/build-components/FormSubmitButton/FormSubmitButtonProps.generated.ts +3 -26
- package/src/build-components/OnboardButton/OnboardButton.tsx +1 -8
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +3 -26
- package/src/build-components/OnboardButton/pattern.json +3 -5
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +0 -12
- package/src/build-components/OnboardProvider/pattern.json +1 -9
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +0 -12
- package/src/build-components/PaywallProvider/pattern.json +1 -9
- package/src/build-components/RenderNode.generated.tsx +0 -10
- package/src/build-components/SystemButton/SystemButton.tsx +0 -6
- package/src/build-components/SystemButton/SystemButtonProps.generated.ts +3 -26
- package/src/build-components/SystemButton/pattern.json +3 -5
- package/src/build-components/SystemButton/usePlacementButtonEvents.ts +27 -51
- package/src/build-components/Text/Text.tsx +2 -3
- package/src/build-components/index.ts +0 -10
- package/src/build-components/patterns.generated.ts +1379 -2270
- package/src/components/AttributesEditorPanel.tsx +0 -1
- package/src/components/DeviceButton.tsx +34 -1
- package/src/components/EditorHeader.tsx +22 -4
- package/src/modals/CreateDeviceModal.tsx +264 -0
- package/src/modals/DeviceSelectorModal.tsx +44 -7
- package/src/product-base/extractIOSParams.ts +10 -4
- package/src/product-base/periodLocalizationKeys.ts +46 -0
- package/src/store/customDeviceStore.ts +38 -0
- package/src/styles/components/_editor-shell.scss +12 -2
- package/src/styles/index.scss +1 -1
- package/src/styles/modals/_create-device.scss +113 -0
- package/src/utils/__special_exceptions.ts +8 -0
- package/src/utils/analyseNodeByPatterns.ts +10 -6
- package/src/utils/nodeTree.ts +0 -115
- package/dist/build-components/GlobalProvider/GlobalContext.d.ts +0 -28
- package/dist/build-components/GlobalProvider/GlobalProvider.d.ts +0 -5
- package/dist/build-components/GlobalProvider/GlobalProviderProps.generated.d.ts +0 -60
- package/dist/build-components/GlobalProvider/globalProviderUtils.d.ts +0 -28
- package/dist/build-components/GlobalProvider/useGlobalNavigation.d.ts +0 -19
- package/dist/build-components/GlobalProvider/useGlobalProviderLogic.d.ts +0 -15
- package/dist/build-components/TermsProvider/TermsProvider.d.ts +0 -5
- package/dist/build-components/TermsProvider/TermsProviderProps.generated.d.ts +0 -55
- package/src/assets/samples/global-onboard-flow.json +0 -735
- package/src/build-components/GlobalProvider/GlobalContext.ts +0 -48
- package/src/build-components/GlobalProvider/GlobalProvider.tsx +0 -51
- package/src/build-components/GlobalProvider/GlobalProviderProps.generated.ts +0 -78
- package/src/build-components/GlobalProvider/globalProviderUtils.ts +0 -204
- package/src/build-components/GlobalProvider/pattern.json +0 -55
- package/src/build-components/GlobalProvider/useGlobalNavigation.ts +0 -65
- package/src/build-components/GlobalProvider/useGlobalProviderLogic.ts +0 -172
- package/src/build-components/TermsProvider/TermsProvider.tsx +0 -45
- package/src/build-components/TermsProvider/TermsProviderProps.generated.ts +0 -82
- package/src/build-components/TermsProvider/pattern.json +0 -35
- package/src/patterns/event-constants.json +0 -19
- package/src/styles/components/_global-provider.scss +0 -131
|
@@ -13,6 +13,7 @@ type DeviceButtonProps = {
|
|
|
13
13
|
onSelect: (device: Device) => void;
|
|
14
14
|
isFavorite?: boolean;
|
|
15
15
|
onToggleFavorite?: (device: Device, e: React.MouseEvent) => void;
|
|
16
|
+
onEdit?: (device: Device, e: React.MouseEvent) => void;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export function DeviceButton({
|
|
@@ -21,6 +22,7 @@ export function DeviceButton({
|
|
|
21
22
|
onSelect,
|
|
22
23
|
isFavorite,
|
|
23
24
|
onToggleFavorite,
|
|
25
|
+
onEdit,
|
|
24
26
|
}: DeviceButtonProps) {
|
|
25
27
|
const platformIcon = platformIcons[device.platform];
|
|
26
28
|
const aspect =
|
|
@@ -74,10 +76,41 @@ export function DeviceButton({
|
|
|
74
76
|
{device.name} <br />
|
|
75
77
|
{device.width}×{device.height} ({aspect})
|
|
76
78
|
{platformIcon && (
|
|
77
|
-
<span>
|
|
79
|
+
<span className="editor-device-button__platform">
|
|
78
80
|
<img src={platformIcon} alt="" aria-hidden="true" />
|
|
79
81
|
</span>
|
|
80
82
|
)}
|
|
83
|
+
{onEdit && (
|
|
84
|
+
<span
|
|
85
|
+
className="editor-device-button__edit"
|
|
86
|
+
onClick={(e) => {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
onEdit(device, e);
|
|
89
|
+
}}
|
|
90
|
+
title="Edit device"
|
|
91
|
+
style={{
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
bottom: 4,
|
|
94
|
+
left: 4,
|
|
95
|
+
cursor: 'pointer',
|
|
96
|
+
opacity: 0.5,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<svg
|
|
100
|
+
width="12"
|
|
101
|
+
height="12"
|
|
102
|
+
viewBox="0 0 24 24"
|
|
103
|
+
fill="none"
|
|
104
|
+
stroke="currentColor"
|
|
105
|
+
strokeWidth="2"
|
|
106
|
+
strokeLinecap="round"
|
|
107
|
+
strokeLinejoin="round"
|
|
108
|
+
>
|
|
109
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
110
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
|
111
|
+
</svg>
|
|
112
|
+
</span>
|
|
113
|
+
)}
|
|
81
114
|
</button>
|
|
82
115
|
);
|
|
83
116
|
}
|
|
@@ -11,6 +11,7 @@ import { DeviceSelectorModal } from '../modals/DeviceSelectorModal';
|
|
|
11
11
|
import { toast } from 'react-toastify';
|
|
12
12
|
import { getSamples } from '../assets/samples/getSamples';
|
|
13
13
|
import type { Project } from '../types/Project';
|
|
14
|
+
import { useCustomDeviceStore } from '../store/customDeviceStore';
|
|
14
15
|
|
|
15
16
|
const devices = getDevices();
|
|
16
17
|
interface EditorHeaderProps {
|
|
@@ -53,14 +54,31 @@ export function EditorHeader({
|
|
|
53
54
|
toggleFavoriteDevice: s.toggleFavoriteDevice,
|
|
54
55
|
}));
|
|
55
56
|
|
|
57
|
+
const { customDevices } = useCustomDeviceStore();
|
|
56
58
|
const headerDevices = useMemo(() => {
|
|
59
|
+
// 1. Get all potential devices
|
|
60
|
+
const allAvailable = [...customDevices, ...devices];
|
|
61
|
+
|
|
62
|
+
// 2. Filter favorites
|
|
57
63
|
const favs = favoriteDevices
|
|
58
|
-
.map((name) =>
|
|
64
|
+
.map((name) => allAvailable.find((d) => d.name === name))
|
|
59
65
|
.filter((d): d is Device => d !== undefined);
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
// 3. Get others (custom first, then default)
|
|
68
|
+
const others = allAvailable.filter(
|
|
69
|
+
(d) => !favoriteDevices.includes(d.name),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Combine and limit to 5
|
|
73
|
+
const top5 = [...favs, ...others].slice(0, 5);
|
|
74
|
+
|
|
75
|
+
// If selected device is not in the top 5, append it at the end
|
|
76
|
+
if (selectedDevice && !top5.some((d) => d.name === selectedDevice.name)) {
|
|
77
|
+
return [...top5, selectedDevice];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return top5;
|
|
81
|
+
}, [favoriteDevices, customDevices, selectedDevice]);
|
|
64
82
|
|
|
65
83
|
const sortedSamples = useMemo(() => {
|
|
66
84
|
const weight = (t?: Project['type']) => {
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import Modal from './Modal';
|
|
4
|
+
import { Device } from '../types/Device';
|
|
5
|
+
import { useCustomDeviceStore } from '../store/customDeviceStore';
|
|
6
|
+
|
|
7
|
+
type CreateDeviceModalProps = {
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
onSuccess: (device: Device) => void;
|
|
10
|
+
deviceToEdit?: Device;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type FormData = {
|
|
14
|
+
name: string;
|
|
15
|
+
platform: 'ios' | 'android';
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
type: 'phone' | 'tablet';
|
|
19
|
+
radius: number;
|
|
20
|
+
insetTop: number;
|
|
21
|
+
insetRight: number;
|
|
22
|
+
insetBottom: number;
|
|
23
|
+
insetLeft: number;
|
|
24
|
+
navigationBarType: Device['navigationBarType'];
|
|
25
|
+
importance: number;
|
|
26
|
+
multiplier: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function CreateDeviceModal({
|
|
30
|
+
onClose,
|
|
31
|
+
onSuccess,
|
|
32
|
+
deviceToEdit,
|
|
33
|
+
}: CreateDeviceModalProps) {
|
|
34
|
+
const { addCustomDevice, updateCustomDevice } = useCustomDeviceStore();
|
|
35
|
+
const {
|
|
36
|
+
register,
|
|
37
|
+
handleSubmit,
|
|
38
|
+
watch,
|
|
39
|
+
formState: { errors },
|
|
40
|
+
} = useForm<FormData>({
|
|
41
|
+
defaultValues: deviceToEdit
|
|
42
|
+
? {
|
|
43
|
+
name: deviceToEdit.name,
|
|
44
|
+
platform: deviceToEdit.platform as 'ios' | 'android',
|
|
45
|
+
type: deviceToEdit.type,
|
|
46
|
+
width: deviceToEdit.width,
|
|
47
|
+
height: deviceToEdit.height,
|
|
48
|
+
insetTop: deviceToEdit.insets?.[0] ?? 0,
|
|
49
|
+
insetRight: deviceToEdit.insets?.[1] ?? 0,
|
|
50
|
+
insetBottom: deviceToEdit.insets?.[2] ?? 0,
|
|
51
|
+
insetLeft: deviceToEdit.insets?.[3] ?? 0,
|
|
52
|
+
navigationBarType: deviceToEdit.navigationBarType,
|
|
53
|
+
radius: deviceToEdit.radius,
|
|
54
|
+
importance: deviceToEdit.importance,
|
|
55
|
+
multiplier: deviceToEdit.multiplier,
|
|
56
|
+
}
|
|
57
|
+
: {
|
|
58
|
+
platform: 'ios',
|
|
59
|
+
type: 'phone',
|
|
60
|
+
navigationBarType: 'homeIndicator',
|
|
61
|
+
width: 375,
|
|
62
|
+
height: 812,
|
|
63
|
+
insetTop: 44,
|
|
64
|
+
insetBottom: 34,
|
|
65
|
+
insetLeft: 0,
|
|
66
|
+
insetRight: 0,
|
|
67
|
+
radius: 40,
|
|
68
|
+
importance: 10,
|
|
69
|
+
multiplier: 1,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const platform = watch('platform');
|
|
74
|
+
|
|
75
|
+
const onSubmit = (data: FormData) => {
|
|
76
|
+
const newDevice: Device = {
|
|
77
|
+
name: data.name,
|
|
78
|
+
platform: data.platform,
|
|
79
|
+
width: Number(data.width),
|
|
80
|
+
height: Number(data.height),
|
|
81
|
+
type: data.type,
|
|
82
|
+
radius: Number(data.radius),
|
|
83
|
+
insets: [
|
|
84
|
+
Number(data.insetTop),
|
|
85
|
+
Number(data.insetRight),
|
|
86
|
+
Number(data.insetBottom),
|
|
87
|
+
Number(data.insetLeft),
|
|
88
|
+
],
|
|
89
|
+
navigationBarType: data.navigationBarType,
|
|
90
|
+
aspect: (() => {
|
|
91
|
+
const r = Number(data.height) / Number(data.width);
|
|
92
|
+
if (r >= 2.05) return 'tall';
|
|
93
|
+
if (r <= 1.75) return 'wide';
|
|
94
|
+
return 'regular';
|
|
95
|
+
})(),
|
|
96
|
+
importance: Number(data.importance),
|
|
97
|
+
multiplier: Number(data.multiplier),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (deviceToEdit) {
|
|
101
|
+
updateCustomDevice(deviceToEdit.name, newDevice);
|
|
102
|
+
} else {
|
|
103
|
+
addCustomDevice(newDevice);
|
|
104
|
+
}
|
|
105
|
+
onSuccess(newDevice);
|
|
106
|
+
onClose();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Modal
|
|
111
|
+
onClose={onClose}
|
|
112
|
+
ariaLabelledBy="create-device-title"
|
|
113
|
+
contentClassName="create-device-modal"
|
|
114
|
+
>
|
|
115
|
+
<div className="modal__header">
|
|
116
|
+
<h3 id="create-device-title" className="modal__title">
|
|
117
|
+
{deviceToEdit ? 'Edit Custom Device' : 'Create Custom Device'}
|
|
118
|
+
</h3>
|
|
119
|
+
<button type="button" className="editor-button" onClick={onClose}>
|
|
120
|
+
Cancel
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
<form
|
|
124
|
+
onSubmit={handleSubmit(onSubmit)}
|
|
125
|
+
className="modal__body create-device-form"
|
|
126
|
+
>
|
|
127
|
+
<div className="create-device-form__group">
|
|
128
|
+
<label className="create-device-form__label">Device Name</label>
|
|
129
|
+
<input
|
|
130
|
+
{...register('name', { required: 'Name is required' })}
|
|
131
|
+
className="editor-input"
|
|
132
|
+
placeholder="e.g. My Custom iPhone"
|
|
133
|
+
/>
|
|
134
|
+
{errors.name && (
|
|
135
|
+
<span className="create-device-form__error">
|
|
136
|
+
{errors.name.message}
|
|
137
|
+
</span>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="create-device-form__row">
|
|
142
|
+
<div className="create-device-form__group">
|
|
143
|
+
<label className="create-device-form__label">Platform</label>
|
|
144
|
+
<select {...register('platform')} className="editor-input">
|
|
145
|
+
<option value="ios">iOS</option>
|
|
146
|
+
<option value="android">Android</option>
|
|
147
|
+
</select>
|
|
148
|
+
</div>
|
|
149
|
+
<div className="create-device-form__group">
|
|
150
|
+
<label className="create-device-form__label">Type</label>
|
|
151
|
+
<select {...register('type')} className="editor-input">
|
|
152
|
+
<option value="phone">Phone</option>
|
|
153
|
+
<option value="tablet">Tablet</option>
|
|
154
|
+
</select>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div className="create-device-form__row">
|
|
159
|
+
<div className="create-device-form__group">
|
|
160
|
+
<label className="create-device-form__label">Width (px)</label>
|
|
161
|
+
<input
|
|
162
|
+
type="number"
|
|
163
|
+
{...register('width', { required: true, min: 1 })}
|
|
164
|
+
className="editor-input"
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="create-device-form__group">
|
|
168
|
+
<label className="create-device-form__label">Height (px)</label>
|
|
169
|
+
<input
|
|
170
|
+
type="number"
|
|
171
|
+
{...register('height', { required: true, min: 1 })}
|
|
172
|
+
className="editor-input"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div className="create-device-form__row">
|
|
178
|
+
<div className="create-device-form__group">
|
|
179
|
+
<label className="create-device-form__label">Safe Area Top</label>
|
|
180
|
+
<input
|
|
181
|
+
type="number"
|
|
182
|
+
{...register('insetTop', { min: 0 })}
|
|
183
|
+
className="editor-input"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
<div className="create-device-form__group">
|
|
187
|
+
<label className="create-device-form__label">
|
|
188
|
+
Safe Area Bottom
|
|
189
|
+
</label>
|
|
190
|
+
<input
|
|
191
|
+
type="number"
|
|
192
|
+
{...register('insetBottom', { min: 0 })}
|
|
193
|
+
className="editor-input"
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div className="create-device-form__row">
|
|
199
|
+
<div className="create-device-form__group">
|
|
200
|
+
<label className="create-device-form__label">Navigation Bar</label>
|
|
201
|
+
<select {...register('navigationBarType')} className="editor-input">
|
|
202
|
+
<option value="none">None</option>
|
|
203
|
+
{platform === 'ios' ? (
|
|
204
|
+
<>
|
|
205
|
+
<option value="homeIndicator">Home Indicator</option>
|
|
206
|
+
<option value="tabBar">Tab Bar</option>
|
|
207
|
+
</>
|
|
208
|
+
) : (
|
|
209
|
+
<>
|
|
210
|
+
<option value="threeButtons">Three Buttons</option>
|
|
211
|
+
<option value="gesture">Gesture</option>
|
|
212
|
+
</>
|
|
213
|
+
)}
|
|
214
|
+
</select>
|
|
215
|
+
</div>
|
|
216
|
+
<div className="create-device-form__group">
|
|
217
|
+
<label className="create-device-form__label">Corner Radius</label>
|
|
218
|
+
<input
|
|
219
|
+
type="number"
|
|
220
|
+
{...register('radius', { min: 0 })}
|
|
221
|
+
className="editor-input"
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div className="create-device-form__row">
|
|
227
|
+
<div className="create-device-form__group">
|
|
228
|
+
<label className="create-device-form__label">
|
|
229
|
+
Scale Multiplier
|
|
230
|
+
</label>
|
|
231
|
+
<input
|
|
232
|
+
type="number"
|
|
233
|
+
step="0.01"
|
|
234
|
+
{...register('multiplier', { min: 0.1, max: 2 })}
|
|
235
|
+
className="editor-input"
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
<div className="create-device-form__group">
|
|
239
|
+
<label className="create-device-form__label">
|
|
240
|
+
Importance (1-100)
|
|
241
|
+
</label>
|
|
242
|
+
<input
|
|
243
|
+
type="number"
|
|
244
|
+
{...register('importance', { min: 1, max: 100 })}
|
|
245
|
+
className="editor-input"
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div className="create-device-modal__footer">
|
|
251
|
+
<button
|
|
252
|
+
type="submit"
|
|
253
|
+
className="editor-button editor-button--primary"
|
|
254
|
+
style={{ width: '100%' }}
|
|
255
|
+
>
|
|
256
|
+
{deviceToEdit ? 'Update Device' : 'Save Device'}
|
|
257
|
+
</button>
|
|
258
|
+
</div>
|
|
259
|
+
</form>
|
|
260
|
+
</Modal>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default CreateDeviceModal;
|
|
@@ -3,6 +3,8 @@ import { Device } from '../types/Device';
|
|
|
3
3
|
import Modal from './Modal';
|
|
4
4
|
import { DeviceButton } from '../components/DeviceButton';
|
|
5
5
|
import { useRenderStore } from '../store';
|
|
6
|
+
import { useCustomDeviceStore } from '../store/customDeviceStore';
|
|
7
|
+
import { CreateDeviceModal } from './CreateDeviceModal';
|
|
6
8
|
|
|
7
9
|
type DeviceSelectorModalProps = {
|
|
8
10
|
devices: Device[];
|
|
@@ -18,10 +20,17 @@ export function DeviceSelectorModal({
|
|
|
18
20
|
onClose,
|
|
19
21
|
}: DeviceSelectorModalProps) {
|
|
20
22
|
const [searchTerm, setSearchTerm] = useState('');
|
|
23
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
24
|
+
const [editingDevice, setEditingDevice] = useState<Device | null>(null);
|
|
21
25
|
const { favoriteDevices, toggleFavoriteDevice } = useRenderStore((state) => ({
|
|
22
26
|
favoriteDevices: state.favoriteDevices || [],
|
|
23
27
|
toggleFavoriteDevice: state.toggleFavoriteDevice,
|
|
24
28
|
}));
|
|
29
|
+
const { customDevices, removeCustomDevice } = useCustomDeviceStore();
|
|
30
|
+
|
|
31
|
+
const allDevices = useMemo(() => {
|
|
32
|
+
return [...customDevices, ...devices];
|
|
33
|
+
}, [customDevices, devices]);
|
|
25
34
|
|
|
26
35
|
const handleDeviceSelect = (device: Device) => {
|
|
27
36
|
onSelect(device);
|
|
@@ -29,10 +38,10 @@ export function DeviceSelectorModal({
|
|
|
29
38
|
};
|
|
30
39
|
|
|
31
40
|
const filteredDevices = useMemo(() => {
|
|
32
|
-
return
|
|
41
|
+
return allDevices.filter((device) =>
|
|
33
42
|
device.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
34
43
|
);
|
|
35
|
-
}, [
|
|
44
|
+
}, [allDevices, searchTerm]);
|
|
36
45
|
|
|
37
46
|
const { favorites, others } = useMemo(() => {
|
|
38
47
|
const favs: Device[] = [];
|
|
@@ -66,18 +75,21 @@ export function DeviceSelectorModal({
|
|
|
66
75
|
Close
|
|
67
76
|
</button>
|
|
68
77
|
</div>
|
|
69
|
-
<div
|
|
70
|
-
className="device-selector-modal__search"
|
|
71
|
-
style={{ padding: '0 16px', marginBottom: '8px' }}
|
|
72
|
-
>
|
|
78
|
+
<div className="device-selector-modal__search-container">
|
|
73
79
|
<input
|
|
74
80
|
type="text"
|
|
75
81
|
placeholder="Search devices..."
|
|
76
82
|
value={searchTerm}
|
|
77
83
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
78
84
|
className="editor-input"
|
|
79
|
-
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
|
|
80
85
|
/>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
className="editor-button editor-button--primary"
|
|
89
|
+
onClick={() => setIsCreateModalOpen(true)}
|
|
90
|
+
>
|
|
91
|
+
Add New
|
|
92
|
+
</button>
|
|
81
93
|
</div>
|
|
82
94
|
<div
|
|
83
95
|
className="device-selector-modal__body"
|
|
@@ -103,6 +115,7 @@ export function DeviceSelectorModal({
|
|
|
103
115
|
onSelect={handleDeviceSelect}
|
|
104
116
|
isFavorite={true}
|
|
105
117
|
onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
|
|
118
|
+
onEdit={(d) => setEditingDevice(d)}
|
|
106
119
|
/>
|
|
107
120
|
))}
|
|
108
121
|
</div>
|
|
@@ -129,11 +142,35 @@ export function DeviceSelectorModal({
|
|
|
129
142
|
onSelect={handleDeviceSelect}
|
|
130
143
|
isFavorite={false}
|
|
131
144
|
onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
|
|
145
|
+
onEdit={
|
|
146
|
+
customDevices.some((cd) => cd.name === device.name)
|
|
147
|
+
? (d) => setEditingDevice(d)
|
|
148
|
+
: undefined
|
|
149
|
+
}
|
|
132
150
|
/>
|
|
133
151
|
))}
|
|
134
152
|
</div>
|
|
135
153
|
</section>
|
|
136
154
|
</div>
|
|
155
|
+
{isCreateModalOpen && (
|
|
156
|
+
<CreateDeviceModal
|
|
157
|
+
onClose={() => setIsCreateModalOpen(false)}
|
|
158
|
+
onSuccess={(newDevice) => {
|
|
159
|
+
onSelect(newDevice);
|
|
160
|
+
onClose();
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
{editingDevice && (
|
|
165
|
+
<CreateDeviceModal
|
|
166
|
+
deviceToEdit={editingDevice}
|
|
167
|
+
onClose={() => setEditingDevice(null)}
|
|
168
|
+
onSuccess={(updatedDevice) => {
|
|
169
|
+
onSelect(updatedDevice);
|
|
170
|
+
onClose();
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
137
174
|
</Modal>
|
|
138
175
|
);
|
|
139
176
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
convertIOSPeriodUnit,
|
|
3
|
+
normalizeIOSPeriod,
|
|
4
|
+
} from './periodLocalizationKeys';
|
|
2
5
|
import { iapLogger } from '../logger';
|
|
3
6
|
import {
|
|
4
7
|
calculateDiscount,
|
|
@@ -60,10 +63,13 @@ export function extractIOSParams(
|
|
|
60
63
|
const currency = product.currency || product.currencyCode || '';
|
|
61
64
|
const localizedPrice = product.localizedPrice || '';
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
// iOS bazen WEEK yerine DAY+7 döndürür — normalize et
|
|
67
|
+
const normalizedPeriod = normalizeIOSPeriod(
|
|
68
|
+
product.subscriptionPeriodUnitIOS,
|
|
69
|
+
product.subscriptionPeriodNumberIOS,
|
|
65
70
|
);
|
|
66
|
-
const
|
|
71
|
+
const periodUnit = normalizedPeriod.unit;
|
|
72
|
+
const periodValue = String(normalizedPeriod.value);
|
|
67
73
|
const periodType = `${periodValue} ${periodUnit}${parseInt(periodValue, 10) > 1 ? 's' : ''}`;
|
|
68
74
|
|
|
69
75
|
const introPrice =
|
|
@@ -112,3 +112,49 @@ export function convertIOSPeriodUnit(
|
|
|
112
112
|
const normalized = unitMap[iosUnit.toUpperCase()];
|
|
113
113
|
return normalized || 'month';
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* iOS'un döndüğü ham period değerlerini semantik olarak normalize eder.
|
|
118
|
+
*
|
|
119
|
+
* iOS bazen "WEEK" yerine "DAY" + 7, "MONTH" yerine "DAY" + 30 döndürür.
|
|
120
|
+
* Bu fonksiyon bu ham değerleri doğru semantic period'a çevirir.
|
|
121
|
+
*
|
|
122
|
+
* @example normalizeIOSPeriod('DAY', 7) → { unit: 'week', value: 1 }
|
|
123
|
+
* @example normalizeIOSPeriod('DAY', 14) → { unit: 'week', value: 2 }
|
|
124
|
+
* @example normalizeIOSPeriod('DAY', 30) → { unit: 'month', value: 1 }
|
|
125
|
+
* @example normalizeIOSPeriod('DAY', 365) → { unit: 'year', value: 1 }
|
|
126
|
+
* @example normalizeIOSPeriod('MONTH', 1) → { unit: 'month', value: 1 }
|
|
127
|
+
*/
|
|
128
|
+
export function normalizeIOSPeriod(
|
|
129
|
+
iosUnit: string | undefined,
|
|
130
|
+
iosValue: number | undefined,
|
|
131
|
+
): { unit: 'day' | 'week' | 'month' | 'year'; value: number } {
|
|
132
|
+
const rawUnit = convertIOSPeriodUnit(iosUnit);
|
|
133
|
+
const rawValue = iosValue || 1;
|
|
134
|
+
|
|
135
|
+
// DAY cinsinden gelen değerleri daha büyük birime çevir
|
|
136
|
+
if (rawUnit === 'day') {
|
|
137
|
+
if (rawValue % 365 === 0) {
|
|
138
|
+
return { unit: 'year', value: rawValue / 365 };
|
|
139
|
+
}
|
|
140
|
+
if (rawValue === 366) {
|
|
141
|
+
return { unit: 'year', value: 1 };
|
|
142
|
+
}
|
|
143
|
+
if (rawValue % 30 === 0) {
|
|
144
|
+
return { unit: 'month', value: rawValue / 30 };
|
|
145
|
+
}
|
|
146
|
+
if (rawValue === 31) {
|
|
147
|
+
return { unit: 'month', value: 1 };
|
|
148
|
+
}
|
|
149
|
+
if (rawValue % 7 === 0) {
|
|
150
|
+
return { unit: 'week', value: rawValue / 7 };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// MONTH cinsinden gelen değerleri yıla çevir
|
|
155
|
+
if (rawUnit === 'month' && rawValue % 12 === 0) {
|
|
156
|
+
return { unit: 'year', value: rawValue / 12 };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { unit: rawUnit, value: rawValue };
|
|
160
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { persist } from 'zustand/middleware';
|
|
3
|
+
import { Device } from '../types/Device';
|
|
4
|
+
|
|
5
|
+
interface CustomDeviceState {
|
|
6
|
+
customDevices: Device[];
|
|
7
|
+
addCustomDevice: (device: Device) => void;
|
|
8
|
+
removeCustomDevice: (name: string) => void;
|
|
9
|
+
updateCustomDevice: (name: string, updatedDevice: Device) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useCustomDeviceStore = create<CustomDeviceState>()(
|
|
13
|
+
persist(
|
|
14
|
+
(set) => ({
|
|
15
|
+
customDevices: [],
|
|
16
|
+
addCustomDevice: (device) =>
|
|
17
|
+
set((state) => ({
|
|
18
|
+
customDevices: [
|
|
19
|
+
device,
|
|
20
|
+
...state.customDevices.filter((d) => d.name !== device.name),
|
|
21
|
+
],
|
|
22
|
+
})),
|
|
23
|
+
removeCustomDevice: (name) =>
|
|
24
|
+
set((state) => ({
|
|
25
|
+
customDevices: state.customDevices.filter((d) => d.name !== name),
|
|
26
|
+
})),
|
|
27
|
+
updateCustomDevice: (name, updatedDevice) =>
|
|
28
|
+
set((state) => ({
|
|
29
|
+
customDevices: state.customDevices.map((d) =>
|
|
30
|
+
d.name === name ? updatedDevice : d,
|
|
31
|
+
),
|
|
32
|
+
})),
|
|
33
|
+
}),
|
|
34
|
+
{
|
|
35
|
+
name: 'custom-devices-storage',
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
);
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
.editor-device-
|
|
97
|
+
.editor-device-button__platform {
|
|
98
98
|
position: absolute;
|
|
99
99
|
bottom: 4px;
|
|
100
100
|
right: 4px;
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
overflow: hidden;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
.editor-device-
|
|
109
|
+
.editor-device-button__platform img {
|
|
110
110
|
position: absolute;
|
|
111
111
|
top: 50%;
|
|
112
112
|
left: 50%;
|
|
@@ -117,6 +117,16 @@
|
|
|
117
117
|
height: auto;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
.editor-device-button__edit {
|
|
121
|
+
transition:
|
|
122
|
+
opacity 0.2s ease,
|
|
123
|
+
transform 0.2s ease;
|
|
124
|
+
&:hover {
|
|
125
|
+
opacity: 1 !important;
|
|
126
|
+
transform: scale(1.1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
.editor-header__actions {
|
|
121
131
|
margin-left: auto; /* push actions to the far right */
|
|
122
132
|
display: flex;
|
package/src/styles/index.scss
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
@use './components/mockos-router';
|
|
17
17
|
@use './components/bottom-bar';
|
|
18
18
|
@use './components/onboard';
|
|
19
|
-
@use './components/global-provider';
|
|
20
19
|
|
|
21
20
|
@use './components/webview';
|
|
22
21
|
@use './components/checkbox';
|
|
@@ -33,5 +32,6 @@
|
|
|
33
32
|
@use './modals/benefit-presets-modal';
|
|
34
33
|
@use './modals/inspect-modal';
|
|
35
34
|
@use './modals/prompt-manager-modal';
|
|
35
|
+
@use './modals/create-device';
|
|
36
36
|
|
|
37
37
|
@use './utilities/carousel';
|