rn-onboarding-analytics 1.7.5 → 1.7.6

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.
Files changed (42) hide show
  1. package/README.md +9 -10
  2. package/lib/module/spill-onboarding/analytics.js +2 -1
  3. package/lib/module/spill-onboarding/analytics.js.map +1 -1
  4. package/lib/module/spill-onboarding/components/OnboardingImageContainer.js +13 -22
  5. package/lib/module/spill-onboarding/components/OnboardingImageContainer.js.map +1 -1
  6. package/lib/module/spill-onboarding/components/OnboardingIntroPanel.js +11 -6
  7. package/lib/module/spill-onboarding/components/OnboardingIntroPanel.js.map +1 -1
  8. package/lib/module/spill-onboarding/components/OnboardingPaywallPanel.js +127 -107
  9. package/lib/module/spill-onboarding/components/OnboardingPaywallPanel.js.map +1 -1
  10. package/lib/module/spill-onboarding/components/OnboardingStepContainer.js +2 -1
  11. package/lib/module/spill-onboarding/components/OnboardingStepContainer.js.map +1 -1
  12. package/lib/module/spill-onboarding/hooks/useLogDev.js +24 -0
  13. package/lib/module/spill-onboarding/hooks/useLogDev.js.map +1 -0
  14. package/lib/module/spill-onboarding/index.js +16 -6
  15. package/lib/module/spill-onboarding/index.js.map +1 -1
  16. package/lib/module/utils/theme.js +22 -3
  17. package/lib/module/utils/theme.js.map +1 -1
  18. package/lib/typescript/src/spill-onboarding/analytics.d.ts.map +1 -1
  19. package/lib/typescript/src/spill-onboarding/components/OnboardingImageContainer.d.ts +4 -2
  20. package/lib/typescript/src/spill-onboarding/components/OnboardingImageContainer.d.ts.map +1 -1
  21. package/lib/typescript/src/spill-onboarding/components/OnboardingIntroPanel.d.ts +1 -1
  22. package/lib/typescript/src/spill-onboarding/components/OnboardingIntroPanel.d.ts.map +1 -1
  23. package/lib/typescript/src/spill-onboarding/components/OnboardingPaywallPanel.d.ts +1 -1
  24. package/lib/typescript/src/spill-onboarding/components/OnboardingPaywallPanel.d.ts.map +1 -1
  25. package/lib/typescript/src/spill-onboarding/hooks/useLogDev.d.ts +7 -0
  26. package/lib/typescript/src/spill-onboarding/hooks/useLogDev.d.ts.map +1 -0
  27. package/lib/typescript/src/spill-onboarding/index.d.ts +1 -1
  28. package/lib/typescript/src/spill-onboarding/index.d.ts.map +1 -1
  29. package/lib/typescript/src/spill-onboarding/types.d.ts +38 -14
  30. package/lib/typescript/src/spill-onboarding/types.d.ts.map +1 -1
  31. package/lib/typescript/src/utils/theme.d.ts +1 -0
  32. package/lib/typescript/src/utils/theme.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/spill-onboarding/analytics.ts +2 -4
  35. package/src/spill-onboarding/components/OnboardingImageContainer.tsx +21 -25
  36. package/src/spill-onboarding/components/OnboardingIntroPanel.tsx +15 -4
  37. package/src/spill-onboarding/components/OnboardingPaywallPanel.tsx +170 -114
  38. package/src/spill-onboarding/components/OnboardingStepContainer.tsx +1 -0
  39. package/src/spill-onboarding/hooks/useLogDev.ts +26 -0
  40. package/src/spill-onboarding/index.tsx +21 -4
  41. package/src/spill-onboarding/types.ts +44 -14
  42. package/src/utils/theme.ts +21 -3
@@ -9,6 +9,10 @@ import {
9
9
  Dimensions,
10
10
  Platform,
11
11
  } from 'react-native';
