- 5-tab navigation (Home/Clients/Collect/Tickets/Profile) - Inline styles throughout (17px min font, SafeAreaView) - Dashboard fixed to match real API shape - Ticket detail: 2 tabs (Details + Comments), always-visible comment input - Installation confirmation: GPS coordinate capture + client location update - User management screens (Admin only): list, create, detail + role/active toggle - Tasks folder replaces tickets folder - Remittance detail: inline styles - Record payment: prefill from client, live button text - Icon component with SVG icons - Color system: primary #0891B2
84 lines
4.1 KiB
TypeScript
84 lines
4.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { View, Text, TextInput, TouchableOpacity, ActivityIndicator, Alert, KeyboardAvoidingView, Platform } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useLocalSearchParams, router } from 'expo-router';
|
|
import { useAuthStore } from '../../stores/authStore';
|
|
|
|
export default function LoginScreen() {
|
|
const { tenantSlug } = useLocalSearchParams<{ tenantSlug: string }>();
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const { login, isLoading } = useAuthStore();
|
|
|
|
const handleLogin = async () => {
|
|
if (!email.trim() || !password) return Alert.alert('Required', 'Please enter email and password.');
|
|
try {
|
|
await login(tenantSlug, email.trim(), password);
|
|
router.replace('/(app)/dashboard');
|
|
} catch (e: any) {
|
|
const msg = e?.response?.data?.message ?? 'Login failed. Check your credentials.';
|
|
Alert.alert('Login Failed', Array.isArray(msg) ? msg.join('\n') : msg);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<SafeAreaView style={{ flex: 1, backgroundColor: '#FFF' }}>
|
|
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
|
|
<View style={{ flex: 1, justifyContent: 'center', paddingHorizontal: 24 }}>
|
|
<TouchableOpacity onPress={() => router.back()} style={{ marginBottom: 32 }} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
|
<Text style={{ fontSize: 17, fontWeight: '600', color: '#0891B2' }}>← Back</Text>
|
|
</TouchableOpacity>
|
|
|
|
<Text style={{ fontSize: 32, fontWeight: '900', color: '#0F172A', letterSpacing: -0.5, marginBottom: 6 }}>Welcome back</Text>
|
|
<Text style={{ fontSize: 16, color: '#64748B', marginBottom: 32 }}>
|
|
Signing in to <Text style={{ fontWeight: '700', color: '#0F172A' }}>{tenantSlug}</Text>
|
|
</Text>
|
|
|
|
{/* Email */}
|
|
<Text style={{ fontSize: 15, fontWeight: '700', color: '#374151', marginBottom: 8 }}>Email</Text>
|
|
<TextInput
|
|
style={{ backgroundColor: '#F8FAFC', borderWidth: 1.5, borderColor: '#E2E8F0', borderRadius: 14, paddingHorizontal: 18, paddingVertical: 16, fontSize: 17, color: '#0F172A', marginBottom: 16 }}
|
|
placeholder="Enter your email"
|
|
placeholderTextColor="#94A3B8"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
autoCapitalize="none"
|
|
keyboardType="email-address"
|
|
autoFocus
|
|
/>
|
|
|
|
{/* Password */}
|
|
<Text style={{ fontSize: 15, fontWeight: '700', color: '#374151', marginBottom: 8 }}>Password</Text>
|
|
<View style={{ backgroundColor: '#F8FAFC', borderWidth: 1.5, borderColor: '#E2E8F0', borderRadius: 14, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 18, marginBottom: 28 }}>
|
|
<TextInput
|
|
style={{ flex: 1, paddingVertical: 16, fontSize: 17, color: '#0F172A' }}
|
|
placeholder="Enter your password"
|
|
placeholderTextColor="#94A3B8"
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
secureTextEntry={!showPassword}
|
|
onSubmitEditing={handleLogin}
|
|
/>
|
|
<TouchableOpacity onPress={() => setShowPassword(!showPassword)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
|
<Text style={{ fontSize: 15, fontWeight: '600', color: '#0891B2' }}>{showPassword ? 'Hide' : 'Show'}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={{ backgroundColor: '#0891B2', borderRadius: 14, paddingVertical: 18, alignItems: 'center' }}
|
|
onPress={handleLogin}
|
|
disabled={isLoading}
|
|
activeOpacity={0.8}
|
|
>
|
|
{isLoading
|
|
? <ActivityIndicator color="#FFF" />
|
|
: <Text style={{ color: '#FFF', fontSize: 17, fontWeight: '700' }}>Sign In</Text>
|
|
}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</KeyboardAvoidingView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|