@croacroa/react-native-template 2.1.0 → 3.2.0
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/.env.example +5 -0
- package/.eslintrc.js +8 -0
- package/.github/workflows/ci.yml +187 -187
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/.github/workflows/npm-publish.yml +57 -0
- package/CHANGELOG.md +195 -106
- package/CONTRIBUTING.md +377 -377
- package/LICENSE +21 -21
- package/README.md +446 -402
- package/__tests__/accessibility/components.test.tsx +285 -0
- package/__tests__/components/Button.test.tsx +2 -4
- package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
- package/__tests__/components/snapshots.test.tsx +131 -131
- package/__tests__/helpers/a11y.ts +54 -0
- package/__tests__/hooks/useAnalytics.test.ts +100 -0
- package/__tests__/hooks/useAnimations.test.ts +70 -0
- package/__tests__/hooks/useAuth.test.tsx +71 -28
- package/__tests__/hooks/useMedia.test.ts +318 -0
- package/__tests__/hooks/usePayments.test.tsx +307 -0
- package/__tests__/hooks/usePermission.test.ts +230 -0
- package/__tests__/hooks/useWebSocket.test.ts +329 -0
- package/__tests__/integration/auth-api.test.tsx +224 -227
- package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
- package/__tests__/services/api.test.ts +24 -6
- package/app/(auth)/home.tsx +11 -9
- package/app/(auth)/profile.tsx +8 -6
- package/app/(auth)/settings.tsx +11 -9
- package/app/(public)/forgot-password.tsx +25 -15
- package/app/(public)/login.tsx +48 -12
- package/app/(public)/onboarding.tsx +5 -5
- package/app/(public)/register.tsx +24 -15
- package/app/_layout.tsx +6 -3
- package/app.config.ts +27 -2
- package/assets/images/.gitkeep +7 -7
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/notification-icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/components/ErrorBoundary.tsx +73 -28
- package/components/auth/SocialLoginButtons.tsx +168 -0
- package/components/forms/FormInput.tsx +5 -3
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/AnalyticsProvider.tsx +67 -0
- package/components/providers/SuspenseBoundary.tsx +359 -357
- package/components/providers/index.ts +24 -21
- package/components/ui/AnimatedButton.tsx +1 -9
- package/components/ui/AnimatedList.tsx +98 -0
- package/components/ui/AnimatedScreen.tsx +89 -0
- package/components/ui/Avatar.tsx +319 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Button.tsx +11 -3
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/FeatureGate.tsx +57 -0
- package/components/ui/ForceUpdateScreen.tsx +108 -0
- package/components/ui/ImagePickerButton.tsx +180 -0
- package/components/ui/Input.stories.tsx +2 -10
- package/components/ui/Input.tsx +2 -10
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Paywall.tsx +253 -0
- package/components/ui/PermissionGate.tsx +155 -0
- package/components/ui/PurchaseButton.tsx +84 -0
- package/components/ui/Select.tsx +240 -240
- package/components/ui/Skeleton.tsx +3 -1
- package/components/ui/Toast.tsx +427 -418
- package/components/ui/UploadProgress.tsx +189 -0
- package/components/ui/VirtualizedList.tsx +288 -285
- package/components/ui/index.ts +28 -30
- package/constants/config.ts +135 -97
- package/docs/adr/001-state-management.md +79 -79
- package/docs/adr/002-styling-approach.md +130 -130
- package/docs/adr/003-data-fetching.md +155 -155
- package/docs/adr/004-auth-adapter-pattern.md +144 -144
- package/docs/adr/README.md +78 -78
- package/docs/guides/analytics-posthog.md +121 -0
- package/docs/guides/auth-supabase.md +162 -0
- package/docs/guides/feature-flags-launchdarkly.md +150 -0
- package/docs/guides/payments-revenuecat.md +169 -0
- package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
- package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
- package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
- package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
- package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
- package/eas.json +2 -1
- package/hooks/index.ts +70 -40
- package/hooks/useAnimatedEntry.ts +204 -0
- package/hooks/useApi.ts +5 -4
- package/hooks/useAuth.tsx +7 -3
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useChannel.ts +111 -0
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useExperiment.ts +36 -0
- package/hooks/useFeatureFlag.ts +59 -0
- package/hooks/useForceUpdate.ts +91 -0
- package/hooks/useImagePicker.ts +281 -375
- package/hooks/useInAppReview.ts +64 -0
- package/hooks/useMFA.ts +509 -499
- package/hooks/useParallax.ts +142 -0
- package/hooks/usePerformance.ts +434 -434
- package/hooks/usePermission.ts +190 -0
- package/hooks/usePresence.ts +129 -0
- package/hooks/useProducts.ts +36 -0
- package/hooks/usePurchase.ts +103 -0
- package/hooks/useRateLimit.ts +70 -0
- package/hooks/useSubscription.ts +49 -0
- package/hooks/useTrackEvent.ts +52 -0
- package/hooks/useTrackScreen.ts +40 -0
- package/hooks/useUpdates.ts +358 -358
- package/hooks/useUpload.ts +165 -0
- package/hooks/useWebSocket.ts +111 -0
- package/i18n/index.ts +197 -194
- package/i18n/locales/ar.json +170 -101
- package/i18n/locales/de.json +170 -101
- package/i18n/locales/en.json +170 -101
- package/i18n/locales/es.json +170 -101
- package/i18n/locales/fr.json +170 -101
- package/jest.config.js +1 -1
- package/maestro/README.md +113 -113
- package/maestro/config.yaml +35 -35
- package/maestro/flows/login.yaml +62 -62
- package/maestro/flows/mfa-login.yaml +92 -92
- package/maestro/flows/mfa-setup.yaml +86 -86
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -101
- package/maestro/flows/offline-sync.yaml +128 -128
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +188 -176
- package/scripts/generate-placeholders.js +38 -0
- package/services/analytics/adapters/console.ts +50 -0
- package/services/analytics/analytics-adapter.ts +94 -0
- package/services/analytics/types.ts +73 -0
- package/services/analytics.ts +428 -428
- package/services/api.ts +419 -340
- package/services/auth/social/apple.ts +110 -0
- package/services/auth/social/google.ts +159 -0
- package/services/auth/social/social-auth.ts +100 -0
- package/services/auth/social/types.ts +80 -0
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +652 -626
- package/services/feature-flags/adapters/mock.ts +108 -0
- package/services/feature-flags/feature-flag-adapter.ts +174 -0
- package/services/feature-flags/types.ts +79 -0
- package/services/force-update.ts +140 -0
- package/services/index.ts +116 -54
- package/services/media/compression.ts +91 -0
- package/services/media/media-picker.ts +151 -0
- package/services/media/media-upload.ts +160 -0
- package/services/payments/adapters/mock.ts +159 -0
- package/services/payments/payment-adapter.ts +118 -0
- package/services/payments/types.ts +131 -0
- package/services/permissions/permission-manager.ts +284 -0
- package/services/permissions/types.ts +104 -0
- package/services/realtime/types.ts +100 -0
- package/services/realtime/websocket-manager.ts +441 -0
- package/services/security.ts +289 -286
- package/services/sentry.ts +4 -4
- package/stores/appStore.ts +9 -0
- package/stores/notificationStore.ts +3 -1
- package/tailwind.config.js +47 -47
- package/tsconfig.json +37 -13
- package/types/user.ts +1 -1
- package/utils/accessibility.ts +446 -446
- package/utils/animations/presets.ts +182 -0
- package/utils/animations/transitions.ts +62 -0
- package/utils/index.ts +63 -52
- package/utils/toast.ts +9 -2
- package/utils/validation.ts +4 -1
- package/utils/withAccessibility.tsx +272 -272
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Payments: RevenueCat Integration
|
|
2
|
+
|
|
3
|
+
Replace the mock payment adapter with [RevenueCat](https://www.revenuecat.com/docs) to handle real in-app purchases and subscriptions.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- A RevenueCat account ([sign up here](https://app.revenuecat.com/signup))
|
|
8
|
+
- An iOS and/or Android app configured in the RevenueCat dashboard
|
|
9
|
+
- At least one product created in App Store Connect or Google Play Console and linked in RevenueCat
|
|
10
|
+
|
|
11
|
+
## 1. Install the SDK
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx expo install react-native-purchases
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> RevenueCat requires native modules. You will need a development build (`npx expo prebuild` or EAS Build) -- Expo Go is not supported.
|
|
18
|
+
|
|
19
|
+
## 2. Add Environment Variables
|
|
20
|
+
|
|
21
|
+
Add this to your `.env` file:
|
|
22
|
+
|
|
23
|
+
```env
|
|
24
|
+
EXPO_PUBLIC_REVENUECAT_API_KEY=your_revenuecat_api_key
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use the **public SDK key** from RevenueCat dashboard > Project > API Keys. If you ship on both platforms with different keys, add a second variable and pick the right one at runtime.
|
|
28
|
+
|
|
29
|
+
## 3. Create the Adapter
|
|
30
|
+
|
|
31
|
+
Create `services/payments/adapters/revenuecat.ts`:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import Purchases, {
|
|
35
|
+
type PurchasesStoreProduct,
|
|
36
|
+
type CustomerInfo,
|
|
37
|
+
LOG_LEVEL,
|
|
38
|
+
} from "react-native-purchases";
|
|
39
|
+
import type {
|
|
40
|
+
PaymentAdapter,
|
|
41
|
+
Product,
|
|
42
|
+
Purchase,
|
|
43
|
+
SubscriptionInfo,
|
|
44
|
+
} from "../types";
|
|
45
|
+
|
|
46
|
+
/** Map a RevenueCat store product to the template's Product shape */
|
|
47
|
+
function toProduct(p: PurchasesStoreProduct): Product {
|
|
48
|
+
return {
|
|
49
|
+
id: p.identifier,
|
|
50
|
+
title: p.title,
|
|
51
|
+
description: p.description,
|
|
52
|
+
price: p.price,
|
|
53
|
+
priceString: p.priceString,
|
|
54
|
+
currency: p.currencyCode,
|
|
55
|
+
type: p.productCategory === "SUBSCRIPTION" ? "subscription" : "non_consumable",
|
|
56
|
+
subscriptionPeriod:
|
|
57
|
+
p.subscriptionPeriod === "P1M"
|
|
58
|
+
? "monthly"
|
|
59
|
+
: p.subscriptionPeriod === "P1Y"
|
|
60
|
+
? "yearly"
|
|
61
|
+
: p.subscriptionPeriod === "P1W"
|
|
62
|
+
? "weekly"
|
|
63
|
+
: p.subscriptionPeriod === "P3M"
|
|
64
|
+
? "quarterly"
|
|
65
|
+
: undefined,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Derive subscription status from RevenueCat CustomerInfo */
|
|
70
|
+
function toSubscriptionInfo(info: CustomerInfo): SubscriptionInfo {
|
|
71
|
+
const entitlement = info.entitlements.active["premium"];
|
|
72
|
+
|
|
73
|
+
if (!entitlement) {
|
|
74
|
+
return { status: "none", productId: null, expiresAt: null, willRenew: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
status: entitlement.isActive ? "active" : "expired",
|
|
79
|
+
productId: entitlement.productIdentifier,
|
|
80
|
+
expiresAt: entitlement.expirationDate,
|
|
81
|
+
willRenew: entitlement.willRenew,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class RevenueCatAdapter implements PaymentAdapter {
|
|
86
|
+
async initialize(): Promise<void> {
|
|
87
|
+
// Enable verbose logs in dev builds for easier debugging
|
|
88
|
+
if (__DEV__) {
|
|
89
|
+
Purchases.setLogLevel(LOG_LEVEL.VERBOSE);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Purchases.configure({
|
|
93
|
+
apiKey: process.env.EXPO_PUBLIC_REVENUECAT_API_KEY!,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getProducts(ids: string[]): Promise<Product[]> {
|
|
98
|
+
const products = await Purchases.getProducts(ids);
|
|
99
|
+
return products.map(toProduct);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async purchase(productId: string): Promise<Purchase> {
|
|
103
|
+
// Fetch the full store product first; RevenueCat needs it
|
|
104
|
+
const [product] = await Purchases.getProducts([productId]);
|
|
105
|
+
if (!product) throw new Error(`Product not found: ${productId}`);
|
|
106
|
+
|
|
107
|
+
const { customerInfo, productIdentifier } =
|
|
108
|
+
await Purchases.purchaseStoreProduct(product);
|
|
109
|
+
|
|
110
|
+
// Find the latest transaction for this product
|
|
111
|
+
const txnDate =
|
|
112
|
+
customerInfo.allPurchaseDates[productIdentifier] ??
|
|
113
|
+
new Date().toISOString();
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
id: customerInfo.originalAppUserId + "_" + Date.now(),
|
|
117
|
+
productId: productIdentifier,
|
|
118
|
+
transactionDate: txnDate,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async restorePurchases(): Promise<Purchase[]> {
|
|
123
|
+
const info = await Purchases.restorePurchases();
|
|
124
|
+
|
|
125
|
+
return Object.entries(info.allPurchaseDates).map(
|
|
126
|
+
([productId, date]) => ({
|
|
127
|
+
id: info.originalAppUserId + "_" + productId,
|
|
128
|
+
productId,
|
|
129
|
+
transactionDate: date,
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getSubscriptionStatus(): Promise<SubscriptionInfo> {
|
|
135
|
+
const info = await Purchases.getCustomerInfo();
|
|
136
|
+
return toSubscriptionInfo(info);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> The adapter maps to the `"premium"` entitlement by default. Change the string in `toSubscriptionInfo` if your RevenueCat entitlement has a different identifier.
|
|
142
|
+
|
|
143
|
+
## 4. Activate the Adapter
|
|
144
|
+
|
|
145
|
+
In your `app/_layout.tsx` (or a dedicated providers file), set the adapter **before** calling `initialize()`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { Payments } from "@/services/payments/payment-adapter";
|
|
149
|
+
import { RevenueCatAdapter } from "@/services/payments/adapters/revenuecat";
|
|
150
|
+
|
|
151
|
+
// Swap in RevenueCat -- do this once, before initialize()
|
|
152
|
+
Payments.setAdapter(new RevenueCatAdapter());
|
|
153
|
+
await Payments.initialize();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
No other files need to change. Every screen that calls `Payments.getProducts()` or `Payments.purchase()` will now go through RevenueCat.
|
|
157
|
+
|
|
158
|
+
## 5. Verify
|
|
159
|
+
|
|
160
|
+
1. Create a **development build**: `npx expo run:ios` or `npx expo run:android`
|
|
161
|
+
2. Confirm products load: `await Payments.getProducts(["premium_monthly"])`
|
|
162
|
+
3. Test a sandbox purchase (use a sandbox Apple ID or Google test account)
|
|
163
|
+
4. Call `await Payments.getSubscriptionStatus()` and confirm the status is `"active"`
|
|
164
|
+
|
|
165
|
+
## What's Next
|
|
166
|
+
|
|
167
|
+
- **Identify users:** Call `Purchases.logIn(userId)` after authentication so subscriptions follow the user across devices
|
|
168
|
+
- **Offerings:** Use RevenueCat Offerings instead of hard-coded product IDs for remote paywall configuration
|
|
169
|
+
- **Webhooks:** Connect RevenueCat server-to-server webhooks to your backend for receipt validation
|