@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
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
# ADR-003: Use React Query for Data Fetching
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Accepted
|
|
6
|
-
|
|
7
|
-
## Date
|
|
8
|
-
|
|
9
|
-
2024-01-01
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
We need a data fetching solution that handles:
|
|
14
|
-
|
|
15
|
-
1. Caching and cache invalidation
|
|
16
|
-
2. Loading and error states
|
|
17
|
-
3. Optimistic updates
|
|
18
|
-
4. Offline support
|
|
19
|
-
5. Request deduplication
|
|
20
|
-
6. Background refetching
|
|
21
|
-
|
|
22
|
-
Options considered:
|
|
23
|
-
|
|
24
|
-
- **fetch/axios only**: Manual state management
|
|
25
|
-
- **SWR**: Lightweight, good for web
|
|
26
|
-
- **React Query (TanStack Query)**: Full-featured
|
|
27
|
-
- **RTK Query**: Redux-based
|
|
28
|
-
- **Apollo Client**: GraphQL-focused
|
|
29
|
-
|
|
30
|
-
## Decision
|
|
31
|
-
|
|
32
|
-
We chose **React Query (TanStack Query)** for server state management.
|
|
33
|
-
|
|
34
|
-
## Rationale
|
|
35
|
-
|
|
36
|
-
1. **Comprehensive Feature Set**
|
|
37
|
-
- Automatic caching and cache invalidation
|
|
38
|
-
- Background refetching
|
|
39
|
-
- Optimistic updates
|
|
40
|
-
- Infinite queries for pagination
|
|
41
|
-
- Offline support with persistence
|
|
42
|
-
|
|
43
|
-
2. **Excellent Developer Experience**
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
const { data, isLoading, error } = useQuery({
|
|
47
|
-
queryKey: ["users", userId],
|
|
48
|
-
queryFn: () => api.get(`/users/${userId}`),
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
3. **Offline Support**: Works great with AsyncStorage persister
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
const persister = createAsyncStoragePersister({
|
|
56
|
-
storage: AsyncStorage,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
<PersistQueryClientProvider
|
|
60
|
-
client={queryClient}
|
|
61
|
-
persistOptions={{ persister }}
|
|
62
|
-
>
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
4. **Performance**: Smart refetching, request deduplication
|
|
66
|
-
|
|
67
|
-
5. **DevTools**: React Query DevTools for debugging
|
|
68
|
-
|
|
69
|
-
## Consequences
|
|
70
|
-
|
|
71
|
-
### Positive
|
|
72
|
-
|
|
73
|
-
- No need for manual cache management
|
|
74
|
-
- Automatic loading/error states
|
|
75
|
-
- Works offline with persisted cache
|
|
76
|
-
- Reduces boilerplate significantly
|
|
77
|
-
- Battle-tested and well-documented
|
|
78
|
-
|
|
79
|
-
### Negative
|
|
80
|
-
|
|
81
|
-
- Learning curve for query keys and cache invalidation
|
|
82
|
-
- Can be overkill for simple apps
|
|
83
|
-
- Adds bundle size (~12KB gzipped)
|
|
84
|
-
|
|
85
|
-
### Mitigation
|
|
86
|
-
|
|
87
|
-
- Create query key factories for consistency
|
|
88
|
-
- Document common patterns
|
|
89
|
-
- Create custom hooks for reusable queries
|
|
90
|
-
|
|
91
|
-
## Implementation
|
|
92
|
-
|
|
93
|
-
### Query Client Configuration
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
// services/queryClient.ts
|
|
97
|
-
export const queryClient = new QueryClient({
|
|
98
|
-
defaultOptions: {
|
|
99
|
-
queries: {
|
|
100
|
-
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
101
|
-
gcTime: 30 * 60 * 1000, // 30 minutes
|
|
102
|
-
retry: 3,
|
|
103
|
-
refetchOnReconnect: true,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Query Keys Factory
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
// hooks/useApi.ts
|
|
113
|
-
export const queryKeys = {
|
|
114
|
-
users: {
|
|
115
|
-
all: ["users"] as const,
|
|
116
|
-
detail: (id: string) => ["users", id] as const,
|
|
117
|
-
me: () => ["users", "me"] as const,
|
|
118
|
-
},
|
|
119
|
-
posts: {
|
|
120
|
-
all: ["posts"] as const,
|
|
121
|
-
list: (filters: PostFilters) => ["posts", filters] as const,
|
|
122
|
-
detail: (id: string) => ["posts", id] as const,
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Custom Hook Pattern
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
export function useUser(userId: string) {
|
|
131
|
-
return useQuery({
|
|
132
|
-
queryKey: queryKeys.users.detail(userId),
|
|
133
|
-
queryFn: () => api.get<User>(`/users/${userId}`),
|
|
134
|
-
enabled: !!userId,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function useUpdateUser() {
|
|
139
|
-
const queryClient = useQueryClient();
|
|
140
|
-
|
|
141
|
-
return useMutation({
|
|
142
|
-
mutationFn: (data: UpdateUserInput) => api.patch<User>("/users/me", data),
|
|
143
|
-
onSuccess: () => {
|
|
144
|
-
queryClient.invalidateQueries({
|
|
145
|
-
queryKey: queryKeys.users.me(),
|
|
146
|
-
});
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## References
|
|
153
|
-
|
|
154
|
-
- [TanStack Query Documentation](https://tanstack.com/query)
|
|
155
|
-
- [React Query Offline Support](https://tanstack.com/query/latest/docs/react/plugins/persistQueryClient)
|
|
1
|
+
# ADR-003: Use React Query for Data Fetching
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2024-01-01
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
We need a data fetching solution that handles:
|
|
14
|
+
|
|
15
|
+
1. Caching and cache invalidation
|
|
16
|
+
2. Loading and error states
|
|
17
|
+
3. Optimistic updates
|
|
18
|
+
4. Offline support
|
|
19
|
+
5. Request deduplication
|
|
20
|
+
6. Background refetching
|
|
21
|
+
|
|
22
|
+
Options considered:
|
|
23
|
+
|
|
24
|
+
- **fetch/axios only**: Manual state management
|
|
25
|
+
- **SWR**: Lightweight, good for web
|
|
26
|
+
- **React Query (TanStack Query)**: Full-featured
|
|
27
|
+
- **RTK Query**: Redux-based
|
|
28
|
+
- **Apollo Client**: GraphQL-focused
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
We chose **React Query (TanStack Query)** for server state management.
|
|
33
|
+
|
|
34
|
+
## Rationale
|
|
35
|
+
|
|
36
|
+
1. **Comprehensive Feature Set**
|
|
37
|
+
- Automatic caching and cache invalidation
|
|
38
|
+
- Background refetching
|
|
39
|
+
- Optimistic updates
|
|
40
|
+
- Infinite queries for pagination
|
|
41
|
+
- Offline support with persistence
|
|
42
|
+
|
|
43
|
+
2. **Excellent Developer Experience**
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const { data, isLoading, error } = useQuery({
|
|
47
|
+
queryKey: ["users", userId],
|
|
48
|
+
queryFn: () => api.get(`/users/${userId}`),
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. **Offline Support**: Works great with AsyncStorage persister
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const persister = createAsyncStoragePersister({
|
|
56
|
+
storage: AsyncStorage,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
<PersistQueryClientProvider
|
|
60
|
+
client={queryClient}
|
|
61
|
+
persistOptions={{ persister }}
|
|
62
|
+
>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. **Performance**: Smart refetching, request deduplication
|
|
66
|
+
|
|
67
|
+
5. **DevTools**: React Query DevTools for debugging
|
|
68
|
+
|
|
69
|
+
## Consequences
|
|
70
|
+
|
|
71
|
+
### Positive
|
|
72
|
+
|
|
73
|
+
- No need for manual cache management
|
|
74
|
+
- Automatic loading/error states
|
|
75
|
+
- Works offline with persisted cache
|
|
76
|
+
- Reduces boilerplate significantly
|
|
77
|
+
- Battle-tested and well-documented
|
|
78
|
+
|
|
79
|
+
### Negative
|
|
80
|
+
|
|
81
|
+
- Learning curve for query keys and cache invalidation
|
|
82
|
+
- Can be overkill for simple apps
|
|
83
|
+
- Adds bundle size (~12KB gzipped)
|
|
84
|
+
|
|
85
|
+
### Mitigation
|
|
86
|
+
|
|
87
|
+
- Create query key factories for consistency
|
|
88
|
+
- Document common patterns
|
|
89
|
+
- Create custom hooks for reusable queries
|
|
90
|
+
|
|
91
|
+
## Implementation
|
|
92
|
+
|
|
93
|
+
### Query Client Configuration
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// services/queryClient.ts
|
|
97
|
+
export const queryClient = new QueryClient({
|
|
98
|
+
defaultOptions: {
|
|
99
|
+
queries: {
|
|
100
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
101
|
+
gcTime: 30 * 60 * 1000, // 30 minutes
|
|
102
|
+
retry: 3,
|
|
103
|
+
refetchOnReconnect: true,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Query Keys Factory
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// hooks/useApi.ts
|
|
113
|
+
export const queryKeys = {
|
|
114
|
+
users: {
|
|
115
|
+
all: ["users"] as const,
|
|
116
|
+
detail: (id: string) => ["users", id] as const,
|
|
117
|
+
me: () => ["users", "me"] as const,
|
|
118
|
+
},
|
|
119
|
+
posts: {
|
|
120
|
+
all: ["posts"] as const,
|
|
121
|
+
list: (filters: PostFilters) => ["posts", filters] as const,
|
|
122
|
+
detail: (id: string) => ["posts", id] as const,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Custom Hook Pattern
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
export function useUser(userId: string) {
|
|
131
|
+
return useQuery({
|
|
132
|
+
queryKey: queryKeys.users.detail(userId),
|
|
133
|
+
queryFn: () => api.get<User>(`/users/${userId}`),
|
|
134
|
+
enabled: !!userId,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function useUpdateUser() {
|
|
139
|
+
const queryClient = useQueryClient();
|
|
140
|
+
|
|
141
|
+
return useMutation({
|
|
142
|
+
mutationFn: (data: UpdateUserInput) => api.patch<User>("/users/me", data),
|
|
143
|
+
onSuccess: () => {
|
|
144
|
+
queryClient.invalidateQueries({
|
|
145
|
+
queryKey: queryKeys.users.me(),
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## References
|
|
153
|
+
|
|
154
|
+
- [TanStack Query Documentation](https://tanstack.com/query)
|
|
155
|
+
- [React Query Offline Support](https://tanstack.com/query/latest/docs/react/plugins/persistQueryClient)
|
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
# ADR-004: Auth Adapter Pattern for Authentication
|
|
2
|
-
|
|
3
|
-
## Status
|
|
4
|
-
|
|
5
|
-
Accepted
|
|
6
|
-
|
|
7
|
-
## Date
|
|
8
|
-
|
|
9
|
-
2024-01-15
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
The template needs to support multiple authentication providers (Supabase, Firebase, Auth0, custom backends) without requiring significant code changes. We need:
|
|
14
|
-
|
|
15
|
-
1. Easy switching between auth providers
|
|
16
|
-
2. Consistent API regardless of provider
|
|
17
|
-
3. Type safety
|
|
18
|
-
4. Testability with mock implementations
|
|
19
|
-
|
|
20
|
-
## Decision
|
|
21
|
-
|
|
22
|
-
Implement an **Adapter Pattern** for authentication that abstracts the auth provider behind a common interface.
|
|
23
|
-
|
|
24
|
-
## Rationale
|
|
25
|
-
|
|
26
|
-
1. **Flexibility**: Change auth providers without touching app code
|
|
27
|
-
2. **Testing**: Easy to mock for unit tests
|
|
28
|
-
3. **Consistency**: Same API for all providers
|
|
29
|
-
4. **Gradual Migration**: Can switch providers incrementally
|
|
30
|
-
|
|
31
|
-
## Implementation
|
|
32
|
-
|
|
33
|
-
### Interface Definition
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
// services/authAdapter.ts
|
|
37
|
-
export interface AuthAdapter {
|
|
38
|
-
signIn(email: string, password: string): Promise<AuthResult>;
|
|
39
|
-
signUp(email: string, password: string, name: string): Promise<AuthResult>;
|
|
40
|
-
signOut(): Promise<void>;
|
|
41
|
-
refreshToken(refreshToken: string): Promise<AuthTokens>;
|
|
42
|
-
forgotPassword(email: string): Promise<void>;
|
|
43
|
-
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
44
|
-
getSession(): Promise<AuthResult | null>;
|
|
45
|
-
onAuthStateChange?(callback: (user: User | null) => void): () => void;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface AuthResult {
|
|
49
|
-
user: User;
|
|
50
|
-
tokens: AuthTokens;
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Mock Implementation
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
export const mockAuthAdapter: AuthAdapter = {
|
|
58
|
-
async signIn(email, password) {
|
|
59
|
-
await delay(1000); // Simulate network
|
|
60
|
-
return {
|
|
61
|
-
user: { id: '1', email, name: email.split('@')[0] },
|
|
62
|
-
tokens: { accessToken: 'mock', refreshToken: 'mock', expiresAt: ... },
|
|
63
|
-
};
|
|
64
|
-
},
|
|
65
|
-
// ... other methods
|
|
66
|
-
};
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Supabase Implementation
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
export const supabaseAuthAdapter: AuthAdapter = {
|
|
73
|
-
async signIn(email, password) {
|
|
74
|
-
const { data, error } = await supabase.auth.signInWithPassword({
|
|
75
|
-
email,
|
|
76
|
-
password,
|
|
77
|
-
});
|
|
78
|
-
if (error) throw error;
|
|
79
|
-
return transformSupabaseSession(data);
|
|
80
|
-
},
|
|
81
|
-
// ... other methods
|
|
82
|
-
};
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Usage
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
// services/authAdapter.ts
|
|
89
|
-
// Change this line to switch providers
|
|
90
|
-
export const authAdapter: AuthAdapter = mockAuthAdapter;
|
|
91
|
-
// export const authAdapter: AuthAdapter = supabaseAuthAdapter;
|
|
92
|
-
// export const authAdapter: AuthAdapter = firebaseAuthAdapter;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Consequences
|
|
96
|
-
|
|
97
|
-
### Positive
|
|
98
|
-
|
|
99
|
-
- Provider-agnostic code
|
|
100
|
-
- Easy to test with mocks
|
|
101
|
-
- Clear contract for auth operations
|
|
102
|
-
- Can support multiple providers simultaneously
|
|
103
|
-
|
|
104
|
-
### Negative
|
|
105
|
-
|
|
106
|
-
- Some provider-specific features may not fit the interface
|
|
107
|
-
- Additional abstraction layer
|
|
108
|
-
- Need to maintain multiple implementations
|
|
109
|
-
|
|
110
|
-
### Mitigation
|
|
111
|
-
|
|
112
|
-
- Allow optional methods in interface
|
|
113
|
-
- Document provider-specific extensions
|
|
114
|
-
- Keep interface focused on common operations
|
|
115
|
-
|
|
116
|
-
## Testing
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
// __tests__/auth.test.ts
|
|
120
|
-
import { mockAuthAdapter } from "@/services/authAdapter";
|
|
121
|
-
|
|
122
|
-
describe("Auth", () => {
|
|
123
|
-
it("signs in successfully", async () => {
|
|
124
|
-
const result = await mockAuthAdapter.signIn("test@example.com", "password");
|
|
125
|
-
expect(result.user.email).toBe("test@example.com");
|
|
126
|
-
expect(result.tokens.accessToken).toBeDefined();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Migration Guide
|
|
132
|
-
|
|
133
|
-
To switch from mock to Supabase:
|
|
134
|
-
|
|
135
|
-
1. Install Supabase: `npx expo install @supabase/supabase-js`
|
|
136
|
-
2. Configure environment variables
|
|
137
|
-
3. Implement `supabaseAuthAdapter`
|
|
138
|
-
4. Update export in `authAdapter.ts`
|
|
139
|
-
|
|
140
|
-
## References
|
|
141
|
-
|
|
142
|
-
- [Adapter Pattern](https://refactoring.guru/design-patterns/adapter)
|
|
143
|
-
- [Supabase Auth](https://supabase.com/docs/guides/auth)
|
|
144
|
-
- [Firebase Auth](https://firebase.google.com/docs/auth)
|
|
1
|
+
# ADR-004: Auth Adapter Pattern for Authentication
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Date
|
|
8
|
+
|
|
9
|
+
2024-01-15
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
The template needs to support multiple authentication providers (Supabase, Firebase, Auth0, custom backends) without requiring significant code changes. We need:
|
|
14
|
+
|
|
15
|
+
1. Easy switching between auth providers
|
|
16
|
+
2. Consistent API regardless of provider
|
|
17
|
+
3. Type safety
|
|
18
|
+
4. Testability with mock implementations
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Implement an **Adapter Pattern** for authentication that abstracts the auth provider behind a common interface.
|
|
23
|
+
|
|
24
|
+
## Rationale
|
|
25
|
+
|
|
26
|
+
1. **Flexibility**: Change auth providers without touching app code
|
|
27
|
+
2. **Testing**: Easy to mock for unit tests
|
|
28
|
+
3. **Consistency**: Same API for all providers
|
|
29
|
+
4. **Gradual Migration**: Can switch providers incrementally
|
|
30
|
+
|
|
31
|
+
## Implementation
|
|
32
|
+
|
|
33
|
+
### Interface Definition
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// services/authAdapter.ts
|
|
37
|
+
export interface AuthAdapter {
|
|
38
|
+
signIn(email: string, password: string): Promise<AuthResult>;
|
|
39
|
+
signUp(email: string, password: string, name: string): Promise<AuthResult>;
|
|
40
|
+
signOut(): Promise<void>;
|
|
41
|
+
refreshToken(refreshToken: string): Promise<AuthTokens>;
|
|
42
|
+
forgotPassword(email: string): Promise<void>;
|
|
43
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
44
|
+
getSession(): Promise<AuthResult | null>;
|
|
45
|
+
onAuthStateChange?(callback: (user: User | null) => void): () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AuthResult {
|
|
49
|
+
user: User;
|
|
50
|
+
tokens: AuthTokens;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Mock Implementation
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const mockAuthAdapter: AuthAdapter = {
|
|
58
|
+
async signIn(email, password) {
|
|
59
|
+
await delay(1000); // Simulate network
|
|
60
|
+
return {
|
|
61
|
+
user: { id: '1', email, name: email.split('@')[0] },
|
|
62
|
+
tokens: { accessToken: 'mock', refreshToken: 'mock', expiresAt: ... },
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
// ... other methods
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Supabase Implementation
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
export const supabaseAuthAdapter: AuthAdapter = {
|
|
73
|
+
async signIn(email, password) {
|
|
74
|
+
const { data, error } = await supabase.auth.signInWithPassword({
|
|
75
|
+
email,
|
|
76
|
+
password,
|
|
77
|
+
});
|
|
78
|
+
if (error) throw error;
|
|
79
|
+
return transformSupabaseSession(data);
|
|
80
|
+
},
|
|
81
|
+
// ... other methods
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Usage
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// services/authAdapter.ts
|
|
89
|
+
// Change this line to switch providers
|
|
90
|
+
export const authAdapter: AuthAdapter = mockAuthAdapter;
|
|
91
|
+
// export const authAdapter: AuthAdapter = supabaseAuthAdapter;
|
|
92
|
+
// export const authAdapter: AuthAdapter = firebaseAuthAdapter;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Consequences
|
|
96
|
+
|
|
97
|
+
### Positive
|
|
98
|
+
|
|
99
|
+
- Provider-agnostic code
|
|
100
|
+
- Easy to test with mocks
|
|
101
|
+
- Clear contract for auth operations
|
|
102
|
+
- Can support multiple providers simultaneously
|
|
103
|
+
|
|
104
|
+
### Negative
|
|
105
|
+
|
|
106
|
+
- Some provider-specific features may not fit the interface
|
|
107
|
+
- Additional abstraction layer
|
|
108
|
+
- Need to maintain multiple implementations
|
|
109
|
+
|
|
110
|
+
### Mitigation
|
|
111
|
+
|
|
112
|
+
- Allow optional methods in interface
|
|
113
|
+
- Document provider-specific extensions
|
|
114
|
+
- Keep interface focused on common operations
|
|
115
|
+
|
|
116
|
+
## Testing
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// __tests__/auth.test.ts
|
|
120
|
+
import { mockAuthAdapter } from "@/services/authAdapter";
|
|
121
|
+
|
|
122
|
+
describe("Auth", () => {
|
|
123
|
+
it("signs in successfully", async () => {
|
|
124
|
+
const result = await mockAuthAdapter.signIn("test@example.com", "password");
|
|
125
|
+
expect(result.user.email).toBe("test@example.com");
|
|
126
|
+
expect(result.tokens.accessToken).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Migration Guide
|
|
132
|
+
|
|
133
|
+
To switch from mock to Supabase:
|
|
134
|
+
|
|
135
|
+
1. Install Supabase: `npx expo install @supabase/supabase-js`
|
|
136
|
+
2. Configure environment variables
|
|
137
|
+
3. Implement `supabaseAuthAdapter`
|
|
138
|
+
4. Update export in `authAdapter.ts`
|
|
139
|
+
|
|
140
|
+
## References
|
|
141
|
+
|
|
142
|
+
- [Adapter Pattern](https://refactoring.guru/design-patterns/adapter)
|
|
143
|
+
- [Supabase Auth](https://supabase.com/docs/guides/auth)
|
|
144
|
+
- [Firebase Auth](https://firebase.google.com/docs/auth)
|