@nextsparkjs/mobile 0.1.0-beta.1
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/README.md +339 -0
- package/dist/api/client.d.ts +102 -0
- package/dist/api/client.js +189 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/client.types.d.ts +39 -0
- package/dist/api/client.types.js +12 -0
- package/dist/api/client.types.js.map +1 -0
- package/dist/api/core/auth.d.ts +26 -0
- package/dist/api/core/auth.js +52 -0
- package/dist/api/core/auth.js.map +1 -0
- package/dist/api/core/index.d.ts +4 -0
- package/dist/api/core/index.js +5 -0
- package/dist/api/core/index.js.map +1 -0
- package/dist/api/core/teams.d.ts +20 -0
- package/dist/api/core/teams.js +19 -0
- package/dist/api/core/teams.js.map +1 -0
- package/dist/api/core/types.d.ts +58 -0
- package/dist/api/core/types.js +1 -0
- package/dist/api/core/types.js.map +1 -0
- package/dist/api/core/users.d.ts +43 -0
- package/dist/api/core/users.js +41 -0
- package/dist/api/core/users.js.map +1 -0
- package/dist/api/entities/factory.d.ts +43 -0
- package/dist/api/entities/factory.js +31 -0
- package/dist/api/entities/factory.js.map +1 -0
- package/dist/api/entities/index.d.ts +3 -0
- package/dist/api/entities/index.js +3 -0
- package/dist/api/entities/index.js.map +1 -0
- package/dist/api/entities/types.d.ts +32 -0
- package/dist/api/entities/types.js +1 -0
- package/dist/api/entities/types.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +15 -0
- package/dist/api/index.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/alert.d.ts +34 -0
- package/dist/lib/alert.js +73 -0
- package/dist/lib/alert.js.map +1 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.js +29 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/providers/AuthProvider.d.ts +21 -0
- package/dist/providers/AuthProvider.js +113 -0
- package/dist/providers/AuthProvider.js.map +1 -0
- package/dist/providers/QueryProvider.d.ts +11 -0
- package/dist/providers/QueryProvider.js +23 -0
- package/dist/providers/QueryProvider.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/storage-BaRppHUz.d.ts +22 -0
- package/package.json +99 -0
- package/templates/app/(app)/_layout.tsx +216 -0
- package/templates/app/(app)/customer/[id].tsx +68 -0
- package/templates/app/(app)/customer/create.tsx +24 -0
- package/templates/app/(app)/customers.tsx +164 -0
- package/templates/app/(app)/index.tsx +310 -0
- package/templates/app/(app)/notifications.tsx +242 -0
- package/templates/app/(app)/profile.tsx +254 -0
- package/templates/app/(app)/settings.tsx +241 -0
- package/templates/app/(app)/task/[id].tsx +70 -0
- package/templates/app/(app)/task/create.tsx +24 -0
- package/templates/app/(app)/tasks.tsx +164 -0
- package/templates/app/_layout.tsx +54 -0
- package/templates/app/index.tsx +35 -0
- package/templates/app/login.tsx +179 -0
- package/templates/app.config.ts +39 -0
- package/templates/babel.config.js +9 -0
- package/templates/eas.json +18 -0
- package/templates/jest.config.js +12 -0
- package/templates/metro.config.js +23 -0
- package/templates/package.json.template +52 -0
- package/templates/src/components/entities/customers/CustomerCard.tsx +59 -0
- package/templates/src/components/entities/customers/CustomerForm.tsx +194 -0
- package/templates/src/components/entities/customers/index.ts +6 -0
- package/templates/src/components/entities/index.ts +9 -0
- package/templates/src/components/entities/tasks/TaskCard.tsx +89 -0
- package/templates/src/components/entities/tasks/TaskForm.tsx +231 -0
- package/templates/src/components/entities/tasks/index.ts +6 -0
- package/templates/src/components/features/index.ts +6 -0
- package/templates/src/components/navigation/BottomTabBar.tsx +80 -0
- package/templates/src/components/navigation/CreateSheet.tsx +108 -0
- package/templates/src/components/navigation/MoreSheet.tsx +403 -0
- package/templates/src/components/navigation/TopBar.tsx +74 -0
- package/templates/src/components/navigation/index.ts +8 -0
- package/templates/src/components/ui/index.ts +89 -0
- package/templates/src/components/ui/text.tsx +64 -0
- package/templates/src/config/api.config.ts +26 -0
- package/templates/src/config/app.config.ts +15 -0
- package/templates/src/config/hooks.ts +58 -0
- package/templates/src/config/permissions.config.ts +119 -0
- package/templates/src/constants/colors.ts +55 -0
- package/templates/src/data/notifications.mock.json +100 -0
- package/templates/src/entities/customers/api.ts +10 -0
- package/templates/src/entities/customers/constants.internal.ts +6 -0
- package/templates/src/entities/customers/constants.ts +14 -0
- package/templates/src/entities/customers/index.ts +9 -0
- package/templates/src/entities/customers/mutations.ts +58 -0
- package/templates/src/entities/customers/queries.ts +40 -0
- package/templates/src/entities/customers/types.ts +43 -0
- package/templates/src/entities/index.ts +8 -0
- package/templates/src/entities/tasks/api.ts +10 -0
- package/templates/src/entities/tasks/constants.internal.ts +6 -0
- package/templates/src/entities/tasks/constants.ts +39 -0
- package/templates/src/entities/tasks/index.ts +9 -0
- package/templates/src/entities/tasks/mutations.ts +108 -0
- package/templates/src/entities/tasks/queries.ts +42 -0
- package/templates/src/entities/tasks/types.ts +52 -0
- package/templates/src/hooks/useCustomers.ts +17 -0
- package/templates/src/hooks/useTasks.ts +18 -0
- package/templates/src/lib/utils.ts +10 -0
- package/templates/src/styles/globals.css +103 -0
- package/templates/src/types/index.ts +45 -0
- package/templates/tailwind.config.js +108 -0
- package/templates/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# @nextsparkjs/mobile
|
|
2
|
+
|
|
3
|
+
Mobile app infrastructure for NextSpark. Provides API client, authentication providers, and utilities for building Expo apps that connect to your NextSpark backend.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Add mobile app to your NextSpark project
|
|
9
|
+
npx nextspark add:mobile
|
|
10
|
+
|
|
11
|
+
# Or install manually
|
|
12
|
+
npm install @nextsparkjs/mobile
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Configure API URL
|
|
18
|
+
|
|
19
|
+
In `app.config.ts`:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
export default {
|
|
23
|
+
// ...
|
|
24
|
+
extra: {
|
|
25
|
+
apiUrl: process.env.EXPO_PUBLIC_API_URL || 'http://localhost:5173',
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Setup Providers
|
|
31
|
+
|
|
32
|
+
In `app/_layout.tsx`:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { AuthProvider, QueryProvider } from '@nextsparkjs/mobile'
|
|
36
|
+
|
|
37
|
+
export default function RootLayout() {
|
|
38
|
+
return (
|
|
39
|
+
<QueryProvider>
|
|
40
|
+
<AuthProvider>
|
|
41
|
+
<Stack />
|
|
42
|
+
</AuthProvider>
|
|
43
|
+
</QueryProvider>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Use Authentication
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { useAuth } from '@nextsparkjs/mobile'
|
|
52
|
+
|
|
53
|
+
function LoginScreen() {
|
|
54
|
+
const { login, isLoading } = useAuth()
|
|
55
|
+
|
|
56
|
+
const handleLogin = async () => {
|
|
57
|
+
await login('user@example.com', 'password')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ...
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Create Entity APIs
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createEntityApi } from '@nextsparkjs/mobile'
|
|
68
|
+
import type { Task } from './types'
|
|
69
|
+
|
|
70
|
+
export const tasksApi = createEntityApi<Task>('tasks')
|
|
71
|
+
|
|
72
|
+
// Use in queries
|
|
73
|
+
const { data } = await tasksApi.list()
|
|
74
|
+
const task = await tasksApi.get(id)
|
|
75
|
+
await tasksApi.create({ title: 'New Task' })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API Reference
|
|
79
|
+
|
|
80
|
+
### Providers
|
|
81
|
+
|
|
82
|
+
#### `AuthProvider`
|
|
83
|
+
Authentication context provider. Wrap your app with this provider to enable authentication.
|
|
84
|
+
|
|
85
|
+
**Props:**
|
|
86
|
+
- `children: ReactNode` - Your app components
|
|
87
|
+
|
|
88
|
+
**Context Value:**
|
|
89
|
+
- `user: User | null` - Current authenticated user
|
|
90
|
+
- `team: Team | null` - Current team
|
|
91
|
+
- `isAuthenticated: boolean` - Whether user is logged in
|
|
92
|
+
- `isLoading: boolean` - Whether auth is loading
|
|
93
|
+
- `login(email, password): Promise<void>` - Login user
|
|
94
|
+
- `logout(): Promise<void>` - Logout user
|
|
95
|
+
- `selectTeam(teamId): Promise<void>` - Switch to different team
|
|
96
|
+
|
|
97
|
+
#### `QueryProvider`
|
|
98
|
+
TanStack Query provider for data fetching. Configures default options for queries and mutations.
|
|
99
|
+
|
|
100
|
+
**Props:**
|
|
101
|
+
- `children: ReactNode` - Your app components
|
|
102
|
+
|
|
103
|
+
### Hooks
|
|
104
|
+
|
|
105
|
+
#### `useAuth()`
|
|
106
|
+
Hook to access authentication context.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const { user, team, isAuthenticated, login, logout, selectTeam } = useAuth()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### API Client
|
|
113
|
+
|
|
114
|
+
#### `apiClient`
|
|
115
|
+
Singleton HTTP client for API requests.
|
|
116
|
+
|
|
117
|
+
**Methods:**
|
|
118
|
+
- `init(): Promise<void>` - Initialize client (load stored credentials)
|
|
119
|
+
- `get<T>(endpoint, config?): Promise<T>` - GET request
|
|
120
|
+
- `post<T>(endpoint, data, config?): Promise<T>` - POST request
|
|
121
|
+
- `patch<T>(endpoint, data, config?): Promise<T>` - PATCH request
|
|
122
|
+
- `delete<T>(endpoint, config?): Promise<T>` - DELETE request
|
|
123
|
+
- `setToken(token): Promise<void>` - Set authentication token
|
|
124
|
+
- `setTeamId(teamId): Promise<void>` - Set current team ID
|
|
125
|
+
- `clearAuth(): Promise<void>` - Clear all authentication data
|
|
126
|
+
|
|
127
|
+
#### `createEntityApi<T>(entity: string)`
|
|
128
|
+
Factory function to create CRUD API for an entity.
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
- `entity: string` - Entity name (e.g., 'tasks', 'users')
|
|
132
|
+
|
|
133
|
+
**Returns:** `EntityApi<T>` with methods:
|
|
134
|
+
- `list(params?): Promise<PaginatedResponse<T>>` - List entities
|
|
135
|
+
- `get(id): Promise<T>` - Get single entity
|
|
136
|
+
- `create(data): Promise<T>` - Create entity
|
|
137
|
+
- `update(id, data): Promise<T>` - Update entity
|
|
138
|
+
- `delete(id): Promise<void>` - Delete entity
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
```typescript
|
|
142
|
+
import { createEntityApi } from '@nextsparkjs/mobile'
|
|
143
|
+
|
|
144
|
+
interface Task {
|
|
145
|
+
id: string
|
|
146
|
+
title: string
|
|
147
|
+
status: 'pending' | 'completed'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const tasksApi = createEntityApi<Task>('tasks')
|
|
151
|
+
|
|
152
|
+
// Usage
|
|
153
|
+
const tasks = await tasksApi.list({ page: 1, limit: 10 })
|
|
154
|
+
const task = await tasksApi.get('task-id')
|
|
155
|
+
await tasksApi.create({ title: 'New Task', status: 'pending' })
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Utilities
|
|
159
|
+
|
|
160
|
+
#### `Storage`
|
|
161
|
+
Secure storage wrapper using Expo SecureStore.
|
|
162
|
+
|
|
163
|
+
**Methods:**
|
|
164
|
+
- `Storage.getItem(key): Promise<string | null>` - Get item
|
|
165
|
+
- `Storage.setItem(key, value): Promise<void>` - Set item
|
|
166
|
+
- `Storage.removeItem(key): Promise<void>` - Remove item
|
|
167
|
+
|
|
168
|
+
#### `alert(title, message?)`
|
|
169
|
+
Show alert dialog.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { alert } from '@nextsparkjs/mobile'
|
|
173
|
+
|
|
174
|
+
alert('Success', 'Task created successfully')
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### `confirm(title, message?)`
|
|
178
|
+
Show confirmation dialog.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { confirm } from '@nextsparkjs/mobile'
|
|
182
|
+
|
|
183
|
+
const confirmed = await confirm('Delete Task', 'Are you sure?')
|
|
184
|
+
if (confirmed) {
|
|
185
|
+
// Delete task
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### `confirmDestructive(title, message?)`
|
|
190
|
+
Show destructive confirmation dialog (red action).
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { confirmDestructive } from '@nextsparkjs/mobile'
|
|
194
|
+
|
|
195
|
+
const confirmed = await confirmDestructive('Delete All', 'This cannot be undone')
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Core Services
|
|
199
|
+
|
|
200
|
+
Pre-built API services for common entities:
|
|
201
|
+
|
|
202
|
+
### `authApi`
|
|
203
|
+
- `login(email, password): Promise<LoginResponse>`
|
|
204
|
+
- `logout(): Promise<void>`
|
|
205
|
+
- `getSession(): Promise<SessionResponse>`
|
|
206
|
+
|
|
207
|
+
### `teamsApi`
|
|
208
|
+
- `getTeams(): Promise<PaginatedResponse<Team>>`
|
|
209
|
+
- `switchTeam(teamId): Promise<void>`
|
|
210
|
+
|
|
211
|
+
### `usersApi`
|
|
212
|
+
- `getCurrentUser(): Promise<User>`
|
|
213
|
+
- `updateProfile(data): Promise<User>`
|
|
214
|
+
|
|
215
|
+
## TypeScript Support
|
|
216
|
+
|
|
217
|
+
Full TypeScript support with generated type definitions.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import type {
|
|
221
|
+
User,
|
|
222
|
+
Team,
|
|
223
|
+
Session,
|
|
224
|
+
PaginatedResponse,
|
|
225
|
+
EntityQueryParams
|
|
226
|
+
} from '@nextsparkjs/mobile'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Environment Configuration
|
|
230
|
+
|
|
231
|
+
The API URL is resolved in this order:
|
|
232
|
+
|
|
233
|
+
1. **app.config.ts** `extra.apiUrl`
|
|
234
|
+
2. **Environment variable** `EXPO_PUBLIC_API_URL`
|
|
235
|
+
3. **Auto-detect** from Expo dev server
|
|
236
|
+
4. **Fallback** to `http://localhost:5173`
|
|
237
|
+
|
|
238
|
+
**Development:**
|
|
239
|
+
```bash
|
|
240
|
+
# .env
|
|
241
|
+
EXPO_PUBLIC_API_URL=http://localhost:5173
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Production (EAS Build):**
|
|
245
|
+
```json
|
|
246
|
+
// eas.json
|
|
247
|
+
{
|
|
248
|
+
"build": {
|
|
249
|
+
"production": {
|
|
250
|
+
"env": {
|
|
251
|
+
"EXPO_PUBLIC_API_URL": "https://api.yourapp.com"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Examples
|
|
259
|
+
|
|
260
|
+
### Complete Login Flow
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { useAuth } from '@nextsparkjs/mobile'
|
|
264
|
+
import { useState } from 'react'
|
|
265
|
+
|
|
266
|
+
export default function LoginScreen() {
|
|
267
|
+
const { login, isLoading } = useAuth()
|
|
268
|
+
const [email, setEmail] = useState('')
|
|
269
|
+
const [password, setPassword] = useState('')
|
|
270
|
+
|
|
271
|
+
const handleLogin = async () => {
|
|
272
|
+
try {
|
|
273
|
+
await login(email, password)
|
|
274
|
+
// Navigation handled by AuthProvider
|
|
275
|
+
} catch (error) {
|
|
276
|
+
alert('Login Failed', error.message)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
// Your UI
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Using Entity API with React Query
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
290
|
+
import { tasksApi } from './entities/tasks/api'
|
|
291
|
+
|
|
292
|
+
export function useTasks() {
|
|
293
|
+
return useQuery({
|
|
294
|
+
queryKey: ['tasks'],
|
|
295
|
+
queryFn: () => tasksApi.list(),
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function useCreateTask() {
|
|
300
|
+
const queryClient = useQueryClient()
|
|
301
|
+
|
|
302
|
+
return useMutation({
|
|
303
|
+
mutationFn: tasksApi.create,
|
|
304
|
+
onSuccess: () => {
|
|
305
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Troubleshooting
|
|
312
|
+
|
|
313
|
+
### Cannot connect to API
|
|
314
|
+
|
|
315
|
+
1. Check API URL configuration in `app.config.ts`
|
|
316
|
+
2. Verify backend is running
|
|
317
|
+
3. Check network connectivity
|
|
318
|
+
4. On iOS simulator, use `http://localhost:5173`
|
|
319
|
+
5. On Android emulator, use `http://10.0.2.2:5173`
|
|
320
|
+
|
|
321
|
+
### Authentication not persisting
|
|
322
|
+
|
|
323
|
+
The package uses Expo SecureStore which requires:
|
|
324
|
+
- `expo-secure-store` installed
|
|
325
|
+
- Plugin configured in `app.config.ts`
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
export default {
|
|
329
|
+
plugins: ['expo-secure-store'],
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Documentation
|
|
334
|
+
|
|
335
|
+
Full documentation: https://nextspark.dev/docs/mobile
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { RequestConfig } from './client.types.js';
|
|
2
|
+
export { ApiError } from './client.types.js';
|
|
3
|
+
import { User } from './core/types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* API Client for NextSpark Backend
|
|
7
|
+
*
|
|
8
|
+
* Configurable HTTP client with authentication and team context.
|
|
9
|
+
* API_URL is resolved from (in order):
|
|
10
|
+
* 1. app.config.ts extra.apiUrl
|
|
11
|
+
* 2. EXPO_PUBLIC_API_URL environment variable
|
|
12
|
+
* 3. Auto-detect from Expo dev server
|
|
13
|
+
* 4. Fallback to localhost:5173
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve API URL from configuration
|
|
18
|
+
*
|
|
19
|
+
* Priority order:
|
|
20
|
+
* 1. app.config.ts > extra > apiUrl (explicit configuration)
|
|
21
|
+
* 2. EXPO_PUBLIC_API_URL environment variable
|
|
22
|
+
* 3. Auto-detect from Expo dev server hostUri (development)
|
|
23
|
+
* 4. Fallback to http://localhost:5173
|
|
24
|
+
*
|
|
25
|
+
* @returns The resolved API URL
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // In app.config.ts:
|
|
29
|
+
* export default {
|
|
30
|
+
* extra: {
|
|
31
|
+
* apiUrl: 'https://api.myapp.com'
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function getApiUrl(): string;
|
|
37
|
+
declare class ApiClient {
|
|
38
|
+
private token;
|
|
39
|
+
private teamId;
|
|
40
|
+
private storedUser;
|
|
41
|
+
/**
|
|
42
|
+
* Initialize client by loading stored credentials
|
|
43
|
+
*/
|
|
44
|
+
init(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Get stored token
|
|
47
|
+
*/
|
|
48
|
+
getToken(): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Set authentication token
|
|
51
|
+
*/
|
|
52
|
+
setToken(token: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Get stored team ID
|
|
55
|
+
*/
|
|
56
|
+
getTeamId(): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Set team ID
|
|
59
|
+
*/
|
|
60
|
+
setTeamId(teamId: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Get stored user info
|
|
63
|
+
*/
|
|
64
|
+
getStoredUser(): User | null;
|
|
65
|
+
/**
|
|
66
|
+
* Set user info
|
|
67
|
+
*/
|
|
68
|
+
setUser(user: User): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Clear authentication
|
|
71
|
+
*/
|
|
72
|
+
clearAuth(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Build URL with query parameters
|
|
75
|
+
*/
|
|
76
|
+
private buildUrl;
|
|
77
|
+
/**
|
|
78
|
+
* Make authenticated request
|
|
79
|
+
* Uses credentials: 'include' to support cookie-based auth alongside Bearer token
|
|
80
|
+
*/
|
|
81
|
+
request<T>(endpoint: string, options?: RequestConfig): Promise<T>;
|
|
82
|
+
/**
|
|
83
|
+
* GET request with optional query parameters
|
|
84
|
+
*/
|
|
85
|
+
get<T>(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
86
|
+
/**
|
|
87
|
+
* POST request with JSON body
|
|
88
|
+
*/
|
|
89
|
+
post<T>(endpoint: string, data?: unknown): Promise<T>;
|
|
90
|
+
/**
|
|
91
|
+
* PATCH request with JSON body
|
|
92
|
+
*/
|
|
93
|
+
patch<T>(endpoint: string, data?: unknown): Promise<T>;
|
|
94
|
+
/**
|
|
95
|
+
* DELETE request
|
|
96
|
+
* Returns void for delete operations (most common case)
|
|
97
|
+
*/
|
|
98
|
+
delete<T = void>(endpoint: string): Promise<T>;
|
|
99
|
+
}
|
|
100
|
+
declare const apiClient: ApiClient;
|
|
101
|
+
|
|
102
|
+
export { ApiClient, apiClient, getApiUrl };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import Constants from "expo-constants";
|
|
2
|
+
import * as Storage from '../lib/storage.js';
|
|
3
|
+
import { ApiError } from './client.types.js';
|
|
4
|
+
function getApiUrl() {
|
|
5
|
+
const configUrl = Constants.expoConfig?.extra?.apiUrl;
|
|
6
|
+
if (configUrl) return configUrl;
|
|
7
|
+
const envUrl = process.env.EXPO_PUBLIC_API_URL;
|
|
8
|
+
if (envUrl) return envUrl;
|
|
9
|
+
if (Constants.expoConfig?.hostUri) {
|
|
10
|
+
const host = Constants.expoConfig.hostUri.split(":")[0];
|
|
11
|
+
return `http://${host}:5173`;
|
|
12
|
+
}
|
|
13
|
+
return "http://localhost:5173";
|
|
14
|
+
}
|
|
15
|
+
const API_URL = getApiUrl();
|
|
16
|
+
const TOKEN_KEY = "nextspark.auth.token";
|
|
17
|
+
const TEAM_ID_KEY = "nextspark.auth.teamId";
|
|
18
|
+
const USER_KEY = "nextspark.auth.user";
|
|
19
|
+
class ApiClient {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.token = null;
|
|
22
|
+
this.teamId = null;
|
|
23
|
+
this.storedUser = null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initialize client by loading stored credentials
|
|
27
|
+
*/
|
|
28
|
+
async init() {
|
|
29
|
+
this.token = await Storage.getItemAsync(TOKEN_KEY);
|
|
30
|
+
this.teamId = await Storage.getItemAsync(TEAM_ID_KEY);
|
|
31
|
+
const userJson = await Storage.getItemAsync(USER_KEY);
|
|
32
|
+
if (userJson) {
|
|
33
|
+
try {
|
|
34
|
+
this.storedUser = JSON.parse(userJson);
|
|
35
|
+
} catch {
|
|
36
|
+
this.storedUser = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ==========================================
|
|
41
|
+
// Token & Team Management
|
|
42
|
+
// ==========================================
|
|
43
|
+
/**
|
|
44
|
+
* Get stored token
|
|
45
|
+
*/
|
|
46
|
+
getToken() {
|
|
47
|
+
return this.token;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Set authentication token
|
|
51
|
+
*/
|
|
52
|
+
async setToken(token) {
|
|
53
|
+
this.token = token;
|
|
54
|
+
await Storage.setItemAsync(TOKEN_KEY, token);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get stored team ID
|
|
58
|
+
*/
|
|
59
|
+
getTeamId() {
|
|
60
|
+
return this.teamId;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Set team ID
|
|
64
|
+
*/
|
|
65
|
+
async setTeamId(teamId) {
|
|
66
|
+
this.teamId = teamId;
|
|
67
|
+
await Storage.setItemAsync(TEAM_ID_KEY, teamId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get stored user info
|
|
71
|
+
*/
|
|
72
|
+
getStoredUser() {
|
|
73
|
+
return this.storedUser;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set user info
|
|
77
|
+
*/
|
|
78
|
+
async setUser(user) {
|
|
79
|
+
this.storedUser = user;
|
|
80
|
+
await Storage.setItemAsync(USER_KEY, JSON.stringify(user));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Clear authentication
|
|
84
|
+
*/
|
|
85
|
+
async clearAuth() {
|
|
86
|
+
this.token = null;
|
|
87
|
+
this.teamId = null;
|
|
88
|
+
this.storedUser = null;
|
|
89
|
+
await Storage.deleteItemAsync(TOKEN_KEY);
|
|
90
|
+
await Storage.deleteItemAsync(TEAM_ID_KEY);
|
|
91
|
+
await Storage.deleteItemAsync(USER_KEY);
|
|
92
|
+
}
|
|
93
|
+
// ==========================================
|
|
94
|
+
// HTTP Methods
|
|
95
|
+
// ==========================================
|
|
96
|
+
/**
|
|
97
|
+
* Build URL with query parameters
|
|
98
|
+
*/
|
|
99
|
+
buildUrl(endpoint, params) {
|
|
100
|
+
const url = `${API_URL}${endpoint}`;
|
|
101
|
+
if (!params) return url;
|
|
102
|
+
const searchParams = new URLSearchParams();
|
|
103
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
104
|
+
if (value !== void 0) {
|
|
105
|
+
searchParams.set(key, String(value));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const queryString = searchParams.toString();
|
|
109
|
+
return queryString ? `${url}?${queryString}` : url;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Make authenticated request
|
|
113
|
+
* Uses credentials: 'include' to support cookie-based auth alongside Bearer token
|
|
114
|
+
*/
|
|
115
|
+
async request(endpoint, options = {}) {
|
|
116
|
+
const { params, ...fetchOptions } = options;
|
|
117
|
+
const url = this.buildUrl(endpoint, params);
|
|
118
|
+
const headers = {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
...fetchOptions.headers
|
|
121
|
+
};
|
|
122
|
+
if (this.token) {
|
|
123
|
+
;
|
|
124
|
+
headers["Authorization"] = `Bearer ${this.token}`;
|
|
125
|
+
}
|
|
126
|
+
if (this.teamId) {
|
|
127
|
+
;
|
|
128
|
+
headers["x-team-id"] = this.teamId;
|
|
129
|
+
}
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
...fetchOptions,
|
|
132
|
+
headers,
|
|
133
|
+
credentials: "include"
|
|
134
|
+
// Support cookie-based sessions
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const errorData = await response.json().catch(() => ({}));
|
|
138
|
+
throw new ApiError(
|
|
139
|
+
errorData.message || `Request failed with status ${response.status}`,
|
|
140
|
+
response.status,
|
|
141
|
+
errorData
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (response.status === 204) {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
return response.json().catch(() => ({}));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* GET request with optional query parameters
|
|
151
|
+
*/
|
|
152
|
+
async get(endpoint, params) {
|
|
153
|
+
return this.request(endpoint, { method: "GET", params });
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* POST request with JSON body
|
|
157
|
+
*/
|
|
158
|
+
async post(endpoint, data) {
|
|
159
|
+
return this.request(endpoint, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
body: data ? JSON.stringify(data) : void 0
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* PATCH request with JSON body
|
|
166
|
+
*/
|
|
167
|
+
async patch(endpoint, data) {
|
|
168
|
+
return this.request(endpoint, {
|
|
169
|
+
method: "PATCH",
|
|
170
|
+
body: data ? JSON.stringify(data) : void 0
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* DELETE request
|
|
175
|
+
* Returns void for delete operations (most common case)
|
|
176
|
+
*/
|
|
177
|
+
async delete(endpoint) {
|
|
178
|
+
return this.request(endpoint, { method: "DELETE" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const apiClient = new ApiClient();
|
|
182
|
+
import { ApiError as ApiError2 } from './client.types.js';
|
|
183
|
+
export {
|
|
184
|
+
ApiClient,
|
|
185
|
+
ApiError2 as ApiError,
|
|
186
|
+
apiClient,
|
|
187
|
+
getApiUrl
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["/**\n * API Client for NextSpark Backend\n *\n * Configurable HTTP client with authentication and team context.\n * API_URL is resolved from (in order):\n * 1. app.config.ts extra.apiUrl\n * 2. EXPO_PUBLIC_API_URL environment variable\n * 3. Auto-detect from Expo dev server\n * 4. Fallback to localhost:5173\n */\n\nimport Constants from 'expo-constants'\nimport * as Storage from '../lib/storage'\nimport { ApiError, type RequestConfig } from './client.types'\nimport type { User } from './core/types'\n\n/**\n * Resolve API URL from configuration\n *\n * Priority order:\n * 1. app.config.ts > extra > apiUrl (explicit configuration)\n * 2. EXPO_PUBLIC_API_URL environment variable\n * 3. Auto-detect from Expo dev server hostUri (development)\n * 4. Fallback to http://localhost:5173\n *\n * @returns The resolved API URL\n * @example\n * ```ts\n * // In app.config.ts:\n * export default {\n * extra: {\n * apiUrl: 'https://api.myapp.com'\n * }\n * }\n * ```\n */\nexport function getApiUrl(): string {\n // 1. From Expo config (app.config.ts > extra > apiUrl)\n const configUrl = Constants.expoConfig?.extra?.apiUrl\n if (configUrl) return configUrl\n\n // 2. From environment variable (EXPO_PUBLIC_API_URL)\n const envUrl = process.env.EXPO_PUBLIC_API_URL\n if (envUrl) return envUrl\n\n // 3. Auto-detect from Expo dev server (development mode)\n if (Constants.expoConfig?.hostUri) {\n const host = Constants.expoConfig.hostUri.split(':')[0]\n return `http://${host}:5173`\n }\n\n // 4. Fallback for local development\n return 'http://localhost:5173'\n}\n\nconst API_URL = getApiUrl()\n\n// Storage keys (namespaced to avoid conflicts)\n// SecureStore only allows alphanumeric, \".\", \"-\", and \"_\"\nconst TOKEN_KEY = 'nextspark.auth.token'\nconst TEAM_ID_KEY = 'nextspark.auth.teamId'\nconst USER_KEY = 'nextspark.auth.user'\n\nexport class ApiClient {\n private token: string | null = null\n private teamId: string | null = null\n private storedUser: User | null = null\n\n /**\n * Initialize client by loading stored credentials\n */\n async init(): Promise<void> {\n this.token = await Storage.getItemAsync(TOKEN_KEY)\n this.teamId = await Storage.getItemAsync(TEAM_ID_KEY)\n const userJson = await Storage.getItemAsync(USER_KEY)\n if (userJson) {\n try {\n this.storedUser = JSON.parse(userJson)\n } catch {\n this.storedUser = null\n }\n }\n }\n\n // ==========================================\n // Token & Team Management\n // ==========================================\n\n /**\n * Get stored token\n */\n getToken(): string | null {\n return this.token\n }\n\n /**\n * Set authentication token\n */\n async setToken(token: string): Promise<void> {\n this.token = token\n await Storage.setItemAsync(TOKEN_KEY, token)\n }\n\n /**\n * Get stored team ID\n */\n getTeamId(): string | null {\n return this.teamId\n }\n\n /**\n * Set team ID\n */\n async setTeamId(teamId: string): Promise<void> {\n this.teamId = teamId\n await Storage.setItemAsync(TEAM_ID_KEY, teamId)\n }\n\n /**\n * Get stored user info\n */\n getStoredUser(): User | null {\n return this.storedUser\n }\n\n /**\n * Set user info\n */\n async setUser(user: User): Promise<void> {\n this.storedUser = user\n await Storage.setItemAsync(USER_KEY, JSON.stringify(user))\n }\n\n /**\n * Clear authentication\n */\n async clearAuth(): Promise<void> {\n this.token = null\n this.teamId = null\n this.storedUser = null\n await Storage.deleteItemAsync(TOKEN_KEY)\n await Storage.deleteItemAsync(TEAM_ID_KEY)\n await Storage.deleteItemAsync(USER_KEY)\n }\n\n // ==========================================\n // HTTP Methods\n // ==========================================\n\n /**\n * Build URL with query parameters\n */\n private buildUrl(endpoint: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = `${API_URL}${endpoint}`\n if (!params) return url\n\n const searchParams = new URLSearchParams()\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.set(key, String(value))\n }\n })\n\n const queryString = searchParams.toString()\n return queryString ? `${url}?${queryString}` : url\n }\n\n /**\n * Make authenticated request\n * Uses credentials: 'include' to support cookie-based auth alongside Bearer token\n */\n async request<T>(endpoint: string, options: RequestConfig = {}): Promise<T> {\n const { params, ...fetchOptions } = options\n const url = this.buildUrl(endpoint, params)\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n ...fetchOptions.headers,\n }\n\n // Add Bearer token if available (Better Auth mobile flow)\n if (this.token) {\n ;(headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`\n }\n\n // Add team context header\n if (this.teamId) {\n ;(headers as Record<string, string>)['x-team-id'] = this.teamId\n }\n\n const response = await fetch(url, {\n ...fetchOptions,\n headers,\n credentials: 'include', // Support cookie-based sessions\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n throw new ApiError(\n errorData.message || `Request failed with status ${response.status}`,\n response.status,\n errorData\n )\n }\n\n // Handle 204 No Content\n if (response.status === 204) {\n return {} as T\n }\n\n // Defensive JSON parsing - handle malformed responses\n return response.json().catch(() => ({} as T))\n }\n\n /**\n * GET request with optional query parameters\n */\n async get<T>(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {\n return this.request<T>(endpoint, { method: 'GET', params })\n }\n\n /**\n * POST request with JSON body\n */\n async post<T>(endpoint: string, data?: unknown): Promise<T> {\n return this.request<T>(endpoint, {\n method: 'POST',\n body: data ? JSON.stringify(data) : undefined,\n })\n }\n\n /**\n * PATCH request with JSON body\n */\n async patch<T>(endpoint: string, data?: unknown): Promise<T> {\n return this.request<T>(endpoint, {\n method: 'PATCH',\n body: data ? JSON.stringify(data) : undefined,\n })\n }\n\n /**\n * DELETE request\n * Returns void for delete operations (most common case)\n */\n async delete<T = void>(endpoint: string): Promise<T> {\n return this.request<T>(endpoint, { method: 'DELETE' })\n }\n}\n\n// Export singleton instance\nexport const apiClient = new ApiClient()\n\n// Re-export ApiError for convenience\nexport { ApiError } from './client.types'\n"],"mappings":"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","names":["ApiError"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client Types
|
|
3
|
+
*
|
|
4
|
+
* Base types for API responses and error handling.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Paginated API response
|
|
8
|
+
*/
|
|
9
|
+
interface PaginatedResponse<T> {
|
|
10
|
+
data: T[];
|
|
11
|
+
meta: {
|
|
12
|
+
total: number;
|
|
13
|
+
page: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
totalPages: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Single item API response
|
|
20
|
+
*/
|
|
21
|
+
interface SingleResponse<T> {
|
|
22
|
+
data: T;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Request configuration extending fetch options
|
|
26
|
+
*/
|
|
27
|
+
interface RequestConfig extends RequestInit {
|
|
28
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Custom API Error class
|
|
32
|
+
*/
|
|
33
|
+
declare class ApiError extends Error {
|
|
34
|
+
status: number;
|
|
35
|
+
data: unknown;
|
|
36
|
+
constructor(message: string, status: number, data?: unknown);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { ApiError, type PaginatedResponse, type RequestConfig, type SingleResponse };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/api/client.types.ts"],"sourcesContent":["/**\n * API Client Types\n *\n * Base types for API responses and error handling.\n */\n\n/**\n * Paginated API response\n */\nexport interface PaginatedResponse<T> {\n data: T[]\n meta: {\n total: number\n page: number\n limit: number\n totalPages: number\n }\n}\n\n/**\n * Single item API response\n */\nexport interface SingleResponse<T> {\n data: T\n}\n\n/**\n * Request configuration extending fetch options\n */\nexport interface RequestConfig extends RequestInit {\n params?: Record<string, string | number | boolean | undefined>\n}\n\n/**\n * Custom API Error class\n */\nexport class ApiError extends Error {\n status: number\n data: unknown\n\n constructor(message: string, status: number, data?: unknown) {\n super(message)\n this.name = 'ApiError'\n this.status = status\n this.data = data\n }\n}\n"],"mappings":"AAoCO,MAAM,iBAAiB,MAAM;AAAA,EAIlC,YAAY,SAAiB,QAAgB,MAAgB;AAC3D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;","names":[]}
|