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;
+ }>;
+}