From baed6dc8d51584d0fb247ef84188145763a3e3e6 Mon Sep 17 00:00:00 2001 From: Nemo Date: Mon, 23 Mar 2026 21:22:57 +0800 Subject: [PATCH] feat: complete all screens - client tabs, payments list, remittance detail, new ticket, installations tab - Client detail: Subscription/Invoices/Payments tabs now fully functional - Payments: proper list with today's total + live search prefill from client - Record payment: debounced live search, reference required for non-cash - Remittances: detail screen with included payments breakdown - Tickets: status filter chips + create button, new ticket with categories - Installations: tab now visible with list + confirm flow - Fix: remove duplicate @react-navigation/elements causing Metro asset error - Fix: metro.config.js asset resolution from node_modules --- app.json | 11 + app/(app)/_layout.tsx | 5 +- app/(app)/clients/[id].tsx | 202 +++++- app/(app)/dashboard.tsx | 114 +++- app/(app)/installations/index.tsx | 110 ++++ app/(app)/payments/index.tsx | 84 ++- app/(app)/payments/record.tsx | 176 ++++-- app/(app)/remittances/[id].tsx | 84 ++- app/(app)/tickets/[id].tsx | 212 ++++++- app/(app)/tickets/index.tsx | 75 ++- app/(app)/tickets/new.tsx | 182 ++++++ babel.config.js | 1 + metro.config.js | 4 + nativewind-env.d.ts | 45 +- package-lock.json | 990 +++++++++++++++--------------- package.json | 6 +- tsconfig.json | 10 +- types/expo-modules.d.ts | 39 ++ 18 files changed, 1685 insertions(+), 665 deletions(-) create mode 100644 app/(app)/installations/index.tsx create mode 100644 app/(app)/tickets/new.tsx create mode 100644 types/expo-modules.d.ts diff --git a/app.json b/app.json index 55d7762..6a6b98a 100644 --- a/app.json +++ b/app.json @@ -23,12 +23,17 @@ }, "package": "com.fiberops.mobile", "permissions": [ + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO", + "android.permission.ACCESS_COARSE_LOCATION", + "android.permission.ACCESS_FINE_LOCATION", "android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION" ] }, + "platforms": ["android", "ios"], "web": { "favicon": "./assets/favicon.png" }, @@ -61,6 +66,12 @@ "eas": { "projectId": "15d21320-57f5-4fb6-a116-82d222d914e2" } + }, + "runtimeVersion": { + "policy": "appVersion" + }, + "updates": { + "url": "https://u.expo.dev/15d21320-57f5-4fb6-a116-82d222d914e2" } } } diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index 85b723f..5889056 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -31,11 +31,14 @@ export default function AppLayout() { name="tickets" options={{ title: 'Tickets', tabBarIcon: ({ color }) => ๐ŸŽซ }} /> + ๐Ÿ”Œ }} + /> ๐Ÿ‘ค }} /> - ); } diff --git a/app/(app)/clients/[id].tsx b/app/(app)/clients/[id].tsx index 1ec6c2a..ed1ec4b 100644 --- a/app/(app)/clients/[id].tsx +++ b/app/(app)/clients/[id].tsx @@ -6,6 +6,35 @@ import { api } from '../../../services/api'; const TABS = ['Profile', 'Subscription', 'Invoices', 'Payments']; +const STATUS_COLORS: Record = { + ACTIVE: '#16A34A', SUSPENDED: '#D97706', CANCELLED: '#DC2626', PENDING: '#6B7280', +}; + +const INV_STATUS: Record = { + PAID: { label: 'Paid', color: '#16A34A' }, + UNPAID: { label: 'Unpaid', color: '#D97706' }, + OVERDUE: { label: 'Overdue', color: '#DC2626' }, + PARTIAL: { label: 'Partial', color: '#2563EB' }, + VOID: { label: 'Void', color: '#6B7280' }, +}; + +const PAYMENT_METHODS: Record = { + CASH: '๐Ÿ’ต', GCASH: '๐Ÿ“ฑ', MAYA: '๐Ÿ’™', BANK: '๐Ÿฆ', +}; + +function InfoRow({ label, value, onPress, isLast }: { label: string; value?: string | null; onPress?: () => void; isLast?: boolean }) { + return ( + + {label} + {value ?? 'โ€”'} + + ); +} + export default function ClientDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const [tab, setTab] = useState('Profile'); @@ -15,54 +44,175 @@ export default function ClientDetailScreen() { queryFn: () => api.get(`/api/v1/clients/${id}`).then(r => r.data), }); - if (isLoading) return ; + const { data: subData, isLoading: subLoading } = useQuery({ + queryKey: ['client-subscription', id], + queryFn: () => api.get(`/api/v1/subscriptions?clientId=${id}&limit=1`).then(r => r.data?.data?.[0] ?? r.data?.[0] ?? null), + enabled: tab === 'Subscription', + }); + + const { data: invoices, isLoading: invLoading } = useQuery({ + queryKey: ['client-invoices', id], + queryFn: () => api.get(`/api/v1/invoices?clientId=${id}&limit=20`).then(r => r.data?.data ?? r.data ?? []), + enabled: tab === 'Invoices', + }); + + const { data: payments, isLoading: payLoading } = useQuery({ + queryKey: ['client-payments', id], + queryFn: () => api.get(`/api/v1/payments?clientId=${id}&limit=20`).then(r => r.data?.data ?? r.data ?? []), + enabled: tab === 'Payments', + }); + + if (isLoading) return ( + + + + ); return ( + {/* Header */} - router.back()} className="mr-3"> + router.back()} className="mr-3 p-1"> โ† - + {client?.firstName} {client?.lastName} {client?.accountNumber} + + + {client?.status} + + + {/* Tabs */} {TABS.map(t => ( - setTab(t)} className={`flex-1 py-3 items-center border-b-2 ${tab === t ? 'border-primary' : 'border-transparent'}`}> - {t} + setTab(t)} + className={`flex-1 py-3 items-center border-b-2 ${tab === t ? 'border-primary' : 'border-transparent'}`} + > + {t} ))} + {/* PROFILE TAB */} {tab === 'Profile' && ( - {[ - { label: 'Account #', value: client?.accountNumber }, - { label: 'Status', value: client?.status }, - { label: 'Email', value: client?.email }, - { label: 'Phone', value: client?.phone, onPress: () => client?.phone && Linking.openURL(`tel:${client.phone}`) }, - { label: 'Address', value: client?.address }, - { label: 'Area', value: client?.area?.name }, - ].map((item, i) => ( - 0 ? 'border-t border-gray-100' : ''}`} - > - {item.label} - {item.value ?? 'โ€”'} - - ))} + + + client?.phone && Linking.openURL(`tel:${client.phone}`)} /> + + + )} - {tab === 'Subscription' && Subscription details coming soon} - {tab === 'Invoices' && Invoices coming soon} - {tab === 'Payments' && Payments coming soon} + + {/* SUBSCRIPTION TAB */} + {tab === 'Subscription' && ( + subLoading ? ( + + ) : !subData ? ( + + ๐Ÿ“ก + No active subscription + + ) : ( + + + + + + + + + + {subData.nextBillingDate && ( + + + ๐Ÿ“… Next billing: {new Date(subData.nextBillingDate).toLocaleDateString()} + + + )} + + ) + )} + + {/* INVOICES TAB */} + {tab === 'Invoices' && ( + invLoading ? ( + + ) : !invoices?.length ? ( + + ๐Ÿงพ + No invoices yet + + ) : ( + invoices.map((inv: any) => { + const st = INV_STATUS[inv.status] ?? { label: inv.status, color: '#6B7280' }; + return ( + + + {inv.invoiceNumber} + + {st.label} + + + + {inv.dueDate ? new Date(inv.dueDate).toLocaleDateString() : 'โ€”'} + โ‚ฑ{Number(inv.amount ?? inv.totalAmount).toLocaleString()} + + {inv.balance > 0 && ( + Balance: โ‚ฑ{Number(inv.balance).toLocaleString()} + )} + + ); + }) + ) + )} + + {/* PAYMENTS TAB */} + {tab === 'Payments' && ( + payLoading ? ( + + ) : !payments?.length ? ( + + ๐Ÿ’ณ + No payments recorded + + ) : ( + <> + router.push({ pathname: '/(app)/payments/record', params: { prefillClientId: id, prefillName: `${client?.firstName} ${client?.lastName}`, prefillAccountNumber: client?.accountNumber } })} + > + + Record Payment + + {payments.map((p: any) => ( + + + + + {PAYMENT_METHODS[p.paymentMethod] ?? '๐Ÿ’ณ'} {p.paymentMethod} + + + {p.paymentDate ? new Date(p.paymentDate).toLocaleDateString() : new Date(p.createdAt).toLocaleDateString()} + + {p.referenceNumber && ( + Ref: {p.referenceNumber} + )} + + โ‚ฑ{Number(p.amount).toLocaleString()} + + + ))} + + ) + )} ); diff --git a/app/(app)/dashboard.tsx b/app/(app)/dashboard.tsx index 57fddeb..37ea1ba 100644 --- a/app/(app)/dashboard.tsx +++ b/app/(app)/dashboard.tsx @@ -1,14 +1,31 @@ -import { View, Text, ScrollView, RefreshControl, ActivityIndicator } from 'react-native'; +import { View, Text, ScrollView, RefreshControl, ActivityIndicator, TouchableOpacity } from 'react-native'; import { useQuery } from '@tanstack/react-query'; +import { router } from 'expo-router'; import { api } from '../../services/api'; import { useAuthStore } from '../../stores/authStore'; -function KpiCard({ label, value, color }: { label: string; value: string | number; color: string }) { +const PRIORITY_COLOR: Record = { HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#6B7280' }; +const TICKET_STATUS_COLOR: Record = { OPEN: '#2563EB', IN_PROGRESS: '#D97706', RESOLVED: '#16A34A', CLOSED: '#6B7280' }; + +function KpiCard({ label, value, color, onPress }: { label: string; value: string | number; color: string; onPress?: () => void }) { return ( - + {label} - {value} - + {value} + + ); +} + +function QuickAction({ icon, label, onPress }: { icon: string; label: string; onPress: () => void }) { + return ( + + {icon} + {label} + ); } @@ -19,14 +36,23 @@ export default function DashboardScreen() { queryFn: () => api.get('/api/v1/dashboard/summary').then(r => r.data), }); + const greeting = () => { + const h = new Date().getHours(); + if (h < 12) return 'Good morning'; + if (h < 17) return 'Good afternoon'; + return 'Good evening'; + }; + return ( } > - - Welcome back, - {user?.firstName ?? 'Field Staff'} + {/* Header */} + + {greeting()}, + {user?.firstName ?? 'Field Staff'} ๐Ÿ‘‹ + {new Date().toLocaleDateString('en-PH', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })} {isLoading ? ( @@ -35,27 +61,77 @@ export default function DashboardScreen() { ) : ( + {/* KPIs */} Overview - - - + + router.push('/(app)/clients')} + /> + - - - + + router.push('/(app)/clients')} + /> + router.push('/(app)/payments')} + /> + {/* Quick Actions */} + Quick Actions + + router.push('/(app)/payments/record')} /> + router.push('/(app)/tickets/create')} /> + router.push('/(app)/installations')} /> + router.push('/(app)/remittances/submit')} /> + + + {/* Recent Tickets */} Recent Tickets {(data?.recentTickets ?? []).length === 0 ? ( - No recent tickets + No recent tickets + router.push('/(app)/tickets/create')} + > + Create Ticket + ) : ( (data?.recentTickets ?? []).map((t: any) => ( - - {t.subject} - {t.clientName} ยท {t.status} - + router.push(`/(app)/tickets/${t.id}`)} + > + + {t.subject} + + {t.priority} + + + + {t.clientName ?? 'No client'} + + {t.status} + + + )) )} diff --git a/app/(app)/installations/index.tsx b/app/(app)/installations/index.tsx new file mode 100644 index 0000000..95e940f --- /dev/null +++ b/app/(app)/installations/index.tsx @@ -0,0 +1,110 @@ +import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native'; +import { useQuery } from '@tanstack/react-query'; +import { router } from 'expo-router'; +import { api } from '../../../services/api'; + +// Installations are tickets of type INSTALLATION (or filtered by subject prefix) +// We query tickets with type=INSTALLATION if the API supports it, fallback to all open tickets +async function fetchInstallations() { + try { + const res = await api.get('/api/v1/tickets?type=INSTALLATION&limit=50'); + return res.data?.data ?? res.data ?? []; + } catch { + // Fallback: all OPEN tickets + const res = await api.get('/api/v1/tickets?status=OPEN&limit=50'); + return res.data?.data ?? res.data ?? []; + } +} + +const STATUS_STYLE: Record = { + OPEN: { bg: '#EFF6FF', text: '#2563EB' }, + IN_PROGRESS: { bg: '#FFFBEB', text: '#D97706' }, + RESOLVED: { bg: '#F0FDF4', text: '#16A34A' }, + CLOSED: { bg: '#F3F4F6', text: '#6B7280' }, +}; + +export default function InstallationsScreen() { + const { data, isLoading, refetch, isRefetching } = useQuery({ + queryKey: ['installations'], + queryFn: fetchInstallations, + }); + + const installations: any[] = data ?? []; + + return ( + + {/* Header */} + + Installations + + {installations.length} pending + + + + {isLoading ? ( + + + + ) : ( + item.id} + refreshControl={} + contentContainerStyle={{ padding: 16 }} + renderItem={({ item }) => { + const statusStyle = STATUS_STYLE[item.status] ?? { bg: '#F3F4F6', text: '#6B7280' }; + return ( + router.push(`/(app)/installations/${item.id}`)} + > + + + {item.subject} + + + + {item.status?.replace('_', ' ')} + + + + + + + {item.client?.firstName} {item.client?.lastName} + + + {item.createdAt ? new Date(item.createdAt).toLocaleDateString() : ''} + + + + {item.client?.address && ( + + ๐Ÿ“ {item.client.address} + + )} + + {/* Confirm button if not yet resolved */} + {item.status !== 'RESOLVED' && item.status !== 'CLOSED' && ( + router.push(`/(app)/installations/${item.id}`)} + > + ๐Ÿ“ท Confirm Installation + + )} + + ); + }} + ListEmptyComponent={ + + ๐Ÿ”Œ + No installations pending + All caught up! + + } + /> + )} + + ); +} diff --git a/app/(app)/payments/index.tsx b/app/(app)/payments/index.tsx index b384f10..81d9331 100644 --- a/app/(app)/payments/index.tsx +++ b/app/(app)/payments/index.tsx @@ -1,23 +1,87 @@ -import { View, Text, TouchableOpacity } from 'react-native'; +import { useState } from 'react'; +import { View, Text, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native'; +import { useQuery } from '@tanstack/react-query'; import { router } from 'expo-router'; +import { api } from '../../../services/api'; + +const METHOD_ICON: Record = { CASH: '๐Ÿ’ต', GCASH: '๐Ÿ“ฑ', MAYA: '๐Ÿ’™', BANK: '๐Ÿฆ' }; export default function PaymentsScreen() { + const { data, isLoading, refetch, isRefetching } = useQuery({ + queryKey: ['payments'], + queryFn: () => api.get('/api/v1/payments?limit=50').then(r => r.data?.data ?? r.data ?? []), + }); + + const payments: any[] = data ?? []; + + // Calculate today's total + const today = new Date().toDateString(); + const todayTotal = payments + .filter((p: any) => new Date(p.paymentDate ?? p.createdAt).toDateString() === today) + .reduce((sum: number, p: any) => sum + Number(p.amount), 0); + return ( - - Payments - - - ๐Ÿ’ฐ - Record a Payment - Collect payments from clients in the field + {/* Header */} + + + Payments + Today: โ‚ฑ{todayTotal.toLocaleString()} + router.push('/(app)/payments/record')} > - Record Payment + + Record + + {isLoading ? ( + + ) : ( + item.id} + refreshControl={} + contentContainerStyle={{ padding: 16, paddingBottom: 32 }} + ListEmptyComponent={ + + ๐Ÿ’ณ + No payments yet + router.push('/(app)/payments/record')} + > + Record First Payment + + + } + renderItem={({ item }) => ( + + + + + {METHOD_ICON[item.paymentMethod] ?? '๐Ÿ’ณ'} + + {item.client?.firstName} {item.client?.lastName} + + + {item.client?.accountNumber} + + {new Date(item.paymentDate ?? item.createdAt).toLocaleDateString()} ยท {item.paymentMethod} + + {item.referenceNumber && ( + Ref: {item.referenceNumber} + )} + + + โ‚ฑ{Number(item.amount).toLocaleString()} + + + + )} + /> + )} ); } diff --git a/app/(app)/payments/record.tsx b/app/(app)/payments/record.tsx index f839a82..6e9bf58 100644 --- a/app/(app)/payments/record.tsx +++ b/app/(app)/payments/record.tsx @@ -1,37 +1,45 @@ -import { useState } from 'react'; -import { View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator } from 'react-native'; -import { router } from 'expo-router'; +import { useState, useEffect } from 'react'; +import { View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator, FlatList, Modal } from 'react-native'; +import { router, useLocalSearchParams } from 'expo-router'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { api } from '../../../services/api'; const METHODS = ['CASH', 'GCASH', 'MAYA', 'BANK']; export default function RecordPaymentScreen() { + const params = useLocalSearchParams<{ prefillClientId?: string; prefillName?: string; prefillAccountNumber?: string }>(); + const qc = useQueryClient(); + const [search, setSearch] = useState(''); - const [client, setClient] = useState(null); + const [showPicker, setShowPicker] = useState(false); + const [client, setClient] = useState( + params.prefillClientId + ? { id: params.prefillClientId, firstName: params.prefillName?.split(' ')[0], lastName: params.prefillName?.split(' ').slice(1).join(' '), accountNumber: params.prefillAccountNumber } + : null + ); const [amount, setAmount] = useState(''); const [method, setMethod] = useState('CASH'); const [reference, setReference] = useState(''); + const [notes, setNotes] = useState(''); const [loading, setLoading] = useState(false); - const [searching, setSearching] = useState(false); - const searchClient = async () => { - if (!search.trim()) return; - setSearching(true); - try { - const res = await api.get(`/api/v1/clients?search=${search.trim()}&limit=1`); - const found = res.data?.data?.[0] ?? res.data?.[0]; - if (found) setClient(found); - else Alert.alert('Not Found', 'No client found with that account number or name.'); - } catch { - Alert.alert('Error', 'Search failed.'); - } finally { - setSearching(false); - } - }; + // Debounced client search + const [debouncedSearch, setDebouncedSearch] = useState(''); + useEffect(() => { + const t = setTimeout(() => setDebouncedSearch(search), 400); + return () => clearTimeout(t); + }, [search]); + + const { data: searchResults, isFetching: searching } = useQuery({ + queryKey: ['client-search', debouncedSearch], + queryFn: () => api.get(`/api/v1/clients?search=${debouncedSearch}&limit=8`).then(r => r.data?.data ?? r.data ?? []), + enabled: debouncedSearch.trim().length >= 2, + }); const submit = async () => { - if (!client) return Alert.alert('Required', 'Search and select a client first.'); - if (!amount || isNaN(Number(amount))) return Alert.alert('Required', 'Enter a valid amount.'); + if (!client) return Alert.alert('Required', 'Select a client first.'); + if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) + return Alert.alert('Required', 'Enter a valid amount.'); setLoading(true); try { await api.post('/api/v1/payments', { @@ -39,11 +47,19 @@ export default function RecordPaymentScreen() { amount: Number(amount), paymentMethod: method, referenceNumber: reference || undefined, + notes: notes || undefined, paymentDate: new Date().toISOString(), }); - Alert.alert('Success', 'Payment recorded!', [{ text: 'OK', onPress: () => router.back() }]); + // Invalidate relevant queries + qc.invalidateQueries({ queryKey: ['payments'] }); + qc.invalidateQueries({ queryKey: ['client-payments', client.id] }); + qc.invalidateQueries({ queryKey: ['dashboard'] }); + Alert.alert('โœ… Payment Recorded', `โ‚ฑ${Number(amount).toLocaleString()} from ${client.firstName} ${client.lastName}`, [ + { text: 'Done', onPress: () => router.back() }, + { text: 'Record Another', onPress: () => { setClient(null); setAmount(''); setReference(''); setNotes(''); setSearch(''); } }, + ]); } catch (e: any) { - Alert.alert('Error', e?.response?.data?.message ?? 'Payment failed.'); + Alert.alert('Error', e?.response?.data?.message ?? 'Payment failed. Try again.'); } finally { setLoading(false); } @@ -52,69 +68,121 @@ export default function RecordPaymentScreen() { return ( - router.back()} className="mr-3"> + router.back()} className="mr-3 p-1"> โ† Record Payment - - Search Client - - - - {searching ? : Find} - - - {client && ( - - {client.firstName} {client.lastName} - {client.accountNumber} + + {/* Client selector */} + Client * + {client ? ( + + + {client.firstName} {client.lastName} + {client.accountNumber} + + { setClient(null); setSearch(''); }} className="p-2"> + Change + + + ) : ( + + + + {searching && } + + {debouncedSearch.trim().length >= 2 && ( + + {(searchResults ?? []).length === 0 && !searching && ( + No clients found + )} + {(searchResults ?? []).map((c: any) => ( + { setClient(c); setSearch(''); }} + > + {c.firstName} {c.lastName} + {c.accountNumber} + + ))} + + )} )} - Amount (โ‚ฑ) + {/* Amount */} + Amount (โ‚ฑ) * - Payment Method + {/* Payment method */} + Payment Method * {METHODS.map(m => ( setMethod(m)} - className={`rounded-xl px-4 py-2 mr-2 mb-2 border ${method === m ? 'bg-primary border-primary' : 'bg-white border-gray-200'}`} + className={`rounded-xl px-5 py-2.5 mr-2 mb-2 border ${method === m ? 'bg-primary border-primary' : 'bg-white border-gray-200'}`} > {m} ))} - Reference # (optional) + {/* Reference (for non-cash) */} + {method !== 'CASH' && ( + <> + Reference # * + + + )} + + {/* Notes */} + Notes (optional) + {/* Submit */} - {loading ? : Submit Payment} + {loading + ? + : + Submit Payment {amount ? `ยท โ‚ฑ${Number(amount || 0).toLocaleString()}` : ''} + + } + + ); diff --git a/app/(app)/remittances/[id].tsx b/app/(app)/remittances/[id].tsx index 08b879b..cbf8c4a 100644 --- a/app/(app)/remittances/[id].tsx +++ b/app/(app)/remittances/[id].tsx @@ -3,38 +3,86 @@ import { useLocalSearchParams, router } from 'expo-router'; import { useQuery } from '@tanstack/react-query'; import { api } from '../../../services/api'; +const STATUS_COLOR: Record = { PENDING: '#D97706', CONFIRMED: '#16A34A', DISPUTED: '#DC2626' }; +const METHOD_ICON: Record = { CASH: '๐Ÿ’ต', GCASH: '๐Ÿ“ฑ', MAYA: '๐Ÿ’™', BANK: '๐Ÿฆ' }; + export default function RemittanceDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); + const { data, isLoading } = useQuery({ queryKey: ['remittance', id], queryFn: () => api.get(`/api/v1/remittances/${id}`).then(r => r.data), }); - if (isLoading) return ; + if (isLoading) return ( + + + + ); + + const status = data?.status ?? 'PENDING'; + const statusColor = STATUS_COLOR[status] ?? '#6B7280'; return ( - router.back()} className="mr-3"> + router.back()} className="mr-3 p-1"> โ† - Remittance Detail - - - - โ‚ฑ{Number(data?.totalAmount ?? 0).toLocaleString()} - {new Date(data?.createdAt).toLocaleDateString()} - - Status - {data?.status} - - {data?.notes && ( - - Notes - {data.notes} - - )} + + Remittance + {data?.createdAt ? new Date(data.createdAt).toLocaleDateString() : ''} + + {status} + + + + + {/* Summary card */} + + Total Amount + โ‚ฑ{Number(data?.totalAmount ?? 0).toLocaleString()} + {data?.notes && {data.notes}} + + + {/* Details */} + + {[ + { label: 'Submitted by', value: data?.collector?.firstName ? `${data.collector.firstName} ${data.collector.lastName}` : undefined }, + { label: 'Submitted on', value: data?.createdAt ? new Date(data.createdAt).toLocaleString() : undefined }, + { label: 'Confirmed on', value: data?.confirmedAt ? new Date(data.confirmedAt).toLocaleString() : undefined }, + { label: 'Confirmed by', value: data?.confirmedBy?.firstName ? `${data.confirmedBy.firstName} ${data.confirmedBy.lastName}` : undefined }, + ].filter(r => r.value).map((row, i, arr) => ( + + {row.label} + {row.value} + + ))} + + + {/* Included payments */} + {(data?.payments ?? []).length > 0 && ( + <> + Included Payments ({data.payments.length}) + {data.payments.map((p: any) => ( + + + + + {METHOD_ICON[p.paymentMethod] ?? '๐Ÿ’ณ'} {p.client?.firstName} {p.client?.lastName} + + {p.client?.accountNumber} ยท {p.paymentMethod} + {p.referenceNumber && Ref: {p.referenceNumber}} + + โ‚ฑ{Number(p.amount).toLocaleString()} + + + ))} + + )} + + ); diff --git a/app/(app)/tickets/[id].tsx b/app/(app)/tickets/[id].tsx index c4d152c..984c065 100644 --- a/app/(app)/tickets/[id].tsx +++ b/app/(app)/tickets/[id].tsx @@ -1,12 +1,30 @@ import { useState } from 'react'; -import { View, Text, ScrollView, TextInput, TouchableOpacity, ActivityIndicator, Alert } from 'react-native'; +import { + View, Text, ScrollView, TextInput, TouchableOpacity, + ActivityIndicator, Alert, Modal, +} from 'react-native'; import { useLocalSearchParams, router } from 'expo-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '../../../services/api'; +const STATUS_FLOW = ['OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED'] as const; +type TicketStatus = typeof STATUS_FLOW[number]; + +const STATUS_STYLE: Record = { + OPEN: { bg: '#EFF6FF', text: '#2563EB' }, + IN_PROGRESS: { bg: '#FFFBEB', text: '#D97706' }, + RESOLVED: { bg: '#F0FDF4', text: '#16A34A' }, + CLOSED: { bg: '#F3F4F6', text: '#6B7280' }, +}; + +const PRIORITY_COLOR: Record = { + HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#6B7280', +}; + export default function TicketDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const [reply, setReply] = useState(''); + const [showStatusPicker, setShowStatusPicker] = useState(false); const qc = useQueryClient(); const { data, isLoading } = useQuery({ @@ -16,49 +34,183 @@ export default function TicketDetailScreen() { const addReply = useMutation({ mutationFn: () => api.post(`/api/v1/tickets/${id}/messages`, { message: reply }), - onSuccess: () => { setReply(''); qc.invalidateQueries({ queryKey: ['ticket', id] }); }, + onSuccess: () => { + setReply(''); + qc.invalidateQueries({ queryKey: ['ticket', id] }); + }, onError: () => Alert.alert('Error', 'Could not send reply.'), }); - if (isLoading) return ; + const updateStatus = useMutation({ + mutationFn: (status: TicketStatus) => + api.patch(`/api/v1/tickets/${id}`, { status }), + onSuccess: () => { + setShowStatusPicker(false); + qc.invalidateQueries({ queryKey: ['ticket', id] }); + qc.invalidateQueries({ queryKey: ['tickets'] }); + }, + onError: () => Alert.alert('Error', 'Could not update status.'), + }); + + if (isLoading) { + return ( + + + + ); + } + + const currentStatus: string = data?.status ?? 'OPEN'; + const statusStyle = STATUS_STYLE[currentStatus] ?? { bg: '#F3F4F6', text: '#6B7280' }; + const priorityColor = PRIORITY_COLOR[data?.priority] ?? '#6B7280'; return ( - - router.back()} className="mr-3"> - โ† - - - {data?.subject} - {data?.status} ยท {data?.priority} + {/* Header */} + + + router.back()} className="mr-3"> + โ† + + + {data?.subject} + + + + {/* Status badge - tappable */} + setShowStatusPicker(true)} + className="rounded-full px-3 py-1 flex-row items-center" + style={{ backgroundColor: statusStyle.bg }} + > + + {currentStatus.replace('_', ' ')} + + โ–พ + + {/* Priority */} + + + {data?.priority} + + + {/* Client name */} + {data?.client && ( + + {data.client.firstName} {data.client.lastName} + + )} + + {/* Messages */} - {(data?.messages ?? []).map((m: any) => ( - - - {m.message} - - {m.senderName} + {data?.description && ( + + Description + {data.description} - ))} + )} + + {(data?.messages ?? []).length === 0 && !data?.description && ( + + No messages yet. Send the first reply. + + )} + + {(data?.messages ?? []).map((m: any) => { + const isAgent = m.senderType === 'AGENT' || m.senderType === 'STAFF'; + return ( + + + {m.message} + + + {m.senderName ?? m.sender?.name ?? 'System'} + + + ); + })} - - + + {/* Reply bar โ€” hide if ticket is closed */} + {currentStatus !== 'CLOSED' ? ( + + + reply.trim() && addReply.mutate()} + disabled={addReply.isPending || !reply.trim()} + style={{ opacity: !reply.trim() ? 0.5 : 1 }} + > + {addReply.isPending + ? + : Send + } + + + ) : ( + + This ticket is closed + + )} + + {/* Status picker modal */} + setShowStatusPicker(false)} + > reply.trim() && addReply.mutate()} - disabled={addReply.isPending} + className="flex-1 bg-black/50 justify-end" + activeOpacity={1} + onPress={() => setShowStatusPicker(false)} > - {addReply.isPending ? : Send} + + Update Status + + Current: {currentStatus.replace('_', ' ')} + + {STATUS_FLOW.map((s) => { + const style = STATUS_STYLE[s] ?? { bg: '#F3F4F6', text: '#6B7280' }; + const isActive = s === currentStatus; + return ( + !isActive && updateStatus.mutate(s)} + disabled={isActive || updateStatus.isPending} + className={`flex-row items-center justify-between p-4 rounded-xl mb-2 ${isActive ? 'opacity-40' : ''}`} + style={{ backgroundColor: style.bg }} + > + + {s.replace('_', ' ')} + + {isActive && โœ“ Current} + {updateStatus.isPending && !isActive && } + + ); + })} + setShowStatusPicker(false)} + > + Cancel + + - + ); } diff --git a/app/(app)/tickets/index.tsx b/app/(app)/tickets/index.tsx index e8d81ac..ae9f600 100644 --- a/app/(app)/tickets/index.tsx +++ b/app/(app)/tickets/index.tsx @@ -1,35 +1,64 @@ import { useState } from 'react'; -import { View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native'; +import { View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, RefreshControl, ScrollView } from 'react-native'; import { useQuery } from '@tanstack/react-query'; import { router } from 'expo-router'; import { api } from '../../../services/api'; -const PRIORITY_COLOR: Record = { HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#6B7280' }; +const PRIORITY_COLOR: Record = { HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#16A34A' }; +const STATUS_FILTERS = ['ALL', 'OPEN', 'IN_PROGRESS', 'RESOLVED', 'CLOSED']; export default function TicketsScreen() { const [search, setSearch] = useState(''); + const [statusFilter, setStatusFilter] = useState('ALL'); + const { data, isLoading, refetch, isRefetching } = useQuery({ queryKey: ['tickets'], - queryFn: () => api.get('/api/v1/tickets?limit=50').then(r => r.data?.data ?? r.data), + queryFn: () => api.get('/api/v1/tickets?limit=100').then(r => r.data?.data ?? r.data ?? []), }); - const tickets = (data ?? []).filter((t: any) => - `${t.subject} ${t.client?.firstName} ${t.client?.lastName}`.toLowerCase().includes(search.toLowerCase()) - ); + const tickets = (data ?? []).filter((t: any) => { + const matchSearch = `${t.subject} ${t.client?.firstName} ${t.client?.lastName}`.toLowerCase().includes(search.toLowerCase()); + const matchStatus = statusFilter === 'ALL' || t.status === statusFilter; + return matchSearch && matchStatus; + }); return ( - - Helpdesk Tickets + {/* Header */} + + Tickets + router.push('/(app)/tickets/new')} + > + + New + - + + {/* Search */} + + {/* Status filter chips */} + + {STATUS_FILTERS.map(s => ( + setStatusFilter(s)} + className={`rounded-full px-3 py-1.5 mr-2 ${statusFilter === s ? 'bg-primary' : 'bg-gray-100'}`} + > + + {s.replace('_', ' ')} + + + ))} + + {isLoading ? ( ) : ( @@ -37,22 +66,38 @@ export default function TicketsScreen() { data={tickets} keyExtractor={(item) => item.id} refreshControl={} - contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 24 }} + contentContainerStyle={{ paddingHorizontal: 16, paddingVertical: 12, paddingBottom: 32 }} renderItem={({ item }) => ( router.push(`/(app)/tickets/${item.id}`)} > - - {item.subject} + + {item.subject} {item.priority} - {item.client?.firstName} {item.client?.lastName} ยท {item.status} + + + {item.client?.firstName} {item.client?.lastName} + + {item.status?.replace('_', ' ')} + )} - ListEmptyComponent={No tickets found} + ListEmptyComponent={ + + ๐ŸŽซ + No tickets found + router.push('/(app)/tickets/new')} + > + Create First Ticket + + + } /> )} diff --git a/app/(app)/tickets/new.tsx b/app/(app)/tickets/new.tsx new file mode 100644 index 0000000..ff5c5b8 --- /dev/null +++ b/app/(app)/tickets/new.tsx @@ -0,0 +1,182 @@ +import { useState } from 'react'; +import { View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator } from 'react-native'; +import { router } from 'expo-router'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { api } from '../../../services/api'; + +const PRIORITIES = ['LOW', 'MEDIUM', 'HIGH']; +const PRIORITY_COLOR: Record = { HIGH: '#DC2626', MEDIUM: '#D97706', LOW: '#16A34A' }; + +const CATEGORIES: { value: string; label: string }[] = [ + { value: 'NO_SIGNAL', label: 'No Signal' }, + { value: 'SLOW_CONNECTION', label: 'Slow Connection' }, + { value: 'BILLING', label: 'Billing' }, + { value: 'INSTALLATION', label: 'Installation' }, + { value: 'RELOCATION', label: 'Relocation' }, + { value: 'OTHER', label: 'Other' }, +]; + +export default function NewTicketScreen() { + const qc = useQueryClient(); + const [subject, setSubject] = useState(''); + const [description, setDescription] = useState(''); + const [priority, setPriority] = useState('MEDIUM'); + const [category, setCategory] = useState('NO_SIGNAL'); + const [search, setSearch] = useState(''); + const [debouncedSearch, setDebouncedSearch] = useState(''); + const [client, setClient] = useState(null); + const [loading, setLoading] = useState(false); + + const { data: searchResults, isFetching: searching } = useQuery({ + queryKey: ['client-search', debouncedSearch], + queryFn: () => api.get(`/api/v1/clients?search=${debouncedSearch}&limit=8`).then(r => r.data?.data ?? r.data ?? []), + enabled: debouncedSearch.trim().length >= 2, + }); + + const handleSearchChange = (v: string) => { + setSearch(v); + setTimeout(() => setDebouncedSearch(v), 400); + }; + + const submit = async () => { + if (!subject.trim()) return Alert.alert('Required', 'Enter a subject.'); + if (!client) return Alert.alert('Required', 'Select a client.'); + setLoading(true); + try { + await api.post('/api/v1/tickets', { + subject: subject.trim(), + description: description.trim() || undefined, + priority, + category, + clientId: client.id, + }); + qc.invalidateQueries({ queryKey: ['tickets'] }); + qc.invalidateQueries({ queryKey: ['dashboard'] }); + Alert.alert('โœ… Ticket Created', subject, [{ text: 'OK', onPress: () => router.back() }]); + } catch (e: any) { + Alert.alert('Error', e?.response?.data?.message ?? 'Could not create ticket.'); + } finally { + setLoading(false); + } + }; + + return ( + + + router.back()} className="mr-3 p-1"> + โ† + + New Ticket + + + + {/* Client */} + Client * + {client ? ( + + + {client.firstName} {client.lastName} + {client.accountNumber} + + { setClient(null); setSearch(''); setDebouncedSearch(''); }} className="p-2"> + Change + + + ) : ( + + + + {searching && } + + {debouncedSearch.trim().length >= 2 && ( + + {(searchResults ?? []).length === 0 && !searching && ( + No clients found + )} + {(searchResults ?? []).map((c: any) => ( + { setClient(c); setSearch(''); setDebouncedSearch(''); }} + > + {c.firstName} {c.lastName} + {c.accountNumber} + + ))} + + )} + + )} + + {/* Subject */} + Subject * + + + {/* Category */} + Category + + {CATEGORIES.map(c => ( + setCategory(c.value)} + className={`rounded-xl px-3 py-2 mr-2 mb-2 border ${category === c.value ? 'bg-primary border-primary' : 'bg-white border-gray-200'}`} + > + + {c.label} + + + ))} + + + {/* Priority */} + Priority + + {PRIORITIES.map(p => ( + setPriority(p)} + className={`flex-1 rounded-xl py-2.5 items-center mx-1 border ${priority === p ? 'border-transparent' : 'bg-white border-gray-200'}`} + style={priority === p ? { backgroundColor: PRIORITY_COLOR[p] } : {}} + > + {p} + + ))} + + + {/* Description */} + Description (optional) + + + + {loading ? : Create Ticket} + + + + + + ); +} diff --git a/babel.config.js b/babel.config.js index 1d1ac9c..f6fc0b6 100644 --- a/babel.config.js +++ b/babel.config.js @@ -5,5 +5,6 @@ module.exports = function (api) { ['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel', ], + plugins: ['react-native-reanimated/plugin'], }; }; diff --git a/metro.config.js b/metro.config.js index f3321ba..e621938 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,6 +1,10 @@ const { getDefaultConfig } = require('expo/metro-config'); const { withNativeWind } = require('nativewind/metro'); +const path = require('path'); const config = getDefaultConfig(__dirname); +// Allow Metro to resolve assets (png/jpg) from inside node_modules +config.resolver.assetExts.push('png', 'jpg', 'jpeg', 'gif', 'webp'); + module.exports = withNativeWind(config, { input: './global.css' }); diff --git a/nativewind-env.d.ts b/nativewind-env.d.ts index c0d8380..efff04b 100644 --- a/nativewind-env.d.ts +++ b/nativewind-env.d.ts @@ -1,3 +1,44 @@ -/// +// NativeWind v4 className augmentation +import 'react-native'; -// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind. \ No newline at end of file +declare module 'react-native' { + interface ViewProps { + className?: string; + } + interface TextProps { + className?: string; + } + interface ImageProps { + className?: string; + } + interface TextInputProps { + className?: string; + } + interface TouchableOpacityProps { + className?: string; + } + interface TouchableHighlightProps { + className?: string; + } + interface TouchableWithoutFeedbackProps { + className?: string; + } + interface ScrollViewProps { + className?: string; + } + interface FlatListProps { + className?: string; + } + interface SectionListProps { + className?: string; + } + interface ModalProps { + className?: string; + } + interface PressableProps { + className?: string; + } + interface KeyboardAvoidingViewProps { + className?: string; + } +} diff --git a/package-lock.json b/package-lock.json index 8146d50..4519a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,11 +21,15 @@ "expo-router": "^55.0.7", "expo-secure-store": "^55.0.9", "expo-status-bar": "~55.0.4", - "nativewind": "^4.2.3", + "expo-updates": "~55.0.15", + "hermes-parser": "0.32.0", + "nativewind": "^4.1.23", "react": "19.2.0", "react-native": "0.83.2", + "react-native-reanimated": "4.2.1", "react-native-safe-area-context": "^5.6.2", "react-native-screens": "^4.23.0", + "react-native-worklets": "0.7.2", "zustand": "^5.0.12" }, "devDependencies": { @@ -1322,6 +1326,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", @@ -3535,6 +3554,196 @@ "@babel/core": "^7.0.0 || ^8.0.0-0" } }, + "node_modules/babel-preset-expo": { + "version": "55.0.12", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.12.tgz", + "integrity": "sha512-oR46ExGZpRijmPUsr0rFH5X4lR/mvwqJAFXJRLpynZcvyv2pHPTeGMNfd/p5oPMbdbaeMS6G+3k18p48u2Qjbw==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.20.5", + "@babel/helper-module-imports": "^7.25.9", + "@babel/plugin-proposal-decorators": "^7.12.9", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", + "@react-native/babel-preset": "0.83.2", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^55.0.6", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } + } + }, + "node_modules/babel-preset-expo/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.83.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.2.tgz", + "integrity": "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.83.2" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/babel-preset-expo/node_modules/@react-native/babel-preset": { + "version": "0.83.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.2.tgz", + "integrity": "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.83.2", + "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/babel-preset-expo/node_modules/@react-native/codegen": { + "version": "0.83.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz", + "integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.32.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/babel-preset-expo/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/babel-preset-expo/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/babel-preset-expo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-preset-expo/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", @@ -4344,15 +4553,12 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-node-es": { @@ -4641,6 +4847,12 @@ "react-native": "*" } }, + "node_modules/expo-eas-client": { + "version": "55.0.2", + "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-55.0.2.tgz", + "integrity": "sha512-fjOgSXaZFBK2Xmzn/uw0DTF3BsYv97JEa4PYXXqVCEvNJPwJB1cV1eX6Xyq6iKGIhMPH9k62sOc+oUdt094WCw==", + "license": "MIT" + }, "node_modules/expo-font": { "version": "55.0.4", "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz", @@ -4707,6 +4919,12 @@ "expo": "*" } }, + "node_modules/expo-json-utils": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-55.0.0.tgz", + "integrity": "sha512-aupt/o5PDAb8dXDCb0JcRdkqnTLxe/F+La7jrnyd/sXlYFfRgBJLFOa1SqVFXm1E/Xam1SE/yw6eAb+DGY7Arg==", + "license": "MIT" + }, "node_modules/expo-linking": { "version": "55.0.8", "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-55.0.8.tgz", @@ -4733,6 +4951,19 @@ "expo": "*" } }, + "node_modules/expo-manifests": { + "version": "55.0.11", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-55.0.11.tgz", + "integrity": "sha512-3+pFun4C9F/eFMVpwZgOBrBWq5sfu7rS1uxTrcg9G7jUFatNe5W6hr+M7z7aQPDf0J1afaSudUZPawx1LLf15w==", + "license": "MIT", + "dependencies": { + "@expo/config": "~55.0.10", + "expo-json-utils": "~55.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "55.0.11", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.11.tgz", @@ -4951,6 +5182,12 @@ "react-native": "*" } }, + "node_modules/expo-structured-headers": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-55.0.0.tgz", + "integrity": "sha512-udaNvuWb45/Sryq9FLC/blwgOChhznuqlTrUzVjC0T83pMdcmscKJX23lnNDW6hCec8p81Y3z1DIFwIyk0g/PQ==", + "license": "MIT" + }, "node_modules/expo-symbols": { "version": "55.0.5", "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-55.0.5.tgz", @@ -4967,6 +5204,51 @@ "react-native": "*" } }, + "node_modules/expo-updates": { + "version": "55.0.15", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-55.0.15.tgz", + "integrity": "sha512-UE9Ik56trq//kNeJ/BlC5vOTYdNTvsHwhfWFYMazP1UOQK4lnX59/t0qz8Ut+3aPXZZT7+B6mnbWtic0QqN1wA==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/plist": "^0.5.2", + "@expo/spawn-async": "^1.7.2", + "arg": "^4.1.0", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "expo-eas-client": "~55.0.2", + "expo-manifests": "~55.0.11", + "expo-structured-headers": "~55.0.0", + "expo-updates-interface": "~55.1.3", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "ignore": "^5.3.1", + "resolve-from": "^5.0.0" + }, + "bin": { + "expo-updates": "bin/cli.js" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-updates-interface": { + "version": "55.1.3", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-55.1.3.tgz", + "integrity": "sha512-UVVIiZqymQZJL+o/jh65kXOI97xdkbqBJJM0LMabaPMNLFnc6/WvOMOzmQs7SPyKb8+0PeBaFd7tj5DzF6JeQg==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-updates/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/expo/node_modules/@expo/cli": { "version": "55.0.18", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.18.tgz", @@ -5128,184 +5410,6 @@ "react-native": "*" } }, - "node_modules/expo/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.2.tgz", - "integrity": "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.83.2" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/expo/node_modules/@react-native/babel-preset": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.2.tgz", - "integrity": "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.83.2", - "babel-plugin-syntax-hermes-parser": "0.32.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/expo/node_modules/@react-native/codegen": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz", - "integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "glob": "^7.1.1", - "hermes-parser": "0.32.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/expo/node_modules/@react-native/codegen/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/expo/node_modules/babel-preset-expo": { - "version": "55.0.12", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.12.tgz", - "integrity": "sha512-oR46ExGZpRijmPUsr0rFH5X4lR/mvwqJAFXJRLpynZcvyv2pHPTeGMNfd/p5oPMbdbaeMS6G+3k18p48u2Qjbw==", - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.20.5", - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-react": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.83.2", - "babel-plugin-react-compiler": "^1.0.0", - "babel-plugin-react-native-web": "~0.21.0", - "babel-plugin-syntax-hermes-parser": "^0.32.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.20.0", - "expo": "*", - "expo-widgets": "^55.0.6", - "react-refresh": ">=0.14.0 <1.0.0" - }, - "peerDependenciesMeta": { - "@babel/runtime": { - "optional": true - }, - "expo": { - "optional": true - }, - "expo-widgets": { - "optional": true - } - } - }, - "node_modules/expo/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/expo/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/expo/node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5321,15 +5425,6 @@ "node": ">=8" } }, - "node_modules/expo/node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/expo/node_modules/expo-asset": { "version": "55.0.10", "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.10.tgz", @@ -5365,247 +5460,6 @@ "react": "*" } }, - "node_modules/expo/node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/expo/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/expo/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/expo/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -6745,12 +6599,12 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz", - "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { - "detect-libc": "^1.0.3" + "detect-libc": "^2.0.3" }, "engines": { "node": ">= 12.0.0" @@ -6760,16 +6614,17 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.27.0", - "lightningcss-darwin-x64": "1.27.0", - "lightningcss-freebsd-x64": "1.27.0", - "lightningcss-linux-arm-gnueabihf": "1.27.0", - "lightningcss-linux-arm64-gnu": "1.27.0", - "lightningcss-linux-arm64-musl": "1.27.0", - "lightningcss-linux-x64-gnu": "1.27.0", - "lightningcss-linux-x64-musl": "1.27.0", - "lightningcss-win32-arm64-msvc": "1.27.0", - "lightningcss-win32-x64-msvc": "1.27.0" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { @@ -6793,9 +6648,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz", - "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -6813,9 +6668,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz", - "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -6833,9 +6688,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz", - "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -6853,9 +6708,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz", - "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -6873,9 +6728,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz", - "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -6893,9 +6748,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz", - "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -6913,9 +6768,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz", - "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -6933,9 +6788,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz", - "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -6953,9 +6808,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz", - "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -6973,9 +6828,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz", - "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -7621,14 +7476,14 @@ } }, "node_modules/nativewind": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-4.2.3.tgz", - "integrity": "sha512-HglF1v6A8CqBFpXWs0d3yf4qQGurrreLuyE8FTRI/VDH8b0npZa2SDG5tviTkLiBg0s5j09mQALZOjxuocgMLA==", + "version": "4.1.23", + "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-4.1.23.tgz", + "integrity": "sha512-oLX3suGI6ojQqWxdQezOSM5GmJ4KvMnMtmaSMN9Ggb5j7ysFt4nHxb1xs8RDjZR7BWc+bsetNJU8IQdQMHqRpg==", "license": "MIT", "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", - "react-native-css-interop": "0.2.3" + "react-native-css-interop": "0.1.22" }, "engines": { "node": ">=16" @@ -8518,16 +8373,16 @@ } }, "node_modules/react-native-css-interop": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.2.3.tgz", - "integrity": "sha512-wc+JI7iUfdFBqnE18HhMTtD0q9vkhuMczToA87UdHGWwMyxdT5sCcNy+i4KInPCE855IY0Ic8kLQqecAIBWz7w==", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.1.22.tgz", + "integrity": "sha512-Mu01e+H9G+fxSWvwtgWlF5MJBJC4VszTCBXopIpeR171lbeBInHb8aHqoqRPxmJpi3xIHryzqKFOJYAdk7PBxg==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/traverse": "^7.23.0", "@babel/types": "^7.23.0", "debug": "^4.3.7", - "lightningcss": "~1.27.0", + "lightningcss": "^1.27.0", "semver": "^7.6.3" }, "engines": { @@ -8558,6 +8413,43 @@ "react-native": "*" } }, + "node_modules/react-native-reanimated": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", + "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "1.2.1", + "semver": "7.7.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": ">=0.7.0" + } + }, + "node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/react-native-safe-area-context": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", @@ -8582,6 +8474,128 @@ "react-native": "*" } }, + "node_modules/react-native-worklets": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.2.tgz", + "integrity": "sha512-DuLu1kMV/Uyl9pQHp3hehAlThoLw7Yk2FwRTpzASOmI+cd4845FWn3m2bk9MnjUw8FBRIyhwLqYm2AJaXDXsog==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "7.27.1", + "@babel/plugin-transform-class-properties": "7.27.1", + "@babel/plugin-transform-classes": "7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", + "@babel/plugin-transform-optional-chaining": "7.27.1", + "@babel/plugin-transform-shorthand-properties": "7.27.1", + "@babel/plugin-transform-template-literals": "7.27.1", + "@babel/plugin-transform-unicode-regex": "7.27.1", + "@babel/preset-typescript": "7.27.1", + "convert-source-map": "2.0.0", + "semver": "7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/react-native-worklets/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/@react-native/codegen": { "version": "0.83.2", "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz", diff --git a/package.json b/package.json index 1a7fb38..a97bbe0 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,15 @@ "expo-router": "^55.0.7", "expo-secure-store": "^55.0.9", "expo-status-bar": "~55.0.4", - "nativewind": "^4.2.3", + "expo-updates": "~55.0.15", + "hermes-parser": "0.32.0", + "nativewind": "^4.1.23", "react": "19.2.0", "react-native": "0.83.2", + "react-native-reanimated": "4.2.1", "react-native-safe-area-context": "^5.6.2", "react-native-screens": "^4.23.0", + "react-native-worklets": "0.7.2", "zustand": "^5.0.12" }, "devDependencies": { diff --git a/tsconfig.json b/tsconfig.json index b47bc3e..56c26ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,13 @@ "@/hooks/*": ["./hooks/*"], "@/constants/*": ["./constants/*"] } - } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "nativewind-env.d.ts", + "types/**/*.d.ts", + ".expo/types/**/*.d.ts", + "expo-env.d.ts" + ] } diff --git a/types/expo-modules.d.ts b/types/expo-modules.d.ts new file mode 100644 index 0000000..9f7a287 --- /dev/null +++ b/types/expo-modules.d.ts @@ -0,0 +1,39 @@ +declare module 'expo-image-picker' { + export function requestCameraPermissionsAsync(): Promise<{ status: string }>; + export function launchCameraAsync(options?: { + quality?: number; + base64?: boolean; + allowsEditing?: boolean; + }): Promise<{ canceled: boolean; assets: Array<{ uri: string; base64?: string }> }>; + export function launchImageLibraryAsync(options?: { + quality?: number; + base64?: boolean; + mediaTypes?: string; + }): Promise<{ canceled: boolean; assets: Array<{ uri: string; base64?: string }> }>; +} + +declare module 'expo-location' { + export enum Accuracy { + Lowest = 1, + Low = 2, + Balanced = 3, + High = 4, + Highest = 5, + BestForNavigation = 6, + } + export function requestForegroundPermissionsAsync(): Promise<{ status: string }>; + export function getCurrentPositionAsync(options?: { + accuracy?: Accuracy; + }): Promise<{ + coords: { + latitude: number; + longitude: number; + altitude: number | null; + accuracy: number; + altitudeAccuracy: number | null; + heading: number | null; + speed: number | null; + }; + timestamp: number; + }>; +}