@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.
Files changed (123) hide show
  1. package/README.md +339 -0
  2. package/dist/api/client.d.ts +102 -0
  3. package/dist/api/client.js +189 -0
  4. package/dist/api/client.js.map +1 -0
  5. package/dist/api/client.types.d.ts +39 -0
  6. package/dist/api/client.types.js +12 -0
  7. package/dist/api/client.types.js.map +1 -0
  8. package/dist/api/core/auth.d.ts +26 -0
  9. package/dist/api/core/auth.js +52 -0
  10. package/dist/api/core/auth.js.map +1 -0
  11. package/dist/api/core/index.d.ts +4 -0
  12. package/dist/api/core/index.js +5 -0
  13. package/dist/api/core/index.js.map +1 -0
  14. package/dist/api/core/teams.d.ts +20 -0
  15. package/dist/api/core/teams.js +19 -0
  16. package/dist/api/core/teams.js.map +1 -0
  17. package/dist/api/core/types.d.ts +58 -0
  18. package/dist/api/core/types.js +1 -0
  19. package/dist/api/core/types.js.map +1 -0
  20. package/dist/api/core/users.d.ts +43 -0
  21. package/dist/api/core/users.js +41 -0
  22. package/dist/api/core/users.js.map +1 -0
  23. package/dist/api/entities/factory.d.ts +43 -0
  24. package/dist/api/entities/factory.js +31 -0
  25. package/dist/api/entities/factory.js.map +1 -0
  26. package/dist/api/entities/index.d.ts +3 -0
  27. package/dist/api/entities/index.js +3 -0
  28. package/dist/api/entities/index.js.map +1 -0
  29. package/dist/api/entities/types.d.ts +32 -0
  30. package/dist/api/entities/types.js +1 -0
  31. package/dist/api/entities/types.js.map +1 -0
  32. package/dist/api/index.d.ts +7 -0
  33. package/dist/api/index.js +15 -0
  34. package/dist/api/index.js.map +1 -0
  35. package/dist/hooks/index.d.ts +4 -0
  36. package/dist/hooks/index.js +5 -0
  37. package/dist/hooks/index.js.map +1 -0
  38. package/dist/index.d.ts +14 -0
  39. package/dist/index.js +28 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/lib/alert.d.ts +34 -0
  42. package/dist/lib/alert.js +73 -0
  43. package/dist/lib/alert.js.map +1 -0
  44. package/dist/lib/index.d.ts +2 -0
  45. package/dist/lib/index.js +10 -0
  46. package/dist/lib/index.js.map +1 -0
  47. package/dist/lib/storage.d.ts +1 -0
  48. package/dist/lib/storage.js +29 -0
  49. package/dist/lib/storage.js.map +1 -0
  50. package/dist/providers/AuthProvider.d.ts +21 -0
  51. package/dist/providers/AuthProvider.js +113 -0
  52. package/dist/providers/AuthProvider.js.map +1 -0
  53. package/dist/providers/QueryProvider.d.ts +11 -0
  54. package/dist/providers/QueryProvider.js +23 -0
  55. package/dist/providers/QueryProvider.js.map +1 -0
  56. package/dist/providers/index.d.ts +6 -0
  57. package/dist/providers/index.js +9 -0
  58. package/dist/providers/index.js.map +1 -0
  59. package/dist/storage-BaRppHUz.d.ts +22 -0
  60. package/package.json +99 -0
  61. package/templates/app/(app)/_layout.tsx +216 -0
  62. package/templates/app/(app)/customer/[id].tsx +68 -0
  63. package/templates/app/(app)/customer/create.tsx +24 -0
  64. package/templates/app/(app)/customers.tsx +164 -0
  65. package/templates/app/(app)/index.tsx +310 -0
  66. package/templates/app/(app)/notifications.tsx +242 -0
  67. package/templates/app/(app)/profile.tsx +254 -0
  68. package/templates/app/(app)/settings.tsx +241 -0
  69. package/templates/app/(app)/task/[id].tsx +70 -0
  70. package/templates/app/(app)/task/create.tsx +24 -0
  71. package/templates/app/(app)/tasks.tsx +164 -0
  72. package/templates/app/_layout.tsx +54 -0
  73. package/templates/app/index.tsx +35 -0
  74. package/templates/app/login.tsx +179 -0
  75. package/templates/app.config.ts +39 -0
  76. package/templates/babel.config.js +9 -0
  77. package/templates/eas.json +18 -0
  78. package/templates/jest.config.js +12 -0
  79. package/templates/metro.config.js +23 -0
  80. package/templates/package.json.template +52 -0
  81. package/templates/src/components/entities/customers/CustomerCard.tsx +59 -0
  82. package/templates/src/components/entities/customers/CustomerForm.tsx +194 -0
  83. package/templates/src/components/entities/customers/index.ts +6 -0
  84. package/templates/src/components/entities/index.ts +9 -0
  85. package/templates/src/components/entities/tasks/TaskCard.tsx +89 -0
  86. package/templates/src/components/entities/tasks/TaskForm.tsx +231 -0
  87. package/templates/src/components/entities/tasks/index.ts +6 -0
  88. package/templates/src/components/features/index.ts +6 -0
  89. package/templates/src/components/navigation/BottomTabBar.tsx +80 -0
  90. package/templates/src/components/navigation/CreateSheet.tsx +108 -0
  91. package/templates/src/components/navigation/MoreSheet.tsx +403 -0
  92. package/templates/src/components/navigation/TopBar.tsx +74 -0
  93. package/templates/src/components/navigation/index.ts +8 -0
  94. package/templates/src/components/ui/index.ts +89 -0
  95. package/templates/src/components/ui/text.tsx +64 -0
  96. package/templates/src/config/api.config.ts +26 -0
  97. package/templates/src/config/app.config.ts +15 -0
  98. package/templates/src/config/hooks.ts +58 -0
  99. package/templates/src/config/permissions.config.ts +119 -0
  100. package/templates/src/constants/colors.ts +55 -0
  101. package/templates/src/data/notifications.mock.json +100 -0
  102. package/templates/src/entities/customers/api.ts +10 -0
  103. package/templates/src/entities/customers/constants.internal.ts +6 -0
  104. package/templates/src/entities/customers/constants.ts +14 -0
  105. package/templates/src/entities/customers/index.ts +9 -0
  106. package/templates/src/entities/customers/mutations.ts +58 -0
  107. package/templates/src/entities/customers/queries.ts +40 -0
  108. package/templates/src/entities/customers/types.ts +43 -0
  109. package/templates/src/entities/index.ts +8 -0
  110. package/templates/src/entities/tasks/api.ts +10 -0
  111. package/templates/src/entities/tasks/constants.internal.ts +6 -0
  112. package/templates/src/entities/tasks/constants.ts +39 -0
  113. package/templates/src/entities/tasks/index.ts +9 -0
  114. package/templates/src/entities/tasks/mutations.ts +108 -0
  115. package/templates/src/entities/tasks/queries.ts +42 -0
  116. package/templates/src/entities/tasks/types.ts +52 -0
  117. package/templates/src/hooks/useCustomers.ts +17 -0
  118. package/templates/src/hooks/useTasks.ts +18 -0
  119. package/templates/src/lib/utils.ts +10 -0
  120. package/templates/src/styles/globals.css +103 -0
  121. package/templates/src/types/index.ts +45 -0
  122. package/templates/tailwind.config.js +108 -0
  123. 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,12 @@
1
+ class ApiError extends Error {
2
+ constructor(message, status, data) {
3
+ super(message);
4
+ this.name = "ApiError";
5
+ this.status = status;
6
+ this.data = data;
7
+ }
8
+ }
9
+ export {
10
+ ApiError
11
+ };
12
+ //# sourceMappingURL=client.types.js.map
@@ -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":[]}