@nextsparkjs/mobile 0.1.0-beta.147 → 0.1.0-beta.148

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/mobile",
3
- "version": "0.1.0-beta.147",
3
+ "version": "0.1.0-beta.148",
4
4
  "description": "Mobile app infrastructure for NextSpark - API client, providers, and utilities for Expo apps",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -1,216 +0,0 @@
1
- /**
2
- * App Layout - NextSpark Mobile Style with Bottom Tab Navigation
3
- */
4
-
5
- import { useEffect, useState, useMemo } from 'react'
6
- import { View, StyleSheet } from 'react-native'
7
- import { Alert, useAuth } from '@nextsparkjs/mobile'
8
- import { Stack, router } from 'expo-router'
9
- import { useQueryClient } from '@tanstack/react-query'
10
- import {
11
- TopBar,
12
- BottomTabBar,
13
- MoreSheet,
14
- CreateSheet,
15
- type TabKey,
16
- } from '@/src/components/navigation'
17
- import { Colors } from '@/src/constants/colors'
18
- import notificationsData from '@/src/data/notifications.mock.json'
19
-
20
- export default function AppLayout() {
21
- const { isAuthenticated, isLoading, logout } = useAuth()
22
- const queryClient = useQueryClient()
23
- const [activeTab, setActiveTab] = useState<TabKey>('home')
24
- const [moreSheetVisible, setMoreSheetVisible] = useState(false)
25
- const [createSheetVisible, setCreateSheetVisible] = useState(false)
26
-
27
- // Calculate unread notifications count from mock data
28
- const unreadNotificationCount = useMemo(
29
- () => notificationsData.notifications.filter((n) => !n.read).length,
30
- []
31
- )
32
-
33
- useEffect(() => {
34
- if (!isLoading && !isAuthenticated) {
35
- router.replace('/login')
36
- }
37
- }, [isAuthenticated, isLoading])
38
-
39
- if (isLoading || !isAuthenticated) {
40
- return null
41
- }
42
-
43
- const handleTabPress = (tab: TabKey) => {
44
- if (tab === 'more') {
45
- setMoreSheetVisible(true)
46
- return
47
- }
48
-
49
- if (tab === 'create') {
50
- setCreateSheetVisible(true)
51
- return
52
- }
53
-
54
- setActiveTab(tab)
55
-
56
- // Navigate based on tab
57
- switch (tab) {
58
- case 'home':
59
- router.replace('/(app)' as const)
60
- break
61
- case 'tasks':
62
- router.replace('/(app)/tasks')
63
- break
64
- case 'customers':
65
- router.replace('/(app)/customers')
66
- break
67
- }
68
- }
69
-
70
- const handleMoreNavigate = (screen: string) => {
71
- switch (screen) {
72
- case 'profile':
73
- router.push('/(app)/profile')
74
- break
75
- case 'settings':
76
- router.push('/(app)/settings')
77
- break
78
- case 'billing':
79
- // TODO: Add billing screen
80
- break
81
- case 'api-keys':
82
- // TODO: Add API keys screen
83
- break
84
- }
85
- }
86
-
87
- const handleCreateEntity = (entity: string) => {
88
- switch (entity) {
89
- case 'customer':
90
- router.push('/(app)/customer/create')
91
- break
92
- case 'task':
93
- router.push('/(app)/task/create')
94
- break
95
- }
96
- }
97
-
98
- const handleLogout = async () => {
99
- setMoreSheetVisible(false)
100
- const confirmed = await Alert.confirmDestructive(
101
- 'Cerrar Sesión',
102
- '¿Estás seguro que deseas salir?',
103
- 'Salir'
104
- )
105
-
106
- if (confirmed) {
107
- await logout()
108
- router.replace('/login')
109
- }
110
- }
111
-
112
- const handleTeamChange = () => {
113
- // Invalidate all queries to refresh data for the new team
114
- queryClient.invalidateQueries()
115
- // Navigate to home to show refreshed data
116
- setActiveTab('home')
117
- router.replace('/(app)' as const)
118
- }
119
-
120
- return (
121
- <View style={styles.container}>
122
- {/* Top Bar */}
123
- <TopBar notificationCount={unreadNotificationCount} />
124
-
125
- {/* Screen Content */}
126
- <View style={styles.content}>
127
- <Stack
128
- screenOptions={{
129
- headerShown: false,
130
- contentStyle: { backgroundColor: Colors.backgroundSecondary },
131
- }}
132
- >
133
- <Stack.Screen name="index" />
134
- <Stack.Screen name="tasks" />
135
- <Stack.Screen name="customers" />
136
- <Stack.Screen name="settings" />
137
- <Stack.Screen name="profile" />
138
- <Stack.Screen
139
- name="notifications"
140
- options={{
141
- headerShown: false,
142
- presentation: 'modal',
143
- }}
144
- />
145
- <Stack.Screen
146
- name="task/create"
147
- options={{
148
- presentation: 'modal',
149
- headerShown: true,
150
- headerTitle: 'Nueva Tarea',
151
- headerStyle: { backgroundColor: Colors.background },
152
- headerTintColor: Colors.foreground,
153
- }}
154
- />
155
- <Stack.Screen
156
- name="task/[id]"
157
- options={{
158
- headerShown: true,
159
- headerTitle: 'Editar Tarea',
160
- headerStyle: { backgroundColor: Colors.background },
161
- headerTintColor: Colors.foreground,
162
- }}
163
- />
164
- <Stack.Screen
165
- name="customer/create"
166
- options={{
167
- presentation: 'modal',
168
- headerShown: true,
169
- headerTitle: 'Nuevo Cliente',
170
- headerStyle: { backgroundColor: Colors.background },
171
- headerTintColor: Colors.foreground,
172
- }}
173
- />
174
- <Stack.Screen
175
- name="customer/[id]"
176
- options={{
177
- headerShown: true,
178
- headerTitle: 'Editar Cliente',
179
- headerStyle: { backgroundColor: Colors.background },
180
- headerTintColor: Colors.foreground,
181
- }}
182
- />
183
- </Stack>
184
- </View>
185
-
186
- {/* Bottom Tab Bar */}
187
- <BottomTabBar activeTab={activeTab} onTabPress={handleTabPress} />
188
-
189
- {/* More Options Sheet */}
190
- <MoreSheet
191
- visible={moreSheetVisible}
192
- onClose={() => setMoreSheetVisible(false)}
193
- onNavigate={handleMoreNavigate}
194
- onLogout={handleLogout}
195
- onTeamChange={handleTeamChange}
196
- />
197
-
198
- {/* Create Sheet */}
199
- <CreateSheet
200
- visible={createSheetVisible}
201
- onClose={() => setCreateSheetVisible(false)}
202
- onCreateEntity={handleCreateEntity}
203
- />
204
- </View>
205
- )
206
- }
207
-
208
- const styles = StyleSheet.create({
209
- container: {
210
- flex: 1,
211
- backgroundColor: Colors.background,
212
- },
213
- content: {
214
- flex: 1,
215
- },
216
- })
@@ -1,68 +0,0 @@
1
- /**
2
- * Edit Customer Screen
3
- */
4
-
5
- import { useLocalSearchParams, router } from 'expo-router'
6
- import { View, Text, ActivityIndicator, StyleSheet } from 'react-native'
7
- import { CustomerForm } from '@/src/components/entities/customers'
8
- import { useCustomer, useUpdateCustomer, useDeleteCustomer, type UpdateCustomerInput } from '@/src/entities/customers'
9
-
10
- export default function EditCustomerScreen() {
11
- const { id } = useLocalSearchParams<{ id: string }>()
12
- const { data, isLoading, error } = useCustomer(id)
13
- const updateCustomer = useUpdateCustomer()
14
- const deleteCustomer = useDeleteCustomer()
15
-
16
- const handleSubmit = async (formData: UpdateCustomerInput) => {
17
- if (!id) return
18
- await updateCustomer.mutateAsync({ id, data: formData })
19
- router.back()
20
- }
21
-
22
- const handleDelete = async () => {
23
- if (!id) return
24
- await deleteCustomer.mutateAsync(id)
25
- router.replace('/(app)/customers')
26
- }
27
-
28
- if (isLoading) {
29
- return (
30
- <View style={styles.centered}>
31
- <ActivityIndicator size="large" color="#10B981" />
32
- </View>
33
- )
34
- }
35
-
36
- if (error || !data?.data) {
37
- return (
38
- <View style={styles.centered}>
39
- <Text style={styles.errorText}>
40
- {error instanceof Error ? error.message : 'Customer not found'}
41
- </Text>
42
- </View>
43
- )
44
- }
45
-
46
- return (
47
- <CustomerForm
48
- mode="edit"
49
- initialData={data.data}
50
- onSubmit={handleSubmit}
51
- onDelete={handleDelete}
52
- isLoading={updateCustomer.isPending || deleteCustomer.isPending}
53
- />
54
- )
55
- }
56
-
57
- const styles = StyleSheet.create({
58
- centered: {
59
- flex: 1,
60
- justifyContent: 'center',
61
- alignItems: 'center',
62
- backgroundColor: '#F9FAFB',
63
- },
64
- errorText: {
65
- color: '#DC2626',
66
- fontSize: 16,
67
- },
68
- })
@@ -1,24 +0,0 @@
1
- /**
2
- * Create Customer Screen
3
- */
4
-
5
- import { router } from 'expo-router'
6
- import { CustomerForm } from '@/src/components/entities/customers'
7
- import { useCreateCustomer, type CreateCustomerInput } from '@/src/entities/customers'
8
-
9
- export default function CreateCustomerScreen() {
10
- const createCustomer = useCreateCustomer()
11
-
12
- const handleSubmit = async (data: CreateCustomerInput) => {
13
- await createCustomer.mutateAsync(data)
14
- router.back()
15
- }
16
-
17
- return (
18
- <CustomerForm
19
- mode="create"
20
- onSubmit={handleSubmit}
21
- isLoading={createCustomer.isPending}
22
- />
23
- )
24
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * Customers List Screen
3
- */
4
-
5
- import { useCallback, useState } from 'react'
6
- import {
7
- View,
8
- Text,
9
- FlatList,
10
- RefreshControl,
11
- StyleSheet,
12
- } from 'react-native'
13
- import { router } from 'expo-router'
14
- import { useCustomers, type Customer } from '@/src/entities/customers'
15
- import { CustomerCard } from '@/src/components/entities/customers'
16
- import { Button } from '@/src/components/ui'
17
- import { Colors } from '@/src/constants/colors'
18
-
19
- export default function CustomersListScreen() {
20
- const { data, isLoading, error, refetch } = useCustomers()
21
- const [refreshing, setRefreshing] = useState(false)
22
-
23
- const onRefresh = useCallback(async () => {
24
- setRefreshing(true)
25
- try {
26
- await refetch()
27
- } finally {
28
- setRefreshing(false)
29
- }
30
- }, [refetch])
31
-
32
- const handleCustomerPress = (customer: Customer) => {
33
- router.push(`/(app)/customer/${customer.id}`)
34
- }
35
-
36
- const renderCustomer = ({ item }: { item: Customer }) => (
37
- <CustomerCard customer={item} onPress={() => handleCustomerPress(item)} />
38
- )
39
-
40
- const renderEmpty = () => {
41
- if (isLoading) return null
42
-
43
- return (
44
- <View style={styles.emptyContainer}>
45
- <Text style={styles.emptyIcon}>👥</Text>
46
- <Text style={styles.emptyTitle}>Sin clientes</Text>
47
- <Text style={styles.emptyText}>
48
- Toca el botón Crear para agregar tu primer cliente
49
- </Text>
50
- </View>
51
- )
52
- }
53
-
54
- const renderError = () => (
55
- <View style={styles.errorContainer}>
56
- <Text style={styles.errorTitle}>Algo salió mal</Text>
57
- <Text style={styles.errorText}>
58
- {error instanceof Error ? error.message : 'Error al cargar clientes'}
59
- </Text>
60
- <Button onPress={() => refetch()}>
61
- Reintentar
62
- </Button>
63
- </View>
64
- )
65
-
66
- return (
67
- <View style={styles.container}>
68
- {/* Page Header */}
69
- <View style={styles.header}>
70
- <Text style={styles.pageTitle}>Clientes</Text>
71
- <Text style={styles.pageSubtitle}>
72
- Gestiona tu cartera de clientes
73
- </Text>
74
- </View>
75
-
76
- {/* Error State */}
77
- {error && renderError()}
78
-
79
- {/* Customers List */}
80
- {!error && (
81
- <FlatList
82
- data={data?.data || []}
83
- renderItem={renderCustomer}
84
- keyExtractor={(item) => item.id}
85
- contentContainerStyle={styles.listContent}
86
- refreshControl={
87
- <RefreshControl
88
- refreshing={refreshing}
89
- onRefresh={onRefresh}
90
- tintColor={Colors.primary}
91
- />
92
- }
93
- ListEmptyComponent={renderEmpty}
94
- showsVerticalScrollIndicator={false}
95
- />
96
- )}
97
- </View>
98
- )
99
- }
100
-
101
- const styles = StyleSheet.create({
102
- container: {
103
- flex: 1,
104
- backgroundColor: Colors.backgroundSecondary,
105
- },
106
- header: {
107
- padding: 20,
108
- paddingBottom: 12,
109
- },
110
- pageTitle: {
111
- fontSize: 28,
112
- fontWeight: '700',
113
- color: Colors.foreground,
114
- marginBottom: 4,
115
- },
116
- pageSubtitle: {
117
- fontSize: 15,
118
- color: Colors.foregroundSecondary,
119
- },
120
- listContent: {
121
- paddingBottom: 12,
122
- flexGrow: 1,
123
- },
124
- emptyContainer: {
125
- flex: 1,
126
- justifyContent: 'center',
127
- alignItems: 'center',
128
- paddingHorizontal: 32,
129
- },
130
- emptyIcon: {
131
- fontSize: 48,
132
- marginBottom: 16,
133
- opacity: 0.5,
134
- },
135
- emptyTitle: {
136
- fontSize: 18,
137
- fontWeight: '600',
138
- color: Colors.foreground,
139
- marginBottom: 8,
140
- },
141
- emptyText: {
142
- fontSize: 14,
143
- color: Colors.foregroundSecondary,
144
- textAlign: 'center',
145
- },
146
- errorContainer: {
147
- flex: 1,
148
- justifyContent: 'center',
149
- alignItems: 'center',
150
- paddingHorizontal: 32,
151
- },
152
- errorTitle: {
153
- fontSize: 18,
154
- fontWeight: '600',
155
- color: Colors.destructive,
156
- marginBottom: 8,
157
- },
158
- errorText: {
159
- fontSize: 14,
160
- color: Colors.foregroundSecondary,
161
- textAlign: 'center',
162
- marginBottom: 16,
163
- },
164
- })