@croacroa/react-native-template 1.0.0 โ 2.0.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/.github/workflows/ci.yml +187 -184
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/CHANGELOG.md +106 -106
- package/CONTRIBUTING.md +377 -377
- package/README.md +399 -399
- package/__tests__/components/snapshots.test.tsx +131 -0
- package/__tests__/integration/auth-api.test.tsx +227 -0
- package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
- package/app/(public)/onboarding.tsx +5 -5
- package/app.config.ts +45 -2
- package/assets/images/.gitkeep +7 -7
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/SuspenseBoundary.tsx +357 -0
- package/components/providers/index.ts +13 -0
- package/components/ui/Avatar.tsx +316 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Select.tsx +240 -240
- package/components/ui/VirtualizedList.tsx +285 -0
- package/components/ui/index.ts +23 -18
- package/constants/config.ts +97 -54
- 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/hooks/index.ts +27 -25
- package/hooks/useApi.ts +102 -5
- package/hooks/useAuth.tsx +82 -0
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useMFA.ts +499 -0
- package/hooks/useNotifications.ts +39 -0
- package/hooks/useOffline.ts +32 -2
- package/hooks/usePerformance.ts +434 -434
- package/hooks/useTheme.tsx +76 -0
- package/hooks/useUpdates.ts +358 -358
- package/i18n/index.ts +194 -77
- package/i18n/locales/ar.json +101 -0
- package/i18n/locales/de.json +101 -0
- package/i18n/locales/en.json +101 -101
- package/i18n/locales/es.json +101 -0
- package/i18n/locales/fr.json +101 -101
- package/jest.config.js +4 -4
- 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 -0
- package/maestro/flows/mfa-setup.yaml +86 -0
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -0
- package/maestro/flows/offline-sync.yaml +128 -0
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +175 -170
- package/services/analytics.ts +428 -428
- package/services/api.ts +340 -340
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +626 -0
- package/services/index.ts +54 -22
- package/services/security.ts +229 -0
- package/tailwind.config.js +47 -47
- package/utils/accessibility.ts +446 -446
- package/utils/index.ts +52 -43
- package/utils/withAccessibility.tsx +272 -0
package/README.md
CHANGED
|
@@ -1,399 +1,399 @@
|
|
|
1
|
-
# @croacroa/react-native-template
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@croacroa/react-native-template)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
A production-ready React Native template with Expo SDK 52, featuring authentication, i18n, biometrics, offline support, and more.
|
|
7
|
-
|
|
8
|
-
## โจ Features
|
|
9
|
-
|
|
10
|
-
### Core
|
|
11
|
-
|
|
12
|
-
- **Expo SDK 52** with TypeScript
|
|
13
|
-
- **Expo Router** for file-based navigation
|
|
14
|
-
- **NativeWind** (Tailwind CSS) for styling
|
|
15
|
-
- **Zustand** for state management
|
|
16
|
-
- **TanStack Query** with offline persistence
|
|
17
|
-
- **React Hook Form + Zod** for form validation
|
|
18
|
-
|
|
19
|
-
### Authentication & Security
|
|
20
|
-
|
|
21
|
-
- **Auth Adapter Pattern** - Easy switching between Supabase, Firebase, etc.
|
|
22
|
-
- **Biometric Auth** - Face ID / Touch ID support
|
|
23
|
-
- **Secure token storage** with expo-secure-store
|
|
24
|
-
- **Automatic token refresh** with race condition handling
|
|
25
|
-
|
|
26
|
-
### Internationalization
|
|
27
|
-
|
|
28
|
-
- **i18n** with expo-localization + i18next
|
|
29
|
-
- **English & French** translations included
|
|
30
|
-
- **Language detection** and persistence
|
|
31
|
-
|
|
32
|
-
### UX Features
|
|
33
|
-
|
|
34
|
-
- **Dark/Light Theme** with system preference support
|
|
35
|
-
- **Onboarding Screens** with animated pagination
|
|
36
|
-
- **Push Notifications** with Expo Notifications
|
|
37
|
-
- **Toast Notifications** with Burnt
|
|
38
|
-
- **Deep Linking** support with route parsing
|
|
39
|
-
- **Skeleton Loaders** with shimmer animation
|
|
40
|
-
- **Offline Support** with connection status toasts
|
|
41
|
-
- **OTA Updates** with expo-updates integration
|
|
42
|
-
|
|
43
|
-
### UI Components
|
|
44
|
-
|
|
45
|
-
- Button, Input, Card, Modal, Skeleton
|
|
46
|
-
- **Select/Dropdown**, Checkbox, Switch
|
|
47
|
-
- **BottomSheet** with @gorhom/bottom-sheet
|
|
48
|
-
- **Avatar** with initials fallback
|
|
49
|
-
- **Badge, Chip, CountBadge**
|
|
50
|
-
- **OptimizedImage** with expo-image
|
|
51
|
-
|
|
52
|
-
### DevOps & Quality
|
|
53
|
-
|
|
54
|
-
- **GitHub Actions** CI/CD workflows
|
|
55
|
-
- **Maestro** E2E tests
|
|
56
|
-
- **Sentry** for crash reporting
|
|
57
|
-
- **Analytics Adapter** for multiple providers
|
|
58
|
-
- **Performance Monitoring** hooks
|
|
59
|
-
- **Accessibility** utilities and hooks
|
|
60
|
-
- **Jest + Testing Library** with 58+ tests
|
|
61
|
-
- **Storybook** for component documentation
|
|
62
|
-
- **ESLint + Prettier + Husky** for code quality
|
|
63
|
-
|
|
64
|
-
## ๐ Quick Start
|
|
65
|
-
|
|
66
|
-
### Option 1: Using npx (Recommended)
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npx create-expo-app my-app --template @croacroa/react-native-template
|
|
70
|
-
cd my-app
|
|
71
|
-
npm install
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Option 2: Using degit
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
npx degit croacroa-dev-team/template-react-native my-app
|
|
78
|
-
cd my-app
|
|
79
|
-
./scripts/init.sh # macOS/Linux
|
|
80
|
-
# or
|
|
81
|
-
.\scripts\init.ps1 # Windows PowerShell
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Option 3: Clone Repository
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
git clone https://github.com/croacroa-dev-team/template-react-native my-app
|
|
88
|
-
cd my-app
|
|
89
|
-
rm -rf .git
|
|
90
|
-
npm install
|
|
91
|
-
cp .env.example .env
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Then update:
|
|
95
|
-
|
|
96
|
-
- `app.config.ts` - App name, bundle ID, scheme
|
|
97
|
-
- `package.json` - Package name
|
|
98
|
-
- `constants/config.ts` - API URLs
|
|
99
|
-
|
|
100
|
-
### Run the App
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
npm start # Start development server
|
|
104
|
-
npm run ios # Run on iOS simulator
|
|
105
|
-
npm run android # Run on Android emulator
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## ๐ Project Structure
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
โโโ app/ # Expo Router pages
|
|
112
|
-
โ โโโ (auth)/ # Protected routes (home, profile, settings)
|
|
113
|
-
โ โโโ (public)/ # Public routes (login, register, forgot-password)
|
|
114
|
-
โ โโโ _layout.tsx # Root layout with providers
|
|
115
|
-
โโโ components/
|
|
116
|
-
โ โโโ ui/ # UI components (Button, Card, Modal, Skeleton)
|
|
117
|
-
โ โโโ forms/ # Form components (FormInput)
|
|
118
|
-
โ โโโ ErrorBoundary.tsx # Global error handling
|
|
119
|
-
โโโ hooks/ # useAuth, useTheme, useNotifications, useApi, useOffline
|
|
120
|
-
โโโ stores/ # Zustand stores (appStore, notificationStore)
|
|
121
|
-
โโโ services/
|
|
122
|
-
โ โโโ api.ts # HTTP client with 401 retry
|
|
123
|
-
โ โโโ queryClient.ts # TanStack Query with persistence
|
|
124
|
-
โ โโโ sentry.ts # Crash reporting
|
|
125
|
-
โ โโโ storage.ts # AsyncStorage & SecureStore helpers
|
|
126
|
-
โโโ utils/ # cn, toast, validation schemas
|
|
127
|
-
โโโ constants/ # App configuration
|
|
128
|
-
โโโ types/ # TypeScript types
|
|
129
|
-
โโโ __tests__/ # Test files (58+ tests)
|
|
130
|
-
โโโ scripts/ # Init scripts for template setup
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## ๐ Authentication
|
|
134
|
-
|
|
135
|
-
Complete auth flow with automatic token refresh:
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
import { useAuth } from "@/hooks/useAuth";
|
|
139
|
-
|
|
140
|
-
function MyComponent() {
|
|
141
|
-
const {
|
|
142
|
-
user,
|
|
143
|
-
isAuthenticated,
|
|
144
|
-
isLoading,
|
|
145
|
-
signIn,
|
|
146
|
-
signUp,
|
|
147
|
-
signOut,
|
|
148
|
-
updateUser,
|
|
149
|
-
refreshSession,
|
|
150
|
-
} = useAuth();
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Features:
|
|
155
|
-
|
|
156
|
-
- Tokens stored securely with expo-secure-store
|
|
157
|
-
- Automatic refresh 5 minutes before expiry
|
|
158
|
-
- Race condition handling for concurrent requests
|
|
159
|
-
- Redirect to login on session expiry
|
|
160
|
-
|
|
161
|
-
## ๐ก API Client
|
|
162
|
-
|
|
163
|
-
Robust HTTP client with automatic retry:
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
import { api } from "@/services/api";
|
|
167
|
-
|
|
168
|
-
// Basic requests
|
|
169
|
-
const users = await api.get<User[]>("/users");
|
|
170
|
-
const user = await api.post<User>("/users", { name: "John" });
|
|
171
|
-
await api.put("/users/1", { name: "Jane" });
|
|
172
|
-
await api.delete("/users/1");
|
|
173
|
-
|
|
174
|
-
// Skip auth for public endpoints
|
|
175
|
-
await api.get("/public", { requiresAuth: false });
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### 401 Handling
|
|
179
|
-
|
|
180
|
-
The API client automatically:
|
|
181
|
-
|
|
182
|
-
1. Catches 401 responses
|
|
183
|
-
2. Refreshes the access token
|
|
184
|
-
3. Retries the original request
|
|
185
|
-
4. Redirects to login if refresh fails
|
|
186
|
-
|
|
187
|
-
## ๐ Data Fetching
|
|
188
|
-
|
|
189
|
-
TanStack Query with offline persistence:
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
import { useCurrentUser, useUpdateUser } from "@/hooks/useApi";
|
|
193
|
-
|
|
194
|
-
function Profile() {
|
|
195
|
-
const { data: user, isLoading, error } = useCurrentUser();
|
|
196
|
-
const updateUser = useUpdateUser();
|
|
197
|
-
|
|
198
|
-
const handleUpdate = () => {
|
|
199
|
-
updateUser.mutate(
|
|
200
|
-
{ name: "New Name" },
|
|
201
|
-
{ onSuccess: () => toast.success("Updated!") }
|
|
202
|
-
);
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### CRUD Factory
|
|
208
|
-
|
|
209
|
-
Create hooks for any resource:
|
|
210
|
-
|
|
211
|
-
```tsx
|
|
212
|
-
import { createCrudHooks } from "@/hooks/useApi";
|
|
213
|
-
|
|
214
|
-
const postsApi = createCrudHooks<Post>({
|
|
215
|
-
baseKey: ["posts"],
|
|
216
|
-
endpoint: "/posts",
|
|
217
|
-
entityName: "Post",
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Usage
|
|
221
|
-
const { data: posts } = postsApi.useList();
|
|
222
|
-
const { data: post } = postsApi.useById("123");
|
|
223
|
-
const createPost = postsApi.useCreate();
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## ๐ด Offline Support
|
|
227
|
-
|
|
228
|
-
Automatic offline handling:
|
|
229
|
-
|
|
230
|
-
```tsx
|
|
231
|
-
import { useOffline } from "@/hooks/useOffline";
|
|
232
|
-
|
|
233
|
-
function MyComponent() {
|
|
234
|
-
const { isOffline, isOnline } = useOffline({ showToast: true });
|
|
235
|
-
// Shows toast when connection lost/restored
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Query cache persisted to AsyncStorage - data available offline.
|
|
240
|
-
|
|
241
|
-
## ๐จ Skeleton Loaders
|
|
242
|
-
|
|
243
|
-
Pre-built skeleton components:
|
|
244
|
-
|
|
245
|
-
```tsx
|
|
246
|
-
import {
|
|
247
|
-
Skeleton,
|
|
248
|
-
SkeletonText,
|
|
249
|
-
SkeletonCard,
|
|
250
|
-
SkeletonProfile,
|
|
251
|
-
SkeletonList,
|
|
252
|
-
} from "@/components/ui/Skeleton";
|
|
253
|
-
|
|
254
|
-
// Single skeleton
|
|
255
|
-
<Skeleton width={200} height={20} />
|
|
256
|
-
|
|
257
|
-
// Profile placeholder
|
|
258
|
-
<SkeletonProfile />
|
|
259
|
-
|
|
260
|
-
// List of cards
|
|
261
|
-
<SkeletonList count={5} variant="card" />
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## ๐ Toast Notifications
|
|
265
|
-
|
|
266
|
-
Centralized toast system:
|
|
267
|
-
|
|
268
|
-
```tsx
|
|
269
|
-
import { toast, handleApiError } from "@/utils/toast";
|
|
270
|
-
|
|
271
|
-
// Simple toasts
|
|
272
|
-
toast.success("Profile updated");
|
|
273
|
-
toast.error("Something went wrong", "Please try again");
|
|
274
|
-
toast.info("New message received");
|
|
275
|
-
|
|
276
|
-
// Handle API errors automatically
|
|
277
|
-
try {
|
|
278
|
-
await api.post("/endpoint", data);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
handleApiError(error); // Shows appropriate toast
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
## ๐ก๏ธ Error Boundary
|
|
285
|
-
|
|
286
|
-
Global error handling with Sentry:
|
|
287
|
-
|
|
288
|
-
```tsx
|
|
289
|
-
// Already wrapped in _layout.tsx
|
|
290
|
-
<ErrorBoundary>
|
|
291
|
-
<App />
|
|
292
|
-
</ErrorBoundary>;
|
|
293
|
-
|
|
294
|
-
// Or use HOC for specific components
|
|
295
|
-
import { withErrorBoundary } from "@/components/ErrorBoundary";
|
|
296
|
-
|
|
297
|
-
const SafeComponent = withErrorBoundary(RiskyComponent);
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
## ๐ Form Validation
|
|
301
|
-
|
|
302
|
-
React Hook Form + Zod:
|
|
303
|
-
|
|
304
|
-
```tsx
|
|
305
|
-
import { useForm } from "react-hook-form";
|
|
306
|
-
import { zodResolver } from "@hookform/resolvers/zod";
|
|
307
|
-
import { FormInput } from "@/components/forms";
|
|
308
|
-
import { loginSchema, LoginFormData } from "@/utils/validation";
|
|
309
|
-
|
|
310
|
-
function LoginForm() {
|
|
311
|
-
const { control, handleSubmit } = useForm<LoginFormData>({
|
|
312
|
-
resolver: zodResolver(loginSchema),
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
return (
|
|
316
|
-
<FormInput
|
|
317
|
-
name="email"
|
|
318
|
-
control={control}
|
|
319
|
-
label="Email"
|
|
320
|
-
keyboardType="email-address"
|
|
321
|
-
/>
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
Pre-built schemas: `loginSchema`, `registerSchema`, `forgotPasswordSchema`, `profileSchema`
|
|
327
|
-
|
|
328
|
-
## ๐ญ Theming
|
|
329
|
-
|
|
330
|
-
Dark/light mode with persistence:
|
|
331
|
-
|
|
332
|
-
```tsx
|
|
333
|
-
import { useTheme } from "@/hooks/useTheme";
|
|
334
|
-
|
|
335
|
-
function MyComponent() {
|
|
336
|
-
const { isDark, mode, toggleTheme, setMode } = useTheme();
|
|
337
|
-
// mode: 'light' | 'dark' | 'system'
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
## ๐ง Configuration
|
|
342
|
-
|
|
343
|
-
### Environment Variables
|
|
344
|
-
|
|
345
|
-
```env
|
|
346
|
-
# .env
|
|
347
|
-
EXPO_PUBLIC_SENTRY_DSN=your-sentry-dsn
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Sentry Setup
|
|
351
|
-
|
|
352
|
-
1. Create project at [sentry.io](https://sentry.io)
|
|
353
|
-
2. Copy DSN to `.env`
|
|
354
|
-
3. Errors automatically reported in production
|
|
355
|
-
|
|
356
|
-
## ๐งช Testing
|
|
357
|
-
|
|
358
|
-
58+ tests included:
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
npm test # Run all tests
|
|
362
|
-
npm run test:watch # Watch mode
|
|
363
|
-
npm run test:coverage # With coverage
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
Test coverage:
|
|
367
|
-
|
|
368
|
-
- `useAuth` hook - 24 tests
|
|
369
|
-
- `ApiClient` - 22 tests
|
|
370
|
-
- UI components - 12 tests
|
|
371
|
-
|
|
372
|
-
## ๐ Available Scripts
|
|
373
|
-
|
|
374
|
-
| Command | Description |
|
|
375
|
-
| ----------------------- | ------------------------ |
|
|
376
|
-
| `npm start` | Start Expo dev server |
|
|
377
|
-
| `npm run ios` | Run on iOS simulator |
|
|
378
|
-
| `npm run android` | Run on Android emulator |
|
|
379
|
-
| `npm test` | Run tests |
|
|
380
|
-
| `npm run storybook` | Start Storybook |
|
|
381
|
-
| `npm run lint` | Run ESLint |
|
|
382
|
-
| `npm run typecheck` | TypeScript check |
|
|
383
|
-
| `npm run build:dev` | Build development client |
|
|
384
|
-
| `npm run build:preview` | Build preview APK/IPA |
|
|
385
|
-
| `npm run build:prod` | Build production release |
|
|
386
|
-
|
|
387
|
-
## โ
Customization Checklist
|
|
388
|
-
|
|
389
|
-
- [ ] Run init script or manually update placeholders
|
|
390
|
-
- [ ] Replace icons in `assets/images/`
|
|
391
|
-
- [ ] Configure API URL in `constants/config.ts`
|
|
392
|
-
- [ ] Set up Sentry DSN in `.env`
|
|
393
|
-
- [ ] Configure EAS: `eas build:configure`
|
|
394
|
-
- [ ] Implement real API calls in `services/api.ts`
|
|
395
|
-
- [ ] Add your analytics
|
|
396
|
-
|
|
397
|
-
## ๐ License
|
|
398
|
-
|
|
399
|
-
MIT
|
|
1
|
+
# @croacroa/react-native-template
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@croacroa/react-native-template)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A production-ready React Native template with Expo SDK 52, featuring authentication, i18n, biometrics, offline support, and more.
|
|
7
|
+
|
|
8
|
+
## โจ Features
|
|
9
|
+
|
|
10
|
+
### Core
|
|
11
|
+
|
|
12
|
+
- **Expo SDK 52** with TypeScript
|
|
13
|
+
- **Expo Router** for file-based navigation
|
|
14
|
+
- **NativeWind** (Tailwind CSS) for styling
|
|
15
|
+
- **Zustand** for state management
|
|
16
|
+
- **TanStack Query** with offline persistence
|
|
17
|
+
- **React Hook Form + Zod** for form validation
|
|
18
|
+
|
|
19
|
+
### Authentication & Security
|
|
20
|
+
|
|
21
|
+
- **Auth Adapter Pattern** - Easy switching between Supabase, Firebase, etc.
|
|
22
|
+
- **Biometric Auth** - Face ID / Touch ID support
|
|
23
|
+
- **Secure token storage** with expo-secure-store
|
|
24
|
+
- **Automatic token refresh** with race condition handling
|
|
25
|
+
|
|
26
|
+
### Internationalization
|
|
27
|
+
|
|
28
|
+
- **i18n** with expo-localization + i18next
|
|
29
|
+
- **English & French** translations included
|
|
30
|
+
- **Language detection** and persistence
|
|
31
|
+
|
|
32
|
+
### UX Features
|
|
33
|
+
|
|
34
|
+
- **Dark/Light Theme** with system preference support
|
|
35
|
+
- **Onboarding Screens** with animated pagination
|
|
36
|
+
- **Push Notifications** with Expo Notifications
|
|
37
|
+
- **Toast Notifications** with Burnt
|
|
38
|
+
- **Deep Linking** support with route parsing
|
|
39
|
+
- **Skeleton Loaders** with shimmer animation
|
|
40
|
+
- **Offline Support** with connection status toasts
|
|
41
|
+
- **OTA Updates** with expo-updates integration
|
|
42
|
+
|
|
43
|
+
### UI Components
|
|
44
|
+
|
|
45
|
+
- Button, Input, Card, Modal, Skeleton
|
|
46
|
+
- **Select/Dropdown**, Checkbox, Switch
|
|
47
|
+
- **BottomSheet** with @gorhom/bottom-sheet
|
|
48
|
+
- **Avatar** with initials fallback
|
|
49
|
+
- **Badge, Chip, CountBadge**
|
|
50
|
+
- **OptimizedImage** with expo-image
|
|
51
|
+
|
|
52
|
+
### DevOps & Quality
|
|
53
|
+
|
|
54
|
+
- **GitHub Actions** CI/CD workflows
|
|
55
|
+
- **Maestro** E2E tests
|
|
56
|
+
- **Sentry** for crash reporting
|
|
57
|
+
- **Analytics Adapter** for multiple providers
|
|
58
|
+
- **Performance Monitoring** hooks
|
|
59
|
+
- **Accessibility** utilities and hooks
|
|
60
|
+
- **Jest + Testing Library** with 58+ tests
|
|
61
|
+
- **Storybook** for component documentation
|
|
62
|
+
- **ESLint + Prettier + Husky** for code quality
|
|
63
|
+
|
|
64
|
+
## ๐ Quick Start
|
|
65
|
+
|
|
66
|
+
### Option 1: Using npx (Recommended)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx create-expo-app my-app --template @croacroa/react-native-template
|
|
70
|
+
cd my-app
|
|
71
|
+
npm install
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Option 2: Using degit
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx degit croacroa-dev-team/template-react-native my-app
|
|
78
|
+
cd my-app
|
|
79
|
+
./scripts/init.sh # macOS/Linux
|
|
80
|
+
# or
|
|
81
|
+
.\scripts\init.ps1 # Windows PowerShell
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Option 3: Clone Repository
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/croacroa-dev-team/template-react-native my-app
|
|
88
|
+
cd my-app
|
|
89
|
+
rm -rf .git
|
|
90
|
+
npm install
|
|
91
|
+
cp .env.example .env
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then update:
|
|
95
|
+
|
|
96
|
+
- `app.config.ts` - App name, bundle ID, scheme
|
|
97
|
+
- `package.json` - Package name
|
|
98
|
+
- `constants/config.ts` - API URLs
|
|
99
|
+
|
|
100
|
+
### Run the App
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm start # Start development server
|
|
104
|
+
npm run ios # Run on iOS simulator
|
|
105
|
+
npm run android # Run on Android emulator
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## ๐ Project Structure
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
โโโ app/ # Expo Router pages
|
|
112
|
+
โ โโโ (auth)/ # Protected routes (home, profile, settings)
|
|
113
|
+
โ โโโ (public)/ # Public routes (login, register, forgot-password)
|
|
114
|
+
โ โโโ _layout.tsx # Root layout with providers
|
|
115
|
+
โโโ components/
|
|
116
|
+
โ โโโ ui/ # UI components (Button, Card, Modal, Skeleton)
|
|
117
|
+
โ โโโ forms/ # Form components (FormInput)
|
|
118
|
+
โ โโโ ErrorBoundary.tsx # Global error handling
|
|
119
|
+
โโโ hooks/ # useAuth, useTheme, useNotifications, useApi, useOffline
|
|
120
|
+
โโโ stores/ # Zustand stores (appStore, notificationStore)
|
|
121
|
+
โโโ services/
|
|
122
|
+
โ โโโ api.ts # HTTP client with 401 retry
|
|
123
|
+
โ โโโ queryClient.ts # TanStack Query with persistence
|
|
124
|
+
โ โโโ sentry.ts # Crash reporting
|
|
125
|
+
โ โโโ storage.ts # AsyncStorage & SecureStore helpers
|
|
126
|
+
โโโ utils/ # cn, toast, validation schemas
|
|
127
|
+
โโโ constants/ # App configuration
|
|
128
|
+
โโโ types/ # TypeScript types
|
|
129
|
+
โโโ __tests__/ # Test files (58+ tests)
|
|
130
|
+
โโโ scripts/ # Init scripts for template setup
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## ๐ Authentication
|
|
134
|
+
|
|
135
|
+
Complete auth flow with automatic token refresh:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useAuth } from "@/hooks/useAuth";
|
|
139
|
+
|
|
140
|
+
function MyComponent() {
|
|
141
|
+
const {
|
|
142
|
+
user,
|
|
143
|
+
isAuthenticated,
|
|
144
|
+
isLoading,
|
|
145
|
+
signIn,
|
|
146
|
+
signUp,
|
|
147
|
+
signOut,
|
|
148
|
+
updateUser,
|
|
149
|
+
refreshSession,
|
|
150
|
+
} = useAuth();
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Features:
|
|
155
|
+
|
|
156
|
+
- Tokens stored securely with expo-secure-store
|
|
157
|
+
- Automatic refresh 5 minutes before expiry
|
|
158
|
+
- Race condition handling for concurrent requests
|
|
159
|
+
- Redirect to login on session expiry
|
|
160
|
+
|
|
161
|
+
## ๐ก API Client
|
|
162
|
+
|
|
163
|
+
Robust HTTP client with automatic retry:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { api } from "@/services/api";
|
|
167
|
+
|
|
168
|
+
// Basic requests
|
|
169
|
+
const users = await api.get<User[]>("/users");
|
|
170
|
+
const user = await api.post<User>("/users", { name: "John" });
|
|
171
|
+
await api.put("/users/1", { name: "Jane" });
|
|
172
|
+
await api.delete("/users/1");
|
|
173
|
+
|
|
174
|
+
// Skip auth for public endpoints
|
|
175
|
+
await api.get("/public", { requiresAuth: false });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 401 Handling
|
|
179
|
+
|
|
180
|
+
The API client automatically:
|
|
181
|
+
|
|
182
|
+
1. Catches 401 responses
|
|
183
|
+
2. Refreshes the access token
|
|
184
|
+
3. Retries the original request
|
|
185
|
+
4. Redirects to login if refresh fails
|
|
186
|
+
|
|
187
|
+
## ๐ Data Fetching
|
|
188
|
+
|
|
189
|
+
TanStack Query with offline persistence:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { useCurrentUser, useUpdateUser } from "@/hooks/useApi";
|
|
193
|
+
|
|
194
|
+
function Profile() {
|
|
195
|
+
const { data: user, isLoading, error } = useCurrentUser();
|
|
196
|
+
const updateUser = useUpdateUser();
|
|
197
|
+
|
|
198
|
+
const handleUpdate = () => {
|
|
199
|
+
updateUser.mutate(
|
|
200
|
+
{ name: "New Name" },
|
|
201
|
+
{ onSuccess: () => toast.success("Updated!") }
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### CRUD Factory
|
|
208
|
+
|
|
209
|
+
Create hooks for any resource:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { createCrudHooks } from "@/hooks/useApi";
|
|
213
|
+
|
|
214
|
+
const postsApi = createCrudHooks<Post>({
|
|
215
|
+
baseKey: ["posts"],
|
|
216
|
+
endpoint: "/posts",
|
|
217
|
+
entityName: "Post",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Usage
|
|
221
|
+
const { data: posts } = postsApi.useList();
|
|
222
|
+
const { data: post } = postsApi.useById("123");
|
|
223
|
+
const createPost = postsApi.useCreate();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## ๐ด Offline Support
|
|
227
|
+
|
|
228
|
+
Automatic offline handling:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { useOffline } from "@/hooks/useOffline";
|
|
232
|
+
|
|
233
|
+
function MyComponent() {
|
|
234
|
+
const { isOffline, isOnline } = useOffline({ showToast: true });
|
|
235
|
+
// Shows toast when connection lost/restored
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Query cache persisted to AsyncStorage - data available offline.
|
|
240
|
+
|
|
241
|
+
## ๐จ Skeleton Loaders
|
|
242
|
+
|
|
243
|
+
Pre-built skeleton components:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import {
|
|
247
|
+
Skeleton,
|
|
248
|
+
SkeletonText,
|
|
249
|
+
SkeletonCard,
|
|
250
|
+
SkeletonProfile,
|
|
251
|
+
SkeletonList,
|
|
252
|
+
} from "@/components/ui/Skeleton";
|
|
253
|
+
|
|
254
|
+
// Single skeleton
|
|
255
|
+
<Skeleton width={200} height={20} />
|
|
256
|
+
|
|
257
|
+
// Profile placeholder
|
|
258
|
+
<SkeletonProfile />
|
|
259
|
+
|
|
260
|
+
// List of cards
|
|
261
|
+
<SkeletonList count={5} variant="card" />
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## ๐ Toast Notifications
|
|
265
|
+
|
|
266
|
+
Centralized toast system:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import { toast, handleApiError } from "@/utils/toast";
|
|
270
|
+
|
|
271
|
+
// Simple toasts
|
|
272
|
+
toast.success("Profile updated");
|
|
273
|
+
toast.error("Something went wrong", "Please try again");
|
|
274
|
+
toast.info("New message received");
|
|
275
|
+
|
|
276
|
+
// Handle API errors automatically
|
|
277
|
+
try {
|
|
278
|
+
await api.post("/endpoint", data);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
handleApiError(error); // Shows appropriate toast
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## ๐ก๏ธ Error Boundary
|
|
285
|
+
|
|
286
|
+
Global error handling with Sentry:
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
// Already wrapped in _layout.tsx
|
|
290
|
+
<ErrorBoundary>
|
|
291
|
+
<App />
|
|
292
|
+
</ErrorBoundary>;
|
|
293
|
+
|
|
294
|
+
// Or use HOC for specific components
|
|
295
|
+
import { withErrorBoundary } from "@/components/ErrorBoundary";
|
|
296
|
+
|
|
297
|
+
const SafeComponent = withErrorBoundary(RiskyComponent);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## ๐ Form Validation
|
|
301
|
+
|
|
302
|
+
React Hook Form + Zod:
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { useForm } from "react-hook-form";
|
|
306
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
307
|
+
import { FormInput } from "@/components/forms";
|
|
308
|
+
import { loginSchema, LoginFormData } from "@/utils/validation";
|
|
309
|
+
|
|
310
|
+
function LoginForm() {
|
|
311
|
+
const { control, handleSubmit } = useForm<LoginFormData>({
|
|
312
|
+
resolver: zodResolver(loginSchema),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<FormInput
|
|
317
|
+
name="email"
|
|
318
|
+
control={control}
|
|
319
|
+
label="Email"
|
|
320
|
+
keyboardType="email-address"
|
|
321
|
+
/>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Pre-built schemas: `loginSchema`, `registerSchema`, `forgotPasswordSchema`, `profileSchema`
|
|
327
|
+
|
|
328
|
+
## ๐ญ Theming
|
|
329
|
+
|
|
330
|
+
Dark/light mode with persistence:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
import { useTheme } from "@/hooks/useTheme";
|
|
334
|
+
|
|
335
|
+
function MyComponent() {
|
|
336
|
+
const { isDark, mode, toggleTheme, setMode } = useTheme();
|
|
337
|
+
// mode: 'light' | 'dark' | 'system'
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## ๐ง Configuration
|
|
342
|
+
|
|
343
|
+
### Environment Variables
|
|
344
|
+
|
|
345
|
+
```env
|
|
346
|
+
# .env
|
|
347
|
+
EXPO_PUBLIC_SENTRY_DSN=your-sentry-dsn
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Sentry Setup
|
|
351
|
+
|
|
352
|
+
1. Create project at [sentry.io](https://sentry.io)
|
|
353
|
+
2. Copy DSN to `.env`
|
|
354
|
+
3. Errors automatically reported in production
|
|
355
|
+
|
|
356
|
+
## ๐งช Testing
|
|
357
|
+
|
|
358
|
+
58+ tests included:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
npm test # Run all tests
|
|
362
|
+
npm run test:watch # Watch mode
|
|
363
|
+
npm run test:coverage # With coverage
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Test coverage:
|
|
367
|
+
|
|
368
|
+
- `useAuth` hook - 24 tests
|
|
369
|
+
- `ApiClient` - 22 tests
|
|
370
|
+
- UI components - 12 tests
|
|
371
|
+
|
|
372
|
+
## ๐ Available Scripts
|
|
373
|
+
|
|
374
|
+
| Command | Description |
|
|
375
|
+
| ----------------------- | ------------------------ |
|
|
376
|
+
| `npm start` | Start Expo dev server |
|
|
377
|
+
| `npm run ios` | Run on iOS simulator |
|
|
378
|
+
| `npm run android` | Run on Android emulator |
|
|
379
|
+
| `npm test` | Run tests |
|
|
380
|
+
| `npm run storybook` | Start Storybook |
|
|
381
|
+
| `npm run lint` | Run ESLint |
|
|
382
|
+
| `npm run typecheck` | TypeScript check |
|
|
383
|
+
| `npm run build:dev` | Build development client |
|
|
384
|
+
| `npm run build:preview` | Build preview APK/IPA |
|
|
385
|
+
| `npm run build:prod` | Build production release |
|
|
386
|
+
|
|
387
|
+
## โ
Customization Checklist
|
|
388
|
+
|
|
389
|
+
- [ ] Run init script or manually update placeholders
|
|
390
|
+
- [ ] Replace icons in `assets/images/`
|
|
391
|
+
- [ ] Configure API URL in `constants/config.ts`
|
|
392
|
+
- [ ] Set up Sentry DSN in `.env`
|
|
393
|
+
- [ ] Configure EAS: `eas build:configure`
|
|
394
|
+
- [ ] Implement real API calls in `services/api.ts`
|
|
395
|
+
- [ ] Add your analytics
|
|
396
|
+
|
|
397
|
+
## ๐ License
|
|
398
|
+
|
|
399
|
+
MIT
|