@sudobility/building_blocks_rn 0.0.16 → 0.0.17

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/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './src/components/footer';
3
3
  export * from './src/components/layout';
4
4
  export * from './src/components/settings';
5
5
  export * from './src/components/pages';
6
+ export * from './src/components/empty-state';
6
7
  export { SudobilityAppRN } from './src/app/SudobilityAppRN';
7
8
  export type { SudobilityAppRNProps } from './src/app/SudobilityAppRN';
8
9
  export { SafeSubscriptionContext, STUB_SUBSCRIPTION_VALUE, useSafeSubscription, } from './src/components/subscription/SafeSubscriptionContext';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export * from './src/components/footer';
4
4
  export * from './src/components/layout';
5
5
  export * from './src/components/settings';
6
6
  export * from './src/components/pages';
7
+ export * from './src/components/empty-state';
7
8
  // App wrapper without auth dependency
8
9
  export { SudobilityAppRN } from './src/app/SudobilityAppRN';
9
10
  // Subscription components without auth dependency
@@ -0,0 +1,9 @@
1
+ export interface EmptyStateProps {
2
+ /** Descriptive message shown above the action button */
3
+ message: string;
4
+ /** Label for the primary action button */
5
+ buttonLabel: string;
6
+ /** Callback fired when the action button is pressed */
7
+ onPress: () => void;
8
+ }
9
+ export declare function EmptyState({ message, buttonLabel, onPress }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View, Text, Pressable } from 'react-native';
3
+ import { createThemedStyles } from '../../utils/styles';
4
+ export function EmptyState({ message, buttonLabel, onPress }) {
5
+ const styles = useStyles();
6
+ return (_jsxs(View, { style: styles.container, children: [_jsx(Text, { style: styles.message, children: message }), _jsx(Pressable, { style: styles.button, onPress: onPress, accessibilityRole: 'button', accessibilityLabel: buttonLabel, children: _jsx(Text, { style: styles.buttonText, children: buttonLabel }) })] }));
7
+ }
8
+ const useStyles = createThemedStyles(colors => ({
9
+ container: {
10
+ flexGrow: 1,
11
+ justifyContent: 'center',
12
+ alignItems: 'center',
13
+ paddingHorizontal: 32,
14
+ },
15
+ message: {
16
+ fontSize: 15,
17
+ textAlign: 'center',
18
+ lineHeight: 22,
19
+ color: colors.textSecondary,
20
+ marginBottom: 16,
21
+ },
22
+ button: {
23
+ backgroundColor: '#2563eb',
24
+ borderRadius: 6,
25
+ minHeight: 44,
26
+ paddingHorizontal: 24,
27
+ paddingVertical: 8,
28
+ alignItems: 'center',
29
+ justifyContent: 'center',
30
+ },
31
+ buttonText: {
32
+ color: '#ffffff',
33
+ fontSize: 14,
34
+ fontWeight: '500',
35
+ },
36
+ }));
@@ -0,0 +1,2 @@
1
+ export { EmptyState } from './EmptyState';
2
+ export type { EmptyStateProps } from './EmptyState';
@@ -0,0 +1 @@
1
+ export { EmptyState } from './EmptyState';
@@ -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,270 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * @fileoverview Full subscription management page for React Native.
4
+ *
5
+ * Displays the current subscription status (active/inactive badge, plan name,
6
+ * expiration date, auto-renew status), followed by package cards with features,
7
+ * pricing, "Most Popular" badge, and purchase buttons. Also includes a
8
+ * "Restore Purchases" button with loading states.
9
+ */
10
+ import { useState, useCallback } from 'react';
11
+ import { View, Text, Pressable, ScrollView, ActivityIndicator, } from 'react-native';
12
+ import { createThemedStyles } from '../../utils/styles';
13
+ export function AppSubscriptionPage({ currentStatus, packages, labels = {}, formatters = {}, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, isLoading: externalLoading, }) {
14
+ const styles = useStyles();
15
+ const [loading, setLoading] = useState(null);
16
+ const formatPrice = formatters.formatPrice ??
17
+ ((price, currency) => `${currency} ${price.toFixed(2)}`);
18
+ const handlePurchase = useCallback(async (pkg) => {
19
+ if (loading)
20
+ return;
21
+ setLoading(pkg.id);
22
+ onTrack?.({
23
+ eventType: 'subscription_action',
24
+ componentName: 'AppSubscriptionPage',
25
+ label: 'purchase_tapped',
26
+ params: { package_id: pkg.id },
27
+ });
28
+ try {
29
+ const success = await onPurchase(pkg.id);
30
+ if (success) {
31
+ onPurchaseSuccess?.();
32
+ }
33
+ }
34
+ catch (e) {
35
+ onError?.('Purchase Failed', e instanceof Error ? e.message : 'An error occurred.');
36
+ }
37
+ finally {
38
+ setLoading(null);
39
+ }
40
+ }, [loading, onPurchase, onPurchaseSuccess, onError, onTrack]);
41
+ const handleRestore = useCallback(async () => {
42
+ if (loading)
43
+ return;
44
+ setLoading('restore');
45
+ onTrack?.({
46
+ eventType: 'subscription_action',
47
+ componentName: 'AppSubscriptionPage',
48
+ label: 'restore_tapped',
49
+ });
50
+ try {
51
+ const success = await onRestore();
52
+ if (success) {
53
+ onRestoreSuccess?.();
54
+ }
55
+ }
56
+ catch (e) {
57
+ onError?.('Restore Failed', e instanceof Error ? e.message : 'An error occurred.');
58
+ }
59
+ finally {
60
+ setLoading(null);
61
+ }
62
+ }, [loading, onRestore, onRestoreSuccess, onError, onTrack]);
63
+ return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [currentStatus && (_jsxs(View, { style: styles.statusCard, accessibilityRole: 'summary', accessibilityLabel: `Subscription status: ${currentStatus.isActive ? 'Active' : 'Inactive'}`, children: [_jsx(View, { style: styles.statusHeader, children: _jsx(View, { style: [
64
+ styles.statusBadge,
65
+ currentStatus.isActive
66
+ ? styles.statusBadgeActive
67
+ : styles.statusBadgeInactive,
68
+ ], children: _jsx(Text, { style: [
69
+ styles.statusBadgeText,
70
+ currentStatus.isActive
71
+ ? styles.statusBadgeTextActive
72
+ : styles.statusBadgeTextInactive,
73
+ ], children: currentStatus.isActive
74
+ ? (labels.statusActive ?? 'Active')
75
+ : (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
76
+ ? (labels.yes ?? 'Yes')
77
+ : (labels.no ?? 'No') })] }))] })) : (_jsx(Text, { style: styles.statusInactiveMessage, children: labels.statusInactiveMessage ??
78
+ 'Subscribe to unlock premium features' }))] })), externalLoading && (_jsx(View, { style: styles.loadingContainer, children: _jsx(ActivityIndicator, { size: 'large', color: styles.loadingIndicator.color, accessibilityLabel: 'Loading subscription packages' }) })), !externalLoading && packages.length > 0 && (_jsx(View, { style: styles.packageList, accessibilityRole: 'list', children: packages.map(pkg => (_jsxs(View, { style: [
79
+ styles.packageCard,
80
+ pkg.isMostPopular && styles.packageCardPopular,
81
+ ], accessibilityRole: 'summary', accessibilityLabel: `${pkg.title}, ${formatPrice(pkg.price, pkg.currency)}${pkg.isMostPopular ? ', Most Popular' : ''}${pkg.isCurrent ? ', Current Plan' : ''}`, 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: [
82
+ styles.purchaseButton,
83
+ loading === pkg.id && styles.purchaseButtonDisabled,
84
+ ], onPress: () => handlePurchase(pkg), disabled: loading !== null, accessibilityRole: 'button', accessibilityLabel: `${labels.purchase ?? 'Subscribe'} to ${pkg.title}`, accessibilityState: {
85
+ disabled: loading !== null,
86
+ busy: loading === pkg.id,
87
+ }, children: loading === pkg.id ? (_jsx(ActivityIndicator, { color: '#ffffff', size: 'small' })) : (_jsx(Text, { style: styles.purchaseButtonText, children: labels.purchase ?? 'Subscribe' })) }))] }, pkg.id))) })), _jsx(Pressable, { style: [
88
+ styles.restoreButton,
89
+ loading === 'restore' && styles.restoreButtonDisabled,
90
+ ], onPress: handleRestore, disabled: loading !== null, accessibilityRole: 'button', accessibilityLabel: labels.restore ?? 'Restore Purchases', accessibilityState: {
91
+ disabled: loading !== null,
92
+ busy: loading === 'restore',
93
+ }, 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 }))] }));
94
+ }
95
+ const useStyles = createThemedStyles(colors => ({
96
+ container: {
97
+ flex: 1,
98
+ backgroundColor: colors.background,
99
+ },
100
+ content: {
101
+ padding: 16,
102
+ paddingBottom: 32,
103
+ },
104
+ // Status card
105
+ statusCard: {
106
+ backgroundColor: colors.card,
107
+ borderRadius: 16,
108
+ padding: 20,
109
+ borderWidth: 1,
110
+ borderColor: colors.border,
111
+ marginBottom: 16,
112
+ },
113
+ statusHeader: {
114
+ flexDirection: 'row',
115
+ alignItems: 'center',
116
+ marginBottom: 12,
117
+ },
118
+ statusBadge: {
119
+ borderRadius: 12,
120
+ paddingHorizontal: 10,
121
+ paddingVertical: 4,
122
+ },
123
+ statusBadgeActive: {
124
+ backgroundColor: colors.successBg,
125
+ },
126
+ statusBadgeInactive: {
127
+ backgroundColor: colors.surfaceSecondary,
128
+ },
129
+ statusBadgeText: {
130
+ fontSize: 13,
131
+ fontWeight: '600',
132
+ },
133
+ statusBadgeTextActive: {
134
+ color: colors.successText,
135
+ },
136
+ statusBadgeTextInactive: {
137
+ color: colors.textSecondary,
138
+ },
139
+ statusFields: {
140
+ gap: 8,
141
+ },
142
+ statusField: {
143
+ flexDirection: 'row',
144
+ justifyContent: 'space-between',
145
+ alignItems: 'center',
146
+ },
147
+ statusFieldLabel: {
148
+ fontSize: 14,
149
+ color: colors.textSecondary,
150
+ },
151
+ statusFieldValue: {
152
+ fontSize: 14,
153
+ fontWeight: '500',
154
+ color: colors.text,
155
+ },
156
+ statusInactiveMessage: {
157
+ fontSize: 14,
158
+ color: colors.textSecondary,
159
+ },
160
+ // Loading
161
+ loadingContainer: {
162
+ paddingVertical: 48,
163
+ alignItems: 'center',
164
+ },
165
+ loadingIndicator: {
166
+ color: colors.primary,
167
+ },
168
+ // Package cards (mirrors SubscriptionScreen styles)
169
+ packageList: {
170
+ gap: 16,
171
+ },
172
+ packageCard: {
173
+ backgroundColor: colors.card,
174
+ borderRadius: 16,
175
+ padding: 20,
176
+ borderWidth: 1,
177
+ borderColor: colors.border,
178
+ },
179
+ packageCardPopular: {
180
+ borderColor: colors.primary,
181
+ borderWidth: 2,
182
+ },
183
+ popularBadge: {
184
+ backgroundColor: colors.primary,
185
+ borderRadius: 12,
186
+ paddingHorizontal: 10,
187
+ paddingVertical: 4,
188
+ alignSelf: 'flex-start',
189
+ marginBottom: 8,
190
+ },
191
+ popularBadgeText: {
192
+ color: '#ffffff',
193
+ fontSize: 12,
194
+ fontWeight: '600',
195
+ },
196
+ packageTitle: {
197
+ fontSize: 20,
198
+ fontWeight: '600',
199
+ color: colors.text,
200
+ },
201
+ packageDescription: {
202
+ fontSize: 14,
203
+ color: colors.textSecondary,
204
+ marginTop: 4,
205
+ },
206
+ packagePrice: {
207
+ fontSize: 28,
208
+ fontWeight: '700',
209
+ color: colors.text,
210
+ marginTop: 8,
211
+ },
212
+ featureList: {
213
+ marginTop: 12,
214
+ gap: 6,
215
+ },
216
+ featureItem: {
217
+ fontSize: 14,
218
+ color: colors.textSecondary,
219
+ },
220
+ purchaseButton: {
221
+ backgroundColor: colors.primary,
222
+ borderRadius: 10,
223
+ paddingVertical: 14,
224
+ alignItems: 'center',
225
+ marginTop: 16,
226
+ minHeight: 48,
227
+ justifyContent: 'center',
228
+ },
229
+ purchaseButtonDisabled: {
230
+ opacity: 0.6,
231
+ },
232
+ purchaseButtonText: {
233
+ color: '#ffffff',
234
+ fontSize: 16,
235
+ fontWeight: '600',
236
+ },
237
+ currentBadge: {
238
+ borderRadius: 10,
239
+ paddingVertical: 14,
240
+ alignItems: 'center',
241
+ marginTop: 16,
242
+ backgroundColor: colors.surfaceSecondary,
243
+ },
244
+ currentBadgeText: {
245
+ color: colors.textSecondary,
246
+ fontSize: 16,
247
+ fontWeight: '500',
248
+ },
249
+ restoreButton: {
250
+ marginTop: 24,
251
+ paddingVertical: 12,
252
+ alignItems: 'center',
253
+ minHeight: 44,
254
+ justifyContent: 'center',
255
+ },
256
+ restoreButtonDisabled: {
257
+ opacity: 0.6,
258
+ },
259
+ restoreText: {
260
+ color: colors.primary,
261
+ fontSize: 15,
262
+ fontWeight: '500',
263
+ },
264
+ restoreDescription: {
265
+ fontSize: 13,
266
+ color: colors.textMuted,
267
+ textAlign: 'center',
268
+ marginTop: 4,
269
+ },
270
+ }));
@@ -0,0 +1,36 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { SubscriptionPageLabels, SubscriptionPageFormatters, AnalyticsTrackingParams } from '../../types';
3
+ export interface SubscriptionPackage {
4
+ id: string;
5
+ title: string;
6
+ description?: string;
7
+ price: number;
8
+ currency: string;
9
+ period: 'monthly' | 'yearly' | 'lifetime';
10
+ features?: string[];
11
+ isMostPopular?: boolean;
12
+ isCurrent?: boolean;
13
+ }
14
+ export interface SubscriptionScreenProps {
15
+ /** Available subscription packages */
16
+ packages: SubscriptionPackage[];
17
+ /** Labels for UI text */
18
+ labels?: SubscriptionPageLabels;
19
+ /** Formatters for prices and periods */
20
+ formatters?: SubscriptionPageFormatters;
21
+ /** Called when a package is purchased */
22
+ onPurchase: (packageId: string) => Promise<boolean>;
23
+ /** Called when restore purchases is pressed */
24
+ onRestore: () => Promise<boolean>;
25
+ /** Called on successful purchase */
26
+ onPurchaseSuccess?: () => void;
27
+ /** Called on successful restore */
28
+ onRestoreSuccess?: () => void;
29
+ /** Called on error */
30
+ onError?: (title: string, message: string) => void;
31
+ /** Custom style */
32
+ style?: StyleProp<ViewStyle>;
33
+ /** Analytics tracking */
34
+ onTrack?: (params: AnalyticsTrackingParams) => void;
35
+ }
36
+ export declare function SubscriptionScreen({ packages, labels, formatters, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, }: SubscriptionScreenProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,201 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * @fileoverview Subscription plan selector screen for React Native.
4
+ *
5
+ * Simpler plan selector (no current status display) with package cards,
6
+ * purchase buttons, and a restore purchases flow. Uses the same
7
+ * purchase/restore callback pattern as AppSubscriptionPage.
8
+ */
9
+ import { useState, useCallback } from 'react';
10
+ import { View, Text, Pressable, ScrollView, ActivityIndicator, } from 'react-native';
11
+ import { createThemedStyles } from '../../utils/styles';
12
+ export function SubscriptionScreen({ packages, labels = {}, formatters = {}, onPurchase, onRestore, onPurchaseSuccess, onRestoreSuccess, onError, style, onTrack, }) {
13
+ const styles = useStyles();
14
+ const [loading, setLoading] = useState(null);
15
+ const formatPrice = formatters.formatPrice ??
16
+ ((price, currency) => `${currency} ${price.toFixed(2)}`);
17
+ const handlePurchase = useCallback(async (pkg) => {
18
+ if (loading)
19
+ return;
20
+ setLoading(pkg.id);
21
+ onTrack?.({
22
+ eventType: 'subscription_action',
23
+ componentName: 'SubscriptionScreen',
24
+ label: 'purchase_tapped',
25
+ params: { package_id: pkg.id },
26
+ });
27
+ try {
28
+ const success = await onPurchase(pkg.id);
29
+ if (success) {
30
+ onPurchaseSuccess?.();
31
+ }
32
+ }
33
+ catch (e) {
34
+ onError?.('Purchase Failed', e instanceof Error ? e.message : 'An error occurred.');
35
+ }
36
+ finally {
37
+ setLoading(null);
38
+ }
39
+ }, [loading, onPurchase, onPurchaseSuccess, onError, onTrack]);
40
+ const handleRestore = useCallback(async () => {
41
+ if (loading)
42
+ return;
43
+ setLoading('restore');
44
+ onTrack?.({
45
+ eventType: 'subscription_action',
46
+ componentName: 'SubscriptionScreen',
47
+ label: 'restore_tapped',
48
+ });
49
+ try {
50
+ const success = await onRestore();
51
+ if (success) {
52
+ onRestoreSuccess?.();
53
+ }
54
+ }
55
+ catch (e) {
56
+ onError?.('Restore Failed', e instanceof Error ? e.message : 'An error occurred.');
57
+ }
58
+ finally {
59
+ setLoading(null);
60
+ }
61
+ }, [loading, onRestore, onRestoreSuccess, onError, onTrack]);
62
+ return (_jsxs(ScrollView, { style: [styles.container, style], contentContainerStyle: styles.content, children: [_jsx(Text, { style: styles.title, accessibilityRole: 'header', children: labels.title ?? 'Subscription' }), labels.subtitle && (_jsx(Text, { style: styles.subtitle, children: labels.subtitle })), _jsx(View, { style: styles.packageList, accessibilityRole: 'list', children: packages.map(pkg => (_jsxs(View, { style: [
63
+ styles.packageCard,
64
+ pkg.isMostPopular && styles.packageCardPopular,
65
+ ], accessibilityRole: 'summary', accessibilityLabel: `${pkg.title}, ${formatPrice(pkg.price, pkg.currency)}${pkg.isMostPopular ? ', Most Popular' : ''}${pkg.isCurrent ? ', Current Plan' : ''}`, 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: [
66
+ styles.purchaseButton,
67
+ loading === pkg.id && styles.purchaseButtonDisabled,
68
+ ], onPress: () => handlePurchase(pkg), disabled: loading !== null, accessibilityRole: 'button', accessibilityLabel: `${labels.purchase ?? 'Subscribe'} to ${pkg.title}`, accessibilityState: {
69
+ disabled: loading !== null,
70
+ busy: loading === pkg.id,
71
+ }, children: loading === pkg.id ? (_jsx(ActivityIndicator, { color: '#ffffff', size: 'small' })) : (_jsx(Text, { style: styles.purchaseButtonText, children: labels.purchase ?? 'Subscribe' })) }))] }, pkg.id))) }), _jsx(Pressable, { style: [
72
+ styles.restoreButton,
73
+ loading === 'restore' && styles.restoreButtonDisabled,
74
+ ], onPress: handleRestore, disabled: loading !== null, accessibilityRole: 'button', accessibilityLabel: labels.restore ?? 'Restore Purchases', accessibilityState: {
75
+ disabled: loading !== null,
76
+ busy: loading === 'restore',
77
+ }, 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 }))] }));
78
+ }
79
+ const useStyles = createThemedStyles(colors => ({
80
+ container: {
81
+ flex: 1,
82
+ backgroundColor: colors.background,
83
+ },
84
+ content: {
85
+ padding: 16,
86
+ paddingBottom: 32,
87
+ },
88
+ title: {
89
+ fontSize: 28,
90
+ fontWeight: '700',
91
+ color: colors.text,
92
+ marginBottom: 4,
93
+ },
94
+ subtitle: {
95
+ fontSize: 15,
96
+ color: colors.textSecondary,
97
+ marginBottom: 20,
98
+ },
99
+ packageList: {
100
+ gap: 16,
101
+ marginTop: 16,
102
+ },
103
+ packageCard: {
104
+ backgroundColor: colors.card,
105
+ borderRadius: 16,
106
+ padding: 20,
107
+ borderWidth: 1,
108
+ borderColor: colors.border,
109
+ },
110
+ packageCardPopular: {
111
+ borderColor: colors.primary,
112
+ borderWidth: 2,
113
+ },
114
+ popularBadge: {
115
+ backgroundColor: colors.primary,
116
+ borderRadius: 12,
117
+ paddingHorizontal: 10,
118
+ paddingVertical: 4,
119
+ alignSelf: 'flex-start',
120
+ marginBottom: 8,
121
+ },
122
+ popularBadgeText: {
123
+ color: '#ffffff',
124
+ fontSize: 12,
125
+ fontWeight: '600',
126
+ },
127
+ packageTitle: {
128
+ fontSize: 20,
129
+ fontWeight: '600',
130
+ color: colors.text,
131
+ },
132
+ packageDescription: {
133
+ fontSize: 14,
134
+ color: colors.textSecondary,
135
+ marginTop: 4,
136
+ },
137
+ packagePrice: {
138
+ fontSize: 28,
139
+ fontWeight: '700',
140
+ color: colors.text,
141
+ marginTop: 8,
142
+ },
143
+ featureList: {
144
+ marginTop: 12,
145
+ gap: 6,
146
+ },
147
+ featureItem: {
148
+ fontSize: 14,
149
+ color: colors.textSecondary,
150
+ },
151
+ purchaseButton: {
152
+ backgroundColor: colors.primary,
153
+ borderRadius: 10,
154
+ paddingVertical: 14,
155
+ alignItems: 'center',
156
+ marginTop: 16,
157
+ minHeight: 48,
158
+ justifyContent: 'center',
159
+ },
160
+ purchaseButtonDisabled: {
161
+ opacity: 0.6,
162
+ },
163
+ purchaseButtonText: {
164
+ color: '#ffffff',
165
+ fontSize: 16,
166
+ fontWeight: '600',
167
+ },
168
+ currentBadge: {
169
+ borderRadius: 10,
170
+ paddingVertical: 14,
171
+ alignItems: 'center',
172
+ marginTop: 16,
173
+ backgroundColor: colors.surfaceSecondary,
174
+ },
175
+ currentBadgeText: {
176
+ color: colors.textSecondary,
177
+ fontSize: 16,
178
+ fontWeight: '500',
179
+ },
180
+ restoreButton: {
181
+ marginTop: 24,
182
+ paddingVertical: 12,
183
+ alignItems: 'center',
184
+ minHeight: 44,
185
+ justifyContent: 'center',
186
+ },
187
+ restoreButtonDisabled: {
188
+ opacity: 0.6,
189
+ },
190
+ restoreText: {
191
+ color: colors.primary,
192
+ fontSize: 15,
193
+ fontWeight: '500',
194
+ },
195
+ restoreDescription: {
196
+ fontSize: 13,
197
+ color: colors.textMuted,
198
+ textAlign: 'center',
199
+ marginTop: 4,
200
+ },
201
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/building_blocks_rn",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "Higher-level shared UI building blocks for Sudobility React Native apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",