12
+ import {
13
+ useSafeAreaInsets,
14
+ SafeAreaView,
15
+ } from 'react-native-safe-area-context';
12
16
  import {
13
17
  initConnection,
14
18
  getSubscriptions,
@@ -21,12 +25,14 @@ import {
21
25
  type PurchaseError,
22
26
  } from 'react-native-iap';
23
27
  import { useTheme } from '../../utils/ThemeContext';
24
- import type { Theme } from '../../utils/theme';
28
+ import { type Theme, resolveTheme } from '../../utils/theme';
25
29
  import PrimaryButton from '../buttons/PrimaryButton';
26
30
  import SkipButton from '../buttons/SkipButton';
27
31
  import { fontSizes, lineHeights } from '../../utils/fontStyles';
28
32
  import type { OnboardingPaywallPanelProps, PlatformSku } from '../types';
29
33
  import Skeleton from './Skeleton';
34
+ import { trackEvent } from '../analytics';
35
+ import { useLogDev } from '../hooks/useLogDev';
30
36
 
31
37
  const { height: screenHeight } = Dimensions.get('window');
32
38
 
@@ -45,9 +51,25 @@ function OnboardingPaywallPanel({
45
51
  onPrivacy,
46
52
  subscriptionSkus,
47
53
  onPurchaseResult,
54
+ apiKey,
55
+ isDev,
56
+ colors,
57
+ design,
58
+ theme: themeProp,
48
59
  }: OnboardingPaywallPanelProps) {
49
- const { theme } = useTheme();
50
- const styles = useMemo(() => createStyles(theme), [theme]);
60
+ const { theme: contextTheme } = useTheme();
61
+
62
+ const theme = useMemo(() => {
63
+ return resolveTheme(themeProp, contextTheme, 'dark');
64
+ }, [contextTheme, themeProp]);
65
+
66
+ const insets = useSafeAreaInsets();
67
+ const styles = useMemo(
68
+ () => createStyles(theme, colors, !!image, insets),
69
+ [theme, colors, image, insets]
70
+ );
71
+
72
+ const logDev = useLogDev(!!isDev);
51
73
 
52
74
  // Initialize selectedPlanId based on available configuration
53
75
  const initialPlanId = useMemo(() => {
@@ -422,7 +444,19 @@ function OnboardingPaywallPanel({
422
444
 
423
445
  const renderButton = () => {
424
446
  const handlePress = async () => {
425
- console.log('OnboardingPaywallPanel: handlePress', { selectedPlanId });
447
+ logDev('OnboardingPaywallPanel: handlePress', { selectedPlanId });
448
+
449
+ // Track purchase attempt
450
+ trackEvent(
451
+ apiKey,
452
+ 'purchase_attempt',
453
+ {
454
+ plan_id: selectedPlanId,
455
+ design,
456
+ },
457
+ isDev
458
+ );
459
+
426
460
  if ((subscriptionSkus || products) && selectedPlanId) {
427
461
  try {
428
462
  let result;
@@ -436,7 +470,7 @@ function OnboardingPaywallPanel({
436
470
  'subscriptionPeriodNumberIOS' in iapProduct ||
437
471
  'subscriptionOfferDetails' in iapProduct;
438
472
 
439
- console.log('OnboardingPaywallPanel: Processing purchase', {
473
+ logDev('OnboardingPaywallPanel: Processing purchase', {
440
474
  isSubscription,
441
475
  productId: iapProduct.productId,
442
476
  });
@@ -449,7 +483,7 @@ function OnboardingPaywallPanel({
449
483
  ?.offerToken;
450
484
  }
451
485
 
452
- console.log('OnboardingPaywallPanel: Requesting subscription', {
486
+ logDev('OnboardingPaywallPanel: Requesting subscription', {
453
487
  sku: selectedPlanId,
454
488
  offerToken,
455
489
  });
@@ -460,10 +494,9 @@ function OnboardingPaywallPanel({
460
494
  }),
461
495
  });
462
496
  } else {
463
- console.log(
464
- 'OnboardingPaywallPanel: Requesting one-time purchase',
465
- { sku: selectedPlanId }
466
- );
497
+ logDev('OnboardingPaywallPanel: Requesting one-time purchase', {
498
+ sku: selectedPlanId,
499
+ });
467
500
  result = await requestPurchase({
468
501
  sku: selectedPlanId,
469
502
  });
@@ -471,12 +504,12 @@ function OnboardingPaywallPanel({
471
504
  } else {
472
505
  // Fallback if product not found in fetched list but ID exists
473
506
  // Try subscription first as default legacy behavior
474
- console.log(
507
+ logDev(
475
508
  'OnboardingPaywallPanel: Product not in IAP list, attempting fallback purchase',
476
509
  selectedPlanId
477
510
  );
478
511
  try {
479
- console.log(
512
+ logDev(
480
513
  'OnboardingPaywallPanel: Fallback - attempting requestSubscription',
481
514
  selectedPlanId
482
515
  );
@@ -486,7 +519,7 @@ function OnboardingPaywallPanel({
486
519
  'OnboardingPaywallPanel: Fallback requestSubscription failed',
487
520
  subErr
488
521
  );
489
- console.log(
522
+ logDev(
490
523
  'OnboardingPaywallPanel: Fallback - attempting requestPurchase',
491
524
  selectedPlanId
492
525
  );
@@ -509,14 +542,33 @@ function OnboardingPaywallPanel({
509
542
  data: result,
510
543
  });
511
544
  }
545
+
546
+ // Track successful purchase
547
+ trackEvent(
548
+ apiKey,
549
+ 'purchase_success',
550
+ {
551
+ plan_id: selectedPlanId,
552
+ design,
553
+ },
554
+ isDev
555
+ );
512
556
  } catch (err) {
513
- console.warn('Purchase Error:', err);
514
- if (typeof err === 'object') {
515
- console.warn(
516
- 'Purchase Error Details:',
517
- JSON.stringify(err, null, 2)
518
- );
519
- }
557
+ logDev('PaywallScreen: Purchase failed', err);
558
+
559
+ // Track failed purchase
560
+ trackEvent(
561
+ apiKey,
562
+ 'purchase_failed',
563
+ {
564
+ plan_id: selectedPlanId,
565
+ design,
566
+ error:
567
+ typeof err === 'object' ? JSON.stringify(err) : String(err),
568
+ },
569
+ isDev
570
+ );
571
+
520
572
  if (onPurchaseResult) {
521
573
  onPurchaseResult({
522
574
  status: 'error',
@@ -571,7 +623,15 @@ function OnboardingPaywallPanel({
571
623
  };
572
624
 
573
625
  const handleClose = () => {
574
- console.log('[OnboardingPaywallPanel] Close button pressed');
626
+ logDev('[OnboardingPaywallPanel] Close button pressed');
627
+ trackEvent(
628
+ apiKey,
629
+ 'paywall_close',
630
+ {
631
+ design,
632
+ },
633
+ isDev
634
+ );
575
635
  if (onClose) {
576
636
  onClose();
577
637
  } else {
@@ -580,19 +640,21 @@ function OnboardingPaywallPanel({
580
640
  };
581
641
 
582
642
  return (
583
- <View style={styles.mainContainer}>
643
+ <SafeAreaView style={styles.mainContainer} edges={['top', 'bottom']}>
584
644
  {onClose && (
585
645
  <View style={styles.closeButton} pointerEvents="box-none">
586
646
  <SkipButton onPress={handleClose} />
587
647
  </View>
588
648
  )}
589
- <View style={styles.headerImageContainer}>
590
- {typeof image === 'function'
591
- ? image()
592
- : image && (
593
- <Image source={image} style={styles.image} resizeMode="cover" />
594
- )}
595
- </View>
649
+ {image && (
650
+ <View style={styles.headerImageContainer}>
651
+ {typeof image === 'function'
652
+ ? image()
653
+ : image && (
654
+ <Image source={image} style={styles.image} resizeMode="cover" />
655
+ )}
656
+ </View>
657
+ )}
596
658
 
597
659
  <View style={styles.sheetContainer}>
598
660
  <ScrollView
@@ -622,186 +684,180 @@ function OnboardingPaywallPanel({
622
684
  {renderButton()}
623
685
  </View>
624
686
  </View>
625
- </View>
687
+ </SafeAreaView>
626
688
  );
627
689
  }
628
690
 
629
691
  export default OnboardingPaywallPanel;
630
692
 
631
- const createStyles = (theme: Theme) =>
693
+ const createStyles = (
694
+ theme: Theme,
695
+ colors?: OnboardingPaywallPanelProps['colors'],
696
+ hasImage?: boolean,
697
+ insets?: { top: number; bottom: number; left: number; right: number }
698
+ ) =>
632
699
  StyleSheet.create({
633
700
  mainContainer: {
634
701
  flex: 1,
635
- backgroundColor: theme.bg.secondary,
702
+ backgroundColor: colors?.background?.primary || theme.bg.secondary,
636
703
  },
637
704
  closeButton: {
638
705
  position: 'absolute',
639
- top: (theme.insets?.top || 0) + 16,
706
+ top: (insets?.top || 0) + 8,
640
707
  right: 16,
641
- zIndex: 20,
708
+ zIndex: 10,
642
709
  },
643
710
  headerImageContainer: {
644
- height: screenHeight * 0.3,
711
+ height: screenHeight * 0.35,
712
+ width: '100%',
713
+ },
714
+ image: {
645
715
  width: '100%',
716
+ height: '100%',
646
717
  },
647
718
  sheetContainer: {
648
719
  flex: 1,
649
- marginTop: -24,
650
- backgroundColor: theme.bg.secondary,
651
- borderTopLeftRadius: 30,
652
- borderTopRightRadius: 30,
653
- overflow: 'scroll',
720
+ backgroundColor: colors?.background?.primary || theme.bg.secondary,
721
+ borderTopLeftRadius: hasImage ? 24 : 0,
722
+ borderTopRightRadius: hasImage ? 24 : 0,
723
+ marginTop: hasImage ? -24 : 0,
724
+ overflow: 'hidden',
654
725
  },
655
726
  container: {
656
727
  flex: 1,
657
728
  },
658
729
  contentContainer: {
659
- paddingBottom: 24,
660
- },
661
- footerContainer: {
662
- paddingHorizontal: 8,
663
- marginBottom: 40,
664
- paddingTop: 4,
665
- backgroundColor: theme.bg.secondary,
730
+ padding: 24,
731
+ paddingTop: hasImage ? 32 : (insets?.top || 0) + 40,
732
+ paddingBottom: 120, // Add padding for footer
666
733
  },
667
734
  contentWrapper: {
668
- paddingHorizontal: 16,
669
- paddingTop: 32,
670
- },
671
- image: {
672
- width: '100%',
673
- height: '100%',
735
+ flex: 1,
674
736
  },
675
737
  headerContainer: {
676
738
  alignItems: 'center',
677
- marginBottom: 24,
678
- marginTop: 8,
739
+ marginBottom: 32,
679
740
  },
680
741
  text: {
681
- fontSize: fontSizes.xxl,
682
- lineHeight: lineHeights.xxl,
683
742
  textAlign: 'center',
743
+ color: colors?.text?.primary || theme.text.primary,
684
744
  },
685
745
  line1: {
686
- color: theme.text.primary,
746
+ fontSize: fontSizes.xl,
747
+ fontWeight: '700',
748
+ marginBottom: 12,
749
+ lineHeight: lineHeights.xl,
687
750
  },
688
751
  line2: {
689
- marginTop: 8,
690
- color: theme.text.secondary,
691
752
  fontSize: fontSizes.md,
753
+ color: colors?.text?.secondary || theme.text.secondary,
692
754
  lineHeight: lineHeights.md,
755
+ marginTop: 4,
693
756
  },
694
757
  titleText: {
695
- fontFamily: theme.fonts.introTitle,
696
- fontWeight: 'bold',
758
+ color: colors?.text?.primary || theme.text.primary,
697
759
  },
698
760
  subtitleText: {
699
- fontFamily: theme.fonts.introSubtitle,
761
+ color: colors?.text?.secondary || theme.text.secondary,
700
762
  },
701
763
  featuresContainer: {
702
- marginBottom: 24,
703
- paddingHorizontal: 8,
764
+ marginBottom: 32,
704
765
  },
705
766
  featureRow: {
706
767
  flexDirection: 'row',
707
768
  alignItems: 'center',
708
- marginBottom: 12,
769
+ marginBottom: 16,
709
770
  },
710
771
  checkIcon: {
711
- color: theme.bg.accent,
712
- fontSize: fontSizes.lg,
772
+ fontSize: 18,
773
+ color: colors?.background?.accent || theme.bg.accent,
713
774
  marginRight: 12,
714
775
  fontWeight: 'bold',
715
776
  },
716
777
  featureText: {
717
- color: theme.text.primary,
718
778
  fontSize: fontSizes.md,
719
- fontWeight: '500',
779
+ color: colors?.text?.primary || theme.text.primary,
780
+ flex: 1,
781
+ lineHeight: lineHeights.md,
720
782
  },
721
783
  plansContainer: {
722
- gap: 12,
723
784
  marginBottom: 24,
785
+ gap: 12,
724
786
  },
725
787
  planCard: {
726
788
  flexDirection: 'row',
727
- alignItems: 'center',
728
789
  justifyContent: 'space-between',
790
+ alignItems: 'center',
729
791
  padding: 16,
730
- minHeight: 76,
731
- borderRadius: 16,
732
- backgroundColor: theme.bg.secondary,
792
+ borderRadius: 12,
733
793
  borderWidth: 1,
734
- borderColor: theme.bg.label,
735
- // Shadow for elevation
736
- shadowColor: '#000',
737
- shadowOffset: { width: 0, height: 2 },
738
- shadowOpacity: 0.05,
739
- shadowRadius: 4,
740
- elevation: 2,
794
+ borderColor: colors?.background?.label || theme.bg.label,
795
+ backgroundColor: 'transparent',
741
796
  },
742
797
  planCardSelected: {
743
- borderColor: theme.bg.accent,
744
- backgroundColor: theme.bg.label,
745
- borderWidth: 2,
798
+ borderColor: colors?.background?.accent || theme.bg.accent,
799
+ backgroundColor: colors?.background?.label || theme.bg.label,
746
800
  },
747
801
  planTitle: {
748
802
  fontSize: fontSizes.md,
749
803
  fontWeight: '600',
750
- color: theme.text.primary,
804
+ color: colors?.text?.primary || theme.text.primary,
805
+ marginBottom: 4,
751
806
  },
752
807
  planTitleSelected: {
753
- color: theme.text.primary,
754
- fontWeight: '700',
808
+ color: colors?.text?.primary || theme.text.primary,
755
809
  },
756
810
  planInterval: {
757
811
  fontSize: fontSizes.sm,
758
- color: theme.text.secondary,
759
- marginTop: 2,
812
+ color: colors?.text?.secondary || theme.text.secondary,
760
813
  },
761
814
  planIntervalSelected: {
762
- color: theme.text.secondary,
815
+ color: colors?.text?.secondary || theme.text.secondary,
763
816
  },
764
817
  planPrice: {
765
818
  fontSize: fontSizes.lg,
766
819
  fontWeight: '700',
767
- color: theme.text.primary,
820
+ color: colors?.text?.primary || theme.text.primary,
768
821
  },
769
822
  planPriceSelected: {
770
- color: theme.bg.accent,
823
+ color: colors?.text?.primary || theme.text.primary,
771
824
  },
772
- helperTextContainer: {
773
- marginTop: 4,
774
- backgroundColor: theme.bg.accent,
775
- paddingHorizontal: 8,
776
- paddingVertical: 2,
777
- borderRadius: 4,
778
- alignSelf: 'flex-start',
779
- },
780
- helperTextBadge: {
781
- color: theme.text.contrast,
782
- fontSize: fontSizes.xs,
783
- fontWeight: '600',
825
+ footerContainer: {
826
+ padding: 24,
827
+ paddingBottom: (insets?.bottom || 0) + 16,
828
+ borderTopWidth: 1,
829
+ borderTopColor: colors?.background?.label || theme.bg.label,
830
+ backgroundColor: colors?.background?.primary || theme.bg.secondary,
784
831
  },
785
832
  helperText: {
786
833
  textAlign: 'center',
787
- color: theme.text.secondary,
788
- fontSize: fontSizes.sm,
789
- marginBottom: 8,
790
- fontWeight: '500',
834
+ fontSize: fontSizes.xs,
835
+ color: colors?.text?.secondary || theme.text.secondary,
836
+ marginBottom: 16,
791
837
  },
792
838
  footerLinksContainer: {
793
839
  flexDirection: 'row',
794
840
  justifyContent: 'center',
795
841
  alignItems: 'center',
796
- marginTop: 4,
797
- gap: 8,
842
+ marginTop: 24,
843
+ marginBottom: 12,
798
844
  },
799
845
  footerLinkText: {
800
- fontSize: fontSizes.md,
801
- color: theme.text.secondary,
846
+ fontSize: fontSizes.xs,
847
+ color: colors?.text?.secondary || theme.text.secondary,
848
+ textDecorationLine: 'underline',
802
849
  },
803
850
  footerLinkSeparator: {
804
851
  fontSize: fontSizes.xs,
805
- color: theme.text.secondary,
852
+ color: colors?.text?.secondary || theme.text.secondary,
853
+ marginHorizontal: 8,
854
+ },
855
+ helperTextContainer: {
856
+ marginTop: 4,
857
+ },
858
+ helperTextBadge: {
859
+ fontSize: fontSizes.xs,
860
+ color: colors?.background?.accent || theme.bg.accent,
861
+ fontWeight: '600',
806
862
  },
807
863
  });
@@ -71,6 +71,7 @@ const createStyles = (theme: Theme) =>
71
71
  bottom: 0,
72
72
  left: 0,
73
73
  right: 0,
74
+ zIndex: 1, // Reduced from 10 to 1, to allow easier layering control
74
75
  },
75
76
  close: {
76
77
  position: 'absolute',
@@ -0,0 +1,26 @@
1
+ import { useCallback } from 'react';
2
+
3
+ /**
4
+ * Helper function to log messages with the Dev Mode prefix.
5
+ * Can be used outside of React components.
6
+ */
7
+ export const logDevMessage = (isDev: boolean, message: string, data?: any) => {
8
+ if (isDev) {
9
+ if (data) {
10
+ console.log(`🚧 [Dev Mode] ${message}`, JSON.stringify(data, null, 2));
11
+ } else {
12
+ console.log(`🚧 [Dev Mode] ${message}`);
13
+ }
14
+ }
15
+ };
16
+
17
+ export const useLogDev = (isDev: boolean) => {
18
+ const log = useCallback(
19
+ (message: string, data?: any) => {
20
+ logDevMessage(isDev, message, data);
21
+ },
22
+ [isDev]
23
+ );
24
+
25
+ return log;
26
+ };
@@ -18,6 +18,7 @@ import { type OnboardingProps } from './types';
18
18
  import useMeasureHeight from './hooks/useMeasureHeight';
19
19
  import { type Theme } from '../utils/theme';
20
20
  import { trackEvent } from './analytics';
21
+ import { useLogDev } from './hooks/useLogDev';
21
22
 
22
23
  function SpillOnboarding({
23
24
  animationDuration = 500,
@@ -34,8 +35,10 @@ function SpillOnboarding({
34
35
  isDev,
35
36
  paywallPanel: paywallPanelProps,
36
37
  backButtonIcon,
38
+ colors,
37
39
  }: OnboardingProps) {
38
40
  const { theme } = useTheme();
41
+ const logDev = useLogDev(!!isDev);
39
42
 
40
43
  const styles = useMemo(() => createStyles(theme), [theme]);
41
44
  const backgroundSpillProgress = useSharedValue(0);
@@ -67,7 +70,7 @@ function SpillOnboarding({
67
70
  const fromStepName = getStepName(step);
68
71
  const toStepName = getStepName(stepNumber);
69
72
 
70
- console.log('🔄 Onboarding Step Change:', {
73
+ logDev('Onboarding Step Change:', {
71
74
  from: fromStepName,
72
75
  to: toStepName,
73
76
  index: stepNumber,
@@ -88,7 +91,7 @@ function SpillOnboarding({
88
91
  isDev
89
92
  );
90
93
  },
91
- [onStepChangeProps, apiKey, step, steps, isDev]
94
+ [onStepChangeProps, apiKey, step, steps, isDev, logDev]
92
95
  );
93
96
 
94
97
  useEffect(() => {
@@ -202,6 +205,9 @@ function SpillOnboarding({
202
205
  <OnboardingPaywallPanel
203
206
  onPressContinue={handleContinue}
204
207
  onClose={handleClose}
208
+ apiKey={apiKey}
209
+ isDev={isDev}
210
+ colors={colors}
205
211
  {...otherProps}
206
212
  />
207
213
  );
@@ -227,7 +233,7 @@ function SpillOnboarding({
227
233
  buttonLabel={currentStep.buttonLabel}
228
234
  onBackPress={onBackPress}
229
235
  onNextPress={onNextPress}
230
- buttonPrimary={step === steps.length - 1}
236
+ buttonPrimary={false}
231
237
  showBackButton={currentStep.showBackButton ?? showBackButton}
232
238
  backButtonIcon={currentStep.backButtonIcon ?? backButtonIcon}
233
239
  />
@@ -263,13 +269,24 @@ function SpillOnboarding({
263
269
  <OnboardingImageContainer
264
270
  currentStep={currentStep}
265
271
  currentStepImage={currentStepImage}
266
- position={currentStep?.position ?? firstStep?.position ?? 'top'}
267
272
  animationDuration={animationDuration}
268
273
  backgroundSpillProgress={backgroundSpillProgress}
269
274
  screenHeight={screen.height}
270
275
  introPanel={introPanel}
271
276
  stepPanel={stepPanel}
272
277
  background={background}
278
+ propimageStyle={
279
+ typeof introPanelProps !== 'function'
280
+ ? introPanelProps.propimageStyle
281
+ : undefined
282
+ }
283
+ // If introPanel.image is a function, do NOT render it in ImageContainer
284
+ // because it's already rendered in OnboardingIntroPanel
285
+ hideImage={
286
+ !currentStep &&
287
+ typeof introPanelProps !== 'function' &&
288
+ typeof introPanelProps.image === 'function'
289
+ }
273
290
  />
274
291
 
275
292
  {isPaywall ? (