@cxpinsight/react-native 0.9.0 → 0.11.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/CHANGELOG.md +100 -0
- package/lib/CXPinsightProvider.d.ts.map +1 -1
- package/lib/CXPinsightProvider.js +40 -1
- package/lib/CXPinsightProvider.js.map +1 -1
- package/lib/core/capabilities.d.ts +34 -0
- package/lib/core/capabilities.d.ts.map +1 -0
- package/lib/core/capabilities.js +85 -0
- package/lib/core/capabilities.js.map +1 -0
- package/lib/core/triggerEngine.d.ts +26 -0
- package/lib/core/triggerEngine.d.ts.map +1 -1
- package/lib/core/triggerEngine.js +79 -3
- package/lib/core/triggerEngine.js.map +1 -1
- package/lib/survey/SurveyModal.d.ts.map +1 -1
- package/lib/survey/SurveyModal.js +217 -11
- package/lib/survey/SurveyModal.js.map +1 -1
- package/lib/survey/expressionEval.d.ts +18 -0
- package/lib/survey/expressionEval.d.ts.map +1 -1
- package/lib/survey/expressionEval.js +63 -0
- package/lib/survey/expressionEval.js.map +1 -1
- package/lib/survey/interpolate.js +20 -2
- package/lib/survey/interpolate.js.map +1 -1
- package/lib/types.d.ts +36 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -242,13 +242,22 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
242
242
|
// defaults to "visible". Fail-open on parse errors — see
|
|
243
243
|
// expressionEval.ts.
|
|
244
244
|
const isVisible = (q) => {
|
|
245
|
-
|
|
245
|
+
// Prefer the tree the server compiled; fall back to parsing the string.
|
|
246
|
+
// Guarded inside evaluateConditionCompiled, because an app on
|
|
247
|
+
// survey-spec@0.4.0 has no tree walker at all.
|
|
248
|
+
const qc = q;
|
|
249
|
+
if (q.showWhen && !(0, expressionEval_1.evaluateConditionCompiled)(q.showWhen, qc.showWhenCompiled, evalCtx))
|
|
246
250
|
return false;
|
|
247
|
-
if (q.hideWhen && (0, expressionEval_1.
|
|
251
|
+
if (q.hideWhen && (0, expressionEval_1.evaluateConditionCompiled)(q.hideWhen, qc.hideWhenCompiled, evalCtx))
|
|
248
252
|
return false;
|
|
249
253
|
return true;
|
|
250
254
|
};
|
|
251
255
|
const currentQuestions = (pages[currentPage]?.elements ?? []).filter(isVisible);
|
|
256
|
+
// A step ending in an implied question has no Next/Submit — its action
|
|
257
|
+
// buttons are the answer, and pressing one commits. Shared with the web SDK
|
|
258
|
+
// and the React renderer so a Notify/Offer/Comply template behaves the same
|
|
259
|
+
// on a phone as it does on a website. Suppresses the footer below.
|
|
260
|
+
const impliedNow = (0, survey_spec_1.impliedStepActions)(currentQuestions);
|
|
252
261
|
const updateResponse = (0, react_1.useCallback)((name, value) => {
|
|
253
262
|
setResponses((prev) => ({ ...prev, [name]: value }));
|
|
254
263
|
// Live-clear the error entry the moment the new answer passes
|
|
@@ -276,7 +285,8 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
276
285
|
if (!Array.isArray(q.branches) || q.branches.length === 0)
|
|
277
286
|
continue;
|
|
278
287
|
for (const b of q.branches) {
|
|
279
|
-
|
|
288
|
+
const bc = b;
|
|
289
|
+
if (b.when && !(0, expressionEval_1.evaluateConditionCompiled)(b.when, bc.whenCompiled, evalCtx))
|
|
280
290
|
continue;
|
|
281
291
|
if (b?.target === 'flow') {
|
|
282
292
|
if (!b.flowId)
|
|
@@ -383,6 +393,9 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
383
393
|
setForkStack([]);
|
|
384
394
|
}, duration);
|
|
385
395
|
};
|
|
396
|
+
// Kept fresh on every render so an implied action's deferred press runs the
|
|
397
|
+
// current closure rather than the one captured when the button mounted.
|
|
398
|
+
const handleNextRef = react_1.default.useRef(() => { });
|
|
386
399
|
const handleNext = () => {
|
|
387
400
|
const pageErrors = validateCurrentPage();
|
|
388
401
|
if (Object.keys(pageErrors).length > 0) {
|
|
@@ -438,6 +451,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
438
451
|
setHistory((h) => [...h, { pageIndex: currentPage, flow: currentFlow }]);
|
|
439
452
|
setCurrentPage(linearNext);
|
|
440
453
|
};
|
|
454
|
+
handleNextRef.current = handleNext;
|
|
441
455
|
const handleBack = () => {
|
|
442
456
|
// Non-linear navigation: pop the actual history stack, not just
|
|
443
457
|
// currentPage - 1 (which would skip over a jump). Any errors
|
|
@@ -463,6 +477,9 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
463
477
|
setCurrentPage((p) => p - 1);
|
|
464
478
|
}
|
|
465
479
|
};
|
|
480
|
+
// Kept fresh so the memoized pan responder never dismisses through a stale
|
|
481
|
+
// closure — the same reason handleNextRef exists.
|
|
482
|
+
const handleDismissRef = (0, react_1.useRef)(() => { });
|
|
466
483
|
const handleDismiss = () => {
|
|
467
484
|
setCurrentPage(0);
|
|
468
485
|
setResponses({});
|
|
@@ -474,6 +491,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
474
491
|
setForkStack([]);
|
|
475
492
|
onDismiss();
|
|
476
493
|
};
|
|
494
|
+
handleDismissRef.current = handleDismiss;
|
|
477
495
|
// Normalize the v1.1 options[] {value,label} and the legacy
|
|
478
496
|
// choices[] (string | {value,text}) into a single shape that
|
|
479
497
|
// MultipleChoiceQuestion accepts. hasOther appends a synthetic
|
|
@@ -500,7 +518,30 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
500
518
|
}
|
|
501
519
|
return out;
|
|
502
520
|
}
|
|
503
|
-
|
|
521
|
+
/**
|
|
522
|
+
* Compiled props, under the names the existing renderers already read.
|
|
523
|
+
*
|
|
524
|
+
* The renderers were written against the author's question shape
|
|
525
|
+
* (minValue/maxValue, options); the server's compiler emits the contract's
|
|
526
|
+
* names (min/max). Translating onto a COPY avoids touching thirty-nine
|
|
527
|
+
* renderers, and author fields win — the compiler derives csat's 1..5 because
|
|
528
|
+
* the author stated none, and must never override a range they did state.
|
|
529
|
+
*/
|
|
530
|
+
function applyRenderProps(q) {
|
|
531
|
+
const r = q.render;
|
|
532
|
+
if (!r || !r.props)
|
|
533
|
+
return q;
|
|
534
|
+
const out = { ...q };
|
|
535
|
+
const MAP = { min: 'minValue', max: 'maxValue' };
|
|
536
|
+
for (const [k, v] of Object.entries(r.props)) {
|
|
537
|
+
const target = MAP[k] || k;
|
|
538
|
+
if (out[target] === undefined || out[target] === null)
|
|
539
|
+
out[target] = v;
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function renderQuestion(rawQuestion) {
|
|
544
|
+
const question = applyRenderProps(rawQuestion);
|
|
504
545
|
const key = question.id || question.name;
|
|
505
546
|
const val = responses[key];
|
|
506
547
|
// Shown when a question type needs an optional peer the host app did not
|
|
@@ -650,15 +691,45 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
650
691
|
// labels come from the shared spec so "Activate discount" reads the same
|
|
651
692
|
// here as on a website and on a link.
|
|
652
693
|
'implied': () => {
|
|
653
|
-
|
|
694
|
+
// Same resolver the thank-you text uses, so `myapp://cart/{{id}}`
|
|
695
|
+
// opens the customer's cart rather than a literal deep link nothing
|
|
696
|
+
// can handle.
|
|
697
|
+
const actions = (0, survey_spec_1.impliedActions)(question, (t) => (0, interpolate_1.interpolatePlain)(t, evalCtx));
|
|
654
698
|
const list = actions.length ? actions : [{ value: 'acknowledged', label: 'Continue' }];
|
|
655
|
-
|
|
699
|
+
// Consent choices render identically — see impliedEqualProminence.
|
|
700
|
+
const equal = (0, survey_spec_1.impliedEqualProminence)([question]);
|
|
701
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.impliedActions, children: list.map((a, i) => ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, {
|
|
702
|
+
// The press is both the answer and the Next — the footer is
|
|
703
|
+
// hidden on this step. Deferred a tick so setResponses has
|
|
704
|
+
// flushed before handleNext validates what it just replaced.
|
|
705
|
+
onPress: () => {
|
|
706
|
+
// "Remind me later" dismisses instead of answering, so the
|
|
707
|
+
// cadence records a SKIP and brings the interaction back
|
|
708
|
+
// after the author's wait. Answering would record a decision
|
|
709
|
+
// this person explicitly declined to make and retire it
|
|
710
|
+
// against maxSubmissions. It does not follow the action's
|
|
711
|
+
// link either — postponing is not a request to go anywhere.
|
|
712
|
+
if (a.defer) {
|
|
713
|
+
handleDismissRef.current();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
updateResponse(key, a.value);
|
|
717
|
+
setTimeout(() => handleNextRef.current(), 0);
|
|
718
|
+
// Follow the action's deep link. `myapp://cart` is the point
|
|
719
|
+
// of this on a phone; Linking refuses a scheme the OS has no
|
|
720
|
+
// handler for, so a bad target fails quietly rather than
|
|
721
|
+
// crashing the survey. The href already passed
|
|
722
|
+
// safeActionHref in the shared spec.
|
|
723
|
+
if (a.href) {
|
|
724
|
+
react_native_1.Linking.openURL(a.href).catch(() => { });
|
|
725
|
+
}
|
|
726
|
+
}, accessibilityRole: "button", accessibilityState: { selected: val === a.value }, style: [
|
|
656
727
|
styles.impliedBtn,
|
|
657
728
|
// The first action is the affirmative one. An author who
|
|
658
729
|
// lists "Decline" first gets it emphasised — their call.
|
|
659
|
-
i === 0 ? styles.impliedPrimary : styles.impliedSecondary,
|
|
730
|
+
(i === 0 && !equal) ? styles.impliedPrimary : styles.impliedSecondary,
|
|
660
731
|
val === a.value && styles.impliedChosen,
|
|
661
|
-
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: i === 0 ? styles.impliedPrimaryLabel : styles.impliedSecondaryLabel, children: a.label }) }, a.value || String(i)))) }));
|
|
732
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: (i === 0 && !equal) ? styles.impliedPrimaryLabel : styles.impliedSecondaryLabel, children: a.label }) }, a.value || String(i)))) }));
|
|
662
733
|
},
|
|
663
734
|
'signature': () => {
|
|
664
735
|
if (!(0, SignatureQuestion_1.isSignatureAvailable)())
|
|
@@ -677,6 +748,37 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
677
748
|
// still runs. Server-side validation will reject any junk.
|
|
678
749
|
return ((0, jsx_runtime_1.jsx)(TextQuestion_1.TextQuestion, { value: val ?? '', onChange: (v) => updateResponse(key, v), placeholder: question.placeholder, contract: contract }));
|
|
679
750
|
};
|
|
751
|
+
// ── Primitive dispatch ────────────────────────────────────────────
|
|
752
|
+
//
|
|
753
|
+
// The server compiles a question type to one of eight primitives plus a
|
|
754
|
+
// presentation hint. Dispatching on THAT means a question type this build
|
|
755
|
+
// has never heard of still renders — which matters more here than on the
|
|
756
|
+
// web, where a new type arrives with the next SDK fetch. On mobile it would
|
|
757
|
+
// otherwise need an App Store release and every respondent updating.
|
|
758
|
+
//
|
|
759
|
+
// The markup is the existing markup: this maps a primitive onto the
|
|
760
|
+
// renderer that already draws it. Anything without an entry — slider, and
|
|
761
|
+
// the grid/group/display/capture families — falls through to the type
|
|
762
|
+
// table exactly as before.
|
|
763
|
+
const PRIMITIVES = {
|
|
764
|
+
'choice_one:list': 'radio',
|
|
765
|
+
'choice_one:dropdown': 'dropdown',
|
|
766
|
+
'choice_one:toggle': 'binary',
|
|
767
|
+
'choice_one:images': 'image_picker',
|
|
768
|
+
'choice_one:scale': 'nps',
|
|
769
|
+
'choice_one:stars': 'rating',
|
|
770
|
+
'choice_one:actions': 'implied',
|
|
771
|
+
'choice_many:list': 'checkbox',
|
|
772
|
+
'choice_many:rank': 'ranking',
|
|
773
|
+
'text:single': 'text',
|
|
774
|
+
'text:multi': 'comment',
|
|
775
|
+
};
|
|
776
|
+
const render = question.render;
|
|
777
|
+
const primName = render && render.primitive
|
|
778
|
+
? PRIMITIVES[`${render.primitive}:${render.presentation || ''}`]
|
|
779
|
+
: undefined;
|
|
780
|
+
if (primName && RENDERERS[primName])
|
|
781
|
+
return RENDERERS[primName]();
|
|
680
782
|
const _fn = RENDERERS[String(question.type || '')] || renderUnsupported;
|
|
681
783
|
return _fn();
|
|
682
784
|
}
|
|
@@ -1154,6 +1256,75 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1154
1256
|
useNativeDriver: true,
|
|
1155
1257
|
}).start();
|
|
1156
1258
|
}, [visible, needsTopSlide, enterAnim]);
|
|
1259
|
+
// ── Swipe to dismiss ────────────────────────────────────────────────
|
|
1260
|
+
//
|
|
1261
|
+
// A push notification goes away when you flick it. The mobile display slice
|
|
1262
|
+
// drops the ✕ for exactly that reason, so this is the affordance that
|
|
1263
|
+
// replaces it — without it the banner would simply be stuck on screen.
|
|
1264
|
+
//
|
|
1265
|
+
// canSwipeToDismiss requires a popup and vetoes `mustShow`, which is what
|
|
1266
|
+
// keeps a compliance takeover — showClose:false for the OPPOSITE reason —
|
|
1267
|
+
// from ever becoming flickable.
|
|
1268
|
+
const swipeable = (0, survey_spec_1.canSwipeToDismiss)(display, survey.delivery);
|
|
1269
|
+
const swipeXY = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: 0, y: 0 })).current;
|
|
1270
|
+
const swipeResponder = (0, react_1.useMemo)(() => react_native_1.PanResponder.create({
|
|
1271
|
+
onStartShouldSetPanResponder: () => false,
|
|
1272
|
+
// Claim the gesture only once it is clearly a drag, so a tap on the action
|
|
1273
|
+
// button still presses the button.
|
|
1274
|
+
onMoveShouldSetPanResponder: (_e, g) => swipeable && (Math.abs(g.dx) > 8 || Math.abs(g.dy) > 8),
|
|
1275
|
+
onPanResponderMove: (_e, g) => {
|
|
1276
|
+
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1277
|
+
// A top banner travels up, never down — dragging it down is a pull.
|
|
1278
|
+
const dy = placement === 'top' ? Math.min(0, g.dy) : placement === 'bottom' ? Math.max(0, g.dy) : g.dy;
|
|
1279
|
+
swipeXY.setValue(horizontal ? { x: g.dx, y: 0 } : { x: 0, y: dy });
|
|
1280
|
+
},
|
|
1281
|
+
onPanResponderRelease: (_e, g) => {
|
|
1282
|
+
const horizontal = Math.abs(g.dx) > Math.abs(g.dy);
|
|
1283
|
+
const moved = horizontal ? g.dx : g.dy;
|
|
1284
|
+
const fast = Math.abs(horizontal ? g.vx : g.vy) > 0.5;
|
|
1285
|
+
if (Math.abs(moved) >= 56 || fast) {
|
|
1286
|
+
const to = moved < 0 ? -SCREEN_HEIGHT : SCREEN_HEIGHT;
|
|
1287
|
+
react_native_1.Animated.timing(swipeXY, {
|
|
1288
|
+
toValue: horizontal ? { x: moved < 0 ? -SCREEN_WIDTH : SCREEN_WIDTH, y: 0 } : { x: 0, y: to },
|
|
1289
|
+
duration: 180, useNativeDriver: true,
|
|
1290
|
+
}).start(() => { swipeXY.setValue({ x: 0, y: 0 }); handleDismissRef.current(); });
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
react_native_1.Animated.spring(swipeXY, { toValue: { x: 0, y: 0 }, useNativeDriver: true, bounciness: 6 }).start();
|
|
1294
|
+
},
|
|
1295
|
+
}), [swipeable, placement, swipeXY]);
|
|
1296
|
+
// Auto-dismiss. Same rule as web (autoDismissMs vetoes fullscreen and
|
|
1297
|
+
// mustShow, clamps to 2s). Cancelled for good once the respondent touches
|
|
1298
|
+
// anything — a timer that fires after someone has started answering would
|
|
1299
|
+
// pull the survey out from under them.
|
|
1300
|
+
// Which ending this interaction gets — see the render block below.
|
|
1301
|
+
const endingKind = (0, react_1.useMemo)(() => {
|
|
1302
|
+
const sv = survey;
|
|
1303
|
+
const qs = (survey.rawQuestions ?? []);
|
|
1304
|
+
const acts = (0, survey_spec_1.impliedStepActions)(qs) || [];
|
|
1305
|
+
if (!acts.length)
|
|
1306
|
+
return 'thanks';
|
|
1307
|
+
const last = qs[qs.length - 1];
|
|
1308
|
+
const id = last?.name ?? last?.id ?? '';
|
|
1309
|
+
const action = id ? (responses[id] ?? null) : null;
|
|
1310
|
+
return (0, survey_spec_1.outcomeFor)({
|
|
1311
|
+
interactionType: sv.interactionType, action,
|
|
1312
|
+
affirmative: String(action) === String(acts[0].value),
|
|
1313
|
+
reward: sv.reward, compliance: sv.compliance,
|
|
1314
|
+
});
|
|
1315
|
+
}, [survey, responses]);
|
|
1316
|
+
const dismissMs = (0, survey_spec_1.autoDismissMs)(display, survey.delivery);
|
|
1317
|
+
const engagedRef = (0, react_1.useRef)(false);
|
|
1318
|
+
(0, react_1.useEffect)(() => {
|
|
1319
|
+
if (!visible || !dismissMs)
|
|
1320
|
+
return;
|
|
1321
|
+
engagedRef.current = false;
|
|
1322
|
+
const t = setTimeout(() => {
|
|
1323
|
+
if (!engagedRef.current)
|
|
1324
|
+
handleDismissRef.current();
|
|
1325
|
+
}, dismissMs);
|
|
1326
|
+
return () => clearTimeout(t);
|
|
1327
|
+
}, [visible, dismissMs]);
|
|
1157
1328
|
const enterStyle = needsTopSlide
|
|
1158
1329
|
? {
|
|
1159
1330
|
opacity: enterAnim,
|
|
@@ -1309,10 +1480,11 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1309
1480
|
// A sheet sits flush against its edge, so the overlay loses its gutter.
|
|
1310
1481
|
isSheet && { paddingHorizontal: 0, paddingTop: 0, paddingBottom: 0 },
|
|
1311
1482
|
isFullscreen && styles.overlayFullscreen,
|
|
1312
|
-
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { style: [
|
|
1483
|
+
], behavior: react_native_1.Platform.OS === 'ios' ? 'padding' : 'height', children: [(0, jsx_runtime_1.jsxs)(react_native_1.Animated.View, { onTouchStart: () => { engagedRef.current = true; }, ...(swipeable ? swipeResponder.panHandlers : {}), style: [
|
|
1313
1484
|
styles.card,
|
|
1314
1485
|
themeCard,
|
|
1315
1486
|
enterStyle,
|
|
1487
|
+
swipeable && { transform: swipeXY.getTranslateTransform() },
|
|
1316
1488
|
maxWidth !== undefined && !isFullscreen && { width: Math.min(SCREEN_WIDTH - 40, maxWidth), maxWidth },
|
|
1317
1489
|
cornerRadius !== undefined && !isFullscreen && { borderRadius: cornerRadius },
|
|
1318
1490
|
// cardFullscreen fills; an explicit content height/width below OVERRIDES
|
|
@@ -1325,7 +1497,24 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1325
1497
|
], children: [display?.showClose !== false && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [
|
|
1326
1498
|
styles.closeButton,
|
|
1327
1499
|
isRtl ? { left: 12 } : { right: 12 },
|
|
1328
|
-
], onPress: handleDismiss, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.closeText, themeTextSecondary], children: "\u2715" }) })), showThankYou ? (() => {
|
|
1500
|
+
], onPress: handleDismiss, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.closeText, themeTextSecondary], children: "\u2715" }) })), showThankYou && endingKind !== 'thanks' ? (() => {
|
|
1501
|
+
const rw = (0, survey_spec_1.rewardToShow)(survey);
|
|
1502
|
+
const comp = survey.compliance;
|
|
1503
|
+
if (endingKind === 'fulfil' && rw) {
|
|
1504
|
+
const exp = rw.expiresAt ? new Date(rw.expiresAt) : null;
|
|
1505
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: "It\u2019s yours" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: "Use this at checkout." }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: true, style: [styles.rewardCode, fontStyle, themeTextPrimary], children: rw.value }), !!rw.whereValid && ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: rw.whereValid })), !!exp && ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: ["Expires ", exp.toLocaleString()] }))] }));
|
|
1506
|
+
}
|
|
1507
|
+
if (endingKind === 'hold') {
|
|
1508
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, accessibilityLiveRegion: "polite", children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: comp?.reviewRequired ? 'Under review' : 'Not completed' }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: comp?.blockedMessage
|
|
1509
|
+
?? (comp?.reviewRequired
|
|
1510
|
+
? 'We’re checking your details. You’ll be able to continue once that’s done.'
|
|
1511
|
+
: 'You need to accept this before you can continue.') })] }));
|
|
1512
|
+
}
|
|
1513
|
+
if (endingKind === 'unblock') {
|
|
1514
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.thankYou, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouTitle, fontStyle, themeTextPrimary], children: "Recorded" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: "You can carry on." })] }));
|
|
1515
|
+
}
|
|
1516
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.thankYou, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.thankYouSub, fontStyle, themeTextSecondary], children: endingKind === 'navigate' ? 'Taking you there…' : 'Closed.' }) }));
|
|
1517
|
+
})() : showThankYou ? (() => {
|
|
1329
1518
|
// The thank-you message can be rich HTML from the web builder.
|
|
1330
1519
|
// Pull out an embedded <img> (often a data: URI icon) and render
|
|
1331
1520
|
// it as a real Image — stripHtml would otherwise drop it — then
|
|
@@ -1408,7 +1597,7 @@ function SurveyModal({ survey, visible, onSubmit, onDismiss }) {
|
|
|
1408
1597
|
// Right-align parity with web: Back hugs the left edge while
|
|
1409
1598
|
// Next/Submit sits on the right (web uses margin-right:auto).
|
|
1410
1599
|
buttonAlign === 'right' && { marginRight: 'auto' },
|
|
1411
|
-
], onPress: handleBack, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.backButtonText, fontStyle, themeTextSecondary, buttonFS], children: "Back" }) })), (() => {
|
|
1600
|
+
], onPress: handleBack, activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.backButtonText, fontStyle, themeTextSecondary, buttonFS], children: "Back" }) })), impliedNow.length > 0 ? null : (() => {
|
|
1412
1601
|
// Reflect the actual outcome of pressing Next: if a
|
|
1413
1602
|
// branch resolves to __end__ we're submitting even
|
|
1414
1603
|
// when we're not on the last page. Cheap to evaluate
|
|
@@ -1719,5 +1908,22 @@ const makeStyles = (c) => react_native_1.StyleSheet.create({
|
|
|
1719
1908
|
textAlign: 'center',
|
|
1720
1909
|
lineHeight: 26,
|
|
1721
1910
|
},
|
|
1911
|
+
thankYouSub: {
|
|
1912
|
+
fontSize: 14,
|
|
1913
|
+
textAlign: 'center',
|
|
1914
|
+
lineHeight: 20,
|
|
1915
|
+
marginTop: 6,
|
|
1916
|
+
},
|
|
1917
|
+
// Selectable so the code can be copied by long-press — there is no clipboard
|
|
1918
|
+
// button here because RN's clipboard is an optional peer, and a code you can
|
|
1919
|
+
// select is better than a button that may not exist.
|
|
1920
|
+
rewardCode: {
|
|
1921
|
+
fontSize: 22,
|
|
1922
|
+
fontWeight: '700',
|
|
1923
|
+
letterSpacing: 1.5,
|
|
1924
|
+
textAlign: 'center',
|
|
1925
|
+
marginTop: 14,
|
|
1926
|
+
marginBottom: 10,
|
|
1927
|
+
},
|
|
1722
1928
|
});
|
|
1723
1929
|
//# sourceMappingURL=SurveyModal.js.map
|