@nextsparkjs/mobile 0.1.0-beta.146 → 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 +1 -1
- package/templates/app/(app)/_layout.tsx +0 -216
- package/templates/app/(app)/customer/[id].tsx +0 -68
- package/templates/app/(app)/customer/create.tsx +0 -24
- package/templates/app/(app)/customers.tsx +0 -164
- package/templates/app/(app)/index.tsx +0 -310
- package/templates/app/(app)/notifications.tsx +0 -242
- package/templates/app/(app)/profile.tsx +0 -254
- package/templates/app/(app)/settings.tsx +0 -241
- package/templates/app/(app)/task/[id].tsx +0 -70
- package/templates/app/(app)/task/create.tsx +0 -24
- package/templates/app/(app)/tasks.tsx +0 -164
- package/templates/app/_layout.tsx +0 -54
- package/templates/app/index.tsx +0 -35
- package/templates/app/login.tsx +0 -179
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/mobile",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
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
|
-
})
|