Repository navigation
Expand file tree
/
Copy pathflo.tsx
More file actions
1074 lines (1035 loc) · 30.6 KB
/
Copy pathflo.tsx
File metadata and controls
1074 lines (1035 loc) · 30.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { StatusBar } from 'expo-status-bar';
import { useEffect, useRef, useState } from 'react';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import PagerView from './shared/pager-view';
import Animated, {
Easing,
interpolate,
runOnJS,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
type SharedValue,
} from 'react-native-reanimated';
import Svg, { Path } from 'react-native-svg';
import type { PaywallProps } from './types';
import { createActionPress } from './shared/actions';
import { ReplicaPressable } from './shared/pressable';
import { ReferenceCanvas } from './shared/reference-canvas';
type FloMode = 'plans' | 'all-plans';
type FloPlan = 'yearly' | 'family' | 'monthly';
function normalizeMode(value?: string): FloMode {
return value === 'all-plans' ? 'all-plans' : 'plans';
}
function FloBrand() {
return (
<View pointerEvents="none" style={styles.brand}>
<Svg height={31.4} viewBox="0 0 110 64" width={54}>
<Path
d="M21.4129 32.12C21.1729 32.1467 18.8002 32.2266 16.4941 32.5597C16.2408 33.1993 15.9875 33.8522 15.7476 34.5318C13.0682 41.8736 12.2018 46.7904 12.0152 48.3095C11.8286 49.8285 8.04281 48.3761 8.10946 45.7111C8.16278 43.5792 9.50912 38.3426 10.7355 34.3452C9.22919 35.1181 8.00282 36.0641 7.52294 37.1034C5.89667 37.17 3.80385 35.691 4.84359 33.5857C5.88334 31.4937 9.0559 30.6143 12.1485 30.1213C14.0147 24.8714 15.6143 21.0739 16.6674 19.6215C16.7074 19.5682 16.7474 19.5149 16.8007 19.4616C9.97567 20.7541 6.73646 23.1259 5.36347 25.1246C1.81767 25.0579 -0.141855 19.9946 4.75028 17.7161C9.53578 15.4908 16.9206 14.8646 24.1056 14.9179C31.3038 14.9712 36.6891 16.0904 38.502 16.7967C39.875 17.3296 38.782 19.5282 37.3423 19.4483C36.3026 19.3816 31.5704 18.1558 23.799 18.6088C23.799 18.6888 23.759 18.7687 23.7057 18.862C22.2927 20.5675 20.0132 24.3517 17.7738 29.4817C20.1199 29.3485 22.106 29.3884 22.6526 29.4817C23.9989 29.7216 22.9058 31.9868 21.4129 32.12ZM48.4329 40.0348C46.5534 45.0049 43.2742 49.0956 38.6087 49.0556C36.0226 49.029 34.5563 47.0436 34.2098 44.4586C32.6768 46.3641 29.6642 49.0823 26.5716 49.069C22.1727 49.0556 22.0794 43.6192 24.9054 34.9049C27.1715 27.9227 30.0374 23.912 31.1305 23.1658C33.9032 21.2471 38.2621 23.2058 36.7158 24.6582C35.9027 25.4177 32.5302 30.1613 30.2774 35.8243C28.0246 41.4872 27.7313 44.472 29.4509 44.472C31.0105 44.472 33.8099 41.4073 34.583 40.1947C34.6363 40.0082 34.6896 39.835 34.7563 39.6484C36.0093 35.9175 37.6756 33.106 38.8619 32.3732C39.1685 32.1866 39.5418 32.08 39.9284 32.0534C40.7548 31.054 42.6744 29.2685 45.4337 29.2019C48.3396 29.1486 51.3389 32.3465 48.4329 40.0348ZM44.5939 33.2926C44.0607 33.3192 43.6075 33.5324 43.2342 33.8389C43.2475 34.0787 43.1809 34.3186 42.9809 34.5717C42.3944 35.3179 40.0217 38.7823 39.6218 39.3553C38.4354 41.5672 38.502 44.4586 40.1016 44.4453C42.2211 44.432 44.3673 41.847 45.5936 38.809C46.5934 36.3572 46.5001 33.2792 44.5939 33.2926Z"
fill="#FF5A7D"
/>
<Path
d="M55.9709 48.4187C58.7709 48.2562 63.7084 47.5625 68.8209 45.2688C76.9022 41.6438 81.0772 36.3562 81.1397 36.4062C81.3397 36.5625 78.4522 40.7125 73.0209 44.3688C69.1334 46.9813 65.8397 48.3375 62.5084 49.3375C60.4147 49.9625 63.6334 48.8438 66.6397 50.9563C68.7022 52.4063 68.7897 53.9812 68.8709 52.6937C68.9084 52.1 68.8834 51.25 68.8272 50.6125C70.7272 51.15 73.4397 51.65 76.8334 51.275C80.5334 50.8625 83.9459 49.4875 86.9772 47.2125C88.0397 46.4125 87.1022 46.65 86.5584 46.7562C85.2334 47.0125 82.8084 47.275 80.1147 46.6312C81.9334 46.6312 93.6959 45.7812 97.2834 35.4563C101.096 24.4625 95.8834 10.8125 83.0522 10.8125C70.2147 10.8125 68.4834 29.8813 68.7084 33.4125C68.0584 31.5063 67.7897 28.7 68.2334 26.2687C68.4022 25.35 68.6272 24.3 67.4897 25.65C65.3084 28.2437 62.4459 33.25 63.2772 39.9812C62.2647 39.1437 61.3522 38.5625 60.5022 38.125C59.5459 37.6312 59.1334 37.4375 60.2022 38.775C61.0459 39.8312 62.0709 42.2437 60.7834 44.7562C59.7959 46.6812 58.0959 47.6313 56.2834 47.8313C55.4209 47.9063 55.3522 48.4562 55.9709 48.4187Z"
fill="#FF5A7D"
/>
</Svg>
</View>
);
}
const REVIEWS = [
{
author: '333honesty',
copy: '“A ladies best friend. Awesome and quite\naccurate! 👍🏼👍🏼👍🏼”',
},
{
author: 'RachelM',
copy: '“Really accurate!! This app is amazing it’s so\neasy to use and really really awesome. I love\nthis app so much. I’ve tried other apps but this\none is by far the best!!💕💕”',
},
] as const;
function Header({
onActionPress,
onClosePress,
onRestorePress,
}: Pick<PaywallProps, 'onActionPress' | 'onClosePress' | 'onRestorePress'>) {
return (
<>
<ReplicaPressable
accessibilityLabel="Close Flo paywall"
onPress={createActionPress({
actionId: 'flo.close',
fallback: onClosePress,
haptic: 'light',
onActionPress,
})}
style={styles.close}
>
<Text style={styles.closeText}>×</Text>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Restore Flo purchase"
onPress={createActionPress({
actionId: 'flo.restore',
fallback: onRestorePress,
onActionPress,
})}
style={styles.restore}
>
<Text style={styles.restoreText}>Restore</Text>
</ReplicaPressable>
</>
);
}
function TestimonialHeader({
initialPage,
modalVisible,
progress,
}: {
initialPage: number;
modalVisible: boolean;
progress: SharedValue<number>;
}) {
const pagerRef = useRef<PagerView>(null);
const [page, setPage] = useState(initialPage);
useEffect(() => {
if (modalVisible) {
pagerRef.current?.setPage(1);
}
}, [modalVisible]);
useEffect(() => {
const timer = setInterval(() => {
const next = (page + 1) % REVIEWS.length;
pagerRef.current?.setPage(next);
setPage(next);
}, 4800);
return () => clearInterval(timer);
}, [page]);
const headingStyle = useAnimatedStyle(() => ({
top: interpolate(progress.value, [0, 1], [92, 119]),
}));
const starsStyle = useAnimatedStyle(() => ({
top: interpolate(progress.value, [0, 1], [137.5, 179]),
}));
const pagerStyle = useAnimatedStyle(() => ({
top: interpolate(progress.value, [0, 1], [190.5, 226]),
}));
return (
<>
<Animated.Text style={[styles.heading, headingStyle]}>Choose your plan</Animated.Text>
<Animated.Text style={[styles.stars, starsStyle]}>★★★★★</Animated.Text>
<Animated.View style={[styles.reviewPager, pagerStyle]}>
<PagerView
initialPage={initialPage}
onPageSelected={(event) => setPage(event.nativeEvent.position)}
orientation="horizontal"
overdrag
ref={pagerRef}
style={styles.reviewPagerInner}
>
{REVIEWS.map((review) => (
<View collapsable={false} key={review.author} style={styles.reviewPage}>
<Text style={styles.reviewCopy}>{review.copy}</Text>
<Text style={styles.reviewAuthor}>{review.author}</Text>
</View>
))}
</PagerView>
</Animated.View>
</>
);
}
function PlanRadio({ selected }: { selected: boolean }) {
return selected ? (
<View style={styles.radioSelected}><Text style={styles.radioCheck}>✓</Text></View>
) : (
<View style={styles.radioUnselected} />
);
}
function DefaultSheet({
onActionPress,
onChoosePlan,
onClosePress,
onOpenAllPlans,
onPurchasePress,
onToggleTrial,
plan,
trialEnabled,
}: Pick<PaywallProps, 'onActionPress' | 'onClosePress' | 'onPurchasePress'> & {
onChoosePlan: (plan: Exclude<FloPlan, 'monthly'>) => void;
onOpenAllPlans: () => void;
onToggleTrial: () => void;
plan: FloPlan;
trialEnabled: boolean;
}) {
return (
<ScrollView
bounces={false}
contentContainerStyle={styles.defaultSheetContent}
showsVerticalScrollIndicator={false}
style={styles.defaultSheet}
>
<ReplicaPressable
accessibilityLabel="Start a Flo free trial"
accessibilityState={{ checked: trialEnabled }}
onPress={onToggleTrial}
style={styles.defaultTrialRow}
>
<Text style={styles.defaultTrialText}>Not sure yet? Start a free trial</Text>
<View style={styles.defaultTrialRadio}><PlanRadio selected={trialEnabled} /></View>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Flo yearly plan"
accessibilityState={{ selected: plan === 'yearly' }}
onPress={() => onChoosePlan('yearly')}
style={[styles.defaultPlanCard, styles.defaultYearly]}
>
<View style={[styles.defaultCardBackground, plan === 'yearly' && styles.defaultCardSelected]} />
<View style={styles.popularBand}><Text style={styles.popularText}>MOST POPULAR</Text></View>
<Text style={styles.defaultPlanTitle}>Yearly Plan</Text>
<Text style={styles.defaultPlanTerm}>12 mo • $59.99</Text>
<Text style={styles.defaultMonthlyPrice}>$5 / mo</Text>
<View style={styles.defaultPlanRadio}><PlanRadio selected={plan === 'yearly'} /></View>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Flo family plan"
accessibilityState={{ selected: plan === 'family' }}
onPress={() => onChoosePlan('family')}
style={[styles.defaultPlanCard, styles.defaultFamily]}
>
<View style={[styles.defaultCardBackground, plan === 'family' && styles.defaultCardSelected]} />
<Text style={styles.familyTitle}>Flo Family plan</Text>
<Text style={styles.familyMembers}>1-6 MEMBERS</Text>
<Text style={styles.familyTerm}>12 mo • $79.99</Text>
<Text style={styles.familyMonthly}>$6.67 / mo</Text>
<View style={styles.defaultPlanRadio}><PlanRadio selected={plan === 'family'} /></View>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel={`Continue with Flo ${plan} plan`}
onPress={createActionPress({
actionId: `flo.continue-${plan}${trialEnabled ? '-trial' : ''}`,
fallback: onPurchasePress,
haptic: 'light',
onActionPress,
})}
style={styles.continueButton}
>
<Text style={styles.continueText}>Continue</Text>
</ReplicaPressable>
<ReplicaPressable accessibilityLabel="View all Flo plans and trials" onPress={onOpenAllPlans} style={styles.viewAllPlans}>
<Text style={styles.viewAllPlansText}>View all plans and trials</Text>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Subscribe in the App Store"
onPress={createActionPress({
actionId: 'flo.subscribe-app-store',
fallback: onPurchasePress,
onActionPress,
})}
style={styles.subscribeStore}
>
<Text style={styles.subscribeStoreText}>Subscribe in the App Store</Text>
</ReplicaPressable>
<View style={styles.limitedRow}>
<Text style={styles.limitedText}>Cancel your plan anytime or </Text>
<ReplicaPressable
accessibilityLabel="Choose limited Flo access"
onPress={createActionPress({
actionId: 'flo.limited-access',
fallback: onClosePress,
onActionPress,
})}
>
<Text style={styles.limitedLink}>choose limited access</Text>
</ReplicaPressable>
</View>
<ReplicaPressable
accessibilityLabel="Continue without full Flo benefits"
onPress={createActionPress({
actionId: 'flo.limited-access',
fallback: onClosePress,
onActionPress,
})}
style={styles.limitedSecondLine}
>
<Text style={styles.limitedLink}>without full Flo benefits.</Text>
</ReplicaPressable>
</ScrollView>
);
}
function TrialSwitch({ enabled }: { enabled: boolean }) {
const progress = useSharedValue(enabled ? 1 : 0);
useEffect(() => {
progress.value = withTiming(enabled ? 1 : 0, {
duration: 180,
easing: Easing.out(Easing.cubic),
});
}, [enabled, progress]);
const thumbStyle = useAnimatedStyle(() => ({
transform: [{ translateX: interpolate(progress.value, [0, 1], [0, 30]) }],
}));
const trackStyle = useAnimatedStyle(() => ({
backgroundColor: progress.value > 0.5 ? '#FF5A7D' : '#DCD8E5',
}));
return (
<Animated.View style={[styles.switchTrack, trackStyle]}>
<Animated.View style={[styles.switchThumb, thumbStyle]} />
</Animated.View>
);
}
function AllPlansSheet({
onActionPress,
onBack,
onChoosePlan,
onPurchasePress,
onToggleTrial,
panGesture,
plan,
sheetStyle,
trialEnabled,
}: Pick<PaywallProps, 'onActionPress' | 'onPurchasePress'> & {
onBack: () => void;
onChoosePlan: (plan: FloPlan) => void;
onToggleTrial: () => void;
panGesture: ReturnType<typeof Gesture.Pan>;
plan: FloPlan;
sheetStyle: object;
trialEnabled: boolean;
}) {
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.allPlansSheet, sheetStyle]}>
<ScrollView bounces={false} contentContainerStyle={styles.allPlansContent} showsVerticalScrollIndicator={false}>
<ReplicaPressable
accessibilityLabel="Enable Flo free trial"
accessibilityState={{ checked: trialEnabled }}
onPress={onToggleTrial}
style={styles.allTrialRow}
>
<Text style={styles.allTrialText}>Not sure yet? Enable free trial.</Text>
<TrialSwitch enabled={trialEnabled} />
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Flo yearly plan"
accessibilityState={{ selected: plan === 'yearly' }}
onPress={() => onChoosePlan('yearly')}
style={[styles.allPlanCard, styles.allYearly, plan === 'yearly' && styles.allPlanSelected]}
>
<View style={styles.allPopularBand}><Text style={styles.allPopularText}>MOST POPULAR</Text></View>
<Text style={styles.allYearlyTitle}>Yearly Plan</Text>
<Text style={styles.allYearlyTerm}>12 mo • $59.99</Text>
<Text style={styles.allYearlyMonthly}>$5 / mo</Text>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Flo family plan"
accessibilityState={{ selected: plan === 'family' }}
onPress={() => onChoosePlan('family')}
style={[styles.allPlanCard, styles.allFamily, plan === 'family' && styles.allPlanSelected]}
>
<Text style={styles.allFamilyTitle}>Flo Family plan</Text>
<Text style={styles.allFamilyMembers}>1-6 MEMBERS</Text>
<Text style={styles.allFamilyTerm}>12 mo • $79.99</Text>
<Text style={styles.allFamilyMonthly}>$6.67 / mo</Text>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel="Flo monthly plan"
accessibilityState={{ selected: plan === 'monthly' }}
onPress={() => onChoosePlan('monthly')}
style={[styles.allPlanCard, styles.allMonthly, plan === 'monthly' && styles.allPlanSelected]}
>
<Text style={styles.allMonthlyTitle}>Monthly</Text>
<Text style={styles.allMonthlyPrice}>$14.99 / mo</Text>
</ReplicaPressable>
<ReplicaPressable
accessibilityLabel={`Continue with Flo ${plan} plan`}
onPress={createActionPress({
actionId: `flo.continue-${plan}${trialEnabled ? '-trial' : ''}`,
fallback: onPurchasePress,
haptic: 'light',
onActionPress,
})}
style={styles.allContinue}
>
<Text style={styles.continueText}>Continue</Text>
</ReplicaPressable>
<ReplicaPressable accessibilityLabel="Go back to featured Flo plans" onPress={onBack} style={styles.goBack}>
<Text style={styles.goBackText}>Go back</Text>
</ReplicaPressable>
</ScrollView>
</Animated.View>
</GestureDetector>
);
}
function FloPaywallExperience({
initialVariant,
onActionPress,
onClosePress,
onPurchasePress,
onRestorePress,
onVariantChange,
}: PaywallProps) {
const initialMode = normalizeMode(initialVariant);
const [mode, setMode] = useState<FloMode>(initialMode);
const [plan, setPlan] = useState<FloPlan>('yearly');
const [trialEnabled, setTrialEnabled] = useState(false);
const [isClosing, setIsClosing] = useState(false);
const sheetProgress = useSharedValue(initialMode === 'all-plans' ? 1 : 0);
const panY = useSharedValue(0);
const choosePlan = (next: FloPlan) => {
setPlan(next);
createActionPress({ actionId: `flo.select-${next}`, onActionPress })();
};
const toggleTrial = () => {
setTrialEnabled((enabled) => !enabled);
createActionPress({ actionId: 'flo.toggle-trial', onActionPress })();
};
const openAllPlans = () => {
setIsClosing(false);
setMode('all-plans');
panY.value = 0;
sheetProgress.value = withTiming(1, { duration: 310, easing: Easing.out(Easing.cubic) });
onVariantChange?.('all-plans');
createActionPress({ actionId: 'flo.view-all-plans', onActionPress })();
};
const finishClose = () => {
setMode('plans');
setIsClosing(false);
};
const closeAllPlans = (actionId: 'flo.dismiss-all-plans' | 'flo.go-back' = 'flo.go-back') => {
if (isClosing) return;
setIsClosing(true);
panY.value = withTiming(0, { duration: 180 });
sheetProgress.value = withTiming(
0,
{ duration: 260, easing: Easing.inOut(Easing.cubic) },
(finished) => {
if (finished) runOnJS(finishClose)();
},
);
onVariantChange?.('plans');
createActionPress({ actionId, onActionPress })();
};
const panGesture = Gesture.Pan()
.activeOffsetY([-8, 8])
.onUpdate((event) => {
panY.value = Math.max(0, event.translationY);
})
.onEnd((event) => {
if (panY.value > 78 || event.velocityY > 720) {
runOnJS(closeAllPlans)('flo.dismiss-all-plans');
} else {
panY.value = withSpring(0, { damping: 22, stiffness: 260 });
}
});
const backdropStyle = useAnimatedStyle(() => ({
opacity: interpolate(sheetProgress.value, [0, 1], [0, 0.6]),
}));
const sheetStyle = useAnimatedStyle(() => ({
transform: [{ translateY: interpolate(sheetProgress.value, [0, 1], [520, 0]) + panY.value }],
}));
const modalVisible = mode === 'all-plans';
return (
<ReferenceCanvas backgroundColor="#FFFFFF" testID="paywall-flo">
<StatusBar style="dark" />
<TestimonialHeader
initialPage={modalVisible ? 1 : 0}
modalVisible={modalVisible}
progress={sheetProgress}
/>
<DefaultSheet
onActionPress={onActionPress}
onChoosePlan={choosePlan}
onClosePress={onClosePress}
onOpenAllPlans={openAllPlans}
onPurchasePress={onPurchasePress}
onToggleTrial={toggleTrial}
plan={plan === 'monthly' ? 'yearly' : plan}
trialEnabled={trialEnabled}
/>
{modalVisible ? (
<>
<Pressable
accessibilityLabel="Dismiss all Flo plans"
accessibilityRole="button"
onPress={() => closeAllPlans('flo.dismiss-all-plans')}
style={[StyleSheet.absoluteFill, styles.modalBackdropTouch]}
>
<Animated.View pointerEvents="none" style={[styles.backdrop, backdropStyle]} />
</Pressable>
<AllPlansSheet
onActionPress={onActionPress}
onBack={() => closeAllPlans('flo.go-back')}
onChoosePlan={choosePlan}
onPurchasePress={onPurchasePress}
onToggleTrial={toggleTrial}
panGesture={panGesture}
plan={plan}
sheetStyle={sheetStyle}
trialEnabled={trialEnabled}
/>
</>
) : null}
<FloBrand />
<Header onActionPress={onActionPress} onClosePress={onClosePress} onRestorePress={onRestorePress} />
</ReferenceCanvas>
);
}
export function FloPaywall(props: PaywallProps) {
return (
<FloPaywallExperience
key={`${props.initialVariant ?? 'plans'}:${String(props.replayKey ?? 0)}`}
{...props}
/>
);
}
const styles = StyleSheet.create({
brand: {
alignItems: 'center',
height: 31.4,
justifyContent: 'center',
left: 166.2,
position: 'absolute',
top: 55,
width: 64,
zIndex: 60,
},
close: {
alignItems: 'center',
height: 44,
justifyContent: 'center',
left: 2,
position: 'absolute',
top: 47,
width: 44,
zIndex: 30,
},
closeText: {
color: '#000000',
fontFamily: 'Inter_400Regular',
fontSize: 33,
lineHeight: 38,
},
restore: {
alignItems: 'center',
height: 40,
justifyContent: 'center',
position: 'absolute',
right: 10,
top: 48,
width: 78,
zIndex: 30,
},
restoreText: {
color: '#7F7F7F',
fontFamily: 'Inter_400Regular',
fontSize: 15.5,
lineHeight: 21,
},
heading: {
color: '#000000',
fontFamily: 'Inter_800ExtraBold',
fontSize: 32.7,
left: 40,
letterSpacing: -0.45,
lineHeight: 40,
position: 'absolute',
textAlign: 'center',
top: 92,
width: 310,
},
stars: {
color: '#E59E39',
fontFamily: 'Inter_700Bold',
fontSize: 31,
left: 100,
letterSpacing: 1.2,
lineHeight: 40,
position: 'absolute',
textAlign: 'center',
top: 137.5,
width: 190,
},
reviewPager: {
height: 126,
left: 16,
position: 'absolute',
width: 358,
},
reviewPagerInner: {
height: 126,
width: 358,
},
reviewPage: {
alignItems: 'center',
height: 126,
width: 358,
},
reviewCopy: {
color: '#000000',
fontFamily: 'Inter_400Regular',
fontSize: 16.5,
letterSpacing: -0.55,
lineHeight: 23,
textAlign: 'center',
},
reviewAuthor: {
alignSelf: 'flex-end',
color: '#7F7F7F',
fontFamily: 'Inter_400Regular',
fontSize: 14.5,
lineHeight: 20,
marginRight: 2,
marginTop: 13.5,
},
defaultSheet: {
backgroundColor: '#FFFFFF',
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
height: 536,
left: 0,
position: 'absolute',
shadowColor: '#000000',
shadowOffset: { height: -8, width: 0 },
shadowOpacity: 0.11,
shadowRadius: 18,
top: 308,
width: 390,
},
defaultSheetContent: {
height: 536,
position: 'relative',
width: 390,
},
defaultTrialRow: {
alignItems: 'center',
backgroundColor: '#F4EFFC',
borderRadius: 22,
flexDirection: 'row',
height: 58,
left: 17,
position: 'absolute',
top: 17,
width: 356,
},
defaultTrialText: {
color: '#000000',
fontFamily: 'Inter_400Regular',
fontSize: 16.5,
left: 13,
letterSpacing: -0.6,
lineHeight: 22,
position: 'absolute',
},
defaultTrialRadio: {
height: 28,
position: 'absolute',
right: 12,
width: 28,
},
radioSelected: {
alignItems: 'center',
backgroundColor: '#FF5A7D',
borderRadius: 11,
height: 22,
justifyContent: 'center',
width: 22,
},
radioCheck: {
color: '#FFFFFF',
fontFamily: 'Inter_700Bold',
fontSize: 14,
lineHeight: 17,
},
radioUnselected: {
borderColor: '#BDBDC2',
borderRadius: 11,
borderWidth: 1.6,
height: 22,
width: 22,
},
defaultPlanCard: {
left: 17,
position: 'absolute',
width: 356,
},
defaultYearly: { height: 102, top: 92 },
defaultFamily: { height: 75, top: 204 },
defaultCardBackground: {
backgroundColor: '#FFFFFF',
borderColor: '#E7E7E7',
borderRadius: 11,
borderWidth: 1.4,
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
defaultCardSelected: {
backgroundColor: '#FFEEF1',
borderColor: '#FF5A7D',
borderWidth: 2,
},
popularBand: {
alignItems: 'center',
backgroundColor: '#FF5A7D',
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
height: 19,
justifyContent: 'center',
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
popularText: {
color: '#FFFFFF',
fontFamily: 'Inter_500Medium',
fontSize: 12.5,
letterSpacing: -0.25,
},
defaultPlanTitle: {
color: '#000000',
fontFamily: 'Inter_800ExtraBold',
fontSize: 24,
left: 17,
letterSpacing: -0.5,
lineHeight: 29,
position: 'absolute',
top: 33,
},
defaultPlanTerm: {
color: '#000000',
fontFamily: 'Inter_700Bold',
fontSize: 14,
left: 17,
position: 'absolute',
top: 67,
},
defaultMonthlyPrice: {
color: '#000000',
fontFamily: 'Inter_700Bold',
fontSize: 14,
position: 'absolute',
right: 36,
top: 50.5,
},
defaultPlanRadio: {
position: 'absolute',
right: 12,
top: 46,
},
familyTitle: {
color: '#666666',
fontFamily: 'Inter_700Bold',
fontSize: 13.5,
left: 17,
position: 'absolute',
top: 6.5,
},
familyMembers: {
color: '#666666',
fontFamily: 'Inter_700Bold',
fontSize: 13.6,
left: 17,
position: 'absolute',
top: 28,
},
familyTerm: {
color: '#666666',
fontFamily: 'Inter_600SemiBold',
fontSize: 13.5,
left: 17,
position: 'absolute',
top: 49,
},
familyMonthly: {
color: '#666666',
fontFamily: 'Inter_700Bold',
fontSize: 13.5,
position: 'absolute',
right: 38,
top: 28,
},
continueButton: {
alignItems: 'center',
backgroundColor: '#FF5A7D',
borderRadius: 23,
height: 46,
justifyContent: 'center',
left: 17,
position: 'absolute',
top: 297,
width: 356,
},
continueText: {
color: '#FFFFFF',
fontFamily: 'Inter_600SemiBold',
fontSize: 16.6,
letterSpacing: -0.4,
},
viewAllPlans: {
alignItems: 'center',
height: 38,
justifyContent: 'center',
left: 75,
position: 'absolute',
top: 354,
width: 240,
},
viewAllPlansText: {
color: '#000000',
fontFamily: 'Inter_700Bold',
fontSize: 16.7,
letterSpacing: -0.55,
},
subscribeStore: {
alignItems: 'center',
height: 32,
justifyContent: 'center',
left: 90,
position: 'absolute',
top: 398.5,
width: 210,
},
subscribeStoreText: {
color: '#666666',
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
},
limitedRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'center',
left: 20,
position: 'absolute',
top: 443.5,
width: 350,
},
limitedText: {
color: '#666666',
fontFamily: 'Inter_400Regular',
fontSize: 13.5,
letterSpacing: -0.45,
},
limitedLink: {
color: '#FF5A7D',
fontFamily: 'Inter_400Regular',
fontSize: 13.5,
letterSpacing: -0.45,
textDecorationLine: 'underline',
},
limitedSecondLine: {
alignItems: 'center',
height: 32,
left: 95,
position: 'absolute',
top: 464.5,
width: 200,
},
backdrop: {
backgroundColor: '#000000',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
},
modalBackdropTouch: {
zIndex: 40,
},
allPlansSheet: {
backgroundColor: '#FFFFFF',
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
height: 501,
left: 0,
overflow: 'hidden',
position: 'absolute',
shadowColor: '#000000',
shadowOffset: { height: -7, width: 0 },
shadowOpacity: 0.12,
shadowRadius: 18,
top: 343,
width: 390,
zIndex: 50,
},
allPlansContent: {
height: 501,
position: 'relative',
width: 390,
},
allTrialRow: {
alignItems: 'center',
backgroundColor: '#F4EFFC',
borderRadius: 23,
flexDirection: 'row',
height: 58,
left: 17,
position: 'absolute',
top: 16,
width: 356,
},
allTrialText: {
color: '#000000',
fontFamily: 'Inter_400Regular',
fontSize: 16.4,
left: 25,
letterSpacing: -0.55,
position: 'absolute',
},
switchTrack: {
borderRadius: 15,
height: 30,
position: 'absolute',
right: 13,
width: 58,
},
switchThumb: {
backgroundColor: '#FFFFFF',
borderRadius: 13,
height: 26,
left: 2,
position: 'absolute',
shadowColor: '#000000',
shadowOffset: { height: 1, width: 0 },
shadowOpacity: 0.22,
shadowRadius: 2,
top: 2,
width: 26,
},
allPlanCard: {
backgroundColor: '#FFFFFF',
borderColor: '#EFEFEF',
borderRadius: 11,
borderWidth: 1.5,
left: 17,
position: 'absolute',
width: 356,
},
allPlanSelected: {
backgroundColor: '#FFEDF2',
borderColor: '#FF5A7D',
borderWidth: 2,
},
allYearly: {
height: 107,
top: 91,
},
allPopularBand: {
alignItems: 'center',
backgroundColor: '#FF5A7D',
borderTopLeftRadius: 9,
borderTopRightRadius: 9,
height: 24,
justifyContent: 'center',
left: -1.5,
position: 'absolute',
right: -1.5,
top: -1.5,
},
allPopularText: {
color: '#FFFFFF',
fontFamily: 'Inter_500Medium',
fontSize: 13,
},
allYearlyTitle: {
color: '#000000',
fontFamily: 'Inter_800ExtraBold',
fontSize: 23,
left: 17,
position: 'absolute',
top: 38.5,
},
allYearlyTerm: {
color: '#000000',
fontFamily: 'Inter_700Bold',
fontSize: 13.5,
left: 17,
position: 'absolute',
top: 70.5,
},
allYearlyMonthly: {
color: '#000000',
fontFamily: 'Inter_700Bold',
fontSize: 13.5,
position: 'absolute',
right: 17,
top: 55,
},
allFamily: { height: 88, top: 209 },
allFamilyTitle: {