fix: onboarding button disabled state; feat: update GPS from client profile
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Linking, Alert, TextInput, Modal, KeyboardAvoidingView, Platform } from 'react-native';
|
import { View, Text, ScrollView, TouchableOpacity, ActivityIndicator, Linking, Alert, TextInput, Modal, KeyboardAvoidingView, Platform } from 'react-native';
|
||||||
|
import * as Location from 'expo-location';
|
||||||
import { SlideToConfirm } from '../../../components/SlideToConfirm';
|
import { SlideToConfirm } from '../../../components/SlideToConfirm';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useLocalSearchParams, router } from 'expo-router';
|
import { useLocalSearchParams, router } from 'expo-router';
|
||||||
@@ -130,6 +131,7 @@ export default function ClientDetailScreen() {
|
|||||||
const initialTab = (params.tab === 'invoices' ? 'Invoices' : 'Profile') as Tab;
|
const initialTab = (params.tab === 'invoices' ? 'Invoices' : 'Profile') as Tab;
|
||||||
const [activeTab, setActiveTab] = useState<Tab>(initialTab);
|
const [activeTab, setActiveTab] = useState<Tab>(initialTab);
|
||||||
const [payingInvoice, setPayingInvoice] = useState<any>(null);
|
const [payingInvoice, setPayingInvoice] = useState<any>(null);
|
||||||
|
const [locationLoading, setLocationLoading] = useState(false);
|
||||||
|
|
||||||
const { data: client, isLoading } = useQuery({
|
const { data: client, isLoading } = useQuery({
|
||||||
queryKey: ['client', id],
|
queryKey: ['client', id],
|
||||||
@@ -172,6 +174,26 @@ export default function ClientDetailScreen() {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateLocation = async () => {
|
||||||
|
setLocationLoading(true);
|
||||||
|
try {
|
||||||
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
if (status !== 'granted') {
|
||||||
|
Alert.alert('Permission denied', 'Location permission is required to update coordinates.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const loc = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High });
|
||||||
|
const { latitude: lat, longitude: lng } = loc.coords;
|
||||||
|
await api.patch(`/api/v1/clients/${id}`, { lat, lng });
|
||||||
|
qc.setQueryData(['client', id], (old: any) => ({ ...old, lat, lng }));
|
||||||
|
Alert.alert('Location updated', '📍 GPS coordinates saved successfully.');
|
||||||
|
} catch (e: any) {
|
||||||
|
Alert.alert('Error', 'Could not get location. Make sure GPS is enabled.');
|
||||||
|
} finally {
|
||||||
|
setLocationLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1, backgroundColor: '#0891B2' }} edges={['top']}>
|
<SafeAreaView style={{ flex: 1, backgroundColor: '#0891B2' }} edges={['top']}>
|
||||||
@@ -256,6 +278,28 @@ export default function ClientDetailScreen() {
|
|||||||
<Text style={{ fontSize: 13, color: '#CBD5E1', textAlign: 'center' }}>Location is recorded when a technician confirms an installation</Text>
|
<Text style={{ fontSize: 13, color: '#CBD5E1', textAlign: 'center' }}>Location is recorded when a technician confirms an installation</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Update Location */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={updateLocation}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
style={{
|
||||||
|
borderWidth: 1.5, borderColor: '#0891B2', borderRadius: 14,
|
||||||
|
paddingVertical: 14, alignItems: 'center', marginTop: 10,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{locationLoading
|
||||||
|
? <ActivityIndicator color="#0891B2" />
|
||||||
|
: <Text style={{ color: '#0891B2', fontSize: 16, fontWeight: '700' }}>📍 Update Location</Text>
|
||||||
|
}
|
||||||
|
</TouchableOpacity>
|
||||||
|
{client?.lat && client?.lng
|
||||||
|
? <Text style={{ fontSize: 13, color: '#94A3B8', marginTop: 6, textAlign: 'center' }}>
|
||||||
|
📍 {Number(client.lat).toFixed(5)}, {Number(client.lng).toFixed(5)}
|
||||||
|
</Text>
|
||||||
|
: <Text style={{ fontSize: 13, color: '#CBD5E1', marginTop: 6, textAlign: 'center' }}>No location set</Text>
|
||||||
|
}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export default function NewClientScreen() {
|
|||||||
|
|
||||||
// ── Submit ─────────────────────────────────────────────────────────────────
|
// ── Submit ─────────────────────────────────────────────────────────────────
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
if (submitting) return; // guard against double-tap
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
// 1. Create client
|
// 1. Create client
|
||||||
@@ -395,8 +396,8 @@ export default function NewClientScreen() {
|
|||||||
) : (
|
) : (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={submit}
|
onPress={submit}
|
||||||
disabled={submitting}
|
activeOpacity={0.85}
|
||||||
style={{ flex: 2, paddingVertical: 16, borderRadius: 12, backgroundColor: submitting ? '#94A3B8' : '#059669', alignItems: 'center' }}
|
style={{ flex: 2, paddingVertical: 16, borderRadius: 12, backgroundColor: submitting ? '#94A3B8' : '#059669', opacity: 1, alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
{submitting
|
{submitting
|
||||||
? <ActivityIndicator color="#fff" />
|
? <ActivityIndicator color="#fff" />
|
||||||
|
|||||||
Reference in New Issue
Block a user