@wireai/activation 0.1.0
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/AGENTS.md +73 -0
- package/CHANGELOG.md +572 -0
- package/INTEGRATION_PROMPT.md +55 -0
- package/LICENSE +21 -0
- package/README.md +923 -0
- package/dist/CenteredModal-C3qQBHsA.d.mts +38 -0
- package/dist/CenteredModal-Cdgns6--.d.ts +38 -0
- package/dist/coachmarks/index.d.mts +352 -0
- package/dist/coachmarks/index.d.ts +352 -0
- package/dist/coachmarks/index.js +944 -0
- package/dist/coachmarks/index.js.map +1 -0
- package/dist/coachmarks/index.mjs +918 -0
- package/dist/coachmarks/index.mjs.map +1 -0
- package/dist/decision-CFvGY6nP.d.ts +228 -0
- package/dist/decision-Cl8OFYzu.d.mts +228 -0
- package/dist/index.d.mts +1390 -0
- package/dist/index.d.ts +1390 -0
- package/dist/index.js +3260 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3186 -0
- package/dist/index.mjs.map +1 -0
- package/dist/questionnaire/index.d.mts +249 -0
- package/dist/questionnaire/index.d.ts +249 -0
- package/dist/questionnaire/index.js +922 -0
- package/dist/questionnaire/index.js.map +1 -0
- package/dist/questionnaire/index.mjs +906 -0
- package/dist/questionnaire/index.mjs.map +1 -0
- package/dist/reviews/index.d.mts +213 -0
- package/dist/reviews/index.d.ts +213 -0
- package/dist/reviews/index.js +1001 -0
- package/dist/reviews/index.js.map +1 -0
- package/dist/reviews/index.mjs +971 -0
- package/dist/reviews/index.mjs.map +1 -0
- package/dist/showcase/index.d.mts +119 -0
- package/dist/showcase/index.d.ts +119 -0
- package/dist/showcase/index.js +660 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/index.mjs +651 -0
- package/dist/showcase/index.mjs.map +1 -0
- package/dist/types-BKfpdZzX.d.mts +84 -0
- package/dist/types-BKfpdZzX.d.ts +84 -0
- package/dist/types-CMuOexw0.d.mts +116 -0
- package/dist/types-CMuOexw0.d.ts +116 -0
- package/dist/types-GL_hQ0TN.d.mts +46 -0
- package/dist/types-GL_hQ0TN.d.ts +46 -0
- package/llms.txt +36 -0
- package/metro/index.d.ts +32 -0
- package/metro/index.js +83 -0
- package/package.json +171 -0
- package/src/DemoOnboarding.tsx +133 -0
- package/src/OnboardingFlow.tsx +504 -0
- package/src/WireOnboarding.tsx +248 -0
- package/src/analytics/analyticsEvent.ts +69 -0
- package/src/analytics/reportClientEvent.ts +119 -0
- package/src/analytics/sendPreview.ts +73 -0
- package/src/attribution/attribution.ts +46 -0
- package/src/cards/ChipSelectCard.tsx +265 -0
- package/src/cards/InterstitialCard.tsx +299 -0
- package/src/cards/NumberStepperCard.tsx +121 -0
- package/src/cards/SelectionCard.tsx +270 -0
- package/src/cards/StatusCard.tsx +170 -0
- package/src/cards/TextInputCard.tsx +147 -0
- package/src/cards/index.ts +23 -0
- package/src/coachmarks/CoachmarkOverlayHost.tsx +44 -0
- package/src/coachmarks/CoachmarkProvider.tsx +108 -0
- package/src/coachmarks/GestureHint.tsx +331 -0
- package/src/coachmarks/SpotlightOverlay.tsx +254 -0
- package/src/coachmarks/coachmarkAnchorRegistry.ts +70 -0
- package/src/coachmarks/coachmarkOverlayStore.ts +63 -0
- package/src/coachmarks/index.ts +62 -0
- package/src/coachmarks/runtime.ts +103 -0
- package/src/coachmarks/selectTourSteps.ts +45 -0
- package/src/coachmarks/types.ts +58 -0
- package/src/coachmarks/useCoachmarkAnchor.ts +49 -0
- package/src/coachmarks/useCoachmarkTour.ts +200 -0
- package/src/components/AnimatedSparkle.tsx +101 -0
- package/src/components/Button.tsx +129 -0
- package/src/components/CardHandoff.tsx +212 -0
- package/src/components/CardLayout.tsx +103 -0
- package/src/components/CenteredModal.tsx +184 -0
- package/src/components/CompletionView.tsx +186 -0
- package/src/components/DoneBlock.tsx +37 -0
- package/src/components/ErrorBlock.tsx +42 -0
- package/src/components/Illustration.tsx +36 -0
- package/src/components/LoadingBlock.tsx +182 -0
- package/src/components/LoadingScreen.tsx +35 -0
- package/src/components/OnboardingScaffold.tsx +105 -0
- package/src/components/StepProgress.tsx +85 -0
- package/src/components/loaderChrome.ts +28 -0
- package/src/config/onboardingFlag.ts +39 -0
- package/src/config/wireConfigFromEnv.ts +52 -0
- package/src/device/deviceContext.ts +158 -0
- package/src/features/WireFeaturesProvider.tsx +81 -0
- package/src/features/cache.ts +92 -0
- package/src/features/defaults.ts +67 -0
- package/src/features/fetchWireFeatures.ts +86 -0
- package/src/features/index.ts +39 -0
- package/src/features/types.ts +61 -0
- package/src/features/useWireFeatures.ts +57 -0
- package/src/identity/userIdentity.ts +100 -0
- package/src/illustrations/defaultIllustrations.tsx +99 -0
- package/src/index.ts +159 -0
- package/src/motion/centeredModalMotion.ts +45 -0
- package/src/motion/motionSpec.ts +133 -0
- package/src/motion/useReducedMotion.ts +43 -0
- package/src/questionnaire/QuestionnaireGate.tsx +235 -0
- package/src/questionnaire/decision.ts +63 -0
- package/src/questionnaire/index.ts +54 -0
- package/src/questionnaire/runtime.ts +20 -0
- package/src/questionnaire/transport.ts +83 -0
- package/src/questionnaire/types.ts +182 -0
- package/src/questionnaire/useQuestionnaireGate.ts +159 -0
- package/src/reviews/ReviewGate.tsx +316 -0
- package/src/reviews/ReviewModal.tsx +13 -0
- package/src/reviews/decision.ts +120 -0
- package/src/reviews/equality.ts +54 -0
- package/src/reviews/expo-store-review.d.ts +16 -0
- package/src/reviews/feedbackForm.ts +41 -0
- package/src/reviews/index.ts +78 -0
- package/src/reviews/presentation.ts +29 -0
- package/src/reviews/runtime.ts +45 -0
- package/src/reviews/storeReview.ts +62 -0
- package/src/reviews/transport.ts +89 -0
- package/src/reviews/types.ts +172 -0
- package/src/reviews/useReviewGate.ts +137 -0
- package/src/session/persistedSession.ts +143 -0
- package/src/session-analytics/index.ts +18 -0
- package/src/session-analytics/reportSessionStart.ts +144 -0
- package/src/session-analytics/useSessionStart.ts +114 -0
- package/src/showcase/FeatureShowcase.tsx +233 -0
- package/src/showcase/index.ts +19 -0
- package/src/showcase/selectShowcaseSlides.ts +45 -0
- package/src/showcase/showcaseColors.ts +91 -0
- package/src/showcase/types.ts +74 -0
- package/src/theme/ThemeContext.tsx +38 -0
- package/src/theme/defaultTheme.ts +70 -0
- package/src/theme/themeFromBrand.ts +93 -0
- package/src/theme/types.ts +88 -0
- package/src/theme/typography.ts +46 -0
- package/src/types.ts +215 -0
- package/src/utils/deriveAnswers.ts +43 -0
- package/src/utils/readProgress.ts +33 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3186 @@
|
|
|
1
|
+
import React14, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useContext, useMemo } from 'react';
|
|
2
|
+
import { useWireAIThread, useWireAIAction, ComponentRenderer, WireAIProvider } from 'wireai-rn';
|
|
3
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { StyleSheet, Easing, Animated, BackHandler, Modal, Pressable, Text, View, TouchableOpacity, KeyboardAvoidingView, ScrollView, useWindowDimensions, TextInput, Image, AccessibilityInfo, Platform, Dimensions, I18nManager, AppState } from 'react-native';
|
|
5
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/theme/defaultTheme.ts
|
|
15
|
+
var defaultOnboardingTheme = {
|
|
16
|
+
colors: {
|
|
17
|
+
background: "#FFFFFF",
|
|
18
|
+
surface: "#F7F7F8",
|
|
19
|
+
text: "#101114",
|
|
20
|
+
textMuted: "#6B7280",
|
|
21
|
+
primary: "#111111",
|
|
22
|
+
onPrimary: "#FFFFFF",
|
|
23
|
+
primarySoft: "rgba(17,17,17,0.06)",
|
|
24
|
+
border: "#E4E4E7",
|
|
25
|
+
borderStrong: "#C7C7CC",
|
|
26
|
+
disabled: "#EBEBEB",
|
|
27
|
+
success: "#16A34A",
|
|
28
|
+
warning: "#D97706",
|
|
29
|
+
error: "#DC2626",
|
|
30
|
+
info: "#2563EB"
|
|
31
|
+
},
|
|
32
|
+
fonts: {
|
|
33
|
+
regular: void 0,
|
|
34
|
+
medium: void 0,
|
|
35
|
+
bold: void 0,
|
|
36
|
+
baseSize: 16,
|
|
37
|
+
scale: 1
|
|
38
|
+
},
|
|
39
|
+
radius: {
|
|
40
|
+
sm: 8,
|
|
41
|
+
md: 12,
|
|
42
|
+
lg: 16,
|
|
43
|
+
full: 9999
|
|
44
|
+
},
|
|
45
|
+
spacing: {
|
|
46
|
+
xs: 4,
|
|
47
|
+
sm: 8,
|
|
48
|
+
md: 16,
|
|
49
|
+
lg: 24,
|
|
50
|
+
xl: 32
|
|
51
|
+
},
|
|
52
|
+
// Circled (pill) CTA by default; an app can pass `button: { radius: 12 }` for
|
|
53
|
+
// a rounded rectangle, or override height/paddingX to match its design system.
|
|
54
|
+
button: {
|
|
55
|
+
radius: 9999,
|
|
56
|
+
height: 52,
|
|
57
|
+
paddingX: 24
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var mergeTheme = (override) => {
|
|
61
|
+
if (!override) return defaultOnboardingTheme;
|
|
62
|
+
return {
|
|
63
|
+
colors: { ...defaultOnboardingTheme.colors, ...override.colors },
|
|
64
|
+
fonts: { ...defaultOnboardingTheme.fonts, ...override.fonts },
|
|
65
|
+
radius: { ...defaultOnboardingTheme.radius, ...override.radius },
|
|
66
|
+
spacing: { ...defaultOnboardingTheme.spacing, ...override.spacing },
|
|
67
|
+
button: { ...defaultOnboardingTheme.button, ...override.button }
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
var OnboardingThemeContext = createContext(null);
|
|
71
|
+
var OnboardingThemeProvider = ({
|
|
72
|
+
theme,
|
|
73
|
+
children
|
|
74
|
+
}) => {
|
|
75
|
+
const resolved = useMemo(() => mergeTheme(theme), [theme]);
|
|
76
|
+
return /* @__PURE__ */ jsx(OnboardingThemeContext.Provider, { value: resolved, children });
|
|
77
|
+
};
|
|
78
|
+
var useOnboardingTheme = () => {
|
|
79
|
+
const ctx = useContext(OnboardingThemeContext);
|
|
80
|
+
return ctx != null ? ctx : mergeTheme();
|
|
81
|
+
};
|
|
82
|
+
var IllustrationContext = createContext({});
|
|
83
|
+
var IllustrationProvider = ({
|
|
84
|
+
registry,
|
|
85
|
+
children
|
|
86
|
+
}) => /* @__PURE__ */ jsx(IllustrationContext.Provider, { value: registry != null ? registry : {}, children });
|
|
87
|
+
var useIllustration = (name) => {
|
|
88
|
+
const registry = useContext(IllustrationContext);
|
|
89
|
+
if (!name) return void 0;
|
|
90
|
+
return registry[name];
|
|
91
|
+
};
|
|
92
|
+
var Glyph = ({ children }) => {
|
|
93
|
+
const t = useOnboardingTheme();
|
|
94
|
+
return /* @__PURE__ */ jsx(
|
|
95
|
+
View,
|
|
96
|
+
{
|
|
97
|
+
style: [
|
|
98
|
+
styles.disc,
|
|
99
|
+
{ backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
|
|
100
|
+
],
|
|
101
|
+
children: /* @__PURE__ */ jsx(Text, { style: [styles.glyphText, { color: t.colors.primary }], allowFontScaling: false, children })
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
var MomentumGlyph = () => /* @__PURE__ */ jsx(Glyph, { children: "\u2197" });
|
|
106
|
+
var BeforeAfterGlyph = () => {
|
|
107
|
+
const t = useOnboardingTheme();
|
|
108
|
+
return /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
View,
|
|
111
|
+
{
|
|
112
|
+
style: [
|
|
113
|
+
styles.chip,
|
|
114
|
+
{ borderColor: t.colors.border, borderRadius: t.radius.md }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ jsx(Text, { style: [styles.arrow, { color: t.colors.primary }], allowFontScaling: false, children: "\u2192" }),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
View,
|
|
121
|
+
{
|
|
122
|
+
style: [
|
|
123
|
+
styles.chip,
|
|
124
|
+
{ backgroundColor: t.colors.primarySoft, borderColor: t.colors.primary, borderRadius: t.radius.md }
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
] });
|
|
129
|
+
};
|
|
130
|
+
var defaultIllustrations = {
|
|
131
|
+
momentum: /* @__PURE__ */ jsx(MomentumGlyph, {}),
|
|
132
|
+
"before-after": /* @__PURE__ */ jsx(BeforeAfterGlyph, {})
|
|
133
|
+
};
|
|
134
|
+
var styles = StyleSheet.create({
|
|
135
|
+
disc: {
|
|
136
|
+
width: 96,
|
|
137
|
+
height: 96,
|
|
138
|
+
alignItems: "center",
|
|
139
|
+
justifyContent: "center"
|
|
140
|
+
},
|
|
141
|
+
glyphText: {
|
|
142
|
+
fontSize: 44,
|
|
143
|
+
fontWeight: "700",
|
|
144
|
+
lineHeight: 52
|
|
145
|
+
},
|
|
146
|
+
row: {
|
|
147
|
+
flexDirection: "row",
|
|
148
|
+
alignItems: "center",
|
|
149
|
+
justifyContent: "center",
|
|
150
|
+
gap: 12
|
|
151
|
+
},
|
|
152
|
+
chip: {
|
|
153
|
+
width: 56,
|
|
154
|
+
height: 56,
|
|
155
|
+
borderWidth: 2
|
|
156
|
+
},
|
|
157
|
+
arrow: {
|
|
158
|
+
fontSize: 28,
|
|
159
|
+
fontWeight: "700"
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// src/theme/typography.ts
|
|
164
|
+
var round = (n) => Math.round(n);
|
|
165
|
+
var bodyStyle = (f) => ({
|
|
166
|
+
fontFamily: f.regular,
|
|
167
|
+
fontSize: round(f.baseSize * f.scale),
|
|
168
|
+
lineHeight: round(f.baseSize * f.scale * 1.5)
|
|
169
|
+
});
|
|
170
|
+
var captionStyle = (f) => ({
|
|
171
|
+
fontFamily: f.regular,
|
|
172
|
+
fontSize: round(f.baseSize * 0.8125 * f.scale),
|
|
173
|
+
lineHeight: round(f.baseSize * 0.8125 * f.scale * 1.5)
|
|
174
|
+
});
|
|
175
|
+
var labelStyle = (f) => {
|
|
176
|
+
var _a;
|
|
177
|
+
return {
|
|
178
|
+
fontFamily: (_a = f.medium) != null ? _a : f.bold,
|
|
179
|
+
fontSize: round(f.baseSize * f.scale),
|
|
180
|
+
lineHeight: round(f.baseSize * f.scale * 1.4),
|
|
181
|
+
fontWeight: f.medium ? void 0 : "600"
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
var headingStyle = (f) => ({
|
|
185
|
+
fontFamily: f.bold,
|
|
186
|
+
fontSize: round(f.baseSize * 1.25 * f.scale),
|
|
187
|
+
lineHeight: round(f.baseSize * 1.25 * f.scale * 1.3),
|
|
188
|
+
fontWeight: f.bold ? void 0 : "700"
|
|
189
|
+
});
|
|
190
|
+
var buttonLabelStyle = (f) => {
|
|
191
|
+
var _a;
|
|
192
|
+
return {
|
|
193
|
+
fontFamily: (_a = f.bold) != null ? _a : f.medium,
|
|
194
|
+
fontSize: round(f.baseSize * f.scale),
|
|
195
|
+
fontWeight: f.bold ? void 0 : "600"
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/motion/motionSpec.ts
|
|
200
|
+
var motionSpec_exports = {};
|
|
201
|
+
__export(motionSpec_exports, {
|
|
202
|
+
BUTTON_PRESS_SCALE: () => BUTTON_PRESS_SCALE,
|
|
203
|
+
BUTTON_PRESS_SPRING: () => BUTTON_PRESS_SPRING,
|
|
204
|
+
CARD_ENTER_MS: () => CARD_ENTER_MS,
|
|
205
|
+
CARD_ENTER_SCALE_FROM: () => CARD_ENTER_SCALE_FROM,
|
|
206
|
+
CARD_EXIT_LEAD_MS: () => CARD_EXIT_LEAD_MS,
|
|
207
|
+
CARD_EXIT_MS: () => CARD_EXIT_MS,
|
|
208
|
+
CARD_SLIDE_PX: () => CARD_SLIDE_PX,
|
|
209
|
+
CARD_SPRING: () => CARD_SPRING,
|
|
210
|
+
CHECK_SCALE_FROM: () => CHECK_SCALE_FROM,
|
|
211
|
+
CHECK_SPRING: () => CHECK_SPRING,
|
|
212
|
+
CHIP_FILL_MS: () => CHIP_FILL_MS,
|
|
213
|
+
CHIP_PRESS_SCALE: () => CHIP_PRESS_SCALE,
|
|
214
|
+
DURATION_BASE_MS: () => DURATION_BASE_MS,
|
|
215
|
+
DURATION_FAST_MS: () => DURATION_FAST_MS,
|
|
216
|
+
DURATION_SLOW_MS: () => DURATION_SLOW_MS,
|
|
217
|
+
INTERSTITIAL_CHECK_DELAY_MS: () => INTERSTITIAL_CHECK_DELAY_MS,
|
|
218
|
+
INTERSTITIAL_CHECK_SCALE_FROM: () => INTERSTITIAL_CHECK_SCALE_FROM,
|
|
219
|
+
INTERSTITIAL_HEAD_MS: () => INTERSTITIAL_HEAD_MS,
|
|
220
|
+
INTERSTITIAL_HEAD_STAGGER_MS: () => INTERSTITIAL_HEAD_STAGGER_MS,
|
|
221
|
+
INTERSTITIAL_ITEM_BASE_DELAY_MS: () => INTERSTITIAL_ITEM_BASE_DELAY_MS,
|
|
222
|
+
INTERSTITIAL_ITEM_MS: () => INTERSTITIAL_ITEM_MS,
|
|
223
|
+
INTERSTITIAL_ITEM_STAGGER_MS: () => INTERSTITIAL_ITEM_STAGGER_MS,
|
|
224
|
+
LOADER_HANDOFF_FADE_MS: () => LOADER_HANDOFF_FADE_MS,
|
|
225
|
+
LOADER_ORBIT_MS: () => LOADER_ORBIT_MS,
|
|
226
|
+
LOADER_PULSE_MIN_OPACITY: () => LOADER_PULSE_MIN_OPACITY,
|
|
227
|
+
LOADER_PULSE_MS: () => LOADER_PULSE_MS,
|
|
228
|
+
PRESS_SPRING: () => PRESS_SPRING,
|
|
229
|
+
PROGRESS_MS: () => PROGRESS_MS,
|
|
230
|
+
REDUCED_FADE_MS: () => REDUCED_FADE_MS,
|
|
231
|
+
REDUCED_PRESS_OPACITY: () => REDUCED_PRESS_OPACITY,
|
|
232
|
+
ROW_PRESS_SCALE: () => ROW_PRESS_SCALE,
|
|
233
|
+
SELECT_FILL_MS: () => SELECT_FILL_MS,
|
|
234
|
+
SKELETON_DELAY_MS: () => SKELETON_DELAY_MS,
|
|
235
|
+
SKELETON_MIN_OPACITY: () => SKELETON_MIN_OPACITY,
|
|
236
|
+
SKELETON_PULSE_MS: () => SKELETON_PULSE_MS,
|
|
237
|
+
STATUS_BURST_MS: () => STATUS_BURST_MS,
|
|
238
|
+
STATUS_BURST_OPACITY_FROM: () => STATUS_BURST_OPACITY_FROM,
|
|
239
|
+
STATUS_BURST_SCALE_FROM: () => STATUS_BURST_SCALE_FROM,
|
|
240
|
+
STATUS_BURST_SCALE_TO: () => STATUS_BURST_SCALE_TO,
|
|
241
|
+
STATUS_COPY_BASE_DELAY_MS: () => STATUS_COPY_BASE_DELAY_MS,
|
|
242
|
+
STATUS_COPY_MS: () => STATUS_COPY_MS,
|
|
243
|
+
STATUS_COPY_STAGGER_MS: () => STATUS_COPY_STAGGER_MS,
|
|
244
|
+
STATUS_POP_SCALE_FROM: () => STATUS_POP_SCALE_FROM,
|
|
245
|
+
STATUS_POP_SPRING: () => STATUS_POP_SPRING,
|
|
246
|
+
WIRE_BEZIER: () => WIRE_BEZIER,
|
|
247
|
+
entranceSpec: () => entranceSpec,
|
|
248
|
+
getMotionDurationScale: () => getMotionDurationScale,
|
|
249
|
+
interstitialCheckDelay: () => interstitialCheckDelay,
|
|
250
|
+
interstitialItemDelay: () => interstitialItemDelay,
|
|
251
|
+
loopEnabled: () => loopEnabled,
|
|
252
|
+
scaledMs: () => scaledMs,
|
|
253
|
+
setMotionDurationScale: () => setMotionDurationScale,
|
|
254
|
+
stateChangeDuration: () => stateChangeDuration,
|
|
255
|
+
statusCopyDelay: () => statusCopyDelay
|
|
256
|
+
});
|
|
257
|
+
var WIRE_BEZIER = [0.16, 1, 0.3, 1];
|
|
258
|
+
var DURATION_FAST_MS = 150;
|
|
259
|
+
var DURATION_BASE_MS = 300;
|
|
260
|
+
var DURATION_SLOW_MS = 500;
|
|
261
|
+
var REDUCED_FADE_MS = 120;
|
|
262
|
+
var ROW_PRESS_SCALE = 0.975;
|
|
263
|
+
var CHIP_PRESS_SCALE = 0.94;
|
|
264
|
+
var BUTTON_PRESS_SCALE = 0.975;
|
|
265
|
+
var REDUCED_PRESS_OPACITY = 0.85;
|
|
266
|
+
var PRESS_SPRING = { friction: 8, tension: 220 };
|
|
267
|
+
var BUTTON_PRESS_SPRING = { friction: 8, tension: 240 };
|
|
268
|
+
var SELECT_FILL_MS = 180;
|
|
269
|
+
var CHIP_FILL_MS = 160;
|
|
270
|
+
var CHECK_SPRING = { friction: 6, tension: 180 };
|
|
271
|
+
var CHECK_SCALE_FROM = 0.6;
|
|
272
|
+
var CARD_ENTER_MS = 220;
|
|
273
|
+
var CARD_EXIT_MS = 180;
|
|
274
|
+
var CARD_EXIT_LEAD_MS = 80;
|
|
275
|
+
var CARD_SLIDE_PX = 26;
|
|
276
|
+
var CARD_ENTER_SCALE_FROM = 0.96;
|
|
277
|
+
var CARD_SPRING = { damping: 14, stiffness: 180, mass: 1 };
|
|
278
|
+
var LOADER_HANDOFF_FADE_MS = 120;
|
|
279
|
+
var INTERSTITIAL_HEAD_MS = 500;
|
|
280
|
+
var INTERSTITIAL_HEAD_STAGGER_MS = 100;
|
|
281
|
+
var INTERSTITIAL_ITEM_MS = 300;
|
|
282
|
+
var INTERSTITIAL_ITEM_STAGGER_MS = 90;
|
|
283
|
+
var INTERSTITIAL_ITEM_BASE_DELAY_MS = 200;
|
|
284
|
+
var INTERSTITIAL_CHECK_DELAY_MS = 200;
|
|
285
|
+
var INTERSTITIAL_CHECK_SCALE_FROM = 0.4;
|
|
286
|
+
var interstitialItemDelay = (index) => INTERSTITIAL_ITEM_BASE_DELAY_MS + index * INTERSTITIAL_ITEM_STAGGER_MS;
|
|
287
|
+
var interstitialCheckDelay = (index) => interstitialItemDelay(index) + INTERSTITIAL_CHECK_DELAY_MS;
|
|
288
|
+
var STATUS_POP_SPRING = { friction: 6, tension: 200 };
|
|
289
|
+
var STATUS_POP_SCALE_FROM = 0.4;
|
|
290
|
+
var STATUS_BURST_MS = 500;
|
|
291
|
+
var STATUS_BURST_SCALE_FROM = 0.4;
|
|
292
|
+
var STATUS_BURST_SCALE_TO = 1.5;
|
|
293
|
+
var STATUS_BURST_OPACITY_FROM = 0.5;
|
|
294
|
+
var STATUS_COPY_MS = 300;
|
|
295
|
+
var STATUS_COPY_STAGGER_MS = 80;
|
|
296
|
+
var STATUS_COPY_BASE_DELAY_MS = 200;
|
|
297
|
+
var statusCopyDelay = (index) => STATUS_COPY_BASE_DELAY_MS + index * STATUS_COPY_STAGGER_MS;
|
|
298
|
+
var LOADER_PULSE_MS = 1600;
|
|
299
|
+
var LOADER_PULSE_MIN_OPACITY = 0.35;
|
|
300
|
+
var LOADER_ORBIT_MS = 2400;
|
|
301
|
+
var SKELETON_DELAY_MS = 300;
|
|
302
|
+
var SKELETON_PULSE_MS = 1500;
|
|
303
|
+
var SKELETON_MIN_OPACITY = 0.45;
|
|
304
|
+
var PROGRESS_MS = 400;
|
|
305
|
+
var durationScale = 1;
|
|
306
|
+
var setMotionDurationScale = (scale) => {
|
|
307
|
+
durationScale = Number.isFinite(scale) && scale > 0 ? Math.min(10, Math.max(0.1, scale)) : 1;
|
|
308
|
+
};
|
|
309
|
+
var getMotionDurationScale = () => durationScale;
|
|
310
|
+
var scaledMs = (ms) => Math.round(ms * durationScale);
|
|
311
|
+
var entranceSpec = (reduced, durationMs) => reduced ? { duration: REDUCED_FADE_MS, useTransform: false } : { duration: durationMs, useTransform: true };
|
|
312
|
+
var stateChangeDuration = (reduced, durationMs) => reduced ? 0 : durationMs;
|
|
313
|
+
var loopEnabled = (reduced) => !reduced;
|
|
314
|
+
var lastKnown = false;
|
|
315
|
+
var useReducedMotion = () => {
|
|
316
|
+
const [reduced, setReduced] = useState(lastKnown);
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
let mounted = true;
|
|
319
|
+
AccessibilityInfo.isReduceMotionEnabled().then((value) => {
|
|
320
|
+
lastKnown = value;
|
|
321
|
+
if (mounted) setReduced(value);
|
|
322
|
+
}).catch(() => {
|
|
323
|
+
});
|
|
324
|
+
const sub = AccessibilityInfo.addEventListener("reduceMotionChanged", (value) => {
|
|
325
|
+
lastKnown = value;
|
|
326
|
+
setReduced(value);
|
|
327
|
+
});
|
|
328
|
+
return () => {
|
|
329
|
+
mounted = false;
|
|
330
|
+
sub == null ? void 0 : sub.remove();
|
|
331
|
+
};
|
|
332
|
+
}, []);
|
|
333
|
+
return reduced;
|
|
334
|
+
};
|
|
335
|
+
var ROTATION_MS = 1800;
|
|
336
|
+
var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
|
|
337
|
+
const t = useOnboardingTheme();
|
|
338
|
+
const reduced = useReducedMotion();
|
|
339
|
+
const progress = useRef(new Animated.Value(0)).current;
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (!loopEnabled(reduced)) {
|
|
342
|
+
progress.setValue(0);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const loop = variant === "pulse" ? Animated.loop(
|
|
346
|
+
Animated.sequence([
|
|
347
|
+
Animated.timing(progress, {
|
|
348
|
+
toValue: 1,
|
|
349
|
+
duration: scaledMs(LOADER_PULSE_MS / 2),
|
|
350
|
+
easing: Easing.inOut(Easing.ease),
|
|
351
|
+
useNativeDriver: true
|
|
352
|
+
}),
|
|
353
|
+
Animated.timing(progress, {
|
|
354
|
+
toValue: 0,
|
|
355
|
+
duration: scaledMs(LOADER_PULSE_MS / 2),
|
|
356
|
+
easing: Easing.inOut(Easing.ease),
|
|
357
|
+
useNativeDriver: true
|
|
358
|
+
})
|
|
359
|
+
])
|
|
360
|
+
) : Animated.loop(
|
|
361
|
+
Animated.timing(progress, {
|
|
362
|
+
toValue: 1,
|
|
363
|
+
duration: scaledMs(ROTATION_MS),
|
|
364
|
+
easing: Easing.linear,
|
|
365
|
+
useNativeDriver: true
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
loop.start();
|
|
369
|
+
return () => loop.stop();
|
|
370
|
+
}, [progress, variant, reduced]);
|
|
371
|
+
const rotate = progress.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] });
|
|
372
|
+
const pulse = progress.interpolate({
|
|
373
|
+
inputRange: [0, 1],
|
|
374
|
+
outputRange: [1, LOADER_PULSE_MIN_OPACITY]
|
|
375
|
+
});
|
|
376
|
+
const animatedStyle = reduced ? void 0 : variant === "pulse" ? { opacity: pulse } : { transform: [{ rotate }] };
|
|
377
|
+
return /* @__PURE__ */ jsx(Animated.View, { style: animatedStyle, children: /* @__PURE__ */ jsx(
|
|
378
|
+
Text,
|
|
379
|
+
{
|
|
380
|
+
accessibilityElementsHidden: true,
|
|
381
|
+
importantForAccessibility: "no",
|
|
382
|
+
style: [styles2.glyph, { fontSize: size, lineHeight: size * 1.1, color: color != null ? color : t.colors.primary }],
|
|
383
|
+
children: "\u2726"
|
|
384
|
+
}
|
|
385
|
+
) });
|
|
386
|
+
};
|
|
387
|
+
var AnimatedSparkle = React14.memo(_AnimatedSparkle);
|
|
388
|
+
var styles2 = StyleSheet.create({
|
|
389
|
+
glyph: { textAlign: "center" }
|
|
390
|
+
});
|
|
391
|
+
var LoaderChromeContext = createContext({ offsetY: 0 });
|
|
392
|
+
var LoaderChromeProvider = LoaderChromeContext.Provider;
|
|
393
|
+
var useLoaderChrome = () => useContext(LoaderChromeContext);
|
|
394
|
+
var STAGE_SIZE = 96;
|
|
395
|
+
var ORBIT_DOT = 9;
|
|
396
|
+
var OrbitDot = ({ color, reduced }) => {
|
|
397
|
+
const spin = useRef(new Animated.Value(0)).current;
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (!loopEnabled(reduced)) return;
|
|
400
|
+
const loop = Animated.loop(
|
|
401
|
+
Animated.timing(spin, {
|
|
402
|
+
toValue: 1,
|
|
403
|
+
duration: scaledMs(LOADER_ORBIT_MS),
|
|
404
|
+
easing: Easing.linear,
|
|
405
|
+
useNativeDriver: true
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
loop.start();
|
|
409
|
+
return () => loop.stop();
|
|
410
|
+
}, [spin, reduced]);
|
|
411
|
+
if (reduced) return null;
|
|
412
|
+
const rotate = spin.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] });
|
|
413
|
+
return /* @__PURE__ */ jsx(Animated.View, { pointerEvents: "none", style: [StyleSheet.absoluteFill, { transform: [{ rotate }] }], children: /* @__PURE__ */ jsx(View, { style: [styles3.orbitDot, { backgroundColor: color }] }) });
|
|
414
|
+
};
|
|
415
|
+
var SkeletonLine = ({
|
|
416
|
+
width,
|
|
417
|
+
delay,
|
|
418
|
+
color,
|
|
419
|
+
reduced
|
|
420
|
+
}) => {
|
|
421
|
+
const pulse = useRef(new Animated.Value(1)).current;
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (!loopEnabled(reduced)) return;
|
|
424
|
+
const loop = Animated.loop(
|
|
425
|
+
Animated.sequence([
|
|
426
|
+
Animated.delay(delay),
|
|
427
|
+
Animated.timing(pulse, {
|
|
428
|
+
toValue: SKELETON_MIN_OPACITY,
|
|
429
|
+
duration: scaledMs(SKELETON_PULSE_MS / 2),
|
|
430
|
+
easing: Easing.inOut(Easing.ease),
|
|
431
|
+
useNativeDriver: true
|
|
432
|
+
}),
|
|
433
|
+
Animated.timing(pulse, {
|
|
434
|
+
toValue: 1,
|
|
435
|
+
duration: scaledMs(SKELETON_PULSE_MS / 2),
|
|
436
|
+
easing: Easing.inOut(Easing.ease),
|
|
437
|
+
useNativeDriver: true
|
|
438
|
+
})
|
|
439
|
+
])
|
|
440
|
+
);
|
|
441
|
+
loop.start();
|
|
442
|
+
return () => loop.stop();
|
|
443
|
+
}, [pulse, delay, reduced]);
|
|
444
|
+
return /* @__PURE__ */ jsx(
|
|
445
|
+
Animated.View,
|
|
446
|
+
{
|
|
447
|
+
style: [styles3.skeletonLine, { width, backgroundColor: color, opacity: reduced ? 1 : pulse }]
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
var _LoadingBlock = ({
|
|
452
|
+
title = "Thinking\u2026",
|
|
453
|
+
hint = "Personalizing your experience"
|
|
454
|
+
}) => {
|
|
455
|
+
const t = useOnboardingTheme();
|
|
456
|
+
const reduced = useReducedMotion();
|
|
457
|
+
const { offsetY } = useLoaderChrome();
|
|
458
|
+
const [showSkeleton, setShowSkeleton] = useState(false);
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
const timer = setTimeout(() => setShowSkeleton(true), SKELETON_DELAY_MS);
|
|
461
|
+
return () => clearTimeout(timer);
|
|
462
|
+
}, []);
|
|
463
|
+
return /* @__PURE__ */ jsxs(
|
|
464
|
+
View,
|
|
465
|
+
{
|
|
466
|
+
style: [
|
|
467
|
+
styles3.center,
|
|
468
|
+
{ gap: t.spacing.md, padding: t.spacing.lg },
|
|
469
|
+
offsetY ? { transform: [{ translateY: offsetY }] } : null
|
|
470
|
+
],
|
|
471
|
+
children: [
|
|
472
|
+
/* @__PURE__ */ jsxs(View, { style: styles3.stage, children: [
|
|
473
|
+
/* @__PURE__ */ jsx(View, { style: [styles3.ring, { borderColor: t.colors.border, borderRadius: t.radius.full }] }),
|
|
474
|
+
/* @__PURE__ */ jsx(OrbitDot, { color: t.colors.primary, reduced }),
|
|
475
|
+
/* @__PURE__ */ jsx(AnimatedSparkle, { size: 40, variant: "pulse" })
|
|
476
|
+
] }),
|
|
477
|
+
title ? /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) : null,
|
|
478
|
+
hint ? /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: hint }) : null,
|
|
479
|
+
showSkeleton ? /* @__PURE__ */ jsxs(View, { style: [styles3.skeleton, { gap: t.spacing.sm, marginTop: t.spacing.sm }], children: [
|
|
480
|
+
/* @__PURE__ */ jsx(SkeletonLine, { width: "80%", delay: 0, color: t.colors.surface, reduced }),
|
|
481
|
+
/* @__PURE__ */ jsx(SkeletonLine, { width: "60%", delay: 200, color: t.colors.surface, reduced })
|
|
482
|
+
] }) : null
|
|
483
|
+
]
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
};
|
|
487
|
+
var LoadingBlock = React14.memo(_LoadingBlock);
|
|
488
|
+
var styles3 = StyleSheet.create({
|
|
489
|
+
center: { flex: 1, alignItems: "center", justifyContent: "center" },
|
|
490
|
+
stage: {
|
|
491
|
+
width: STAGE_SIZE,
|
|
492
|
+
height: STAGE_SIZE,
|
|
493
|
+
alignItems: "center",
|
|
494
|
+
justifyContent: "center"
|
|
495
|
+
},
|
|
496
|
+
ring: {
|
|
497
|
+
position: "absolute",
|
|
498
|
+
top: 0,
|
|
499
|
+
left: 0,
|
|
500
|
+
right: 0,
|
|
501
|
+
bottom: 0,
|
|
502
|
+
borderWidth: 1.5,
|
|
503
|
+
borderStyle: "dashed"
|
|
504
|
+
},
|
|
505
|
+
orbitDot: {
|
|
506
|
+
position: "absolute",
|
|
507
|
+
top: -ORBIT_DOT / 2 + 1,
|
|
508
|
+
left: STAGE_SIZE / 2 - ORBIT_DOT / 2,
|
|
509
|
+
width: ORBIT_DOT,
|
|
510
|
+
height: ORBIT_DOT,
|
|
511
|
+
borderRadius: ORBIT_DOT / 2
|
|
512
|
+
},
|
|
513
|
+
skeleton: { width: "100%", alignItems: "center" },
|
|
514
|
+
skeletonLine: { height: 12, borderRadius: 6 }
|
|
515
|
+
});
|
|
516
|
+
var _LoadingScreen = ({ title, hint }) => {
|
|
517
|
+
const t = useOnboardingTheme();
|
|
518
|
+
return /* @__PURE__ */ jsx(
|
|
519
|
+
SafeAreaView,
|
|
520
|
+
{
|
|
521
|
+
edges: ["top", "bottom"],
|
|
522
|
+
style: [styles4.flex, { backgroundColor: t.colors.background }],
|
|
523
|
+
children: /* @__PURE__ */ jsx(LoadingBlock, { title, hint })
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
};
|
|
527
|
+
var LoadingScreen = React14.memo(_LoadingScreen);
|
|
528
|
+
var styles4 = StyleSheet.create({
|
|
529
|
+
flex: { flex: 1 }
|
|
530
|
+
});
|
|
531
|
+
var easeWire = Easing.bezier(...WIRE_BEZIER);
|
|
532
|
+
var DECAY = 0.62;
|
|
533
|
+
var MIN_FILL = 0.08;
|
|
534
|
+
var APPROX_CAP = 0.95;
|
|
535
|
+
var targetFor = (step, complete, approxScreens) => {
|
|
536
|
+
if (complete) return 1;
|
|
537
|
+
const s = Number.isFinite(step) && step > 0 ? Math.floor(step) : 1;
|
|
538
|
+
if (approxScreens && approxScreens > 0) {
|
|
539
|
+
return Math.max(MIN_FILL, Math.min(APPROX_CAP, s / approxScreens));
|
|
540
|
+
}
|
|
541
|
+
return Math.max(MIN_FILL, 1 - DECAY ** s);
|
|
542
|
+
};
|
|
543
|
+
var _StepProgress = ({ step, complete, approxScreens }) => {
|
|
544
|
+
const t = useOnboardingTheme();
|
|
545
|
+
const reduced = useReducedMotion();
|
|
546
|
+
const fill = useRef(new Animated.Value(targetFor(step, complete, approxScreens))).current;
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
Animated.timing(fill, {
|
|
549
|
+
toValue: targetFor(step, complete, approxScreens),
|
|
550
|
+
duration: scaledMs(stateChangeDuration(reduced, PROGRESS_MS)),
|
|
551
|
+
easing: easeWire,
|
|
552
|
+
useNativeDriver: false
|
|
553
|
+
// animating width %
|
|
554
|
+
}).start();
|
|
555
|
+
}, [fill, step, complete, approxScreens, reduced]);
|
|
556
|
+
const width = fill.interpolate({ inputRange: [0, 1], outputRange: ["0%", "100%"] });
|
|
557
|
+
return /* @__PURE__ */ jsx(View, { style: [styles5.track, { backgroundColor: t.colors.border, borderRadius: t.radius.full }], children: /* @__PURE__ */ jsx(
|
|
558
|
+
Animated.View,
|
|
559
|
+
{
|
|
560
|
+
style: [
|
|
561
|
+
styles5.fill,
|
|
562
|
+
{ width, backgroundColor: t.colors.primary, borderRadius: t.radius.full }
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
) });
|
|
566
|
+
};
|
|
567
|
+
var StepProgress = React14.memo(_StepProgress);
|
|
568
|
+
var styles5 = StyleSheet.create({
|
|
569
|
+
track: { height: 6, width: "100%", overflow: "hidden" },
|
|
570
|
+
fill: { height: 6 }
|
|
571
|
+
});
|
|
572
|
+
var PROGRESS_TRACK = 6;
|
|
573
|
+
var _OnboardingScaffold = ({
|
|
574
|
+
step,
|
|
575
|
+
complete,
|
|
576
|
+
approxScreens,
|
|
577
|
+
onBack,
|
|
578
|
+
onSkip,
|
|
579
|
+
skipLabel = "Skip",
|
|
580
|
+
backLabel = "Back",
|
|
581
|
+
children
|
|
582
|
+
}) => {
|
|
583
|
+
const t = useOnboardingTheme();
|
|
584
|
+
const hit = { top: 12, bottom: 12, left: 12, right: 12 };
|
|
585
|
+
const topChrome = t.spacing.md + PROGRESS_TRACK + t.spacing.md;
|
|
586
|
+
const loaderOffsetY = -Math.round(topChrome / 2);
|
|
587
|
+
return /* @__PURE__ */ jsxs(
|
|
588
|
+
SafeAreaView,
|
|
589
|
+
{
|
|
590
|
+
edges: ["top", "bottom"],
|
|
591
|
+
style: [styles6.flex, { backgroundColor: t.colors.background }],
|
|
592
|
+
children: [
|
|
593
|
+
/* @__PURE__ */ jsxs(View, { style: [styles6.header, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: [
|
|
594
|
+
/* @__PURE__ */ jsx(View, { style: styles6.progressWrap, children: /* @__PURE__ */ jsx(StepProgress, { step, complete, approxScreens }) }),
|
|
595
|
+
onSkip ? /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onSkip, hitSlop: hit, style: { marginLeft: t.spacing.md }, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: skipLabel }) }) : null
|
|
596
|
+
] }),
|
|
597
|
+
/* @__PURE__ */ jsx(View, { style: [styles6.body, { paddingHorizontal: t.spacing.md, paddingTop: t.spacing.md }], children: /* @__PURE__ */ jsx(LoaderChromeProvider, { value: { offsetY: loaderOffsetY }, children }) }),
|
|
598
|
+
onBack ? /* @__PURE__ */ jsx(View, { style: [styles6.footer, { paddingHorizontal: t.spacing.md, paddingBottom: t.spacing.sm }], children: /* @__PURE__ */ jsx(TouchableOpacity, { onPress: onBack, hitSlop: hit, children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted }], children: backLabel }) }) }) : null
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
};
|
|
603
|
+
var OnboardingScaffold = React14.memo(_OnboardingScaffold);
|
|
604
|
+
var styles6 = StyleSheet.create({
|
|
605
|
+
flex: { flex: 1 },
|
|
606
|
+
header: { flexDirection: "row", alignItems: "center" },
|
|
607
|
+
progressWrap: { flex: 1 },
|
|
608
|
+
body: { flex: 1 },
|
|
609
|
+
footer: { flexDirection: "row", justifyContent: "flex-start" }
|
|
610
|
+
});
|
|
611
|
+
var _Button = ({
|
|
612
|
+
title,
|
|
613
|
+
onPress,
|
|
614
|
+
variant = "primary",
|
|
615
|
+
disabled = false,
|
|
616
|
+
fullWidth = false
|
|
617
|
+
}) => {
|
|
618
|
+
const t = useOnboardingTheme();
|
|
619
|
+
const reduced = useReducedMotion();
|
|
620
|
+
const isPrimary = variant === "primary";
|
|
621
|
+
const pressT = useRef(new Animated.Value(0)).current;
|
|
622
|
+
const pressTo = useCallback(
|
|
623
|
+
(to) => {
|
|
624
|
+
if (reduced) {
|
|
625
|
+
Animated.timing(pressT, {
|
|
626
|
+
toValue: to,
|
|
627
|
+
duration: scaledMs(REDUCED_FADE_MS),
|
|
628
|
+
useNativeDriver: true
|
|
629
|
+
}).start();
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
Animated.spring(pressT, {
|
|
633
|
+
toValue: to,
|
|
634
|
+
friction: BUTTON_PRESS_SPRING.friction,
|
|
635
|
+
tension: BUTTON_PRESS_SPRING.tension,
|
|
636
|
+
useNativeDriver: true
|
|
637
|
+
}).start();
|
|
638
|
+
},
|
|
639
|
+
[pressT, reduced]
|
|
640
|
+
);
|
|
641
|
+
const handlePressIn = useCallback(() => pressTo(1), [pressTo]);
|
|
642
|
+
const handlePressOut = useCallback(() => pressTo(0), [pressTo]);
|
|
643
|
+
const scale = pressT.interpolate({
|
|
644
|
+
inputRange: [0, 1],
|
|
645
|
+
outputRange: [1, reduced ? 1 : BUTTON_PRESS_SCALE]
|
|
646
|
+
});
|
|
647
|
+
const opacity = pressT.interpolate({
|
|
648
|
+
inputRange: [0, 1],
|
|
649
|
+
outputRange: [1, reduced ? REDUCED_PRESS_OPACITY : 1]
|
|
650
|
+
});
|
|
651
|
+
return /* @__PURE__ */ jsx(
|
|
652
|
+
Pressable,
|
|
653
|
+
{
|
|
654
|
+
onPress: disabled ? void 0 : onPress,
|
|
655
|
+
onPressIn: disabled ? void 0 : handlePressIn,
|
|
656
|
+
onPressOut: disabled ? void 0 : handlePressOut,
|
|
657
|
+
disabled,
|
|
658
|
+
accessibilityRole: "button",
|
|
659
|
+
accessibilityState: { disabled },
|
|
660
|
+
style: fullWidth ? styles7.fill : void 0,
|
|
661
|
+
children: /* @__PURE__ */ jsx(
|
|
662
|
+
Animated.View,
|
|
663
|
+
{
|
|
664
|
+
style: [
|
|
665
|
+
styles7.base,
|
|
666
|
+
fullWidth && styles7.fill,
|
|
667
|
+
{
|
|
668
|
+
borderRadius: t.button.radius,
|
|
669
|
+
minHeight: t.button.height,
|
|
670
|
+
paddingHorizontal: t.button.paddingX,
|
|
671
|
+
opacity,
|
|
672
|
+
transform: [{ scale }]
|
|
673
|
+
},
|
|
674
|
+
isPrimary ? { backgroundColor: disabled ? t.colors.disabled : t.colors.primary } : { borderWidth: 1.5, borderColor: disabled ? t.colors.border : t.colors.primary }
|
|
675
|
+
],
|
|
676
|
+
children: /* @__PURE__ */ jsx(
|
|
677
|
+
Text,
|
|
678
|
+
{
|
|
679
|
+
style: [
|
|
680
|
+
buttonLabelStyle(t.fonts),
|
|
681
|
+
{
|
|
682
|
+
color: disabled ? t.colors.textMuted : isPrimary ? t.colors.onPrimary : t.colors.primary
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
children: title
|
|
686
|
+
}
|
|
687
|
+
)
|
|
688
|
+
}
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
};
|
|
693
|
+
var Button = React14.memo(_Button);
|
|
694
|
+
var styles7 = StyleSheet.create({
|
|
695
|
+
base: { alignItems: "center", justifyContent: "center" },
|
|
696
|
+
fill: { width: "100%" }
|
|
697
|
+
});
|
|
698
|
+
var _ErrorBlock = ({
|
|
699
|
+
title = "Something went wrong",
|
|
700
|
+
message = "We couldn't load this step. You can try again.",
|
|
701
|
+
retryLabel = "Try again",
|
|
702
|
+
onRetry
|
|
703
|
+
}) => {
|
|
704
|
+
const t = useOnboardingTheme();
|
|
705
|
+
return /* @__PURE__ */ jsxs(View, { style: [styles8.center, { gap: t.spacing.md, padding: t.spacing.lg }], children: [
|
|
706
|
+
/* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }),
|
|
707
|
+
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }),
|
|
708
|
+
onRetry ? /* @__PURE__ */ jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
|
|
709
|
+
] });
|
|
710
|
+
};
|
|
711
|
+
var ErrorBlock = React14.memo(_ErrorBlock);
|
|
712
|
+
var styles8 = StyleSheet.create({
|
|
713
|
+
center: { flex: 1, alignItems: "center", justifyContent: "center" }
|
|
714
|
+
});
|
|
715
|
+
var _CardLayout = ({
|
|
716
|
+
title,
|
|
717
|
+
subtitle,
|
|
718
|
+
children,
|
|
719
|
+
footer,
|
|
720
|
+
align = "top",
|
|
721
|
+
titleNode
|
|
722
|
+
}) => {
|
|
723
|
+
const t = useOnboardingTheme();
|
|
724
|
+
const hasHeader = !!title || !!subtitle || !!titleNode;
|
|
725
|
+
return /* @__PURE__ */ jsxs(KeyboardAvoidingView, { style: styles9.fill, behavior: "padding", children: [
|
|
726
|
+
hasHeader ? /* @__PURE__ */ jsxs(View, { style: { marginBottom: t.spacing.lg }, children: [
|
|
727
|
+
titleNode != null ? titleNode : title ? /* @__PURE__ */ jsx(Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: title }) : null,
|
|
728
|
+
subtitle ? /* @__PURE__ */ jsx(
|
|
729
|
+
Text,
|
|
730
|
+
{
|
|
731
|
+
style: [
|
|
732
|
+
bodyStyle(t.fonts),
|
|
733
|
+
{ color: t.colors.textMuted, marginTop: t.spacing.xs }
|
|
734
|
+
],
|
|
735
|
+
children: subtitle
|
|
736
|
+
}
|
|
737
|
+
) : null
|
|
738
|
+
] }) : null,
|
|
739
|
+
/* @__PURE__ */ jsx(
|
|
740
|
+
ScrollView,
|
|
741
|
+
{
|
|
742
|
+
style: styles9.fill,
|
|
743
|
+
contentContainerStyle: [
|
|
744
|
+
styles9.scrollContent,
|
|
745
|
+
align === "center" && styles9.center
|
|
746
|
+
],
|
|
747
|
+
keyboardShouldPersistTaps: "handled",
|
|
748
|
+
keyboardDismissMode: "on-drag",
|
|
749
|
+
showsVerticalScrollIndicator: false,
|
|
750
|
+
children
|
|
751
|
+
}
|
|
752
|
+
),
|
|
753
|
+
footer ? /* @__PURE__ */ jsx(View, { style: { paddingTop: t.spacing.lg }, children: footer }) : null
|
|
754
|
+
] });
|
|
755
|
+
};
|
|
756
|
+
var CardLayout = React14.memo(_CardLayout);
|
|
757
|
+
var styles9 = StyleSheet.create({
|
|
758
|
+
fill: { flex: 1 },
|
|
759
|
+
// flexGrow lets a short content block grow to fill (pinning the footer) while a
|
|
760
|
+
// tall one scrolls past the visible area.
|
|
761
|
+
scrollContent: { flexGrow: 1 },
|
|
762
|
+
center: { justifyContent: "center" }
|
|
763
|
+
});
|
|
764
|
+
var easeWire2 = Easing.bezier(...WIRE_BEZIER);
|
|
765
|
+
var BADGE_SIZE = 64;
|
|
766
|
+
var BURST_SIZE = 104;
|
|
767
|
+
var _CompletionView = ({
|
|
768
|
+
title,
|
|
769
|
+
message,
|
|
770
|
+
ctaLabel = "Let's go",
|
|
771
|
+
onContinue
|
|
772
|
+
}) => {
|
|
773
|
+
const t = useOnboardingTheme();
|
|
774
|
+
const reduced = useReducedMotion();
|
|
775
|
+
const [submitted, setSubmitted] = useState(false);
|
|
776
|
+
const pop = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
777
|
+
const burst = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
778
|
+
const titleT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
779
|
+
const messageT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
780
|
+
useEffect(() => {
|
|
781
|
+
if (reduced) {
|
|
782
|
+
pop.setValue(1);
|
|
783
|
+
burst.setValue(1);
|
|
784
|
+
titleT.setValue(1);
|
|
785
|
+
messageT.setValue(1);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const rise = (value, delay) => Animated.timing(value, {
|
|
789
|
+
toValue: 1,
|
|
790
|
+
duration: scaledMs(STATUS_COPY_MS),
|
|
791
|
+
delay,
|
|
792
|
+
easing: easeWire2,
|
|
793
|
+
useNativeDriver: true
|
|
794
|
+
});
|
|
795
|
+
const anims = [
|
|
796
|
+
Animated.spring(pop, {
|
|
797
|
+
toValue: 1,
|
|
798
|
+
friction: STATUS_POP_SPRING.friction,
|
|
799
|
+
tension: STATUS_POP_SPRING.tension,
|
|
800
|
+
useNativeDriver: true
|
|
801
|
+
}),
|
|
802
|
+
// One-shot celebratory burst: fires once, never loops.
|
|
803
|
+
Animated.timing(burst, {
|
|
804
|
+
toValue: 1,
|
|
805
|
+
duration: scaledMs(STATUS_BURST_MS),
|
|
806
|
+
easing: Easing.out(Easing.ease),
|
|
807
|
+
useNativeDriver: true
|
|
808
|
+
}),
|
|
809
|
+
rise(titleT, scaledMs(statusCopyDelay(0))),
|
|
810
|
+
rise(messageT, scaledMs(statusCopyDelay(1)))
|
|
811
|
+
];
|
|
812
|
+
anims.forEach((a) => a.start());
|
|
813
|
+
return () => anims.forEach((a) => a.stop());
|
|
814
|
+
}, [reduced, pop, burst, titleT, messageT]);
|
|
815
|
+
const handlePress = useCallback(() => {
|
|
816
|
+
if (submitted) return;
|
|
817
|
+
setSubmitted(true);
|
|
818
|
+
onContinue();
|
|
819
|
+
}, [submitted, onContinue]);
|
|
820
|
+
const scale = pop.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
|
|
821
|
+
const burstScale = burst.interpolate({
|
|
822
|
+
inputRange: [0, 1],
|
|
823
|
+
outputRange: [STATUS_BURST_SCALE_FROM, STATUS_BURST_SCALE_TO]
|
|
824
|
+
});
|
|
825
|
+
const burstOpacity = burst.interpolate({
|
|
826
|
+
inputRange: [0, 1],
|
|
827
|
+
outputRange: [STATUS_BURST_OPACITY_FROM, 0]
|
|
828
|
+
});
|
|
829
|
+
const riseStyle = (value) => ({
|
|
830
|
+
opacity: value,
|
|
831
|
+
transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [10, 0] }) }]
|
|
832
|
+
});
|
|
833
|
+
return /* @__PURE__ */ jsx(
|
|
834
|
+
CardLayout,
|
|
835
|
+
{
|
|
836
|
+
align: "center",
|
|
837
|
+
footer: /* @__PURE__ */ jsx(Button, { title: ctaLabel, onPress: handlePress, variant: "primary", disabled: submitted }),
|
|
838
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles10.center, { gap: t.spacing.md }], children: [
|
|
839
|
+
/* @__PURE__ */ jsxs(View, { style: styles10.badgeStage, children: [
|
|
840
|
+
/* @__PURE__ */ jsx(
|
|
841
|
+
Animated.View,
|
|
842
|
+
{
|
|
843
|
+
pointerEvents: "none",
|
|
844
|
+
style: [
|
|
845
|
+
styles10.burst,
|
|
846
|
+
{
|
|
847
|
+
backgroundColor: t.colors.primarySoft,
|
|
848
|
+
borderRadius: t.radius.full,
|
|
849
|
+
opacity: burstOpacity,
|
|
850
|
+
transform: [{ scale: burstScale }]
|
|
851
|
+
}
|
|
852
|
+
]
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ jsx(
|
|
856
|
+
Animated.View,
|
|
857
|
+
{
|
|
858
|
+
style: [
|
|
859
|
+
styles10.badge,
|
|
860
|
+
{
|
|
861
|
+
backgroundColor: t.colors.primarySoft,
|
|
862
|
+
borderRadius: t.radius.full,
|
|
863
|
+
transform: [{ scale }],
|
|
864
|
+
opacity: pop
|
|
865
|
+
}
|
|
866
|
+
],
|
|
867
|
+
children: /* @__PURE__ */ jsx(Text, { style: [styles10.check, { color: t.colors.primary }], children: "\u2713" })
|
|
868
|
+
}
|
|
869
|
+
)
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ jsx(Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
|
|
872
|
+
message ? /* @__PURE__ */ jsx(Animated.View, { style: riseStyle(messageT), children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message }) }) : null
|
|
873
|
+
] })
|
|
874
|
+
}
|
|
875
|
+
);
|
|
876
|
+
};
|
|
877
|
+
var CompletionView = React14.memo(_CompletionView);
|
|
878
|
+
var styles10 = StyleSheet.create({
|
|
879
|
+
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
880
|
+
badgeStage: {
|
|
881
|
+
width: BURST_SIZE,
|
|
882
|
+
height: BURST_SIZE,
|
|
883
|
+
alignItems: "center",
|
|
884
|
+
justifyContent: "center"
|
|
885
|
+
},
|
|
886
|
+
burst: { position: "absolute", width: BURST_SIZE, height: BURST_SIZE },
|
|
887
|
+
badge: { width: BADGE_SIZE, height: BADGE_SIZE, alignItems: "center", justifyContent: "center" },
|
|
888
|
+
check: { fontSize: 34, fontWeight: "800" }
|
|
889
|
+
});
|
|
890
|
+
var easeWire3 = Easing.bezier(...WIRE_BEZIER);
|
|
891
|
+
var _CardHandoff = ({
|
|
892
|
+
transitionKey,
|
|
893
|
+
variant = "slide",
|
|
894
|
+
children
|
|
895
|
+
}) => {
|
|
896
|
+
const reduced = useReducedMotion();
|
|
897
|
+
const [leaving, setLeaving] = useState(null);
|
|
898
|
+
const enterT = useRef(new Animated.Value(0)).current;
|
|
899
|
+
const exitT = useRef(new Animated.Value(0)).current;
|
|
900
|
+
const prev = useRef({ key: transitionKey, node: children, variant });
|
|
901
|
+
const [enterVariant, setEnterVariant] = useState(variant);
|
|
902
|
+
const running = useRef({});
|
|
903
|
+
const mounted = useRef(false);
|
|
904
|
+
useEffect(() => {
|
|
905
|
+
var _a, _b;
|
|
906
|
+
const last = prev.current;
|
|
907
|
+
prev.current = { key: transitionKey, node: children, variant };
|
|
908
|
+
if (!mounted.current) {
|
|
909
|
+
mounted.current = true;
|
|
910
|
+
setEnterVariant(variant);
|
|
911
|
+
running.current.enter = startEnter(enterT, variant, reduced, 0);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (last.key === transitionKey) return;
|
|
915
|
+
(_a = running.current.enter) == null ? void 0 : _a.stop();
|
|
916
|
+
(_b = running.current.exit) == null ? void 0 : _b.stop();
|
|
917
|
+
setLeaving(last);
|
|
918
|
+
setEnterVariant(variant);
|
|
919
|
+
enterT.setValue(0);
|
|
920
|
+
exitT.setValue(0);
|
|
921
|
+
const exitDuration = reduced ? REDUCED_FADE_MS : last.variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_EXIT_MS;
|
|
922
|
+
const exit = Animated.timing(exitT, {
|
|
923
|
+
toValue: 1,
|
|
924
|
+
duration: scaledMs(exitDuration),
|
|
925
|
+
easing: easeWire3,
|
|
926
|
+
useNativeDriver: true
|
|
927
|
+
});
|
|
928
|
+
running.current.exit = exit;
|
|
929
|
+
exit.start();
|
|
930
|
+
const enter = startEnter(
|
|
931
|
+
enterT,
|
|
932
|
+
variant,
|
|
933
|
+
reduced,
|
|
934
|
+
reduced ? 0 : CARD_EXIT_LEAD_MS,
|
|
935
|
+
() => setLeaving(null)
|
|
936
|
+
);
|
|
937
|
+
running.current.enter = enter;
|
|
938
|
+
});
|
|
939
|
+
useEffect(
|
|
940
|
+
() => () => {
|
|
941
|
+
var _a, _b;
|
|
942
|
+
(_a = running.current.enter) == null ? void 0 : _a.stop();
|
|
943
|
+
(_b = running.current.exit) == null ? void 0 : _b.stop();
|
|
944
|
+
},
|
|
945
|
+
[]
|
|
946
|
+
);
|
|
947
|
+
const enterStyle = {
|
|
948
|
+
opacity: enterT,
|
|
949
|
+
transform: transformFor(enterVariant, enterT, reduced, "enter")
|
|
950
|
+
};
|
|
951
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
952
|
+
leaving ? /* @__PURE__ */ jsx(
|
|
953
|
+
Animated.View,
|
|
954
|
+
{
|
|
955
|
+
pointerEvents: "none",
|
|
956
|
+
style: [
|
|
957
|
+
StyleSheet.absoluteFill,
|
|
958
|
+
{
|
|
959
|
+
opacity: exitT.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
|
|
960
|
+
transform: transformFor(leaving.variant, exitT, reduced, "exit")
|
|
961
|
+
}
|
|
962
|
+
],
|
|
963
|
+
children: leaving.node
|
|
964
|
+
}
|
|
965
|
+
) : null,
|
|
966
|
+
/* @__PURE__ */ jsx(Animated.View, { style: [styles11.fill, enterStyle], children })
|
|
967
|
+
] });
|
|
968
|
+
};
|
|
969
|
+
var startEnter = (value, variant, reduced, delay, onDone) => {
|
|
970
|
+
value.setValue(0);
|
|
971
|
+
const anim = !reduced && variant === "spring" ? Animated.spring(value, {
|
|
972
|
+
toValue: 1,
|
|
973
|
+
delay: scaledMs(delay),
|
|
974
|
+
damping: CARD_SPRING.damping,
|
|
975
|
+
stiffness: CARD_SPRING.stiffness,
|
|
976
|
+
mass: CARD_SPRING.mass,
|
|
977
|
+
useNativeDriver: true
|
|
978
|
+
}) : Animated.timing(value, {
|
|
979
|
+
toValue: 1,
|
|
980
|
+
delay: scaledMs(delay),
|
|
981
|
+
duration: scaledMs(
|
|
982
|
+
reduced ? REDUCED_FADE_MS : variant === "fade" ? LOADER_HANDOFF_FADE_MS : CARD_ENTER_MS
|
|
983
|
+
),
|
|
984
|
+
easing: easeWire3,
|
|
985
|
+
useNativeDriver: true
|
|
986
|
+
});
|
|
987
|
+
anim.start(({ finished }) => {
|
|
988
|
+
if (finished) onDone == null ? void 0 : onDone();
|
|
989
|
+
});
|
|
990
|
+
return anim;
|
|
991
|
+
};
|
|
992
|
+
var transformFor = (variant, t, reduced, direction) => {
|
|
993
|
+
if (reduced || variant === "fade") return [];
|
|
994
|
+
if (variant === "spring" && direction === "enter") {
|
|
995
|
+
return [{ scale: t.interpolate({ inputRange: [0, 1], outputRange: [CARD_ENTER_SCALE_FROM, 1] }) }];
|
|
996
|
+
}
|
|
997
|
+
return [
|
|
998
|
+
{
|
|
999
|
+
translateX: t.interpolate({
|
|
1000
|
+
inputRange: [0, 1],
|
|
1001
|
+
outputRange: direction === "enter" ? [CARD_SLIDE_PX, 0] : [0, -CARD_SLIDE_PX]
|
|
1002
|
+
})
|
|
1003
|
+
}
|
|
1004
|
+
];
|
|
1005
|
+
};
|
|
1006
|
+
var CardHandoff = React14.memo(_CardHandoff);
|
|
1007
|
+
var styles11 = StyleSheet.create({
|
|
1008
|
+
fill: { flex: 1 }
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
// src/utils/deriveAnswers.ts
|
|
1012
|
+
var stripPrefix = (s) => s.replace(/^(I selected:|My answer is:|I tapped:)\s*/i, "").trim();
|
|
1013
|
+
var tryParse = (s) => {
|
|
1014
|
+
try {
|
|
1015
|
+
return JSON.parse(s);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return s;
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
var deriveAnswers = (messages) => {
|
|
1021
|
+
var _a, _b, _c;
|
|
1022
|
+
const out = {};
|
|
1023
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1024
|
+
const m = messages[i];
|
|
1025
|
+
const r = m == null ? void 0 : m.response;
|
|
1026
|
+
if (!m || m.role !== "assistant" || !r || r.action !== "render") continue;
|
|
1027
|
+
const props = (_a = r.props) != null ? _a : {};
|
|
1028
|
+
const progress = props.progress;
|
|
1029
|
+
const questionText = (_c = (_b = props.title) != null ? _b : props.label) != null ? _c : props.question;
|
|
1030
|
+
const key = (progress == null ? void 0 : progress.key) || questionText;
|
|
1031
|
+
const reply = messages[i + 1];
|
|
1032
|
+
if (key && (reply == null ? void 0 : reply.role) === "user") {
|
|
1033
|
+
out[key] = tryParse(stripPrefix(reply.content));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return out;
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
// src/utils/readProgress.ts
|
|
1040
|
+
var readProgress = (response) => {
|
|
1041
|
+
var _a;
|
|
1042
|
+
if (!response || response.action !== "render") return {};
|
|
1043
|
+
const props = (_a = response.props) != null ? _a : {};
|
|
1044
|
+
const raw = props.progress;
|
|
1045
|
+
if (!raw || typeof raw !== "object") return {};
|
|
1046
|
+
const p = raw;
|
|
1047
|
+
return {
|
|
1048
|
+
step: typeof p.step === "number" ? p.step : void 0,
|
|
1049
|
+
total: typeof p.total === "number" ? p.total : void 0,
|
|
1050
|
+
key: typeof p.key === "string" ? p.key : void 0,
|
|
1051
|
+
skippable: typeof p.skippable === "boolean" ? p.skippable : void 0
|
|
1052
|
+
};
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// src/analytics/reportClientEvent.ts
|
|
1056
|
+
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1057
|
+
var reportClientEvents = (target, events) => {
|
|
1058
|
+
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return;
|
|
1059
|
+
try {
|
|
1060
|
+
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
|
|
1061
|
+
const headers = { "Content-Type": "application/json" };
|
|
1062
|
+
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
1063
|
+
void fetch(url, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
headers,
|
|
1066
|
+
body: JSON.stringify({ events })
|
|
1067
|
+
}).catch(() => {
|
|
1068
|
+
});
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
var reportClientEvent = (target, event) => reportClientEvents(target, [event]);
|
|
1073
|
+
|
|
1074
|
+
// src/analytics/sendPreview.ts
|
|
1075
|
+
var sendPreview = (target, { sessionId, userMessage }) => {
|
|
1076
|
+
if (!(target == null ? void 0 : target.serverUrl)) return;
|
|
1077
|
+
try {
|
|
1078
|
+
const url = `${target.serverUrl.replace(/\/$/, "")}/a2a`;
|
|
1079
|
+
const headers = { "Content-Type": "application/json" };
|
|
1080
|
+
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
1081
|
+
void fetch(url, {
|
|
1082
|
+
method: "POST",
|
|
1083
|
+
headers,
|
|
1084
|
+
body: JSON.stringify({
|
|
1085
|
+
jsonrpc: "2.0",
|
|
1086
|
+
id: Math.floor(Math.random() * 1e9),
|
|
1087
|
+
method: "message/preview",
|
|
1088
|
+
params: {
|
|
1089
|
+
message: { role: "user", parts: [{ text: userMessage }] },
|
|
1090
|
+
contextId: sessionId,
|
|
1091
|
+
metadata: { sessionId }
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
}).catch(() => {
|
|
1095
|
+
});
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
var SKIP_ONE_SENTINEL = "__wireai_skip__";
|
|
1100
|
+
var ANSWER_CALLBACKS = ["onSubmit", "onSelect", "onConfirm", "onPress"];
|
|
1101
|
+
var DEFAULT_COPY = {
|
|
1102
|
+
startingTitle: "Getting started\u2026",
|
|
1103
|
+
startingHint: "Setting up your onboarding",
|
|
1104
|
+
restoringTitle: "Picking up where you left off\u2026",
|
|
1105
|
+
restoringHint: "Restoring your session",
|
|
1106
|
+
thinkingTitle: "Personalizing\u2026",
|
|
1107
|
+
thinkingHint: "Tailoring your next question",
|
|
1108
|
+
checkingTitle: "Checking\u2026",
|
|
1109
|
+
checkingHint: "One moment",
|
|
1110
|
+
completeTitle: "You're all set",
|
|
1111
|
+
completeCta: "Let's go"
|
|
1112
|
+
};
|
|
1113
|
+
var OnboardingFlow = ({
|
|
1114
|
+
validators,
|
|
1115
|
+
onComplete,
|
|
1116
|
+
onError,
|
|
1117
|
+
onEvent,
|
|
1118
|
+
copy,
|
|
1119
|
+
approxScreens,
|
|
1120
|
+
startMessage,
|
|
1121
|
+
startTimeoutMs,
|
|
1122
|
+
fallbackFlow,
|
|
1123
|
+
maxRetries,
|
|
1124
|
+
reportTarget,
|
|
1125
|
+
sessionId,
|
|
1126
|
+
resumed = false,
|
|
1127
|
+
clientContext
|
|
1128
|
+
}) => {
|
|
1129
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1130
|
+
const { messages, error, isLoading, sendMessage, reset } = useWireAIThread();
|
|
1131
|
+
const makeActions = useWireAIAction(sendMessage);
|
|
1132
|
+
const c = useMemo(() => ({ ...DEFAULT_COPY, ...copy }), [copy]);
|
|
1133
|
+
const onEventRef = useRef(onEvent);
|
|
1134
|
+
onEventRef.current = onEvent;
|
|
1135
|
+
const onErrorRef = useRef(onError);
|
|
1136
|
+
onErrorRef.current = onError;
|
|
1137
|
+
const reportTargetRef = useRef(reportTarget);
|
|
1138
|
+
reportTargetRef.current = reportTarget;
|
|
1139
|
+
const sessionIdRef = useRef(sessionId);
|
|
1140
|
+
sessionIdRef.current = sessionId;
|
|
1141
|
+
const clientContextRef = useRef(clientContext);
|
|
1142
|
+
clientContextRef.current = clientContext;
|
|
1143
|
+
const lastScreenIndexRef = useRef(-1);
|
|
1144
|
+
const previewTimer = useRef(null);
|
|
1145
|
+
const started = useRef(false);
|
|
1146
|
+
const finished = useRef(false);
|
|
1147
|
+
const errored = useRef(false);
|
|
1148
|
+
const attempts = useRef(0);
|
|
1149
|
+
const degradedRef = useRef(false);
|
|
1150
|
+
const [degraded, setDegraded] = useState(false);
|
|
1151
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
1152
|
+
const [validationError, setValidationError] = useState();
|
|
1153
|
+
const [validating, setValidating] = useState(false);
|
|
1154
|
+
const lastCard = useMemo(
|
|
1155
|
+
() => [...messages].reverse().find((m) => {
|
|
1156
|
+
var _a2;
|
|
1157
|
+
return m.role === "assistant" && ((_a2 = m.response) == null ? void 0 : _a2.action) === "render";
|
|
1158
|
+
}),
|
|
1159
|
+
[messages]
|
|
1160
|
+
);
|
|
1161
|
+
const renderedCount = useMemo(
|
|
1162
|
+
() => messages.filter((m) => {
|
|
1163
|
+
var _a2;
|
|
1164
|
+
return m.role === "assistant" && ((_a2 = m.response) == null ? void 0 : _a2.action) === "render";
|
|
1165
|
+
}).length,
|
|
1166
|
+
[messages]
|
|
1167
|
+
);
|
|
1168
|
+
const progress = useMemo(() => readProgress(lastCard == null ? void 0 : lastCard.response), [lastCard]);
|
|
1169
|
+
useEffect(() => {
|
|
1170
|
+
var _a2;
|
|
1171
|
+
if (started.current) return;
|
|
1172
|
+
started.current = true;
|
|
1173
|
+
sendMessage(resumed ? SKIP_ONE_SENTINEL : startMessage);
|
|
1174
|
+
(_a2 = onEventRef.current) == null ? void 0 : _a2.call(
|
|
1175
|
+
onEventRef,
|
|
1176
|
+
resumed ? { type: "resumed", contextId: sessionId } : { type: "started", contextId: sessionId }
|
|
1177
|
+
);
|
|
1178
|
+
}, [sendMessage, startMessage, resumed, sessionId]);
|
|
1179
|
+
useEffect(() => {
|
|
1180
|
+
if (lastCard) return;
|
|
1181
|
+
const timer = setTimeout(() => setTimedOut(true), startTimeoutMs);
|
|
1182
|
+
return () => clearTimeout(timer);
|
|
1183
|
+
}, [lastCard, startTimeoutMs]);
|
|
1184
|
+
useEffect(() => {
|
|
1185
|
+
var _a2, _b2, _c2, _d2;
|
|
1186
|
+
setValidationError(void 0);
|
|
1187
|
+
if (!(lastCard == null ? void 0 : lastCard.id)) return;
|
|
1188
|
+
const step2 = (_a2 = progress.step) != null ? _a2 : renderedCount;
|
|
1189
|
+
lastScreenIndexRef.current = Math.max(0, step2 - 1);
|
|
1190
|
+
(_c2 = onEventRef.current) == null ? void 0 : _c2.call(onEventRef, {
|
|
1191
|
+
type: "turn",
|
|
1192
|
+
step: step2,
|
|
1193
|
+
component: (_b2 = lastCard.response) == null ? void 0 : _b2.component
|
|
1194
|
+
});
|
|
1195
|
+
if (((_d2 = lastCard.response) == null ? void 0 : _d2.component) === "InterstitialCard") {
|
|
1196
|
+
sendPreview(reportTargetRef.current, {
|
|
1197
|
+
sessionId: sessionIdRef.current,
|
|
1198
|
+
userMessage: `My answer is: ${JSON.stringify("continue")}`
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
}, [lastCard == null ? void 0 : lastCard.id]);
|
|
1202
|
+
useEffect(() => {
|
|
1203
|
+
return () => {
|
|
1204
|
+
var _a2, _b2, _c2;
|
|
1205
|
+
if (finished.current || degradedRef.current) return;
|
|
1206
|
+
reportClientEvent(reportTargetRef.current, {
|
|
1207
|
+
event_type: "dropped",
|
|
1208
|
+
session_id: sessionIdRef.current,
|
|
1209
|
+
screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
|
|
1210
|
+
device: (_a2 = clientContextRef.current) == null ? void 0 : _a2.device,
|
|
1211
|
+
user_context: (_b2 = clientContextRef.current) == null ? void 0 : _b2.userContext,
|
|
1212
|
+
user_id: (_c2 = clientContextRef.current) == null ? void 0 : _c2.userId
|
|
1213
|
+
});
|
|
1214
|
+
};
|
|
1215
|
+
}, []);
|
|
1216
|
+
const restartThread = useCallback(() => {
|
|
1217
|
+
setTimedOut(false);
|
|
1218
|
+
started.current = false;
|
|
1219
|
+
errored.current = false;
|
|
1220
|
+
reset();
|
|
1221
|
+
}, [reset]);
|
|
1222
|
+
useEffect(() => {
|
|
1223
|
+
var _a2, _b2, _c2, _d2, _e2, _f2, _g;
|
|
1224
|
+
if (errored.current) return;
|
|
1225
|
+
if (!error && !timedOut) return;
|
|
1226
|
+
errored.current = true;
|
|
1227
|
+
const reason = error ? "backend" : "timeout";
|
|
1228
|
+
(_a2 = onEventRef.current) == null ? void 0 : _a2.call(onEventRef, { type: "error", reason });
|
|
1229
|
+
attempts.current += 1;
|
|
1230
|
+
if (attempts.current <= maxRetries) {
|
|
1231
|
+
(_b2 = onEventRef.current) == null ? void 0 : _b2.call(onEventRef, { type: "retry", reason, attempt: attempts.current });
|
|
1232
|
+
restartThread();
|
|
1233
|
+
} else {
|
|
1234
|
+
(_c2 = onEventRef.current) == null ? void 0 : _c2.call(onEventRef, { type: "fallback", reason });
|
|
1235
|
+
reportClientEvent(reportTargetRef.current, {
|
|
1236
|
+
event_type: "client_fallback",
|
|
1237
|
+
session_id: sessionIdRef.current,
|
|
1238
|
+
screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
|
|
1239
|
+
meta: JSON.stringify({ reason }),
|
|
1240
|
+
device: (_d2 = clientContextRef.current) == null ? void 0 : _d2.device,
|
|
1241
|
+
user_context: (_e2 = clientContextRef.current) == null ? void 0 : _e2.userContext,
|
|
1242
|
+
user_id: (_f2 = clientContextRef.current) == null ? void 0 : _f2.userId
|
|
1243
|
+
});
|
|
1244
|
+
degradedRef.current = true;
|
|
1245
|
+
setDegraded(true);
|
|
1246
|
+
(_g = onErrorRef.current) == null ? void 0 : _g.call(onErrorRef, error != null ? error : new Error("Onboarding timed out"));
|
|
1247
|
+
}
|
|
1248
|
+
}, [error, timedOut, maxRetries]);
|
|
1249
|
+
const handleFinish = useCallback(() => {
|
|
1250
|
+
if (finished.current) return;
|
|
1251
|
+
finished.current = true;
|
|
1252
|
+
onComplete({ answers: deriveAnswers(messages), raw: messages });
|
|
1253
|
+
}, [messages, onComplete]);
|
|
1254
|
+
const handleRetry = useCallback(() => {
|
|
1255
|
+
attempts.current = 0;
|
|
1256
|
+
degradedRef.current = false;
|
|
1257
|
+
setDegraded(false);
|
|
1258
|
+
restartThread();
|
|
1259
|
+
}, [restartThread]);
|
|
1260
|
+
const skipQuestion = useCallback(() => {
|
|
1261
|
+
if (isLoading) return;
|
|
1262
|
+
sendMessage(SKIP_ONE_SENTINEL);
|
|
1263
|
+
}, [isLoading, sendMessage]);
|
|
1264
|
+
const previewOnSelect = useCallback((value) => {
|
|
1265
|
+
const target = reportTargetRef.current;
|
|
1266
|
+
if (!(target == null ? void 0 : target.serverUrl)) return;
|
|
1267
|
+
if (Array.isArray(value) ? value.length === 0 : !value) return;
|
|
1268
|
+
if (previewTimer.current) clearTimeout(previewTimer.current);
|
|
1269
|
+
previewTimer.current = setTimeout(() => {
|
|
1270
|
+
sendPreview(target, {
|
|
1271
|
+
sessionId: sessionIdRef.current,
|
|
1272
|
+
userMessage: `I selected: ${JSON.stringify(value)}`
|
|
1273
|
+
});
|
|
1274
|
+
}, 300);
|
|
1275
|
+
}, []);
|
|
1276
|
+
useEffect(() => () => {
|
|
1277
|
+
if (previewTimer.current) clearTimeout(previewTimer.current);
|
|
1278
|
+
}, []);
|
|
1279
|
+
const wrappedActions = useCallback(
|
|
1280
|
+
(messageId) => {
|
|
1281
|
+
const base = makeActions(messageId);
|
|
1282
|
+
const key = progress.key;
|
|
1283
|
+
const validator = key ? validators == null ? void 0 : validators[key] : void 0;
|
|
1284
|
+
if (!validator) return base;
|
|
1285
|
+
const out = { ...base };
|
|
1286
|
+
for (const name of ANSWER_CALLBACKS) {
|
|
1287
|
+
const original = base[name];
|
|
1288
|
+
if (!original) continue;
|
|
1289
|
+
out[name] = (...args) => {
|
|
1290
|
+
const value = args[0];
|
|
1291
|
+
if (typeof value !== "string") return original(...args);
|
|
1292
|
+
setValidating(true);
|
|
1293
|
+
validator(value).then((res) => {
|
|
1294
|
+
var _a2;
|
|
1295
|
+
if (res.ok) {
|
|
1296
|
+
setValidationError(void 0);
|
|
1297
|
+
original(...args);
|
|
1298
|
+
} else {
|
|
1299
|
+
setValidationError((_a2 = res.error) != null ? _a2 : "That value isn't available.");
|
|
1300
|
+
}
|
|
1301
|
+
}).catch(() => {
|
|
1302
|
+
original(...args);
|
|
1303
|
+
}).finally(() => setValidating(false));
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
return out;
|
|
1307
|
+
},
|
|
1308
|
+
[makeActions, progress.key, validators]
|
|
1309
|
+
);
|
|
1310
|
+
if (degraded) {
|
|
1311
|
+
if (fallbackFlow !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: fallbackFlow });
|
|
1312
|
+
if (onError) {
|
|
1313
|
+
return /* @__PURE__ */ jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
|
|
1314
|
+
}
|
|
1315
|
+
return /* @__PURE__ */ jsx(
|
|
1316
|
+
ErrorBlock,
|
|
1317
|
+
{
|
|
1318
|
+
onRetry: handleRetry,
|
|
1319
|
+
message: timedOut ? "This is taking longer than expected. Try again?" : "We couldn't reach the onboarding service. Try again?"
|
|
1320
|
+
}
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (error || timedOut) {
|
|
1324
|
+
return /* @__PURE__ */ jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
|
|
1325
|
+
}
|
|
1326
|
+
const response = lastCard == null ? void 0 : lastCard.response;
|
|
1327
|
+
if (!lastCard || !response) {
|
|
1328
|
+
return /* @__PURE__ */ jsx(LoadingScreen, { title: c.startingTitle, hint: c.startingHint });
|
|
1329
|
+
}
|
|
1330
|
+
const step = (_a = progress.step) != null ? _a : renderedCount;
|
|
1331
|
+
const total = (_b = progress.total) != null ? _b : approxScreens;
|
|
1332
|
+
const isTerminal = response.action === "render" && response.component === "StatusCard";
|
|
1333
|
+
if (isTerminal) {
|
|
1334
|
+
const props = (_c = response.props) != null ? _c : {};
|
|
1335
|
+
return /* @__PURE__ */ jsx(OnboardingScaffold, { step, complete: true, approxScreens: total, children: /* @__PURE__ */ jsx(
|
|
1336
|
+
CompletionView,
|
|
1337
|
+
{
|
|
1338
|
+
title: (_d = props.title) != null ? _d : c.completeTitle,
|
|
1339
|
+
message: props.message,
|
|
1340
|
+
ctaLabel: (_e = props.ctaLabel) != null ? _e : c.completeCta,
|
|
1341
|
+
onContinue: handleFinish
|
|
1342
|
+
}
|
|
1343
|
+
) });
|
|
1344
|
+
}
|
|
1345
|
+
const overrides = {
|
|
1346
|
+
...wrappedActions(lastCard.id),
|
|
1347
|
+
// Inject the inline error onto whichever card supports it (TextInputCard).
|
|
1348
|
+
validationError,
|
|
1349
|
+
// Choice cards (Selection/ChipSelect) call this as the live selection changes, so the flow
|
|
1350
|
+
// can prefetch the next turn before Continue. Non-committing; ignored by cards that don't use it.
|
|
1351
|
+
onSelectionChange: previewOnSelect
|
|
1352
|
+
};
|
|
1353
|
+
return /* @__PURE__ */ jsx(
|
|
1354
|
+
OnboardingScaffold,
|
|
1355
|
+
{
|
|
1356
|
+
step,
|
|
1357
|
+
onSkip: progress.skippable ? skipQuestion : void 0,
|
|
1358
|
+
approxScreens: total,
|
|
1359
|
+
children: /* @__PURE__ */ jsx(
|
|
1360
|
+
CardHandoff,
|
|
1361
|
+
{
|
|
1362
|
+
transitionKey: validating ? "loader:checking" : isLoading ? "loader:thinking" : lastCard.id,
|
|
1363
|
+
variant: validating || isLoading ? "fade" : ((_f = lastCard.response) == null ? void 0 : _f.component) === "InterstitialCard" ? "spring" : "slide",
|
|
1364
|
+
children: validating ? /* @__PURE__ */ jsx(LoadingBlock, { title: c.checkingTitle, hint: c.checkingHint }) : isLoading ? (
|
|
1365
|
+
// Between turns: the answer was sent and we're waiting on the next card.
|
|
1366
|
+
// Show the "thinking" loader instead of leaving the old card on screen.
|
|
1367
|
+
/* @__PURE__ */ jsx(LoadingBlock, { title: c.thinkingTitle, hint: c.thinkingHint })
|
|
1368
|
+
) : (
|
|
1369
|
+
// key={lastCard.id} is load-bearing: cards (SelectionCard/ChipSelectCard)
|
|
1370
|
+
// hold internal `selected`/`submitted` state, and React reuses a component
|
|
1371
|
+
// instance across turns when the rendered type is identical (e.g. two
|
|
1372
|
+
// SelectionCards in a row). Without a per-turn key the second card inherits
|
|
1373
|
+
// the first's `submitted=true` and silently rejects all taps. Keying on the
|
|
1374
|
+
// message id forces a fresh mount every turn. (CardHandoff's transitionKey
|
|
1375
|
+
// shares the id, but this renderer key stays the state-isolation guarantee.)
|
|
1376
|
+
/* @__PURE__ */ jsx(
|
|
1377
|
+
ComponentRenderer,
|
|
1378
|
+
{
|
|
1379
|
+
messageId: lastCard.id,
|
|
1380
|
+
response,
|
|
1381
|
+
callbackOverrides: overrides
|
|
1382
|
+
},
|
|
1383
|
+
lastCard.id
|
|
1384
|
+
)
|
|
1385
|
+
)
|
|
1386
|
+
}
|
|
1387
|
+
)
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
};
|
|
1391
|
+
var easeWire4 = Easing.bezier(...WIRE_BEZIER);
|
|
1392
|
+
var schema = z.object({
|
|
1393
|
+
title: z.string().describe("Question or section header, e.g. 'Which season?'"),
|
|
1394
|
+
chips: z.array(z.string()).min(1).describe("List of option labels shown as chips, e.g. ['Spring','Summer','Autumn','Winter']"),
|
|
1395
|
+
multiSelect: z.boolean().optional().describe("Allow multiple chips selected at once, default true"),
|
|
1396
|
+
submitLabel: z.string().optional().describe("Confirm button label, default: Continue"),
|
|
1397
|
+
maxSelections: z.number().optional().describe("Maximum number of chips the user can select at once")
|
|
1398
|
+
});
|
|
1399
|
+
var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
1400
|
+
const t = useOnboardingTheme();
|
|
1401
|
+
const reduced = useReducedMotion();
|
|
1402
|
+
const handlePress = useCallback(() => onToggle(chip), [chip, onToggle]);
|
|
1403
|
+
const pressScale = useRef(new Animated.Value(1)).current;
|
|
1404
|
+
const pressTo = useCallback(
|
|
1405
|
+
(to) => {
|
|
1406
|
+
if (reduced) return;
|
|
1407
|
+
Animated.spring(pressScale, {
|
|
1408
|
+
toValue: to,
|
|
1409
|
+
friction: PRESS_SPRING.friction,
|
|
1410
|
+
tension: PRESS_SPRING.tension,
|
|
1411
|
+
useNativeDriver: true
|
|
1412
|
+
}).start();
|
|
1413
|
+
},
|
|
1414
|
+
[pressScale, reduced]
|
|
1415
|
+
);
|
|
1416
|
+
const handlePressIn = useCallback(() => pressTo(CHIP_PRESS_SCALE), [pressTo]);
|
|
1417
|
+
const handlePressOut = useCallback(() => pressTo(1), [pressTo]);
|
|
1418
|
+
const fillT = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
|
|
1419
|
+
useEffect(() => {
|
|
1420
|
+
const anim = Animated.timing(fillT, {
|
|
1421
|
+
toValue: isSelected ? 1 : 0,
|
|
1422
|
+
duration: scaledMs(stateChangeDuration(reduced, CHIP_FILL_MS)),
|
|
1423
|
+
easing: easeWire4,
|
|
1424
|
+
useNativeDriver: true
|
|
1425
|
+
});
|
|
1426
|
+
anim.start();
|
|
1427
|
+
return () => anim.stop();
|
|
1428
|
+
}, [isSelected, reduced, fillT]);
|
|
1429
|
+
const checkT = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
|
|
1430
|
+
useEffect(() => {
|
|
1431
|
+
if (!isSelected) {
|
|
1432
|
+
checkT.setValue(0);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
if (reduced) {
|
|
1436
|
+
checkT.setValue(1);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
checkT.setValue(0);
|
|
1440
|
+
const anim = Animated.spring(checkT, {
|
|
1441
|
+
toValue: 1,
|
|
1442
|
+
friction: CHECK_SPRING.friction,
|
|
1443
|
+
tension: CHECK_SPRING.tension,
|
|
1444
|
+
useNativeDriver: true
|
|
1445
|
+
});
|
|
1446
|
+
anim.start();
|
|
1447
|
+
return () => anim.stop();
|
|
1448
|
+
}, [isSelected, reduced, checkT]);
|
|
1449
|
+
const checkScale = checkT.interpolate({ inputRange: [0, 1], outputRange: [CHECK_SCALE_FROM, 1] });
|
|
1450
|
+
return /* @__PURE__ */ jsx(
|
|
1451
|
+
Pressable,
|
|
1452
|
+
{
|
|
1453
|
+
onPress: handlePress,
|
|
1454
|
+
onPressIn: handlePressIn,
|
|
1455
|
+
onPressOut: handlePressOut,
|
|
1456
|
+
accessibilityRole: multiSelect ? "checkbox" : "radio",
|
|
1457
|
+
accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
|
|
1458
|
+
accessibilityLabel: chip,
|
|
1459
|
+
children: /* @__PURE__ */ jsxs(
|
|
1460
|
+
Animated.View,
|
|
1461
|
+
{
|
|
1462
|
+
style: [
|
|
1463
|
+
styles12.chip,
|
|
1464
|
+
{
|
|
1465
|
+
borderRadius: t.radius.full,
|
|
1466
|
+
paddingHorizontal: t.spacing.md,
|
|
1467
|
+
paddingVertical: t.spacing.sm,
|
|
1468
|
+
borderColor: t.colors.border,
|
|
1469
|
+
backgroundColor: t.colors.background,
|
|
1470
|
+
transform: [{ scale: pressScale }]
|
|
1471
|
+
}
|
|
1472
|
+
],
|
|
1473
|
+
children: [
|
|
1474
|
+
/* @__PURE__ */ jsx(
|
|
1475
|
+
Animated.View,
|
|
1476
|
+
{
|
|
1477
|
+
pointerEvents: "none",
|
|
1478
|
+
style: [
|
|
1479
|
+
StyleSheet.absoluteFill,
|
|
1480
|
+
styles12.fillOverlay,
|
|
1481
|
+
{
|
|
1482
|
+
borderRadius: t.radius.full,
|
|
1483
|
+
borderColor: t.colors.primary,
|
|
1484
|
+
backgroundColor: t.colors.primary,
|
|
1485
|
+
opacity: fillT
|
|
1486
|
+
}
|
|
1487
|
+
]
|
|
1488
|
+
}
|
|
1489
|
+
),
|
|
1490
|
+
isSelected ? /* @__PURE__ */ jsx(
|
|
1491
|
+
Animated.Text,
|
|
1492
|
+
{
|
|
1493
|
+
style: [
|
|
1494
|
+
styles12.check,
|
|
1495
|
+
{ color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
1496
|
+
],
|
|
1497
|
+
children: "\u2713"
|
|
1498
|
+
}
|
|
1499
|
+
) : null,
|
|
1500
|
+
/* @__PURE__ */ jsx(
|
|
1501
|
+
Text,
|
|
1502
|
+
{
|
|
1503
|
+
style: [
|
|
1504
|
+
captionStyle(t.fonts),
|
|
1505
|
+
styles12.chipLabel,
|
|
1506
|
+
{ color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
|
|
1507
|
+
],
|
|
1508
|
+
children: chip
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
]
|
|
1512
|
+
}
|
|
1513
|
+
)
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
};
|
|
1517
|
+
var ChipItem = React14.memo(_ChipItem);
|
|
1518
|
+
var _ChipSelectCard = ({
|
|
1519
|
+
title,
|
|
1520
|
+
chips,
|
|
1521
|
+
multiSelect = true,
|
|
1522
|
+
submitLabel = "Continue",
|
|
1523
|
+
maxSelections,
|
|
1524
|
+
onSelect,
|
|
1525
|
+
onSelectionChange
|
|
1526
|
+
}) => {
|
|
1527
|
+
const t = useOnboardingTheme();
|
|
1528
|
+
const [selected, setSelected] = useState([]);
|
|
1529
|
+
const [submitted, setSubmitted] = useState(false);
|
|
1530
|
+
useEffect(() => {
|
|
1531
|
+
if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
|
|
1532
|
+
}, [selected, submitted, multiSelect, onSelectionChange]);
|
|
1533
|
+
const toggle = useCallback(
|
|
1534
|
+
(chip) => {
|
|
1535
|
+
if (submitted) return;
|
|
1536
|
+
if (multiSelect) {
|
|
1537
|
+
setSelected((prev) => {
|
|
1538
|
+
if (prev.includes(chip)) return prev.filter((c) => c !== chip);
|
|
1539
|
+
if (maxSelections && prev.length >= maxSelections) return prev;
|
|
1540
|
+
return [...prev, chip];
|
|
1541
|
+
});
|
|
1542
|
+
} else {
|
|
1543
|
+
setSelected([chip]);
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
[submitted, multiSelect, maxSelections]
|
|
1547
|
+
);
|
|
1548
|
+
const handleSubmit = useCallback(() => {
|
|
1549
|
+
if (!selected.length || !onSelect || submitted) return;
|
|
1550
|
+
setSubmitted(true);
|
|
1551
|
+
onSelect(multiSelect ? selected : selected[0]);
|
|
1552
|
+
}, [selected, onSelect, submitted, multiSelect]);
|
|
1553
|
+
return /* @__PURE__ */ jsx(
|
|
1554
|
+
CardLayout,
|
|
1555
|
+
{
|
|
1556
|
+
title,
|
|
1557
|
+
subtitle: maxSelections ? `Select up to ${maxSelections}` : void 0,
|
|
1558
|
+
footer: /* @__PURE__ */ jsx(
|
|
1559
|
+
Button,
|
|
1560
|
+
{
|
|
1561
|
+
title: submitLabel,
|
|
1562
|
+
onPress: handleSubmit,
|
|
1563
|
+
variant: "primary",
|
|
1564
|
+
disabled: selected.length === 0 || submitted
|
|
1565
|
+
}
|
|
1566
|
+
),
|
|
1567
|
+
children: /* @__PURE__ */ jsx(View, { style: [styles12.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsx(
|
|
1568
|
+
ChipItem,
|
|
1569
|
+
{
|
|
1570
|
+
chip,
|
|
1571
|
+
isSelected: selected.includes(chip),
|
|
1572
|
+
multiSelect,
|
|
1573
|
+
onToggle: toggle
|
|
1574
|
+
},
|
|
1575
|
+
chip
|
|
1576
|
+
)) })
|
|
1577
|
+
}
|
|
1578
|
+
);
|
|
1579
|
+
};
|
|
1580
|
+
var ChipSelectCard = {
|
|
1581
|
+
name: "ChipSelectCard",
|
|
1582
|
+
description: "Use for compact short labels: seasons, activities, travel styles, moods, categories. Ideal for 4\u201312 single-word or two-word options. Use SelectionCard instead when options need descriptions.",
|
|
1583
|
+
component: React14.memo(_ChipSelectCard),
|
|
1584
|
+
propsSchema: schema,
|
|
1585
|
+
defaultProps: { multiSelect: true, submitLabel: "Continue" }
|
|
1586
|
+
};
|
|
1587
|
+
var styles12 = StyleSheet.create({
|
|
1588
|
+
chipsRow: { flexDirection: "row", flexWrap: "wrap" },
|
|
1589
|
+
chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
|
|
1590
|
+
fillOverlay: { borderWidth: 1.5 },
|
|
1591
|
+
chipLabel: { fontWeight: "600" },
|
|
1592
|
+
check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
|
|
1593
|
+
});
|
|
1594
|
+
var schema2 = z.object({
|
|
1595
|
+
label: z.string().describe("Input field label / the question being asked"),
|
|
1596
|
+
hint: z.string().optional().describe(
|
|
1597
|
+
"One short line under the question giving context: what the answer is used for or why you ask. Add it whenever the question alone might be unclear."
|
|
1598
|
+
),
|
|
1599
|
+
placeholder: z.string().optional().describe(
|
|
1600
|
+
'A concrete example answer shown inside the box, phrased like a real user wrote it and starting with "e.g. ". ALWAYS provide one for open-ended questions: it shows the expected answer shape and inspires the user.'
|
|
1601
|
+
),
|
|
1602
|
+
submitLabel: z.string().optional().describe("Submit button label, default: Submit"),
|
|
1603
|
+
multiline: z.boolean().optional().describe(
|
|
1604
|
+
"Tall multi-line box for open-ended answers (goals, notes, feedback). Default true. Set false for a single-line field like a name or username."
|
|
1605
|
+
),
|
|
1606
|
+
lines: z.number().optional().describe("Approx visible rows when multiline (default 6). Height is capped at 40% of the screen.")
|
|
1607
|
+
});
|
|
1608
|
+
var _TextInputCard = ({
|
|
1609
|
+
label,
|
|
1610
|
+
hint,
|
|
1611
|
+
placeholder,
|
|
1612
|
+
submitLabel = "Submit",
|
|
1613
|
+
onSubmit,
|
|
1614
|
+
validationError,
|
|
1615
|
+
multiline = true,
|
|
1616
|
+
lines = 6
|
|
1617
|
+
}) => {
|
|
1618
|
+
const t = useOnboardingTheme();
|
|
1619
|
+
const { height: screenHeight } = useWindowDimensions();
|
|
1620
|
+
const [value, setValue] = useState("");
|
|
1621
|
+
const [submitted, setSubmitted] = useState(false);
|
|
1622
|
+
const locked = submitted && !validationError;
|
|
1623
|
+
const APPROX_ROW_HEIGHT = 26;
|
|
1624
|
+
const minHeight = multiline ? Math.min(Math.max(lines, 4) * APPROX_ROW_HEIGHT + t.spacing.md * 2, screenHeight * 0.4) : 52;
|
|
1625
|
+
const handleSubmit = useCallback(() => {
|
|
1626
|
+
if (value.trim() && onSubmit && !locked) {
|
|
1627
|
+
setSubmitted(true);
|
|
1628
|
+
onSubmit(value.trim());
|
|
1629
|
+
}
|
|
1630
|
+
}, [value, onSubmit, locked]);
|
|
1631
|
+
return /* @__PURE__ */ jsx(
|
|
1632
|
+
CardLayout,
|
|
1633
|
+
{
|
|
1634
|
+
title: label,
|
|
1635
|
+
subtitle: hint,
|
|
1636
|
+
footer: /* @__PURE__ */ jsx(
|
|
1637
|
+
Button,
|
|
1638
|
+
{
|
|
1639
|
+
title: submitLabel,
|
|
1640
|
+
onPress: handleSubmit,
|
|
1641
|
+
variant: "primary",
|
|
1642
|
+
disabled: !value.trim() || locked
|
|
1643
|
+
}
|
|
1644
|
+
),
|
|
1645
|
+
children: /* @__PURE__ */ jsxs(View, { style: { gap: t.spacing.sm }, children: [
|
|
1646
|
+
/* @__PURE__ */ jsx(
|
|
1647
|
+
TextInput,
|
|
1648
|
+
{
|
|
1649
|
+
value,
|
|
1650
|
+
onChangeText: setValue,
|
|
1651
|
+
placeholder: placeholder != null ? placeholder : "",
|
|
1652
|
+
placeholderTextColor: t.colors.textMuted,
|
|
1653
|
+
editable: !locked,
|
|
1654
|
+
multiline,
|
|
1655
|
+
numberOfLines: multiline ? lines : 1,
|
|
1656
|
+
textAlignVertical: multiline ? "top" : "center",
|
|
1657
|
+
style: [
|
|
1658
|
+
bodyStyle(t.fonts),
|
|
1659
|
+
styles13.input,
|
|
1660
|
+
{
|
|
1661
|
+
color: t.colors.text,
|
|
1662
|
+
backgroundColor: t.colors.surface,
|
|
1663
|
+
borderRadius: t.radius.md,
|
|
1664
|
+
borderColor: validationError ? t.colors.error : t.colors.border,
|
|
1665
|
+
padding: t.spacing.md,
|
|
1666
|
+
minHeight
|
|
1667
|
+
}
|
|
1668
|
+
]
|
|
1669
|
+
}
|
|
1670
|
+
),
|
|
1671
|
+
validationError ? /* @__PURE__ */ jsx(Text, { style: [captionStyle(t.fonts), { color: t.colors.error }], children: validationError }) : null
|
|
1672
|
+
] })
|
|
1673
|
+
}
|
|
1674
|
+
);
|
|
1675
|
+
};
|
|
1676
|
+
var TextInputCard = {
|
|
1677
|
+
name: "TextInputCard",
|
|
1678
|
+
description: "Use when the user needs to type a free-text answer: an open-ended goal, notes, feedback, a name, a destination, a search query. Multi-line by default (great for open-ended goals); set multiline:false for a short single-line field like a name. Use NumberStepperCard for numeric ranges with known bounds.",
|
|
1679
|
+
component: React14.memo(_TextInputCard),
|
|
1680
|
+
propsSchema: schema2,
|
|
1681
|
+
defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
|
|
1682
|
+
};
|
|
1683
|
+
var styles13 = StyleSheet.create({
|
|
1684
|
+
input: { borderWidth: 1 }
|
|
1685
|
+
});
|
|
1686
|
+
var easeWire5 = Easing.bezier(...WIRE_BEZIER);
|
|
1687
|
+
var optionObjectSchema = z.object({
|
|
1688
|
+
value: z.string().describe("Option value"),
|
|
1689
|
+
label: z.string().describe("Option display label")
|
|
1690
|
+
});
|
|
1691
|
+
var schema3 = z.object({
|
|
1692
|
+
title: z.string().describe("Question or prompt for the user"),
|
|
1693
|
+
options: z.preprocess(
|
|
1694
|
+
(arr) => Array.isArray(arr) ? arr.map((item) => typeof item === "string" ? { value: item, label: item } : item) : arr,
|
|
1695
|
+
z.array(optionObjectSchema).min(1)
|
|
1696
|
+
).describe("List of choices"),
|
|
1697
|
+
submitLabel: z.string().optional().describe("Submit button label, default: Continue"),
|
|
1698
|
+
multiSelect: z.boolean().optional().describe("Allow multiple selections")
|
|
1699
|
+
});
|
|
1700
|
+
var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
1701
|
+
const t = useOnboardingTheme();
|
|
1702
|
+
const reduced = useReducedMotion();
|
|
1703
|
+
const handlePress = useCallback(() => onToggle(opt.value), [opt.value, onToggle]);
|
|
1704
|
+
const pressScale = useRef(new Animated.Value(1)).current;
|
|
1705
|
+
const pressTo = useCallback(
|
|
1706
|
+
(to) => {
|
|
1707
|
+
if (reduced) return;
|
|
1708
|
+
Animated.spring(pressScale, {
|
|
1709
|
+
toValue: to,
|
|
1710
|
+
friction: PRESS_SPRING.friction,
|
|
1711
|
+
tension: PRESS_SPRING.tension,
|
|
1712
|
+
useNativeDriver: true
|
|
1713
|
+
}).start();
|
|
1714
|
+
},
|
|
1715
|
+
[pressScale, reduced]
|
|
1716
|
+
);
|
|
1717
|
+
const handlePressIn = useCallback(() => pressTo(ROW_PRESS_SCALE), [pressTo]);
|
|
1718
|
+
const handlePressOut = useCallback(() => pressTo(1), [pressTo]);
|
|
1719
|
+
const selectT = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
|
|
1720
|
+
useEffect(() => {
|
|
1721
|
+
const anim = Animated.timing(selectT, {
|
|
1722
|
+
toValue: isSelected ? 1 : 0,
|
|
1723
|
+
duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
|
|
1724
|
+
easing: easeWire5,
|
|
1725
|
+
useNativeDriver: true
|
|
1726
|
+
});
|
|
1727
|
+
anim.start();
|
|
1728
|
+
return () => anim.stop();
|
|
1729
|
+
}, [isSelected, reduced, selectT]);
|
|
1730
|
+
const checkT = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
|
|
1731
|
+
useEffect(() => {
|
|
1732
|
+
if (!isSelected) {
|
|
1733
|
+
checkT.setValue(0);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
if (reduced) {
|
|
1737
|
+
checkT.setValue(1);
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
checkT.setValue(0);
|
|
1741
|
+
const anim = Animated.spring(checkT, {
|
|
1742
|
+
toValue: 1,
|
|
1743
|
+
friction: CHECK_SPRING.friction,
|
|
1744
|
+
tension: CHECK_SPRING.tension,
|
|
1745
|
+
useNativeDriver: true
|
|
1746
|
+
});
|
|
1747
|
+
anim.start();
|
|
1748
|
+
return () => anim.stop();
|
|
1749
|
+
}, [isSelected, reduced, checkT]);
|
|
1750
|
+
const checkScale = checkT.interpolate({ inputRange: [0, 1], outputRange: [CHECK_SCALE_FROM, 1] });
|
|
1751
|
+
return /* @__PURE__ */ jsx(
|
|
1752
|
+
Pressable,
|
|
1753
|
+
{
|
|
1754
|
+
onPress: handlePress,
|
|
1755
|
+
onPressIn: handlePressIn,
|
|
1756
|
+
onPressOut: handlePressOut,
|
|
1757
|
+
accessibilityRole: multiSelect ? "checkbox" : "radio",
|
|
1758
|
+
accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
|
|
1759
|
+
accessibilityLabel: opt.label,
|
|
1760
|
+
children: /* @__PURE__ */ jsxs(
|
|
1761
|
+
Animated.View,
|
|
1762
|
+
{
|
|
1763
|
+
style: [
|
|
1764
|
+
styles14.option,
|
|
1765
|
+
{
|
|
1766
|
+
padding: t.spacing.md,
|
|
1767
|
+
borderRadius: t.radius.md,
|
|
1768
|
+
gap: t.spacing.sm,
|
|
1769
|
+
borderColor: t.colors.border,
|
|
1770
|
+
backgroundColor: t.colors.background,
|
|
1771
|
+
transform: [{ scale: pressScale }]
|
|
1772
|
+
}
|
|
1773
|
+
],
|
|
1774
|
+
children: [
|
|
1775
|
+
/* @__PURE__ */ jsx(
|
|
1776
|
+
Animated.View,
|
|
1777
|
+
{
|
|
1778
|
+
pointerEvents: "none",
|
|
1779
|
+
style: [
|
|
1780
|
+
StyleSheet.absoluteFill,
|
|
1781
|
+
styles14.selectedOverlay,
|
|
1782
|
+
{
|
|
1783
|
+
borderRadius: t.radius.md,
|
|
1784
|
+
borderColor: t.colors.primary,
|
|
1785
|
+
backgroundColor: t.colors.primarySoft,
|
|
1786
|
+
opacity: selectT
|
|
1787
|
+
}
|
|
1788
|
+
]
|
|
1789
|
+
}
|
|
1790
|
+
),
|
|
1791
|
+
/* @__PURE__ */ jsx(
|
|
1792
|
+
View,
|
|
1793
|
+
{
|
|
1794
|
+
style: [
|
|
1795
|
+
styles14.indicator,
|
|
1796
|
+
{ borderRadius: t.radius.full },
|
|
1797
|
+
isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
|
|
1798
|
+
],
|
|
1799
|
+
children: isSelected ? /* @__PURE__ */ jsx(
|
|
1800
|
+
Animated.Text,
|
|
1801
|
+
{
|
|
1802
|
+
style: [
|
|
1803
|
+
styles14.check,
|
|
1804
|
+
{ color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
1805
|
+
],
|
|
1806
|
+
children: "\u2713"
|
|
1807
|
+
}
|
|
1808
|
+
) : null
|
|
1809
|
+
}
|
|
1810
|
+
),
|
|
1811
|
+
/* @__PURE__ */ jsx(Text, { style: [labelStyle(t.fonts), { color: t.colors.text }], children: opt.label })
|
|
1812
|
+
]
|
|
1813
|
+
}
|
|
1814
|
+
)
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1817
|
+
};
|
|
1818
|
+
var OptionRow = React14.memo(_OptionRow);
|
|
1819
|
+
var _SelectionCard = ({
|
|
1820
|
+
title,
|
|
1821
|
+
options,
|
|
1822
|
+
submitLabel = "Continue",
|
|
1823
|
+
multiSelect = false,
|
|
1824
|
+
onSelect,
|
|
1825
|
+
onSelectionChange
|
|
1826
|
+
}) => {
|
|
1827
|
+
const t = useOnboardingTheme();
|
|
1828
|
+
const [selected, setSelected] = useState([]);
|
|
1829
|
+
const [submitted, setSubmitted] = useState(false);
|
|
1830
|
+
useEffect(() => {
|
|
1831
|
+
if (!submitted && selected.length) onSelectionChange == null ? void 0 : onSelectionChange(multiSelect ? selected : selected[0]);
|
|
1832
|
+
}, [selected, submitted, multiSelect, onSelectionChange]);
|
|
1833
|
+
const toggle = useCallback(
|
|
1834
|
+
(value) => {
|
|
1835
|
+
if (submitted) return;
|
|
1836
|
+
if (multiSelect) {
|
|
1837
|
+
setSelected((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]);
|
|
1838
|
+
} else {
|
|
1839
|
+
setSelected([value]);
|
|
1840
|
+
}
|
|
1841
|
+
},
|
|
1842
|
+
[submitted, multiSelect]
|
|
1843
|
+
);
|
|
1844
|
+
const handleSubmit = useCallback(() => {
|
|
1845
|
+
if (!selected.length || !onSelect || submitted) return;
|
|
1846
|
+
setSubmitted(true);
|
|
1847
|
+
onSelect(multiSelect ? selected : selected[0]);
|
|
1848
|
+
}, [selected, onSelect, submitted, multiSelect]);
|
|
1849
|
+
return /* @__PURE__ */ jsx(
|
|
1850
|
+
CardLayout,
|
|
1851
|
+
{
|
|
1852
|
+
title,
|
|
1853
|
+
footer: /* @__PURE__ */ jsx(
|
|
1854
|
+
Button,
|
|
1855
|
+
{
|
|
1856
|
+
title: submitLabel,
|
|
1857
|
+
onPress: handleSubmit,
|
|
1858
|
+
variant: "primary",
|
|
1859
|
+
disabled: selected.length === 0 || submitted
|
|
1860
|
+
}
|
|
1861
|
+
),
|
|
1862
|
+
children: /* @__PURE__ */ jsx(View, { style: { gap: t.spacing.sm + t.spacing.xs }, children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
1863
|
+
OptionRow,
|
|
1864
|
+
{
|
|
1865
|
+
opt,
|
|
1866
|
+
isSelected: selected.includes(opt.value),
|
|
1867
|
+
multiSelect,
|
|
1868
|
+
onToggle: toggle
|
|
1869
|
+
},
|
|
1870
|
+
opt.value
|
|
1871
|
+
)) })
|
|
1872
|
+
}
|
|
1873
|
+
);
|
|
1874
|
+
};
|
|
1875
|
+
var SelectionCard = {
|
|
1876
|
+
name: "SelectionCard",
|
|
1877
|
+
description: "Use for mutually exclusive (radio) or multi-select (checkbox) choices from 2\u20138 options. Use ChipSelectCard instead when options are short single words.",
|
|
1878
|
+
component: React14.memo(_SelectionCard),
|
|
1879
|
+
propsSchema: schema3,
|
|
1880
|
+
defaultProps: { submitLabel: "Continue", multiSelect: false }
|
|
1881
|
+
};
|
|
1882
|
+
var styles14 = StyleSheet.create({
|
|
1883
|
+
option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
|
|
1884
|
+
selectedOverlay: { borderWidth: 1.5 },
|
|
1885
|
+
indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
|
|
1886
|
+
check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
|
|
1887
|
+
});
|
|
1888
|
+
var easeWire6 = Easing.bezier(...WIRE_BEZIER);
|
|
1889
|
+
var schema4 = z.object({
|
|
1890
|
+
status: z.enum(["success", "warning", "error", "info"]).describe("Status type"),
|
|
1891
|
+
title: z.string().describe("Status title"),
|
|
1892
|
+
message: z.string().optional().describe("Supporting message"),
|
|
1893
|
+
ctaLabel: z.string().optional().describe(
|
|
1894
|
+
"Optional call-to-action button label. Always include this on success status to keep the conversation going."
|
|
1895
|
+
)
|
|
1896
|
+
});
|
|
1897
|
+
var STATUS_ICONS = {
|
|
1898
|
+
success: "\u2713",
|
|
1899
|
+
warning: "\u26A0",
|
|
1900
|
+
error: "\u2717",
|
|
1901
|
+
info: "\u2139"
|
|
1902
|
+
};
|
|
1903
|
+
var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSubmit }) => {
|
|
1904
|
+
var _a, _b;
|
|
1905
|
+
const t = useOnboardingTheme();
|
|
1906
|
+
const reduced = useReducedMotion();
|
|
1907
|
+
const [submitted, setSubmitted] = useState(false);
|
|
1908
|
+
const colorFor = {
|
|
1909
|
+
success: t.colors.success,
|
|
1910
|
+
warning: t.colors.warning,
|
|
1911
|
+
error: t.colors.error,
|
|
1912
|
+
info: t.colors.info
|
|
1913
|
+
};
|
|
1914
|
+
const color = (_a = colorFor[status]) != null ? _a : t.colors.info;
|
|
1915
|
+
const popT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
1916
|
+
const copyT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
1917
|
+
useEffect(() => {
|
|
1918
|
+
if (reduced) {
|
|
1919
|
+
popT.setValue(1);
|
|
1920
|
+
copyT.setValue(1);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
const anims = [
|
|
1924
|
+
Animated.spring(popT, {
|
|
1925
|
+
toValue: 1,
|
|
1926
|
+
friction: STATUS_POP_SPRING.friction,
|
|
1927
|
+
tension: STATUS_POP_SPRING.tension,
|
|
1928
|
+
useNativeDriver: true
|
|
1929
|
+
}),
|
|
1930
|
+
Animated.timing(copyT, {
|
|
1931
|
+
toValue: 1,
|
|
1932
|
+
duration: scaledMs(STATUS_COPY_MS),
|
|
1933
|
+
delay: scaledMs(STATUS_COPY_STAGGER_MS),
|
|
1934
|
+
easing: easeWire6,
|
|
1935
|
+
useNativeDriver: true
|
|
1936
|
+
})
|
|
1937
|
+
];
|
|
1938
|
+
anims.forEach((a) => a.start());
|
|
1939
|
+
return () => anims.forEach((a) => a.stop());
|
|
1940
|
+
}, [reduced, popT, copyT]);
|
|
1941
|
+
const popScale = popT.interpolate({ inputRange: [0, 1], outputRange: [STATUS_POP_SCALE_FROM, 1] });
|
|
1942
|
+
const copyRise = copyT.interpolate({ inputRange: [0, 1], outputRange: [10, 0] });
|
|
1943
|
+
const handleContinue = useCallback(() => {
|
|
1944
|
+
if (!submitted) {
|
|
1945
|
+
setSubmitted(true);
|
|
1946
|
+
if (onContinue) onContinue();
|
|
1947
|
+
else if (onPress && ctaLabel) onPress(ctaLabel);
|
|
1948
|
+
else if (onSubmit) onSubmit("continue");
|
|
1949
|
+
}
|
|
1950
|
+
}, [submitted, onContinue, onPress, onSubmit, ctaLabel]);
|
|
1951
|
+
return /* @__PURE__ */ jsx(
|
|
1952
|
+
CardLayout,
|
|
1953
|
+
{
|
|
1954
|
+
align: "center",
|
|
1955
|
+
footer: ctaLabel ? /* @__PURE__ */ jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
|
|
1956
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles15.center, { gap: t.spacing.sm }], children: [
|
|
1957
|
+
/* @__PURE__ */ jsxs(View, { style: [styles15.titleRow, { gap: t.spacing.sm }], children: [
|
|
1958
|
+
/* @__PURE__ */ jsx(
|
|
1959
|
+
Animated.Text,
|
|
1960
|
+
{
|
|
1961
|
+
style: [
|
|
1962
|
+
headingStyle(t.fonts),
|
|
1963
|
+
{ color, opacity: popT, transform: [{ scale: popScale }] }
|
|
1964
|
+
],
|
|
1965
|
+
children: (_b = STATUS_ICONS[status]) != null ? _b : "\xB7"
|
|
1966
|
+
}
|
|
1967
|
+
),
|
|
1968
|
+
/* @__PURE__ */ jsx(
|
|
1969
|
+
Animated.Text,
|
|
1970
|
+
{
|
|
1971
|
+
style: [
|
|
1972
|
+
headingStyle(t.fonts),
|
|
1973
|
+
{ color, textAlign: "center", opacity: copyT, transform: [{ translateY: copyRise }] }
|
|
1974
|
+
],
|
|
1975
|
+
children: title
|
|
1976
|
+
}
|
|
1977
|
+
)
|
|
1978
|
+
] }),
|
|
1979
|
+
message ? /* @__PURE__ */ jsx(
|
|
1980
|
+
Animated.Text,
|
|
1981
|
+
{
|
|
1982
|
+
style: [
|
|
1983
|
+
bodyStyle(t.fonts),
|
|
1984
|
+
{
|
|
1985
|
+
color: t.colors.textMuted,
|
|
1986
|
+
textAlign: "center",
|
|
1987
|
+
opacity: copyT,
|
|
1988
|
+
transform: [{ translateY: copyRise }]
|
|
1989
|
+
}
|
|
1990
|
+
],
|
|
1991
|
+
children: message
|
|
1992
|
+
}
|
|
1993
|
+
) : null
|
|
1994
|
+
] })
|
|
1995
|
+
}
|
|
1996
|
+
);
|
|
1997
|
+
};
|
|
1998
|
+
var StatusCard = {
|
|
1999
|
+
name: "StatusCard",
|
|
2000
|
+
description: "Use ONLY for real outcomes: booking confirmed, data saved, action completed. NEVER for 'you just viewed X' acknowledgments. Always include ctaLabel so the user can continue after seeing the status.",
|
|
2001
|
+
component: React14.memo(_StatusCard),
|
|
2002
|
+
propsSchema: schema4
|
|
2003
|
+
};
|
|
2004
|
+
var styles15 = StyleSheet.create({
|
|
2005
|
+
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
2006
|
+
titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
|
|
2007
|
+
});
|
|
2008
|
+
var schema5 = z.object({
|
|
2009
|
+
label: z.string().describe("What is being counted, e.g. 'Number of days' or 'Travellers'"),
|
|
2010
|
+
min: z.number().optional().describe("Minimum value, default 1"),
|
|
2011
|
+
max: z.number().optional().describe("Maximum value, default 30"),
|
|
2012
|
+
defaultValue: z.number().optional().describe("Starting value"),
|
|
2013
|
+
step: z.number().optional().describe("Increment per tap, default 1"),
|
|
2014
|
+
unit: z.string().optional().describe("Unit label shown next to value, e.g. 'days' or 'people'"),
|
|
2015
|
+
submitLabel: z.string().optional().describe("Confirm button label, default: Confirm")
|
|
2016
|
+
});
|
|
2017
|
+
var safeNum = (n, fallback) => typeof n === "number" && Number.isFinite(n) ? n : fallback;
|
|
2018
|
+
var _NumberStepperCard = ({
|
|
2019
|
+
label,
|
|
2020
|
+
min,
|
|
2021
|
+
max,
|
|
2022
|
+
defaultValue,
|
|
2023
|
+
step,
|
|
2024
|
+
unit,
|
|
2025
|
+
submitLabel = "Confirm",
|
|
2026
|
+
onSubmit
|
|
2027
|
+
}) => {
|
|
2028
|
+
const t = useOnboardingTheme();
|
|
2029
|
+
const safeMin = safeNum(min, 1);
|
|
2030
|
+
const safeMaxRaw = safeNum(max, 30);
|
|
2031
|
+
const safeMax = safeMaxRaw < safeMin ? safeMin : safeMaxRaw;
|
|
2032
|
+
const safeStepRaw = safeNum(step, 1);
|
|
2033
|
+
const safeStep = safeStepRaw > 0 ? safeStepRaw : 1;
|
|
2034
|
+
const safeDefault = safeNum(defaultValue, safeMin);
|
|
2035
|
+
const [value, setValue] = useState(Math.min(Math.max(safeDefault, safeMin), safeMax));
|
|
2036
|
+
const [submitted, setSubmitted] = useState(false);
|
|
2037
|
+
const handleDecrement = useCallback(() => {
|
|
2038
|
+
if (!submitted) setValue((v) => Math.max(v - safeStep, safeMin));
|
|
2039
|
+
}, [submitted, safeStep, safeMin]);
|
|
2040
|
+
const handleIncrement = useCallback(() => {
|
|
2041
|
+
if (!submitted) setValue((v) => Math.min(v + safeStep, safeMax));
|
|
2042
|
+
}, [submitted, safeStep, safeMax]);
|
|
2043
|
+
const handleSubmit = useCallback(() => {
|
|
2044
|
+
if (!submitted) {
|
|
2045
|
+
setSubmitted(true);
|
|
2046
|
+
onSubmit == null ? void 0 : onSubmit(value);
|
|
2047
|
+
}
|
|
2048
|
+
}, [submitted, onSubmit, value]);
|
|
2049
|
+
const atMin = value <= safeMin;
|
|
2050
|
+
const atMax = value >= safeMax;
|
|
2051
|
+
const stepBtnStyle = (disabled) => [
|
|
2052
|
+
styles16.stepBtn,
|
|
2053
|
+
{ borderRadius: t.radius.full, backgroundColor: t.colors.background, borderColor: disabled ? t.colors.border : t.colors.primary }
|
|
2054
|
+
];
|
|
2055
|
+
return /* @__PURE__ */ jsx(
|
|
2056
|
+
CardLayout,
|
|
2057
|
+
{
|
|
2058
|
+
title: label,
|
|
2059
|
+
align: "center",
|
|
2060
|
+
footer: /* @__PURE__ */ jsx(Button, { title: submitLabel, onPress: handleSubmit, variant: "primary", disabled: submitted }),
|
|
2061
|
+
children: /* @__PURE__ */ jsxs(View, { style: { gap: t.spacing.md, alignItems: "center" }, children: [
|
|
2062
|
+
/* @__PURE__ */ jsxs(View, { style: [styles16.stepper, { gap: t.spacing.lg }], children: [
|
|
2063
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: atMin || submitted ? void 0 : handleDecrement, disabled: atMin || submitted, style: stepBtnStyle(atMin), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: atMin ? t.colors.textMuted : t.colors.primary }], children: "\u2212" }) }),
|
|
2064
|
+
/* @__PURE__ */ jsxs(View, { style: styles16.valueBox, children: [
|
|
2065
|
+
/* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.primary }], children: String(value) }),
|
|
2066
|
+
unit ? /* @__PURE__ */ jsx(Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted }], children: unit }) : null
|
|
2067
|
+
] }),
|
|
2068
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: atMax || submitted ? void 0 : handleIncrement, disabled: atMax || submitted, style: stepBtnStyle(atMax), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: atMax ? t.colors.textMuted : t.colors.primary }], children: "+" }) })
|
|
2069
|
+
] }),
|
|
2070
|
+
/* @__PURE__ */ jsx(Text, { style: [captionStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: `${safeMin} \u2013 ${safeMax}${unit ? ` ${unit}` : ""}` })
|
|
2071
|
+
] })
|
|
2072
|
+
}
|
|
2073
|
+
);
|
|
2074
|
+
};
|
|
2075
|
+
var NumberStepperCard = {
|
|
2076
|
+
name: "NumberStepperCard",
|
|
2077
|
+
description: "Use for any bounded numeric input: days, people, portions, budget tiers, ratings. Best for ranges 1\u201399. Set unit (e.g. 'days', 'people') for clarity.",
|
|
2078
|
+
component: React14.memo(_NumberStepperCard),
|
|
2079
|
+
propsSchema: schema5,
|
|
2080
|
+
defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
|
|
2081
|
+
};
|
|
2082
|
+
var styles16 = StyleSheet.create({
|
|
2083
|
+
stepper: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
|
|
2084
|
+
stepBtn: { width: 44, height: 44, borderWidth: 2, alignItems: "center", justifyContent: "center" },
|
|
2085
|
+
valueBox: { alignItems: "center", minWidth: 80 }
|
|
2086
|
+
});
|
|
2087
|
+
var easeWire7 = Easing.bezier(...WIRE_BEZIER);
|
|
2088
|
+
var RISE_PX = 10;
|
|
2089
|
+
var schema6 = z.object({
|
|
2090
|
+
title: z.string().describe("Short, warm headline for the value/momentum moment"),
|
|
2091
|
+
body: z.string().optional().describe("One or two supporting lines that reflect the user's answers back and build momentum"),
|
|
2092
|
+
items: z.array(z.string()).max(4).optional().describe(
|
|
2093
|
+
"2-3 very short 'work being done' lines that each reference something the user already answered (e.g. 'Sized for a small team'). Shown as ticked items appearing in sequence. Prefer these over `body` when you have concrete answers to reflect back."
|
|
2094
|
+
),
|
|
2095
|
+
illustration: z.string().optional().describe(
|
|
2096
|
+
"Name of an app-provided illustration to show (the app's registry decides the artwork). Prefer this over imageUrl."
|
|
2097
|
+
),
|
|
2098
|
+
imageUrl: z.string().url().refine((v) => v.startsWith("https://"), "imageUrl must be an https URL").optional().describe(
|
|
2099
|
+
"Fallback remote HTTPS image URL, used only when no named illustration is registered. A non-https or malformed value is rejected and degrades to the named-illustration fallback."
|
|
2100
|
+
),
|
|
2101
|
+
illustrationFallback: z.string().optional().describe(
|
|
2102
|
+
"Named illustration to show when neither `illustration` nor `imageUrl` resolves (or the image fails to load). Defaults to the kit's always-present 'momentum' art so the screen is never blank."
|
|
2103
|
+
),
|
|
2104
|
+
ctaLabel: z.string().optional().describe("Continue button label, default: Continue")
|
|
2105
|
+
});
|
|
2106
|
+
var MIN_DISPLAY_MS = 500;
|
|
2107
|
+
var _WorkItem = ({
|
|
2108
|
+
text,
|
|
2109
|
+
index,
|
|
2110
|
+
reduced
|
|
2111
|
+
}) => {
|
|
2112
|
+
const t = useOnboardingTheme();
|
|
2113
|
+
const rowT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
2114
|
+
const checkT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
2115
|
+
useEffect(() => {
|
|
2116
|
+
if (reduced) {
|
|
2117
|
+
rowT.setValue(1);
|
|
2118
|
+
checkT.setValue(1);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
const anims = [
|
|
2122
|
+
Animated.timing(rowT, {
|
|
2123
|
+
toValue: 1,
|
|
2124
|
+
duration: scaledMs(INTERSTITIAL_ITEM_MS),
|
|
2125
|
+
delay: scaledMs(interstitialItemDelay(index)),
|
|
2126
|
+
easing: easeWire7,
|
|
2127
|
+
useNativeDriver: true
|
|
2128
|
+
}),
|
|
2129
|
+
Animated.spring(checkT, {
|
|
2130
|
+
toValue: 1,
|
|
2131
|
+
delay: scaledMs(interstitialCheckDelay(index)),
|
|
2132
|
+
friction: CHECK_SPRING.friction,
|
|
2133
|
+
tension: CHECK_SPRING.tension,
|
|
2134
|
+
useNativeDriver: true
|
|
2135
|
+
})
|
|
2136
|
+
];
|
|
2137
|
+
anims.forEach((a) => a.start());
|
|
2138
|
+
return () => anims.forEach((a) => a.stop());
|
|
2139
|
+
}, [reduced, index, rowT, checkT]);
|
|
2140
|
+
const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] });
|
|
2141
|
+
const checkScale = checkT.interpolate({
|
|
2142
|
+
inputRange: [0, 1],
|
|
2143
|
+
outputRange: [INTERSTITIAL_CHECK_SCALE_FROM, 1]
|
|
2144
|
+
});
|
|
2145
|
+
return /* @__PURE__ */ jsxs(
|
|
2146
|
+
Animated.View,
|
|
2147
|
+
{
|
|
2148
|
+
style: [styles17.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
|
|
2149
|
+
children: [
|
|
2150
|
+
/* @__PURE__ */ jsx(
|
|
2151
|
+
View,
|
|
2152
|
+
{
|
|
2153
|
+
style: [
|
|
2154
|
+
styles17.itemBadge,
|
|
2155
|
+
{ backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
|
|
2156
|
+
],
|
|
2157
|
+
children: /* @__PURE__ */ jsx(
|
|
2158
|
+
Animated.Text,
|
|
2159
|
+
{
|
|
2160
|
+
style: [
|
|
2161
|
+
styles17.itemCheck,
|
|
2162
|
+
{ color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
2163
|
+
],
|
|
2164
|
+
children: "\u2713"
|
|
2165
|
+
}
|
|
2166
|
+
)
|
|
2167
|
+
}
|
|
2168
|
+
),
|
|
2169
|
+
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), styles17.itemText, { color: t.colors.text }], children: text })
|
|
2170
|
+
]
|
|
2171
|
+
}
|
|
2172
|
+
);
|
|
2173
|
+
};
|
|
2174
|
+
var WorkItem = React14.memo(_WorkItem);
|
|
2175
|
+
var _InterstitialCard = ({
|
|
2176
|
+
title,
|
|
2177
|
+
body,
|
|
2178
|
+
items,
|
|
2179
|
+
illustration,
|
|
2180
|
+
imageUrl,
|
|
2181
|
+
illustrationFallback,
|
|
2182
|
+
ctaLabel = "Continue",
|
|
2183
|
+
onSubmit,
|
|
2184
|
+
onPress
|
|
2185
|
+
}) => {
|
|
2186
|
+
const t = useOnboardingTheme();
|
|
2187
|
+
const reduced = useReducedMotion();
|
|
2188
|
+
const node = useIllustration(illustration);
|
|
2189
|
+
const fallbackNode = useIllustration(illustrationFallback);
|
|
2190
|
+
const [submitted, setSubmitted] = useState(false);
|
|
2191
|
+
const [imgFailed, setImgFailed] = useState(false);
|
|
2192
|
+
const [canAdvance, setCanAdvance] = useState(false);
|
|
2193
|
+
useEffect(() => {
|
|
2194
|
+
const timer = setTimeout(() => setCanAdvance(true), MIN_DISPLAY_MS);
|
|
2195
|
+
return () => clearTimeout(timer);
|
|
2196
|
+
}, []);
|
|
2197
|
+
const artT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
2198
|
+
const titleT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
2199
|
+
const bodyT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
2200
|
+
useEffect(() => {
|
|
2201
|
+
if (reduced) {
|
|
2202
|
+
artT.setValue(1);
|
|
2203
|
+
titleT.setValue(1);
|
|
2204
|
+
bodyT.setValue(1);
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
const rise = (value, delay) => Animated.timing(value, {
|
|
2208
|
+
toValue: 1,
|
|
2209
|
+
duration: scaledMs(INTERSTITIAL_HEAD_MS),
|
|
2210
|
+
delay,
|
|
2211
|
+
easing: easeWire7,
|
|
2212
|
+
useNativeDriver: true
|
|
2213
|
+
});
|
|
2214
|
+
const anims = [
|
|
2215
|
+
rise(artT, 0),
|
|
2216
|
+
rise(titleT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS)),
|
|
2217
|
+
rise(bodyT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS * 2))
|
|
2218
|
+
];
|
|
2219
|
+
anims.forEach((a) => a.start());
|
|
2220
|
+
return () => anims.forEach((a) => a.stop());
|
|
2221
|
+
}, [reduced, artT, titleT, bodyT]);
|
|
2222
|
+
const riseStyle = (value) => ({
|
|
2223
|
+
opacity: value,
|
|
2224
|
+
transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }]
|
|
2225
|
+
});
|
|
2226
|
+
const handleContinue = useCallback(() => {
|
|
2227
|
+
if (submitted || !canAdvance) return;
|
|
2228
|
+
setSubmitted(true);
|
|
2229
|
+
if (onSubmit) onSubmit("continue");
|
|
2230
|
+
else if (onPress) onPress(ctaLabel);
|
|
2231
|
+
}, [submitted, canAdvance, onSubmit, onPress, ctaLabel]);
|
|
2232
|
+
const showImage = !node && !!imageUrl && !imgFailed;
|
|
2233
|
+
const art = node != null ? node : showImage ? void 0 : fallbackNode;
|
|
2234
|
+
return /* @__PURE__ */ jsx(
|
|
2235
|
+
CardLayout,
|
|
2236
|
+
{
|
|
2237
|
+
align: "center",
|
|
2238
|
+
footer: /* @__PURE__ */ jsx(
|
|
2239
|
+
Button,
|
|
2240
|
+
{
|
|
2241
|
+
title: ctaLabel,
|
|
2242
|
+
onPress: handleContinue,
|
|
2243
|
+
variant: "primary",
|
|
2244
|
+
disabled: submitted || !canAdvance
|
|
2245
|
+
}
|
|
2246
|
+
),
|
|
2247
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles17.center, { gap: t.spacing.md }], children: [
|
|
2248
|
+
/* @__PURE__ */ jsx(Animated.View, { style: [styles17.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsx(
|
|
2249
|
+
Image,
|
|
2250
|
+
{
|
|
2251
|
+
source: { uri: imageUrl },
|
|
2252
|
+
style: styles17.image,
|
|
2253
|
+
resizeMode: "contain",
|
|
2254
|
+
onError: () => setImgFailed(true)
|
|
2255
|
+
}
|
|
2256
|
+
) : art ? /* @__PURE__ */ jsx(View, { style: styles17.art, children: art }) : null }),
|
|
2257
|
+
/* @__PURE__ */ jsx(Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
|
|
2258
|
+
(items == null ? void 0 : items.length) ? /* @__PURE__ */ jsx(View, { style: [styles17.items, { gap: t.spacing.md, marginTop: t.spacing.sm }], children: items.map((item, i) => /* @__PURE__ */ jsx(WorkItem, { text: item, index: i, reduced }, item)) }) : body ? /* @__PURE__ */ jsx(Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: body }) }) : null
|
|
2259
|
+
] })
|
|
2260
|
+
}
|
|
2261
|
+
);
|
|
2262
|
+
};
|
|
2263
|
+
var InterstitialCard = {
|
|
2264
|
+
name: "InterstitialCard",
|
|
2265
|
+
description: "Use ONCE mid-flow (never first, never last) as a momentum/value screen between questions \u2014 an illustration + a warm line that reflects answers back, like Duolingo/Cal-AI. NOT a question; the user just taps Continue. Set `illustration` to an app-registered artwork name. Prefer `items` (2-3 short ticked lines, each reflecting a real answer) when you have the signal. Skip it if you don't have enough signal to make the line specific.",
|
|
2266
|
+
component: React14.memo(_InterstitialCard),
|
|
2267
|
+
propsSchema: schema6,
|
|
2268
|
+
defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
|
|
2269
|
+
};
|
|
2270
|
+
var styles17 = StyleSheet.create({
|
|
2271
|
+
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
2272
|
+
fullWidth: { width: "100%", alignItems: "center" },
|
|
2273
|
+
art: { alignItems: "center", justifyContent: "center" },
|
|
2274
|
+
image: { width: "100%", height: 200 },
|
|
2275
|
+
items: { width: "100%", alignSelf: "stretch" },
|
|
2276
|
+
itemRow: { flexDirection: "row", alignItems: "center" },
|
|
2277
|
+
itemBadge: { width: 24, height: 24, alignItems: "center", justifyContent: "center" },
|
|
2278
|
+
itemCheck: { fontSize: 13, fontWeight: "800", lineHeight: 15 },
|
|
2279
|
+
itemText: { flex: 1 }
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
// src/cards/index.ts
|
|
2283
|
+
var onboardingComponents = [
|
|
2284
|
+
ChipSelectCard,
|
|
2285
|
+
TextInputCard,
|
|
2286
|
+
SelectionCard,
|
|
2287
|
+
StatusCard,
|
|
2288
|
+
NumberStepperCard,
|
|
2289
|
+
InterstitialCard
|
|
2290
|
+
];
|
|
2291
|
+
var deriveFormFactor = (iosIdiom, width, height) => {
|
|
2292
|
+
if (iosIdiom === "pad") return "tablet";
|
|
2293
|
+
if (iosIdiom === "phone") return "phone";
|
|
2294
|
+
if (typeof width === "number" && typeof height === "number") {
|
|
2295
|
+
return Math.min(width, height) >= 600 ? "tablet" : "phone";
|
|
2296
|
+
}
|
|
2297
|
+
return void 0;
|
|
2298
|
+
};
|
|
2299
|
+
var collectDeviceContext = () => {
|
|
2300
|
+
var _a;
|
|
2301
|
+
const ctx = { platform: Platform.OS };
|
|
2302
|
+
try {
|
|
2303
|
+
const version = Platform.Version;
|
|
2304
|
+
if (version !== void 0 && version !== null && String(version)) {
|
|
2305
|
+
ctx.osVersion = String(version);
|
|
2306
|
+
}
|
|
2307
|
+
} catch {
|
|
2308
|
+
}
|
|
2309
|
+
let constants = {};
|
|
2310
|
+
try {
|
|
2311
|
+
constants = (_a = Platform.constants) != null ? _a : {};
|
|
2312
|
+
} catch {
|
|
2313
|
+
constants = {};
|
|
2314
|
+
}
|
|
2315
|
+
let iosIdiom;
|
|
2316
|
+
try {
|
|
2317
|
+
if (Platform.OS === "android") {
|
|
2318
|
+
const brand = constants.Brand;
|
|
2319
|
+
const model = constants.Model;
|
|
2320
|
+
const release = constants.Release;
|
|
2321
|
+
if (typeof brand === "string" && brand) ctx.brand = brand;
|
|
2322
|
+
if (typeof model === "string" && model) ctx.model = model;
|
|
2323
|
+
if (release !== void 0 && release !== null && String(release)) {
|
|
2324
|
+
ctx.osVersion = String(release);
|
|
2325
|
+
}
|
|
2326
|
+
} else if (Platform.OS === "ios") {
|
|
2327
|
+
const osVersion = constants.osVersion;
|
|
2328
|
+
const idiom = constants.interfaceIdiom;
|
|
2329
|
+
if (osVersion !== void 0 && osVersion !== null && String(osVersion)) {
|
|
2330
|
+
ctx.osVersion = String(osVersion);
|
|
2331
|
+
}
|
|
2332
|
+
if (typeof idiom === "string" && idiom) {
|
|
2333
|
+
ctx.interfaceIdiom = idiom;
|
|
2334
|
+
iosIdiom = idiom;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
} catch {
|
|
2338
|
+
}
|
|
2339
|
+
try {
|
|
2340
|
+
const screen = Dimensions.get("screen");
|
|
2341
|
+
if (screen) {
|
|
2342
|
+
if (typeof screen.width === "number") ctx.screenWidth = screen.width;
|
|
2343
|
+
if (typeof screen.height === "number") ctx.screenHeight = screen.height;
|
|
2344
|
+
if (typeof screen.scale === "number") ctx.screenScale = screen.scale;
|
|
2345
|
+
const formFactor = deriveFormFactor(iosIdiom, screen.width, screen.height);
|
|
2346
|
+
if (formFactor) ctx.formFactor = formFactor;
|
|
2347
|
+
}
|
|
2348
|
+
} catch {
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
ctx.isRTL = I18nManager.isRTL;
|
|
2352
|
+
} catch {
|
|
2353
|
+
}
|
|
2354
|
+
try {
|
|
2355
|
+
const resolved = Intl.DateTimeFormat().resolvedOptions();
|
|
2356
|
+
if (resolved.locale) ctx.locale = resolved.locale;
|
|
2357
|
+
if (resolved.timeZone) ctx.timeZone = resolved.timeZone;
|
|
2358
|
+
} catch {
|
|
2359
|
+
}
|
|
2360
|
+
return ctx;
|
|
2361
|
+
};
|
|
2362
|
+
|
|
2363
|
+
// src/session/persistedSession.ts
|
|
2364
|
+
var DEFAULT_SESSION_TTL_MS = 36e5;
|
|
2365
|
+
var READ_TIMEOUT_MS = 1500;
|
|
2366
|
+
var sessionStorageKey = (appId) => `wireai:session:${appId}`;
|
|
2367
|
+
var withTimeout = (p, ms) => {
|
|
2368
|
+
let timer;
|
|
2369
|
+
const timeout = new Promise((resolve) => {
|
|
2370
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
2371
|
+
});
|
|
2372
|
+
return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
|
|
2373
|
+
};
|
|
2374
|
+
var parsePersisted = (raw) => {
|
|
2375
|
+
if (!raw) return void 0;
|
|
2376
|
+
try {
|
|
2377
|
+
const parsed = JSON.parse(raw);
|
|
2378
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.id === "string" && typeof parsed.ts === "number") {
|
|
2379
|
+
return parsed;
|
|
2380
|
+
}
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
return void 0;
|
|
2384
|
+
};
|
|
2385
|
+
var loadPersistedSession = async (storage, key, ttlMs = DEFAULT_SESSION_TTL_MS) => {
|
|
2386
|
+
let stored;
|
|
2387
|
+
try {
|
|
2388
|
+
stored = parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));
|
|
2389
|
+
} catch {
|
|
2390
|
+
stored = void 0;
|
|
2391
|
+
}
|
|
2392
|
+
if (stored && Date.now() - stored.ts < ttlMs) {
|
|
2393
|
+
return { id: stored.id, resumed: true };
|
|
2394
|
+
}
|
|
2395
|
+
const id = makeSessionId();
|
|
2396
|
+
savePersistedSession(storage, key, id);
|
|
2397
|
+
return { id, resumed: false };
|
|
2398
|
+
};
|
|
2399
|
+
var peekPersistedSession = async (storage, key) => {
|
|
2400
|
+
try {
|
|
2401
|
+
return parsePersisted(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS));
|
|
2402
|
+
} catch {
|
|
2403
|
+
return void 0;
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
var savePersistedSession = (storage, key, id) => {
|
|
2407
|
+
try {
|
|
2408
|
+
void storage.setItem(key, JSON.stringify({ id, ts: Date.now() })).catch(() => {
|
|
2409
|
+
});
|
|
2410
|
+
} catch {
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
var clearPersistedSession = (storage, key) => {
|
|
2414
|
+
try {
|
|
2415
|
+
void storage.removeItem(key).catch(() => {
|
|
2416
|
+
});
|
|
2417
|
+
} catch {
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
// src/identity/userIdentity.ts
|
|
2422
|
+
var USER_ID_MAX_LENGTH = 128;
|
|
2423
|
+
var sanitizeUserId = (raw) => {
|
|
2424
|
+
if (typeof raw !== "string") return void 0;
|
|
2425
|
+
const trimmed = raw.trim();
|
|
2426
|
+
if (!trimmed) return void 0;
|
|
2427
|
+
return trimmed.length > USER_ID_MAX_LENGTH ? trimmed.slice(0, USER_ID_MAX_LENGTH) : trimmed;
|
|
2428
|
+
};
|
|
2429
|
+
var identifyOnboarding = async (opts) => {
|
|
2430
|
+
var _a, _b, _c;
|
|
2431
|
+
const userId = sanitizeUserId(opts.userId);
|
|
2432
|
+
if (!userId || !((_a = opts.config) == null ? void 0 : _a.serverUrl)) return false;
|
|
2433
|
+
let contextId = ((_b = opts.contextId) == null ? void 0 : _b.trim()) || void 0;
|
|
2434
|
+
if (!contextId && opts.storage) {
|
|
2435
|
+
const key = (_c = opts.persistKey) != null ? _c : opts.appId ? sessionStorageKey(opts.appId) : void 0;
|
|
2436
|
+
if (key) {
|
|
2437
|
+
const stored = await peekPersistedSession(opts.storage, key);
|
|
2438
|
+
contextId = stored == null ? void 0 : stored.id;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (!contextId) return false;
|
|
2442
|
+
reportClientEvent(
|
|
2443
|
+
{ serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
|
|
2444
|
+
{ event_type: "identify", session_id: contextId, user_id: userId }
|
|
2445
|
+
);
|
|
2446
|
+
return true;
|
|
2447
|
+
};
|
|
2448
|
+
var WireOnboarding = ({
|
|
2449
|
+
config,
|
|
2450
|
+
theme,
|
|
2451
|
+
components,
|
|
2452
|
+
illustrations,
|
|
2453
|
+
validators,
|
|
2454
|
+
onComplete,
|
|
2455
|
+
onSkip,
|
|
2456
|
+
onError,
|
|
2457
|
+
onEvent,
|
|
2458
|
+
copy,
|
|
2459
|
+
approxScreens,
|
|
2460
|
+
startMessage = "start",
|
|
2461
|
+
startTimeoutMs = 15e3,
|
|
2462
|
+
fallbackFlow,
|
|
2463
|
+
maxRetries = 1,
|
|
2464
|
+
storage,
|
|
2465
|
+
sessionTtlMs = DEFAULT_SESSION_TTL_MS,
|
|
2466
|
+
persistKey,
|
|
2467
|
+
userContext,
|
|
2468
|
+
userId
|
|
2469
|
+
}) => {
|
|
2470
|
+
var _a, _b, _c;
|
|
2471
|
+
const boundUserId = useMemo(() => sanitizeUserId(userId), [userId]);
|
|
2472
|
+
const device = useMemo(() => {
|
|
2473
|
+
const collected = collectDeviceContext();
|
|
2474
|
+
return config.appVersion ? { ...collected, appVersion: config.appVersion } : collected;
|
|
2475
|
+
}, [config.appVersion]);
|
|
2476
|
+
const userContextKey = userContext ? JSON.stringify(userContext) : "";
|
|
2477
|
+
const userContextStable = useMemo(
|
|
2478
|
+
() => userContextKey ? JSON.parse(userContextKey) : void 0,
|
|
2479
|
+
[userContextKey]
|
|
2480
|
+
);
|
|
2481
|
+
const clientContext = useMemo(
|
|
2482
|
+
() => ({ device, userContext: userContextStable, userId: boundUserId }),
|
|
2483
|
+
[device, userContextStable, boundUserId]
|
|
2484
|
+
);
|
|
2485
|
+
const [session, setSession] = useState(
|
|
2486
|
+
() => storage ? null : { id: makeSessionId(), resumed: false }
|
|
2487
|
+
);
|
|
2488
|
+
const storageKey = persistKey != null ? persistKey : sessionStorageKey(config.appId);
|
|
2489
|
+
useEffect(() => {
|
|
2490
|
+
if (!storage || session) return;
|
|
2491
|
+
let cancelled = false;
|
|
2492
|
+
void loadPersistedSession(storage, storageKey, sessionTtlMs).then((loaded) => {
|
|
2493
|
+
if (!cancelled) setSession(loaded);
|
|
2494
|
+
});
|
|
2495
|
+
return () => {
|
|
2496
|
+
cancelled = true;
|
|
2497
|
+
};
|
|
2498
|
+
}, [storage, storageKey, sessionTtlMs, session]);
|
|
2499
|
+
const handleComplete = useCallback(
|
|
2500
|
+
(result) => {
|
|
2501
|
+
if (storage) clearPersistedSession(storage, storageKey);
|
|
2502
|
+
onComplete(result);
|
|
2503
|
+
},
|
|
2504
|
+
[storage, storageKey, onComplete]
|
|
2505
|
+
);
|
|
2506
|
+
const sessionId = (_a = session == null ? void 0 : session.id) != null ? _a : "";
|
|
2507
|
+
const startupUserIdRef = useRef(boundUserId);
|
|
2508
|
+
const llm = useMemo(() => {
|
|
2509
|
+
const baseUrl = config.serverUrl.replace(/\/$/, "");
|
|
2510
|
+
return {
|
|
2511
|
+
provider: "a2a",
|
|
2512
|
+
baseUrl: `${baseUrl}/a2a`,
|
|
2513
|
+
model: config.appId,
|
|
2514
|
+
apiKey: config.apiKey,
|
|
2515
|
+
// Carry the kit session id alongside any host attribution so it reaches the A2A
|
|
2516
|
+
// request's `params.metadata` (the correlation seed; see note above), plus the
|
|
2517
|
+
// names of the components this device can actually render — so the backend only
|
|
2518
|
+
// emits cards the kit knows how to draw.
|
|
2519
|
+
metadata: {
|
|
2520
|
+
...config.metadata,
|
|
2521
|
+
sessionId,
|
|
2522
|
+
supportedComponents: (components != null ? components : onboardingComponents).map((c) => c.name),
|
|
2523
|
+
// Device snapshot + host-injected user context ride the session-start metadata so the
|
|
2524
|
+
// backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
|
|
2525
|
+
device,
|
|
2526
|
+
...userContextStable ? { userContext: userContextStable } : {},
|
|
2527
|
+
// The session-start user id (from the ref) rides the session-start metadata so the
|
|
2528
|
+
// server binds the session to a real user at creation. Reading the ref — not
|
|
2529
|
+
// `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
|
|
2530
|
+
// does NOT recreate the adapter; it's bound by the `identify` effect below instead.
|
|
2531
|
+
...startupUserIdRef.current ? { userId: startupUserIdRef.current } : {}
|
|
2532
|
+
},
|
|
2533
|
+
timeoutMs: 6e4
|
|
2534
|
+
};
|
|
2535
|
+
}, [config.serverUrl, config.appId, config.apiKey, config.metadata, sessionId, components, device, userContextStable]);
|
|
2536
|
+
const reportTarget = useMemo(
|
|
2537
|
+
() => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
|
|
2538
|
+
[config.serverUrl, config.apiKey]
|
|
2539
|
+
);
|
|
2540
|
+
const identifiedRef = useRef(void 0);
|
|
2541
|
+
const identifySeededRef = useRef(false);
|
|
2542
|
+
useEffect(() => {
|
|
2543
|
+
if (!session || !sessionId) return;
|
|
2544
|
+
if (!identifySeededRef.current) {
|
|
2545
|
+
identifySeededRef.current = true;
|
|
2546
|
+
identifiedRef.current = session.resumed ? void 0 : boundUserId;
|
|
2547
|
+
}
|
|
2548
|
+
if (!boundUserId || boundUserId === identifiedRef.current) return;
|
|
2549
|
+
identifiedRef.current = boundUserId;
|
|
2550
|
+
reportClientEvent(reportTarget, {
|
|
2551
|
+
event_type: "identify",
|
|
2552
|
+
session_id: sessionId,
|
|
2553
|
+
user_id: boundUserId
|
|
2554
|
+
});
|
|
2555
|
+
}, [session, sessionId, boundUserId, reportTarget]);
|
|
2556
|
+
const cards = components != null ? components : onboardingComponents;
|
|
2557
|
+
if (!session) {
|
|
2558
|
+
return /* @__PURE__ */ jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsx(
|
|
2559
|
+
LoadingScreen,
|
|
2560
|
+
{
|
|
2561
|
+
title: (_b = copy == null ? void 0 : copy.restoringTitle) != null ? _b : DEFAULT_COPY.restoringTitle,
|
|
2562
|
+
hint: (_c = copy == null ? void 0 : copy.restoringHint) != null ? _c : DEFAULT_COPY.restoringHint
|
|
2563
|
+
}
|
|
2564
|
+
) });
|
|
2565
|
+
}
|
|
2566
|
+
return /* @__PURE__ */ jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsx(IllustrationProvider, { registry: { ...defaultIllustrations, ...illustrations }, children: /* @__PURE__ */ jsx(WireAIProvider, { llm, components: cards, children: /* @__PURE__ */ jsx(
|
|
2567
|
+
OnboardingFlow,
|
|
2568
|
+
{
|
|
2569
|
+
validators,
|
|
2570
|
+
onComplete: handleComplete,
|
|
2571
|
+
onSkip,
|
|
2572
|
+
onError,
|
|
2573
|
+
onEvent,
|
|
2574
|
+
copy,
|
|
2575
|
+
approxScreens,
|
|
2576
|
+
startMessage,
|
|
2577
|
+
startTimeoutMs,
|
|
2578
|
+
fallbackFlow,
|
|
2579
|
+
maxRetries,
|
|
2580
|
+
reportTarget,
|
|
2581
|
+
sessionId,
|
|
2582
|
+
resumed: session.resumed,
|
|
2583
|
+
clientContext
|
|
2584
|
+
}
|
|
2585
|
+
) }) }) });
|
|
2586
|
+
};
|
|
2587
|
+
function DemoOnboarding({
|
|
2588
|
+
config,
|
|
2589
|
+
theme,
|
|
2590
|
+
illustrations,
|
|
2591
|
+
validators,
|
|
2592
|
+
copy,
|
|
2593
|
+
label = "Demo onboarding",
|
|
2594
|
+
onComplete,
|
|
2595
|
+
renderTrigger
|
|
2596
|
+
}) {
|
|
2597
|
+
const [open, setOpen] = useState(false);
|
|
2598
|
+
const [runId, setRunId] = useState(0);
|
|
2599
|
+
const t = useMemo(() => mergeTheme(theme != null ? theme : {}), [theme]);
|
|
2600
|
+
const styles20 = useMemo(() => makeStyles(t), [t]);
|
|
2601
|
+
const openDemo = useCallback(() => {
|
|
2602
|
+
setRunId((n) => n + 1);
|
|
2603
|
+
setOpen(true);
|
|
2604
|
+
}, []);
|
|
2605
|
+
const close = useCallback(() => setOpen(false), []);
|
|
2606
|
+
const finish = useCallback(
|
|
2607
|
+
(result) => {
|
|
2608
|
+
onComplete == null ? void 0 : onComplete(result);
|
|
2609
|
+
setOpen(false);
|
|
2610
|
+
},
|
|
2611
|
+
[onComplete]
|
|
2612
|
+
);
|
|
2613
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2614
|
+
renderTrigger ? renderTrigger(openDemo) : /* @__PURE__ */ jsx(Pressable, { onPress: openDemo, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles20.triggerText, children: label }) }),
|
|
2615
|
+
/* @__PURE__ */ jsx(
|
|
2616
|
+
Modal,
|
|
2617
|
+
{
|
|
2618
|
+
visible: open,
|
|
2619
|
+
animationType: "slide",
|
|
2620
|
+
presentationStyle: "fullScreen",
|
|
2621
|
+
onRequestClose: close,
|
|
2622
|
+
children: /* @__PURE__ */ jsx(View, { style: styles20.modal, children: config ? /* @__PURE__ */ jsx(
|
|
2623
|
+
WireOnboarding,
|
|
2624
|
+
{
|
|
2625
|
+
config,
|
|
2626
|
+
theme,
|
|
2627
|
+
illustrations,
|
|
2628
|
+
validators,
|
|
2629
|
+
copy,
|
|
2630
|
+
onComplete: finish,
|
|
2631
|
+
onSkip: close,
|
|
2632
|
+
onError: close
|
|
2633
|
+
},
|
|
2634
|
+
runId
|
|
2635
|
+
) : /* @__PURE__ */ jsxs(View, { style: styles20.notice, children: [
|
|
2636
|
+
/* @__PURE__ */ jsx(Text, { style: styles20.noticeTitle, children: "Wire AI not configured" }),
|
|
2637
|
+
/* @__PURE__ */ jsx(Text, { style: styles20.noticeBody, children: "Set the Wire AI key + server URL (e.g. via wireConfigFromEnv) to demo the AI onboarding here." }),
|
|
2638
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: close, style: styles20.trigger, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles20.triggerText, children: "Close" }) })
|
|
2639
|
+
] }) })
|
|
2640
|
+
}
|
|
2641
|
+
)
|
|
2642
|
+
] });
|
|
2643
|
+
}
|
|
2644
|
+
function makeStyles(t) {
|
|
2645
|
+
return StyleSheet.create({
|
|
2646
|
+
trigger: {
|
|
2647
|
+
backgroundColor: t.colors.primary,
|
|
2648
|
+
borderRadius: t.button.radius,
|
|
2649
|
+
paddingVertical: 14,
|
|
2650
|
+
paddingHorizontal: 20,
|
|
2651
|
+
alignItems: "center"
|
|
2652
|
+
},
|
|
2653
|
+
triggerText: { color: t.colors.onPrimary, fontFamily: t.fonts.medium, fontSize: 16 },
|
|
2654
|
+
modal: { flex: 1, backgroundColor: t.colors.background },
|
|
2655
|
+
notice: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24, gap: 12 },
|
|
2656
|
+
noticeTitle: { color: t.colors.text, fontFamily: t.fonts.bold, fontSize: 20 },
|
|
2657
|
+
noticeBody: {
|
|
2658
|
+
color: t.colors.textMuted,
|
|
2659
|
+
fontFamily: t.fonts.regular,
|
|
2660
|
+
fontSize: 15,
|
|
2661
|
+
textAlign: "center"
|
|
2662
|
+
}
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// src/theme/themeFromBrand.ts
|
|
2667
|
+
var hexToRgb = (hex) => {
|
|
2668
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
2669
|
+
if (!m) return null;
|
|
2670
|
+
let h = m[1];
|
|
2671
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
2672
|
+
const int = parseInt(h, 16);
|
|
2673
|
+
return [int >> 16 & 255, int >> 8 & 255, int & 255];
|
|
2674
|
+
};
|
|
2675
|
+
var softFromPrimary = (primary) => {
|
|
2676
|
+
const rgb = hexToRgb(primary);
|
|
2677
|
+
if (!rgb) return defaultOnboardingTheme.colors.primarySoft;
|
|
2678
|
+
const [r, g, b] = rgb;
|
|
2679
|
+
return `rgba(${r},${g},${b},0.10)`;
|
|
2680
|
+
};
|
|
2681
|
+
var themeFromBrand = (input) => {
|
|
2682
|
+
const { primary, background, surface, text, onPrimary, font, extra } = input;
|
|
2683
|
+
const derived = {
|
|
2684
|
+
colors: {
|
|
2685
|
+
...defaultOnboardingTheme.colors,
|
|
2686
|
+
primary,
|
|
2687
|
+
primarySoft: softFromPrimary(primary),
|
|
2688
|
+
onPrimary: onPrimary != null ? onPrimary : defaultOnboardingTheme.colors.onPrimary,
|
|
2689
|
+
...background ? { background } : {},
|
|
2690
|
+
...surface ? { surface } : {},
|
|
2691
|
+
...text ? { text } : {}
|
|
2692
|
+
},
|
|
2693
|
+
...font ? {
|
|
2694
|
+
fonts: {
|
|
2695
|
+
...defaultOnboardingTheme.fonts,
|
|
2696
|
+
regular: font,
|
|
2697
|
+
medium: font,
|
|
2698
|
+
bold: font
|
|
2699
|
+
}
|
|
2700
|
+
} : {}
|
|
2701
|
+
};
|
|
2702
|
+
const base = mergeTheme(derived);
|
|
2703
|
+
if (!extra) return base;
|
|
2704
|
+
return {
|
|
2705
|
+
colors: { ...base.colors, ...extra.colors },
|
|
2706
|
+
fonts: { ...base.fonts, ...extra.fonts },
|
|
2707
|
+
radius: { ...base.radius, ...extra.radius },
|
|
2708
|
+
spacing: { ...base.spacing, ...extra.spacing },
|
|
2709
|
+
button: { ...base.button, ...extra.button }
|
|
2710
|
+
};
|
|
2711
|
+
};
|
|
2712
|
+
var _DoneBlock = ({
|
|
2713
|
+
title = "You're all set",
|
|
2714
|
+
message = "Personalizing your experience\u2026"
|
|
2715
|
+
}) => {
|
|
2716
|
+
const t = useOnboardingTheme();
|
|
2717
|
+
return /* @__PURE__ */ jsxs(View, { style: [styles18.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
|
|
2718
|
+
/* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
|
|
2719
|
+
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
|
|
2720
|
+
] });
|
|
2721
|
+
};
|
|
2722
|
+
var DoneBlock = React14.memo(_DoneBlock);
|
|
2723
|
+
var styles18 = StyleSheet.create({
|
|
2724
|
+
center: { flex: 1, alignItems: "center", justifyContent: "center" }
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2727
|
+
// src/motion/centeredModalMotion.ts
|
|
2728
|
+
var CENTERED_MODAL_ANIM = {
|
|
2729
|
+
/** Spring config for the card scale/opacity entrance (mirrors CompletionView's pop). */
|
|
2730
|
+
enterSpring: { friction: 7, tension: 80 },
|
|
2731
|
+
/** Exit timing in ms (fade + shrink back out before unmount). */
|
|
2732
|
+
exitMs: 160,
|
|
2733
|
+
/** Card scale at rest (progress 0); grows to 1 at progress 1. */
|
|
2734
|
+
scaleFrom: 0.9,
|
|
2735
|
+
/** Peak dim of the backdrop at progress 1. */
|
|
2736
|
+
backdropOpacity: 0.55
|
|
2737
|
+
};
|
|
2738
|
+
var _CenteredModal = forwardRef(
|
|
2739
|
+
({ theme, dismissible = true, onDismiss, children }, ref) => {
|
|
2740
|
+
const progress = useRef(new Animated.Value(0)).current;
|
|
2741
|
+
const [open, setOpen] = useState(true);
|
|
2742
|
+
const closingRef = useRef(false);
|
|
2743
|
+
const reduced = useReducedMotion();
|
|
2744
|
+
useEffect(() => {
|
|
2745
|
+
const anim = reduced ? Animated.timing(progress, {
|
|
2746
|
+
toValue: 1,
|
|
2747
|
+
duration: REDUCED_FADE_MS,
|
|
2748
|
+
useNativeDriver: true
|
|
2749
|
+
}) : Animated.spring(progress, {
|
|
2750
|
+
toValue: 1,
|
|
2751
|
+
useNativeDriver: true,
|
|
2752
|
+
friction: CENTERED_MODAL_ANIM.enterSpring.friction,
|
|
2753
|
+
tension: CENTERED_MODAL_ANIM.enterSpring.tension
|
|
2754
|
+
});
|
|
2755
|
+
anim.start();
|
|
2756
|
+
return () => anim.stop();
|
|
2757
|
+
}, [progress, reduced]);
|
|
2758
|
+
const close = useCallback(() => {
|
|
2759
|
+
if (closingRef.current) return;
|
|
2760
|
+
closingRef.current = true;
|
|
2761
|
+
Animated.timing(progress, {
|
|
2762
|
+
toValue: 0,
|
|
2763
|
+
duration: reduced ? REDUCED_FADE_MS : CENTERED_MODAL_ANIM.exitMs,
|
|
2764
|
+
easing: Easing.in(Easing.cubic),
|
|
2765
|
+
useNativeDriver: true
|
|
2766
|
+
}).start(() => {
|
|
2767
|
+
setOpen(false);
|
|
2768
|
+
onDismiss();
|
|
2769
|
+
});
|
|
2770
|
+
}, [progress, onDismiss, reduced]);
|
|
2771
|
+
useImperativeHandle(ref, () => ({ close }), [close]);
|
|
2772
|
+
useEffect(() => {
|
|
2773
|
+
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
|
|
2774
|
+
if (dismissible) close();
|
|
2775
|
+
return true;
|
|
2776
|
+
});
|
|
2777
|
+
return () => sub.remove();
|
|
2778
|
+
}, [dismissible, close]);
|
|
2779
|
+
const onBackdrop = useCallback(() => {
|
|
2780
|
+
if (dismissible) close();
|
|
2781
|
+
}, [dismissible, close]);
|
|
2782
|
+
const backdropOpacity = progress.interpolate({
|
|
2783
|
+
inputRange: [0, 1],
|
|
2784
|
+
outputRange: [0, CENTERED_MODAL_ANIM.backdropOpacity]
|
|
2785
|
+
});
|
|
2786
|
+
const cardScale = reduced ? 1 : progress.interpolate({
|
|
2787
|
+
inputRange: [0, 1],
|
|
2788
|
+
outputRange: [CENTERED_MODAL_ANIM.scaleFrom, 1]
|
|
2789
|
+
});
|
|
2790
|
+
return /* @__PURE__ */ jsxs(
|
|
2791
|
+
Modal,
|
|
2792
|
+
{
|
|
2793
|
+
visible: open,
|
|
2794
|
+
transparent: true,
|
|
2795
|
+
animationType: "none",
|
|
2796
|
+
statusBarTranslucent: true,
|
|
2797
|
+
onRequestClose: dismissible ? close : void 0,
|
|
2798
|
+
children: [
|
|
2799
|
+
/* @__PURE__ */ jsx(
|
|
2800
|
+
Animated.View,
|
|
2801
|
+
{
|
|
2802
|
+
style: [styles19.backdrop, { opacity: backdropOpacity }],
|
|
2803
|
+
pointerEvents: "none"
|
|
2804
|
+
}
|
|
2805
|
+
),
|
|
2806
|
+
/* @__PURE__ */ jsx(
|
|
2807
|
+
Pressable,
|
|
2808
|
+
{
|
|
2809
|
+
style: styles19.fill,
|
|
2810
|
+
onPress: onBackdrop,
|
|
2811
|
+
accessibilityRole: "button",
|
|
2812
|
+
accessibilityLabel: "Dismiss",
|
|
2813
|
+
children: /* @__PURE__ */ jsx(SafeAreaView, { style: styles19.center, pointerEvents: "box-none", children: /* @__PURE__ */ jsx(Pressable, { onPress: () => {
|
|
2814
|
+
}, style: styles19.cardWrap, children: /* @__PURE__ */ jsx(
|
|
2815
|
+
Animated.View,
|
|
2816
|
+
{
|
|
2817
|
+
accessibilityViewIsModal: true,
|
|
2818
|
+
accessibilityRole: "alert",
|
|
2819
|
+
style: [
|
|
2820
|
+
styles19.card,
|
|
2821
|
+
{
|
|
2822
|
+
backgroundColor: theme.colors.surface,
|
|
2823
|
+
borderRadius: theme.radius.lg,
|
|
2824
|
+
padding: theme.spacing.lg,
|
|
2825
|
+
gap: theme.spacing.md,
|
|
2826
|
+
opacity: progress,
|
|
2827
|
+
transform: [{ scale: cardScale }]
|
|
2828
|
+
}
|
|
2829
|
+
],
|
|
2830
|
+
children
|
|
2831
|
+
}
|
|
2832
|
+
) }) })
|
|
2833
|
+
}
|
|
2834
|
+
)
|
|
2835
|
+
]
|
|
2836
|
+
}
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
);
|
|
2840
|
+
_CenteredModal.displayName = "CenteredModal";
|
|
2841
|
+
var CenteredModal = React14.memo(_CenteredModal);
|
|
2842
|
+
var styles19 = StyleSheet.create({
|
|
2843
|
+
fill: { flex: 1 },
|
|
2844
|
+
backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
|
|
2845
|
+
center: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
|
|
2846
|
+
cardWrap: { width: "100%", maxWidth: 420 },
|
|
2847
|
+
card: {
|
|
2848
|
+
width: "100%",
|
|
2849
|
+
// Soft elevation so the popup reads as lifted above the app in both themes.
|
|
2850
|
+
shadowColor: "#000",
|
|
2851
|
+
shadowOffset: { width: 0, height: 8 },
|
|
2852
|
+
shadowOpacity: 0.28,
|
|
2853
|
+
shadowRadius: 24,
|
|
2854
|
+
elevation: 12
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2858
|
+
// src/features/defaults.ts
|
|
2859
|
+
var defaultWireFeatures = Object.freeze({
|
|
2860
|
+
coachmarks: Object.freeze({ enabled: true }),
|
|
2861
|
+
showcase: Object.freeze({ enabled: true }),
|
|
2862
|
+
onboarding: Object.freeze({ mode: "ai" }),
|
|
2863
|
+
review: Object.freeze({ enabled: true }),
|
|
2864
|
+
questionnaire: Object.freeze({ enabled: true }),
|
|
2865
|
+
announcements: Object.freeze({ enabled: true })
|
|
2866
|
+
});
|
|
2867
|
+
var VALID_MODES = ["ai", "static", "off"];
|
|
2868
|
+
var readEnabled = (raw) => {
|
|
2869
|
+
if (raw && typeof raw === "object" && typeof raw.enabled === "boolean") {
|
|
2870
|
+
return { enabled: raw.enabled };
|
|
2871
|
+
}
|
|
2872
|
+
return { enabled: true };
|
|
2873
|
+
};
|
|
2874
|
+
var parseWireFeatures = (raw) => {
|
|
2875
|
+
var _a;
|
|
2876
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return defaultWireFeatures;
|
|
2877
|
+
const src = raw;
|
|
2878
|
+
const modeRaw = (_a = src.onboarding) == null ? void 0 : _a.mode;
|
|
2879
|
+
const mode = typeof modeRaw === "string" && VALID_MODES.includes(modeRaw) ? modeRaw : "ai";
|
|
2880
|
+
return {
|
|
2881
|
+
coachmarks: readEnabled(src.coachmarks),
|
|
2882
|
+
showcase: readEnabled(src.showcase),
|
|
2883
|
+
onboarding: { mode },
|
|
2884
|
+
review: readEnabled(src.review),
|
|
2885
|
+
questionnaire: readEnabled(src.questionnaire),
|
|
2886
|
+
announcements: readEnabled(src.announcements)
|
|
2887
|
+
};
|
|
2888
|
+
};
|
|
2889
|
+
var featuresEqual = (a, b) => a === b || a.coachmarks.enabled === b.coachmarks.enabled && a.showcase.enabled === b.showcase.enabled && a.onboarding.mode === b.onboarding.mode && a.review.enabled === b.review.enabled && a.questionnaire.enabled === b.questionnaire.enabled && a.announcements.enabled === b.announcements.enabled;
|
|
2890
|
+
|
|
2891
|
+
// src/features/cache.ts
|
|
2892
|
+
var DEFAULT_FEATURES_TTL_MS = 3e5;
|
|
2893
|
+
var READ_TIMEOUT_MS2 = 1500;
|
|
2894
|
+
var featuresCacheKey = (appId) => `wireai:features:${appId && appId.length > 0 ? appId : "default"}`;
|
|
2895
|
+
var withTimeout2 = (p, ms) => {
|
|
2896
|
+
let timer;
|
|
2897
|
+
const timeout = new Promise((resolve) => {
|
|
2898
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
2899
|
+
});
|
|
2900
|
+
return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
|
|
2901
|
+
};
|
|
2902
|
+
var isFeaturesFresh = (ageMs, ttlMs = DEFAULT_FEATURES_TTL_MS) => ageMs >= 0 && ageMs < ttlMs;
|
|
2903
|
+
var readCachedFeatures = async (storage, key) => {
|
|
2904
|
+
let raw;
|
|
2905
|
+
try {
|
|
2906
|
+
raw = await withTimeout2(storage.getItem(key), READ_TIMEOUT_MS2);
|
|
2907
|
+
} catch {
|
|
2908
|
+
return void 0;
|
|
2909
|
+
}
|
|
2910
|
+
if (!raw) return void 0;
|
|
2911
|
+
try {
|
|
2912
|
+
const parsed = JSON.parse(raw);
|
|
2913
|
+
if (parsed && typeof parsed === "object" && typeof parsed.ts === "number") {
|
|
2914
|
+
const ts = parsed.ts;
|
|
2915
|
+
return {
|
|
2916
|
+
features: parseWireFeatures(parsed.features),
|
|
2917
|
+
ageMs: Date.now() - ts
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
} catch {
|
|
2921
|
+
}
|
|
2922
|
+
return void 0;
|
|
2923
|
+
};
|
|
2924
|
+
var writeCachedFeatures = (storage, key, features) => {
|
|
2925
|
+
try {
|
|
2926
|
+
void storage.setItem(key, JSON.stringify({ features, ts: Date.now() })).catch(() => {
|
|
2927
|
+
});
|
|
2928
|
+
} catch {
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
// src/features/fetchWireFeatures.ts
|
|
2933
|
+
var DEFAULT_TIMEOUT_MS = 4e3;
|
|
2934
|
+
var failOpen = async (storage, key) => {
|
|
2935
|
+
var _a;
|
|
2936
|
+
if (!storage) return defaultWireFeatures;
|
|
2937
|
+
const cached = await readCachedFeatures(storage, key);
|
|
2938
|
+
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
2939
|
+
};
|
|
2940
|
+
var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
|
|
2941
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
2942
|
+
const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
|
|
2943
|
+
try {
|
|
2944
|
+
return await fetch(url, {
|
|
2945
|
+
method: "GET",
|
|
2946
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
2947
|
+
signal: controller == null ? void 0 : controller.signal
|
|
2948
|
+
});
|
|
2949
|
+
} finally {
|
|
2950
|
+
clearTimeout(timer);
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
var fetchWireFeatures = async (config) => {
|
|
2954
|
+
const key = featuresCacheKey(config == null ? void 0 : config.appId);
|
|
2955
|
+
const storage = config == null ? void 0 : config.storage;
|
|
2956
|
+
if (!(config == null ? void 0 : config.serverUrl) || !(config == null ? void 0 : config.apiKey)) return failOpen(storage, key);
|
|
2957
|
+
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
2958
|
+
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
2959
|
+
try {
|
|
2960
|
+
const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
|
|
2961
|
+
if (!res || !res.ok) return failOpen(storage, key);
|
|
2962
|
+
const json = await res.json();
|
|
2963
|
+
const features = parseWireFeatures(json);
|
|
2964
|
+
if (storage) writeCachedFeatures(storage, key, features);
|
|
2965
|
+
return features;
|
|
2966
|
+
} catch {
|
|
2967
|
+
return failOpen(storage, key);
|
|
2968
|
+
}
|
|
2969
|
+
};
|
|
2970
|
+
var useStableValue = (value, isEqual) => {
|
|
2971
|
+
const ref = useRef(value);
|
|
2972
|
+
if (!isEqual(ref.current, value)) ref.current = value;
|
|
2973
|
+
return ref.current;
|
|
2974
|
+
};
|
|
2975
|
+
var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
|
|
2976
|
+
var useWireFeatures = (config) => {
|
|
2977
|
+
const stableConfig = useStableValue(config, sameFetchKey);
|
|
2978
|
+
const [flags, setFlags] = useState(defaultWireFeatures);
|
|
2979
|
+
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
2980
|
+
useEffect(() => {
|
|
2981
|
+
if (!canFetch) {
|
|
2982
|
+
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
let alive = true;
|
|
2986
|
+
void fetchWireFeatures(stableConfig).then((next) => {
|
|
2987
|
+
if (!alive) return;
|
|
2988
|
+
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
2989
|
+
});
|
|
2990
|
+
return () => {
|
|
2991
|
+
alive = false;
|
|
2992
|
+
};
|
|
2993
|
+
}, [canFetch, stableConfig]);
|
|
2994
|
+
return flags;
|
|
2995
|
+
};
|
|
2996
|
+
var WireFeaturesContext = createContext(null);
|
|
2997
|
+
var WireFeaturesProvider = ({
|
|
2998
|
+
config,
|
|
2999
|
+
flags,
|
|
3000
|
+
children
|
|
3001
|
+
}) => {
|
|
3002
|
+
const fetched = useWireFeatures(flags ? void 0 : config);
|
|
3003
|
+
const value = flags != null ? flags : fetched;
|
|
3004
|
+
const stable = useMemo(() => value, [value]);
|
|
3005
|
+
return /* @__PURE__ */ jsx(WireFeaturesContext.Provider, { value: stable, children });
|
|
3006
|
+
};
|
|
3007
|
+
WireFeaturesProvider.displayName = "WireFeaturesProvider";
|
|
3008
|
+
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
3009
|
+
var useResolvedFeatures = (options) => {
|
|
3010
|
+
var _a, _b, _c;
|
|
3011
|
+
const ctx = useWireFeaturesContext();
|
|
3012
|
+
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
3013
|
+
const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
3014
|
+
return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
|
|
3015
|
+
};
|
|
3016
|
+
|
|
3017
|
+
// src/config/wireConfigFromEnv.ts
|
|
3018
|
+
var wireConfigFromEnv = (overrides) => {
|
|
3019
|
+
var _a, _b, _c, _d;
|
|
3020
|
+
const apiKey = (_a = overrides == null ? void 0 : overrides.apiKey) != null ? _a : process.env.EXPO_PUBLIC_WIREAI_API_KEY;
|
|
3021
|
+
const serverUrl = (_b = overrides == null ? void 0 : overrides.serverUrl) != null ? _b : process.env.EXPO_PUBLIC_WIREAI_SERVER_URL;
|
|
3022
|
+
const appId = (_d = (_c = overrides == null ? void 0 : overrides.appId) != null ? _c : process.env.EXPO_PUBLIC_WIREAI_APP_ID) != null ? _d : "default";
|
|
3023
|
+
if (!apiKey || !serverUrl) return null;
|
|
3024
|
+
return {
|
|
3025
|
+
apiKey,
|
|
3026
|
+
serverUrl,
|
|
3027
|
+
appId,
|
|
3028
|
+
...(overrides == null ? void 0 : overrides.metadata) ? { metadata: overrides.metadata } : {}
|
|
3029
|
+
};
|
|
3030
|
+
};
|
|
3031
|
+
|
|
3032
|
+
// src/config/onboardingFlag.ts
|
|
3033
|
+
var isOnboardingEnabled = (opts) => {
|
|
3034
|
+
var _a;
|
|
3035
|
+
const transportPresent = wireConfigFromEnv(opts == null ? void 0 : opts.config) !== null;
|
|
3036
|
+
if (!transportPresent) return false;
|
|
3037
|
+
return (_a = opts == null ? void 0 : opts.remote) != null ? _a : true;
|
|
3038
|
+
};
|
|
3039
|
+
|
|
3040
|
+
// src/analytics/analyticsEvent.ts
|
|
3041
|
+
var WIRE_ONBOARDING_EVENTS = {
|
|
3042
|
+
started: "wire_onboarding_started",
|
|
3043
|
+
/** A persisted session was restored after an app kill (fires instead of `started`). */
|
|
3044
|
+
resumed: "wire_onboarding_resumed",
|
|
3045
|
+
turn: "wire_onboarding_turn",
|
|
3046
|
+
error: "wire_onboarding_error",
|
|
3047
|
+
retry: "wire_onboarding_retry",
|
|
3048
|
+
fallback: "wire_onboarding_fallback",
|
|
3049
|
+
/** Logged by the host on `onComplete` (no matching kit `OnboardingEvent`). */
|
|
3050
|
+
completed: "wire_onboarding_completed"
|
|
3051
|
+
};
|
|
3052
|
+
var toAnalyticsEvent = (event) => {
|
|
3053
|
+
switch (event.type) {
|
|
3054
|
+
case "started":
|
|
3055
|
+
return { name: WIRE_ONBOARDING_EVENTS.started };
|
|
3056
|
+
case "resumed":
|
|
3057
|
+
return { name: WIRE_ONBOARDING_EVENTS.resumed };
|
|
3058
|
+
case "turn":
|
|
3059
|
+
return {
|
|
3060
|
+
name: WIRE_ONBOARDING_EVENTS.turn,
|
|
3061
|
+
params: { step: event.step, component: event.component }
|
|
3062
|
+
};
|
|
3063
|
+
case "error":
|
|
3064
|
+
return { name: WIRE_ONBOARDING_EVENTS.error, params: { reason: event.reason } };
|
|
3065
|
+
case "retry":
|
|
3066
|
+
return {
|
|
3067
|
+
name: WIRE_ONBOARDING_EVENTS.retry,
|
|
3068
|
+
params: { reason: event.reason, attempt: event.attempt }
|
|
3069
|
+
};
|
|
3070
|
+
case "fallback":
|
|
3071
|
+
return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };
|
|
3072
|
+
default: {
|
|
3073
|
+
const _exhaustive = event;
|
|
3074
|
+
return _exhaustive;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
// src/attribution/attribution.ts
|
|
3080
|
+
var attributionMetadata = (a) => {
|
|
3081
|
+
const attribution = {};
|
|
3082
|
+
for (const [key, value] of Object.entries(a)) {
|
|
3083
|
+
if (value !== void 0) attribution[key] = value;
|
|
3084
|
+
}
|
|
3085
|
+
return { attribution };
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
// src/session-analytics/reportSessionStart.ts
|
|
3089
|
+
var SESSION_STARTED_EVENT = "app.session_started";
|
|
3090
|
+
var _emitted = /* @__PURE__ */ new Set();
|
|
3091
|
+
var _GUARD_MAX = 128;
|
|
3092
|
+
var resetSessionStartGuard = () => {
|
|
3093
|
+
_emitted.clear();
|
|
3094
|
+
};
|
|
3095
|
+
var reportSessionStart = (opts) => {
|
|
3096
|
+
var _a;
|
|
3097
|
+
const target = opts.target;
|
|
3098
|
+
if (!(target == null ? void 0 : target.serverUrl)) return;
|
|
3099
|
+
const sessionId = (_a = opts.sessionId) != null ? _a : makeSessionId();
|
|
3100
|
+
if (opts.once !== false) {
|
|
3101
|
+
if (_emitted.has(sessionId)) return;
|
|
3102
|
+
if (_emitted.size >= _GUARD_MAX) {
|
|
3103
|
+
const oldest = _emitted.values().next().value;
|
|
3104
|
+
if (oldest !== void 0) _emitted.delete(oldest);
|
|
3105
|
+
}
|
|
3106
|
+
_emitted.add(sessionId);
|
|
3107
|
+
}
|
|
3108
|
+
try {
|
|
3109
|
+
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
|
|
3110
|
+
const headers = { "Content-Type": "application/json" };
|
|
3111
|
+
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
3112
|
+
const userContext = {};
|
|
3113
|
+
if (opts.deviceKey) userContext.device_key = opts.deviceKey;
|
|
3114
|
+
if (typeof opts.sessionCount === "number" && Number.isFinite(opts.sessionCount)) {
|
|
3115
|
+
userContext.session_count = opts.sessionCount;
|
|
3116
|
+
userContext.returning = opts.sessionCount > 1;
|
|
3117
|
+
}
|
|
3118
|
+
if (opts.appVersion) userContext.app_version = opts.appVersion;
|
|
3119
|
+
if (opts.platform) userContext.platform = opts.platform;
|
|
3120
|
+
const event = {
|
|
3121
|
+
event_type: "app_event",
|
|
3122
|
+
question_key: SESSION_STARTED_EVENT,
|
|
3123
|
+
session_id: sessionId
|
|
3124
|
+
};
|
|
3125
|
+
const userId = sanitizeUserId(opts.userId);
|
|
3126
|
+
if (userId) event.user_id = userId;
|
|
3127
|
+
if (Object.keys(userContext).length > 0) event.user_context = userContext;
|
|
3128
|
+
if (opts.device) event.device = opts.device;
|
|
3129
|
+
if (opts.meta && Object.keys(opts.meta).length > 0) event.meta = JSON.stringify(opts.meta);
|
|
3130
|
+
void fetch(url, {
|
|
3131
|
+
method: "POST",
|
|
3132
|
+
headers,
|
|
3133
|
+
body: JSON.stringify({ events: [event] })
|
|
3134
|
+
}).catch(() => {
|
|
3135
|
+
});
|
|
3136
|
+
} catch {
|
|
3137
|
+
}
|
|
3138
|
+
};
|
|
3139
|
+
var BACKGROUND_SESSION_MS = 30 * 60 * 1e3;
|
|
3140
|
+
var useSessionStart = (config, options = {}) => {
|
|
3141
|
+
const latest = useRef({ config, options });
|
|
3142
|
+
latest.current = { config, options };
|
|
3143
|
+
useEffect(() => {
|
|
3144
|
+
const fire = () => {
|
|
3145
|
+
var _a, _b;
|
|
3146
|
+
const { config: cfg, options: opts } = latest.current;
|
|
3147
|
+
if (!(cfg == null ? void 0 : cfg.serverUrl)) return;
|
|
3148
|
+
if (opts.enabled === false) return;
|
|
3149
|
+
const target = { serverUrl: cfg.serverUrl, apiKey: (_a = cfg.apiKey) != null ? _a : "" };
|
|
3150
|
+
const device = collectDeviceContext();
|
|
3151
|
+
if (cfg.appVersion && !device.appVersion) device.appVersion = cfg.appVersion;
|
|
3152
|
+
reportSessionStart({
|
|
3153
|
+
target,
|
|
3154
|
+
// A fresh per-open id each fire; the emitter's once-guard dedupes within the open.
|
|
3155
|
+
userId: opts.userId,
|
|
3156
|
+
deviceKey: opts.deviceKey,
|
|
3157
|
+
sessionCount: opts.sessionCount,
|
|
3158
|
+
appVersion: (_b = cfg.appVersion) != null ? _b : device.appVersion,
|
|
3159
|
+
platform: Platform.OS,
|
|
3160
|
+
device,
|
|
3161
|
+
meta: opts.meta
|
|
3162
|
+
});
|
|
3163
|
+
};
|
|
3164
|
+
fire();
|
|
3165
|
+
let backgroundedAt = null;
|
|
3166
|
+
const onChange = (state) => {
|
|
3167
|
+
if (state === "background" || state === "inactive") {
|
|
3168
|
+
if (backgroundedAt == null) backgroundedAt = Date.now();
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (state === "active") {
|
|
3172
|
+
const since = backgroundedAt;
|
|
3173
|
+
backgroundedAt = null;
|
|
3174
|
+
if (since != null && Date.now() - since >= BACKGROUND_SESSION_MS) fire();
|
|
3175
|
+
}
|
|
3176
|
+
};
|
|
3177
|
+
const sub = AppState.addEventListener("change", onChange);
|
|
3178
|
+
return () => {
|
|
3179
|
+
if (sub && typeof sub.remove === "function") sub.remove();
|
|
3180
|
+
};
|
|
3181
|
+
}, []);
|
|
3182
|
+
};
|
|
3183
|
+
|
|
3184
|
+
export { AnimatedSparkle, BACKGROUND_SESSION_MS, CardHandoff, CenteredModal, ChipSelectCard, CompletionView, DEFAULT_FEATURES_TTL_MS, DEFAULT_SESSION_TTL_MS, DemoOnboarding, DoneBlock, ErrorBlock, IllustrationProvider, InterstitialCard, LoadingBlock, LoadingScreen, NumberStepperCard, Button as OnboardingButton, OnboardingFlow, OnboardingScaffold, OnboardingThemeProvider, SESSION_STARTED_EVENT, SelectionCard, StatusCard, StepProgress, TextInputCard, USER_ID_MAX_LENGTH, WIRE_ONBOARDING_EVENTS, WireFeaturesProvider, WireOnboarding, attributionMetadata, clearPersistedSession, collectDeviceContext, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, featuresCacheKey, featuresEqual, fetchWireFeatures, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, loadPersistedSession, makeSessionId, mergeTheme, motionSpec_exports as motionSpec, onboardingComponents, parseWireFeatures, peekPersistedSession, readCachedFeatures, readProgress, reportClientEvent, reportClientEvents, reportSessionStart, resetSessionStartGuard, sanitizeUserId, savePersistedSession, sessionStorageKey, themeFromBrand, toAnalyticsEvent, useIllustration, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, writeCachedFeatures };
|
|
3185
|
+
//# sourceMappingURL=index.mjs.map
|
|
3186
|
+
//# sourceMappingURL=index.mjs.map
|