@sudobility/building_blocks_rn 0.0.2 → 0.0.3

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.
@@ -0,0 +1,35 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { SubscriptionPageLabels, SubscriptionPageFormatters, AnalyticsTrackingParams } from '../../types';
3
+ import type { SubscriptionPackage } from '../subscription/SubscriptionScreen';
4
+ export interface CurrentSubscriptionStatus {
5
+ isActive: boolean;
6
+ planName?: string;
7
+ expirationDate?: string | null;
8
+ willRenew?: boolean;
9
+ productIdentifier?: string;
10
+ }
11
+ export interface AppSubscriptionPageLabels extends SubscriptionPageLabels {
12
+ statusActive?: string;
13
+ statusInactive?: string;
14
+ statusInactiveMessage?: string;
15
+ labelPlan?: string;
16
+ labelExpires?: string;
17
+ labelRenews?: string;
18
+ yes?: string;
19
+ no?: string;
20
+ }
21
+ export interface AppSubscriptionPageProps {
22
+ currentStatus?: CurrentSubscriptionStatus;
23
+ packages: SubscriptionPackage[];
24
+ labels?: AppSubscriptionPageLabels;
25
+ formatters?: SubscriptionPageFormatters;
26
+ onPurchase: (packageId: string) => Promise<boolean>;
27
+ onRestore: () => Promise<boolean>;
28
+ onPurchaseSuccess?: () => void;
29
+ onRestoreSuccess?: () => void;
30
+ onError?: (title: string, message: string) => void;
31
+ style?: StyleProp<ViewStyle>;
32
+ onTrack?: (params: AnalyticsTrackingParams) => void;
33
+ isLoading?: boolean;
34
+ }
35
+ export declare function AppSubscriptionPage({ currentStatus, packages, labels, formatters, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, isLoading: externalLoading, }: AppSubscriptionPageProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,256 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { View, Text, Pressable, ScrollView, ActivityIndicator, } from 'react-native';
4
+ import { createThemedStyles } from '../../utils/styles';
5
+ export function AppSubscriptionPage({ currentStatus, packages, labels = {}, formatters = {}, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, isLoading: externalLoading, }) {
6
+ const styles = useStyles();
7
+ const [loading, setLoading] = useState(null);
8
+ const formatPrice = formatters.formatPrice ??
9
+ ((price, currency) => `${currency} ${price.toFixed(2)}`);
10
+ const handlePurchase = useCallback(async (pkg) => {
11
+ if (loading)
12
+ return;
13
+ setLoading(pkg.id);
14
+ onTrack?.({
15
+ eventType: 'subscription_action',
16
+ componentName: 'AppSubscriptionPage',
17
+ label: 'purchase_tapped',
18
+ params: { package_id: pkg.id },
19
+ });
20
+ try {
21
+ const success = await onPurchase(pkg.id);
22
+ if (success) {
23
+ onPurchaseSuccess?.();
24
+ }
25
+ }
26
+ catch (e) {
27
+ onError?.('Purchase Failed', e instanceof Error ? e.message : 'An error occurred.');
28
+ }
29
+ finally {
30
+ setLoading(null);
31
+ }
32
+ }, [loading, onPurchase, onPurchaseSuccess, onError, onTrack]);
33
+ const handleRestore = useCallback(async () => {
34
+ if (loading)
35
+ return;
36
+ setLoading('restore');
37
+ onTrack?.({
38
+ eventType: 'subscription_action',
39
+ componentName: 'AppSubscriptionPage',
40
+ label: 'restore_tapped',
41
+ });
42
+ try {
43
+ const success = await onRestore();
44
+ if (success) {
45
+ onRestoreSuccess?.();
46
+ }
47
+ }
48
+ catch (e) {
49
+ onError?.('Restore Failed', e instanceof Error ? e.message : 'An error occurred.');
50
+ }
51
+ finally {
52
+ setLoading(null);
53
+ }
54
+ }, [loading, onRestore, onRestoreSuccess, onError, onTrack]);
55
+ return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [currentStatus && (_jsxs(View, { style: styles.statusCard, children: [_jsx(View, { style: styles.statusHeader, children: _jsx(View, { style: [
56
+ styles.statusBadge,
57
+ currentStatus.isActive
58
+ ? styles.statusBadgeActive
59
+ : styles.statusBadgeInactive,
60
+ ], children: _jsx(Text, { style: [
61
+ styles.statusBadgeText,
62
+ currentStatus.isActive
63
+ ? styles.statusBadgeTextActive
64
+ : styles.statusBadgeTextInactive,
65
+ ], children: currentStatus.isActive
66
+ ? (labels.statusActive ?? 'Active')
67
+ : (labels.statusInactive ?? 'Inactive') }) }) }), currentStatus.isActive ? (_jsxs(View, { style: styles.statusFields, children: [currentStatus.planName && (_jsxs(View, { style: styles.statusField, children: [_jsx(Text, { style: styles.statusFieldLabel, children: labels.labelPlan ?? 'Plan' }), _jsx(Text, { style: styles.statusFieldValue, children: currentStatus.planName })] })), currentStatus.expirationDate && (_jsxs(View, { style: styles.statusField, children: [_jsx(Text, { style: styles.statusFieldLabel, children: labels.labelExpires ?? 'Expires' }), _jsx(Text, { style: styles.statusFieldValue, children: currentStatus.expirationDate })] })), currentStatus.willRenew !== undefined && (_jsxs(View, { style: styles.statusField, children: [_jsx(Text, { style: styles.statusFieldLabel, children: labels.labelRenews ?? 'Auto-renews' }), _jsx(Text, { style: styles.statusFieldValue, children: currentStatus.willRenew
68
+ ? (labels.yes ?? 'Yes')
69
+ : (labels.no ?? 'No') })] }))] })) : (_jsx(Text, { style: styles.statusInactiveMessage, children: labels.statusInactiveMessage ??
70
+ 'Subscribe to unlock premium features' }))] })), externalLoading && (_jsx(View, { style: styles.loadingContainer, children: _jsx(ActivityIndicator, { size: 'large', color: styles.loadingIndicator.color }) })), !externalLoading && packages.length > 0 && (_jsx(View, { style: styles.packageList, children: packages.map(pkg => (_jsxs(View, { style: [
71
+ styles.packageCard,
72
+ pkg.isMostPopular && styles.packageCardPopular,
73
+ ], children: [pkg.isMostPopular && (_jsx(View, { style: styles.popularBadge, children: _jsx(Text, { style: styles.popularBadgeText, children: labels.mostPopular ?? 'Most Popular' }) })), _jsx(Text, { style: styles.packageTitle, children: pkg.title }), pkg.description && (_jsx(Text, { style: styles.packageDescription, children: pkg.description })), _jsx(Text, { style: styles.packagePrice, children: formatPrice(pkg.price, pkg.currency) }), pkg.features && pkg.features.length > 0 && (_jsx(View, { style: styles.featureList, children: pkg.features.map((feature, i) => (_jsxs(Text, { style: styles.featureItem, children: ['\u2713', " ", feature] }, i))) })), pkg.isCurrent ? (_jsx(View, { style: styles.currentBadge, children: _jsx(Text, { style: styles.currentBadgeText, children: labels.currentlyActive ?? 'Current Plan' }) })) : (_jsx(Pressable, { style: [
74
+ styles.purchaseButton,
75
+ loading === pkg.id && styles.purchaseButtonDisabled,
76
+ ], onPress: () => handlePurchase(pkg), disabled: loading !== null, children: loading === pkg.id ? (_jsx(ActivityIndicator, { color: '#ffffff', size: 'small' })) : (_jsx(Text, { style: styles.purchaseButtonText, children: labels.purchase ?? 'Subscribe' })) }))] }, pkg.id))) })), _jsx(Pressable, { style: [
77
+ styles.restoreButton,
78
+ loading === 'restore' && styles.restoreButtonDisabled,
79
+ ], onPress: handleRestore, disabled: loading !== null, children: loading === 'restore' ? (_jsx(ActivityIndicator, { color: styles.restoreText.color, size: 'small' })) : (_jsx(Text, { style: styles.restoreText, children: labels.restore ?? 'Restore Purchases' })) }), labels.restoreDescription && (_jsx(Text, { style: styles.restoreDescription, children: labels.restoreDescription }))] }));
80
+ }
81
+ const useStyles = createThemedStyles(colors => ({
82
+ container: {
83
+ flex: 1,
84
+ backgroundColor: colors.background,
85
+ },
86
+ content: {
87
+ padding: 16,
88
+ paddingBottom: 32,
89
+ },
90
+ // Status card
91
+ statusCard: {
92
+ backgroundColor: colors.card,
93
+ borderRadius: 16,
94
+ padding: 20,
95
+ borderWidth: 1,
96
+ borderColor: colors.border,
97
+ marginBottom: 16,
98
+ },
99
+ statusHeader: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ marginBottom: 12,
103
+ },
104
+ statusBadge: {
105
+ borderRadius: 12,
106
+ paddingHorizontal: 10,
107
+ paddingVertical: 4,
108
+ },
109
+ statusBadgeActive: {
110
+ backgroundColor: '#dcfce7',
111
+ },
112
+ statusBadgeInactive: {
113
+ backgroundColor: colors.surfaceSecondary,
114
+ },
115
+ statusBadgeText: {
116
+ fontSize: 13,
117
+ fontWeight: '600',
118
+ },
119
+ statusBadgeTextActive: {
120
+ color: '#16a34a',
121
+ },
122
+ statusBadgeTextInactive: {
123
+ color: colors.textSecondary,
124
+ },
125
+ statusFields: {
126
+ gap: 8,
127
+ },
128
+ statusField: {
129
+ flexDirection: 'row',
130
+ justifyContent: 'space-between',
131
+ alignItems: 'center',
132
+ },
133
+ statusFieldLabel: {
134
+ fontSize: 14,
135
+ color: colors.textSecondary,
136
+ },
137
+ statusFieldValue: {
138
+ fontSize: 14,
139
+ fontWeight: '500',
140
+ color: colors.text,
141
+ },
142
+ statusInactiveMessage: {
143
+ fontSize: 14,
144
+ color: colors.textSecondary,
145
+ },
146
+ // Loading
147
+ loadingContainer: {
148
+ paddingVertical: 48,
149
+ alignItems: 'center',
150
+ },
151
+ loadingIndicator: {
152
+ color: colors.primary,
153
+ },
154
+ // Package cards (mirrors SubscriptionScreen styles)
155
+ packageList: {
156
+ gap: 16,
157
+ },
158
+ packageCard: {
159
+ backgroundColor: colors.card,
160
+ borderRadius: 16,
161
+ padding: 20,
162
+ borderWidth: 1,
163
+ borderColor: colors.border,
164
+ },
165
+ packageCardPopular: {
166
+ borderColor: colors.primary,
167
+ borderWidth: 2,
168
+ },
169
+ popularBadge: {
170
+ backgroundColor: colors.primary,
171
+ borderRadius: 12,
172
+ paddingHorizontal: 10,
173
+ paddingVertical: 4,
174
+ alignSelf: 'flex-start',
175
+ marginBottom: 8,
176
+ },
177
+ popularBadgeText: {
178
+ color: '#ffffff',
179
+ fontSize: 12,
180
+ fontWeight: '600',
181
+ },
182
+ packageTitle: {
183
+ fontSize: 20,
184
+ fontWeight: '600',
185
+ color: colors.text,
186
+ },
187
+ packageDescription: {
188
+ fontSize: 14,
189
+ color: colors.textSecondary,
190
+ marginTop: 4,
191
+ },
192
+ packagePrice: {
193
+ fontSize: 28,
194
+ fontWeight: '700',
195
+ color: colors.text,
196
+ marginTop: 8,
197
+ },
198
+ featureList: {
199
+ marginTop: 12,
200
+ gap: 6,
201
+ },
202
+ featureItem: {
203
+ fontSize: 14,
204
+ color: colors.textSecondary,
205
+ },
206
+ purchaseButton: {
207
+ backgroundColor: colors.primary,
208
+ borderRadius: 10,
209
+ paddingVertical: 14,
210
+ alignItems: 'center',
211
+ marginTop: 16,
212
+ minHeight: 48,
213
+ justifyContent: 'center',
214
+ },
215
+ purchaseButtonDisabled: {
216
+ opacity: 0.6,
217
+ },
218
+ purchaseButtonText: {
219
+ color: '#ffffff',
220
+ fontSize: 16,
221
+ fontWeight: '600',
222
+ },
223
+ currentBadge: {
224
+ borderRadius: 10,
225
+ paddingVertical: 14,
226
+ alignItems: 'center',
227
+ marginTop: 16,
228
+ backgroundColor: colors.surfaceSecondary,
229
+ },
230
+ currentBadgeText: {
231
+ color: colors.textSecondary,
232
+ fontSize: 16,
233
+ fontWeight: '500',
234
+ },
235
+ restoreButton: {
236
+ marginTop: 24,
237
+ paddingVertical: 12,
238
+ alignItems: 'center',
239
+ minHeight: 44,
240
+ justifyContent: 'center',
241
+ },
242
+ restoreButtonDisabled: {
243
+ opacity: 0.6,
244
+ },
245
+ restoreText: {
246
+ color: colors.primary,
247
+ fontSize: 15,
248
+ fontWeight: '500',
249
+ },
250
+ restoreDescription: {
251
+ fontSize: 13,
252
+ color: colors.textMuted,
253
+ textAlign: 'center',
254
+ marginTop: 4,
255
+ },
256
+ }));
@@ -2,3 +2,5 @@ export { AppTextScreen } from './AppTextScreen';
2
2
  export type { AppTextScreenProps } from './AppTextScreen';
3
3
  export { LoginScreen } from './LoginScreen';
4
4
  export type { LoginScreenProps } from './LoginScreen';
5
+ export { AppSubscriptionPage } from './AppSubscriptionPage';
6
+ export type { AppSubscriptionPageProps, AppSubscriptionPageLabels, CurrentSubscriptionStatus, } from './AppSubscriptionPage';
@@ -1,2 +1,3 @@
1
1
  export { AppTextScreen } from './AppTextScreen';
2
2
  export { LoginScreen } from './LoginScreen';
3
+ export { AppSubscriptionPage } from './AppSubscriptionPage';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/building_blocks_rn",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Higher-level shared UI building blocks for Sudobility React Native apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",