@umituz/react-native-subscription 2.38.4 → 2.39.1
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/package.json +1 -1
- package/src/domains/paywall/components/PaywallScreen.styles.ts +57 -0
- package/src/domains/paywall/components/PaywallScreen.tsx +1 -1
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.tsx +6 -3
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.types.ts +13 -1
- package/src/index.ts +0 -1
- package/src/domains/paywall/components/PaywallModal.styles.ts +0 -48
- package/src/domains/paywall/components/PaywallModal.tsx +0 -111
- package/src/domains/paywall/components/PaywallModal.types.ts +0 -24
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.tsx +0 -92
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.types.ts +0 -21
- package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.39.1",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
|
|
3
|
+
export const paywallScreenStyles = StyleSheet.create({
|
|
4
|
+
container: {
|
|
5
|
+
flex: 1,
|
|
6
|
+
},
|
|
7
|
+
heroContainer: {
|
|
8
|
+
alignItems: "center",
|
|
9
|
+
marginBottom: 24,
|
|
10
|
+
},
|
|
11
|
+
heroImage: {
|
|
12
|
+
width: 120,
|
|
13
|
+
height: 120,
|
|
14
|
+
borderRadius: 60,
|
|
15
|
+
},
|
|
16
|
+
header: {
|
|
17
|
+
paddingHorizontal: 24,
|
|
18
|
+
marginBottom: 24,
|
|
19
|
+
},
|
|
20
|
+
title: {
|
|
21
|
+
marginBottom: 8,
|
|
22
|
+
},
|
|
23
|
+
subtitle: {
|
|
24
|
+
lineHeight: 22,
|
|
25
|
+
},
|
|
26
|
+
plans: {
|
|
27
|
+
paddingHorizontal: 16,
|
|
28
|
+
marginBottom: 16,
|
|
29
|
+
},
|
|
30
|
+
loading: {
|
|
31
|
+
alignItems: "center",
|
|
32
|
+
paddingVertical: 32,
|
|
33
|
+
},
|
|
34
|
+
loadingText: {
|
|
35
|
+
marginTop: 12,
|
|
36
|
+
},
|
|
37
|
+
stickyFooter: {
|
|
38
|
+
position: "absolute",
|
|
39
|
+
bottom: 0,
|
|
40
|
+
left: 0,
|
|
41
|
+
right: 0,
|
|
42
|
+
paddingHorizontal: 16,
|
|
43
|
+
paddingTop: 12,
|
|
44
|
+
backgroundColor: "#000",
|
|
45
|
+
},
|
|
46
|
+
cta: {
|
|
47
|
+
borderRadius: 14,
|
|
48
|
+
paddingVertical: 16,
|
|
49
|
+
alignItems: "center",
|
|
50
|
+
},
|
|
51
|
+
ctaDisabled: {
|
|
52
|
+
opacity: 0.6,
|
|
53
|
+
},
|
|
54
|
+
ctaText: {
|
|
55
|
+
fontWeight: "600",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -12,7 +12,7 @@ import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area"
|
|
|
12
12
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
13
13
|
import { Image } from "expo-image";
|
|
14
14
|
import { PlanCard } from "./PlanCard";
|
|
15
|
-
import {
|
|
15
|
+
import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
|
|
16
16
|
import { PaywallFeatures } from "./PaywallFeatures";
|
|
17
17
|
import { PaywallFooter } from "./PaywallFooter";
|
|
18
18
|
import { usePaywallActions } from "../hooks/usePaywallActions";
|
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import React, { useMemo, useCallback } from "react";
|
|
10
|
-
import { View, ScrollView, TouchableOpacity, StyleSheet
|
|
10
|
+
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
11
11
|
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
|
|
12
12
|
import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
|
|
13
13
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
14
14
|
import { usePaywallFeedback } from "../../../../../presentation/hooks/feedback/usePaywallFeedback";
|
|
15
15
|
import { FeedbackOption } from "./FeedbackOption";
|
|
16
|
-
import type { PaywallFeedbackScreenProps } from "./PaywallFeedbackScreen.types";
|
|
16
|
+
import type { PaywallFeedbackScreenProps, PaywallFeedbackTranslations } from "./PaywallFeedbackScreen.types";
|
|
17
|
+
|
|
18
|
+
// Re-export types for convenience
|
|
19
|
+
export type { PaywallFeedbackTranslations, PaywallFeedbackScreenProps };
|
|
17
20
|
|
|
18
21
|
const FEEDBACK_OPTION_IDS = [
|
|
19
22
|
"too_expensive",
|
|
@@ -188,6 +191,6 @@ const createScreenStyles = (tokens: any, insets: any) => ({
|
|
|
188
191
|
alignItems: 'center' as const,
|
|
189
192
|
},
|
|
190
193
|
submitText: {
|
|
191
|
-
fontWeight: 600,
|
|
194
|
+
fontWeight: "600",
|
|
192
195
|
},
|
|
193
196
|
});
|
package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.types.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
export interface PaywallFeedbackTranslations {
|
|
2
|
+
title: string;
|
|
3
|
+
subtitle: string;
|
|
4
|
+
submit: string;
|
|
5
|
+
otherPlaceholder: string;
|
|
6
|
+
reasons: {
|
|
7
|
+
too_expensive: string;
|
|
8
|
+
no_need: string;
|
|
9
|
+
trying_out: string;
|
|
10
|
+
technical_issues: string;
|
|
11
|
+
other: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
export interface PaywallFeedbackScreenProps {
|
|
4
16
|
onClose: () => void;
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,6 @@ export * from "./domains/subscription/presentation/components/details/PremiumDet
|
|
|
52
52
|
export { PremiumStatusBadge } from "./domains/subscription/presentation/components/details/PremiumStatusBadge";
|
|
53
53
|
export type { PremiumStatusBadgeProps } from "./domains/subscription/presentation/components/details/PremiumStatusBadge.types";
|
|
54
54
|
export * from "./domains/subscription/presentation/components/sections/SubscriptionSection";
|
|
55
|
-
export * from "./domains/subscription/presentation/components/feedback/PaywallFeedbackModal";
|
|
56
55
|
export * from "./domains/subscription/presentation/components/feedback/PaywallFeedbackScreen";
|
|
57
56
|
export * from "./domains/subscription/presentation/screens/SubscriptionDetailScreen";
|
|
58
57
|
export type {
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from "react-native";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export const paywallModalStyles = StyleSheet.create({
|
|
5
|
-
modalContent: { padding: 0, borderWidth: 0, overflow: "hidden" },
|
|
6
|
-
container: { flex: 1, flexDirection: "column" as const },
|
|
7
|
-
scrollContainer: { flex: 1, flexShrink: 1 },
|
|
8
|
-
closeBtn: {
|
|
9
|
-
position: "absolute",
|
|
10
|
-
top: 12,
|
|
11
|
-
right: 12,
|
|
12
|
-
width: 32,
|
|
13
|
-
height: 32,
|
|
14
|
-
borderRadius: 16,
|
|
15
|
-
justifyContent: "center",
|
|
16
|
-
alignItems: "center",
|
|
17
|
-
zIndex: 10,
|
|
18
|
-
},
|
|
19
|
-
scroll: { flexGrow: 1, padding: 16, paddingTop: 16, paddingBottom: 32 },
|
|
20
|
-
heroContainer: { alignItems: "center", marginBottom: 20, marginTop: 32 },
|
|
21
|
-
heroImage: { width: 180, height: 180, borderRadius: 90 },
|
|
22
|
-
header: { alignItems: "center", marginBottom: 12 },
|
|
23
|
-
title: { fontWeight: "700", textAlign: "center", marginBottom: 4 },
|
|
24
|
-
subtitle: { textAlign: "center" },
|
|
25
|
-
features: { borderRadius: 12, padding: 12, marginBottom: 12, gap: 8 },
|
|
26
|
-
featureRow: { flexDirection: "row", alignItems: "center" },
|
|
27
|
-
featureIcon: {
|
|
28
|
-
width: 32,
|
|
29
|
-
height: 32,
|
|
30
|
-
borderRadius: 16,
|
|
31
|
-
justifyContent: "center",
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
marginRight: 8,
|
|
34
|
-
},
|
|
35
|
-
featureText: { flex: 1, fontWeight: "500" },
|
|
36
|
-
loading: { alignItems: "center", paddingVertical: 24 },
|
|
37
|
-
loadingText: { marginTop: 8 },
|
|
38
|
-
plans: { marginBottom: 12 },
|
|
39
|
-
stickyFooter: { flexShrink: 0, paddingHorizontal: 16, paddingTop: 12, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: "rgba(128,128,128,0.2)" },
|
|
40
|
-
cta: { borderRadius: 12, paddingVertical: 14, alignItems: "center", marginBottom: 12 },
|
|
41
|
-
ctaDisabled: { opacity: 0.5 },
|
|
42
|
-
ctaText: { fontWeight: "700" },
|
|
43
|
-
footer: { flexDirection: "column", alignItems: "center", gap: 8 },
|
|
44
|
-
restoreButton: { marginBottom: 8 },
|
|
45
|
-
restoreButtonDisabled: { opacity: 0.5 },
|
|
46
|
-
legalRow: { flexDirection: "row", justifyContent: "center", gap: 16 },
|
|
47
|
-
footerLink: {},
|
|
48
|
-
});
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect } from "react";
|
|
2
|
-
import { View, ScrollView, TouchableOpacity, Linking } from "react-native";
|
|
3
|
-
import { AtomicText, AtomicIcon, AtomicSpinner } from "@umituz/react-native-design-system/atoms";
|
|
4
|
-
import { BaseModal } from "@umituz/react-native-design-system/molecules";
|
|
5
|
-
import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
|
|
6
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
7
|
-
import { Image } from "expo-image";
|
|
8
|
-
import { PlanCard } from "./PlanCard";
|
|
9
|
-
import { paywallModalStyles as styles } from "./PaywallModal.styles";
|
|
10
|
-
import { PaywallFeatures } from "./PaywallFeatures";
|
|
11
|
-
import { PaywallFooter } from "./PaywallFooter";
|
|
12
|
-
import { usePaywallActions } from "../hooks/usePaywallActions";
|
|
13
|
-
import { PaywallModalProps } from "./PaywallModal.types";
|
|
14
|
-
|
|
15
|
-
export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
16
|
-
const { visible, onClose, translations, packages = [], features = [], legalUrls = {}, bestValueIdentifier, creditAmounts, creditsLabel, heroImage, onPurchase, onRestore, onPurchaseSuccess, onPurchaseError, onAuthRequired, source, isLoadingPackages } = props;
|
|
17
|
-
const tokens = useAppDesignTokens();
|
|
18
|
-
const insets = useSafeAreaInsets();
|
|
19
|
-
|
|
20
|
-
if (__DEV__) {
|
|
21
|
-
console.log("[PaywallModal] Render:", {
|
|
22
|
-
visible,
|
|
23
|
-
packagesCount: packages.length,
|
|
24
|
-
isLoadingPackages,
|
|
25
|
-
source,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { selectedPlanId, setSelectedPlanId, isProcessing, handlePurchase, handleRestore, resetState } = usePaywallActions({
|
|
30
|
-
packages,
|
|
31
|
-
onPurchase,
|
|
32
|
-
onRestore,
|
|
33
|
-
source,
|
|
34
|
-
onPurchaseSuccess,
|
|
35
|
-
onPurchaseError,
|
|
36
|
-
onAuthRequired,
|
|
37
|
-
onClose
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (!visible) resetState();
|
|
42
|
-
}, [visible, resetState]);
|
|
43
|
-
|
|
44
|
-
// Auto-select first package when packages load and none is selected
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (packages.length > 0 && !selectedPlanId) {
|
|
47
|
-
setSelectedPlanId(packages[0].product.identifier);
|
|
48
|
-
}
|
|
49
|
-
}, [packages, selectedPlanId, setSelectedPlanId]);
|
|
50
|
-
|
|
51
|
-
const handleLegalUrl = useCallback(async (url: string | undefined) => {
|
|
52
|
-
if (!url) return;
|
|
53
|
-
try {
|
|
54
|
-
if (await Linking.canOpenURL(url)) await Linking.openURL(url);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('[PaywallModal] Failed to open URL:', error instanceof Error ? error.message : String(error));
|
|
57
|
-
}
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<BaseModal visible={visible} onClose={onClose} contentStyle={styles.modalContent}>
|
|
63
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.surface }]}>
|
|
64
|
-
<TouchableOpacity onPress={onClose} style={[styles.closeBtn, { backgroundColor: tokens.colors.surfaceSecondary, top: Math.max(insets.top, 12) }]} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
|
65
|
-
<AtomicIcon name="close-outline" size="md" customColor={tokens.colors.textPrimary} />
|
|
66
|
-
</TouchableOpacity>
|
|
67
|
-
|
|
68
|
-
{/* Scrollable content — plan cards scroll, CTA is always pinned below */}
|
|
69
|
-
<ScrollView style={styles.scrollContainer} contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>
|
|
70
|
-
{heroImage && <View style={styles.heroContainer}><Image source={heroImage} style={styles.heroImage} contentFit="cover" transition={0} /></View>}
|
|
71
|
-
<View style={styles.header}>
|
|
72
|
-
<AtomicText type="headlineMedium" adjustsFontSizeToFit numberOfLines={2} minimumFontScale={0.75} style={[styles.title, { color: tokens.colors.textPrimary }]}>{translations.title}</AtomicText>
|
|
73
|
-
{translations.subtitle && <AtomicText type="bodyMedium" adjustsFontSizeToFit numberOfLines={3} minimumFontScale={0.8} style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>{translations.subtitle}</AtomicText>}
|
|
74
|
-
</View>
|
|
75
|
-
<PaywallFeatures features={features} />
|
|
76
|
-
<View style={styles.plans}>
|
|
77
|
-
{isLoadingPackages ? (
|
|
78
|
-
<View style={styles.loading}>
|
|
79
|
-
<AtomicSpinner size="md" />
|
|
80
|
-
{translations.processingText && (
|
|
81
|
-
<AtomicText type="bodySmall" style={[styles.loadingText, { color: tokens.colors.textSecondary }]}>{translations.processingText}</AtomicText>
|
|
82
|
-
)}
|
|
83
|
-
</View>
|
|
84
|
-
) : packages.length === 0 ? (
|
|
85
|
-
<View style={styles.loading}>
|
|
86
|
-
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary, textAlign: "center" }}>{translations.emptyText ?? "No packages available"}</AtomicText>
|
|
87
|
-
</View>
|
|
88
|
-
) : (
|
|
89
|
-
packages.map((pkg) => {
|
|
90
|
-
const pid = pkg.product.identifier;
|
|
91
|
-
return (
|
|
92
|
-
<PlanCard key={pid} pkg={pkg} isSelected={selectedPlanId === pid} onSelect={() => setSelectedPlanId(pid)} badge={pid === bestValueIdentifier ? translations.bestValueBadgeText : undefined} creditAmount={creditAmounts?.[pid]} creditsLabel={creditsLabel} />
|
|
93
|
-
);
|
|
94
|
-
})
|
|
95
|
-
)}
|
|
96
|
-
</View>
|
|
97
|
-
</ScrollView>
|
|
98
|
-
|
|
99
|
-
{/* Sticky footer — always visible, never hidden behind scroll content */}
|
|
100
|
-
<View style={[styles.stickyFooter, { paddingBottom: Math.max(insets.bottom, 16) }]}>
|
|
101
|
-
<TouchableOpacity onPress={handlePurchase} disabled={isProcessing || isLoadingPackages || !selectedPlanId} style={[styles.cta, { backgroundColor: tokens.colors.primary }, (isProcessing || isLoadingPackages || !selectedPlanId) && styles.ctaDisabled]} activeOpacity={0.75}>
|
|
102
|
-
<AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>{isProcessing ? translations.processingText : translations.purchaseButtonText}</AtomicText>
|
|
103
|
-
</TouchableOpacity>
|
|
104
|
-
<PaywallFooter translations={translations} legalUrls={legalUrls} isProcessing={isProcessing} onRestore={onRestore ? handleRestore : undefined} onLegalClick={handleLegalUrl} />
|
|
105
|
-
</View>
|
|
106
|
-
</View>
|
|
107
|
-
</BaseModal>
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
PaywallModal.displayName = "PaywallModal";
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ImageSourcePropType } from "react-native";
|
|
2
|
-
import type { PurchasesPackage } from "react-native-purchases";
|
|
3
|
-
import type { SubscriptionFeature, PaywallTranslations, PaywallLegalUrls } from "../entities/types";
|
|
4
|
-
import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
|
|
5
|
-
|
|
6
|
-
export interface PaywallModalProps {
|
|
7
|
-
visible: boolean;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
translations: PaywallTranslations;
|
|
10
|
-
packages?: PurchasesPackage[];
|
|
11
|
-
features?: SubscriptionFeature[];
|
|
12
|
-
legalUrls?: PaywallLegalUrls;
|
|
13
|
-
bestValueIdentifier?: string;
|
|
14
|
-
creditAmounts?: Record<string, number>;
|
|
15
|
-
creditsLabel?: string;
|
|
16
|
-
heroImage?: ImageSourcePropType;
|
|
17
|
-
onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
|
|
18
|
-
onRestore?: () => Promise<void | boolean>;
|
|
19
|
-
onPurchaseSuccess?: () => void;
|
|
20
|
-
onPurchaseError?: (error: Error | string) => void;
|
|
21
|
-
onAuthRequired?: () => void;
|
|
22
|
-
source?: PurchaseSource;
|
|
23
|
-
isLoadingPackages?: boolean;
|
|
24
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from "react";
|
|
2
|
-
import { View, TouchableOpacity } from "react-native";
|
|
3
|
-
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
4
|
-
import { BaseModal } from "@umituz/react-native-design-system/molecules";
|
|
5
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
6
|
-
import { usePaywallFeedback } from "../../../../../presentation/hooks/feedback/usePaywallFeedback";
|
|
7
|
-
import { createPaywallFeedbackStyles } from "./paywallFeedbackStyles";
|
|
8
|
-
import { FeedbackOption } from "./FeedbackOption";
|
|
9
|
-
import type { PaywallFeedbackTranslations, PaywallFeedbackModalProps } from "./PaywallFeedbackModal.types";
|
|
10
|
-
import type { PaywallFeedbackScreenProps } from "./PaywallFeedbackScreen.types";
|
|
11
|
-
|
|
12
|
-
export type { PaywallFeedbackTranslations, PaywallFeedbackModalProps, PaywallFeedbackScreenProps };
|
|
13
|
-
|
|
14
|
-
const FEEDBACK_OPTION_IDS = [
|
|
15
|
-
"too_expensive",
|
|
16
|
-
"no_need",
|
|
17
|
-
"trying_out",
|
|
18
|
-
"technical_issues",
|
|
19
|
-
"other",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
export const PaywallFeedbackModal: React.FC<PaywallFeedbackModalProps> = React.memo(({
|
|
23
|
-
translations,
|
|
24
|
-
visible,
|
|
25
|
-
onClose,
|
|
26
|
-
onSubmit,
|
|
27
|
-
}) => {
|
|
28
|
-
const tokens = useAppDesignTokens();
|
|
29
|
-
|
|
30
|
-
const {
|
|
31
|
-
selectedReason,
|
|
32
|
-
otherText,
|
|
33
|
-
setOtherText,
|
|
34
|
-
selectReason,
|
|
35
|
-
handleSubmit,
|
|
36
|
-
handleSkip,
|
|
37
|
-
canSubmit,
|
|
38
|
-
} = usePaywallFeedback({ onSubmit, onClose });
|
|
39
|
-
|
|
40
|
-
const styles = useMemo(
|
|
41
|
-
() => createPaywallFeedbackStyles(tokens, canSubmit),
|
|
42
|
-
[tokens, canSubmit],
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<BaseModal
|
|
47
|
-
visible={visible}
|
|
48
|
-
onClose={handleSkip}
|
|
49
|
-
title={translations.title}
|
|
50
|
-
subtitle={translations.subtitle}
|
|
51
|
-
>
|
|
52
|
-
<View style={{ paddingHorizontal: tokens.spacing.md, paddingBottom: tokens.spacing.lg }}>
|
|
53
|
-
<View style={[styles.optionsContainer, { backgroundColor: 'transparent', padding: 0 }]}>
|
|
54
|
-
{FEEDBACK_OPTION_IDS.map((optionId) => {
|
|
55
|
-
const isSelected = selectedReason === optionId;
|
|
56
|
-
const isOther = optionId === "other";
|
|
57
|
-
const showInput = isSelected && isOther;
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<FeedbackOption
|
|
61
|
-
key={optionId}
|
|
62
|
-
isSelected={isSelected}
|
|
63
|
-
text={translations.reasons[optionId]}
|
|
64
|
-
showInput={showInput}
|
|
65
|
-
placeholder={translations.otherPlaceholder}
|
|
66
|
-
inputValue={otherText}
|
|
67
|
-
onSelect={() => selectReason(optionId)}
|
|
68
|
-
onChangeText={setOtherText}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
71
|
-
})}
|
|
72
|
-
</View>
|
|
73
|
-
|
|
74
|
-
<TouchableOpacity
|
|
75
|
-
style={[styles.submitButton, { marginTop: tokens.spacing.lg }]}
|
|
76
|
-
onPress={handleSubmit}
|
|
77
|
-
disabled={!canSubmit}
|
|
78
|
-
activeOpacity={0.8}
|
|
79
|
-
>
|
|
80
|
-
<AtomicText type="labelLarge" style={styles.submitText}>
|
|
81
|
-
{translations.submit}
|
|
82
|
-
</AtomicText>
|
|
83
|
-
</TouchableOpacity>
|
|
84
|
-
</View>
|
|
85
|
-
</BaseModal>
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
PaywallFeedbackModal.displayName = "PaywallFeedbackModal";
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.types.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface PaywallFeedbackTranslations {
|
|
2
|
-
title: string;
|
|
3
|
-
subtitle: string;
|
|
4
|
-
submit: string;
|
|
5
|
-
otherPlaceholder: string;
|
|
6
|
-
reasons: {
|
|
7
|
-
too_expensive: string;
|
|
8
|
-
no_need: string;
|
|
9
|
-
trying_out: string;
|
|
10
|
-
technical_issues: string;
|
|
11
|
-
other: string;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface PaywallFeedbackModalProps {
|
|
16
|
-
translations: PaywallFeedbackTranslations;
|
|
17
|
-
visible: boolean;
|
|
18
|
-
onClose: () => void;
|
|
19
|
-
onSubmit: (data: { reason: string; otherText?: string }) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from "react-native";
|
|
2
|
-
import type { DesignTokens } from "@umituz/react-native-design-system/theme";
|
|
3
|
-
|
|
4
|
-
export const createPaywallFeedbackStyles = (
|
|
5
|
-
tokens: DesignTokens,
|
|
6
|
-
canSubmit: boolean
|
|
7
|
-
) => {
|
|
8
|
-
return StyleSheet.create({
|
|
9
|
-
optionsContainer: {
|
|
10
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
11
|
-
borderRadius: 16,
|
|
12
|
-
overflow: "hidden",
|
|
13
|
-
marginBottom: 20,
|
|
14
|
-
},
|
|
15
|
-
submitButton: {
|
|
16
|
-
backgroundColor: canSubmit ? tokens.colors.primary : tokens.colors.surfaceSecondary,
|
|
17
|
-
borderRadius: 14,
|
|
18
|
-
paddingVertical: 16,
|
|
19
|
-
alignItems: "center",
|
|
20
|
-
opacity: canSubmit ? 1 : 0.6,
|
|
21
|
-
},
|
|
22
|
-
submitText: {
|
|
23
|
-
color: canSubmit ? tokens.colors.onPrimary : tokens.colors.textDisabled,
|
|
24
|
-
fontWeight: "600",
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
};
|