285 lines
9.3 KiB
TypeScript
285 lines
9.3 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Text,
|
|
View,
|
|
TouchableOpacity,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
ScrollView,
|
|
ImageBackground,
|
|
ActivityIndicator,
|
|
StatusBar,
|
|
} from 'react-native';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { getStyles } from './login.styles';
|
|
import { useTheme } from '@theme';
|
|
import { useAppDispatch, useAppSelector, RootState, login } from '@store';
|
|
import { LoginRequest } from '@interfaces';
|
|
import { NotificationService } from '@services';
|
|
import { backGroundImage } from '@utils';
|
|
import { FormInput } from '@components';
|
|
import config from 'react-native-config';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
export const LoginScreen = () => {
|
|
const dispatch = useAppDispatch();
|
|
const navigation = useNavigation<any>();
|
|
const { theme: colors, isDark } = useTheme();
|
|
const styles = getStyles(colors, isDark);
|
|
|
|
const [tenancy, setTenancy] = useState('');
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [focusedField, setFocusedField] = useState<string | null>(null);
|
|
const [localError, setLocalError] = useState('');
|
|
|
|
const { loginLoading, loginError, loginSuccess, token, user_data } =
|
|
useAppSelector((state: RootState) => state.auth);
|
|
|
|
const handleLogin = async () => {
|
|
setLocalError('');
|
|
if (!tenancy.trim()) {
|
|
setLocalError('Tenancy name is required');
|
|
return;
|
|
}
|
|
if (!email.trim()) {
|
|
setLocalError('Email address is required');
|
|
return;
|
|
}
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email.trim())) {
|
|
setLocalError('Please enter a valid email address');
|
|
return;
|
|
}
|
|
if (!password.trim()) {
|
|
setLocalError('Password is required');
|
|
return;
|
|
}
|
|
|
|
const deviceToken = await NotificationService.getFCMToken();
|
|
console.log('deviceToken-', deviceToken)
|
|
|
|
const payload: LoginRequest = {
|
|
email,
|
|
password,
|
|
device_token: deviceToken ?? '',
|
|
};
|
|
console.log('payload-', payload)
|
|
console.log('tenancy-', config.BASE_URL)
|
|
if (tenancy == config.BASE_URL) {
|
|
try {
|
|
await AsyncStorage.setItem('base_url', tenancy);
|
|
await AsyncStorage.setItem('email', email);
|
|
await AsyncStorage.setItem('password', password);
|
|
} catch (e) {
|
|
console.error('Failed to save login credentials to AsyncStorage', e);
|
|
}
|
|
await dispatch(login(payload));
|
|
} else {
|
|
setLocalError('Invalid tenancy name');
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const loadCredentials = async () => {
|
|
try {
|
|
const savedBaseUrl = await AsyncStorage.getItem('base_url');
|
|
const savedEmail = await AsyncStorage.getItem('email');
|
|
const savedPassword = await AsyncStorage.getItem('password');
|
|
|
|
if (savedBaseUrl) setTenancy(savedBaseUrl);
|
|
if (savedEmail) setEmail(savedEmail);
|
|
if (savedPassword) setPassword(savedPassword);
|
|
} catch (e) {
|
|
console.error('Failed to load login credentials from AsyncStorage', e);
|
|
}
|
|
};
|
|
loadCredentials();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (
|
|
tenancy.trim() &&
|
|
email.trim() &&
|
|
emailRegex.test(email.trim()) &&
|
|
password.trim()
|
|
) {
|
|
setLocalError('');
|
|
}
|
|
}, [tenancy, email, password]);
|
|
|
|
useEffect(() => {
|
|
if (loginSuccess && token && user_data) {
|
|
navigation.reset({
|
|
index: 0,
|
|
routes: [{ name: 'DrawerStack' }],
|
|
});
|
|
}
|
|
}, [loginSuccess, token, user_data, navigation]);
|
|
|
|
return (
|
|
<ImageBackground
|
|
source={backGroundImage}
|
|
style={styles.backgroundImage}
|
|
resizeMode="cover"
|
|
>
|
|
<StatusBar
|
|
barStyle={isDark ? 'light-content' : 'dark-content'}
|
|
backgroundColor="transparent"
|
|
translucent
|
|
/>
|
|
<View style={styles.overlay}>
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
style={styles.container}
|
|
>
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContainer}
|
|
keyboardShouldPersistTaps="handled"
|
|
showsVerticalScrollIndicator={false}
|
|
bounces={false}
|
|
>
|
|
{/* Top Area Spacer displaying background illustration */}
|
|
<View style={styles.topSpacer} />
|
|
|
|
{/* Bottom Sheet Form Card */}
|
|
<View style={styles.bottomSheetCard}>
|
|
<View style={styles.sheetHandle} />
|
|
|
|
<View style={styles.cardHeader}>
|
|
<Text style={styles.welcomeTitle}>Sign In</Text>
|
|
<Text style={styles.welcomeSubtitle}>
|
|
Enter your credentials to continue
|
|
</Text>
|
|
</View>
|
|
|
|
{localError || loginError ? (
|
|
<View style={styles.errorBanner}>
|
|
<Icon name="alert-circle-outline" size={20} color="#EF4444" />
|
|
<Text style={styles.errorText}>{localError || loginError}</Text>
|
|
</View>
|
|
) : null}
|
|
|
|
{/* Tenancy Field */}
|
|
<FormInput
|
|
label="Tenancy Name"
|
|
value={tenancy}
|
|
onChangeText={setTenancy}
|
|
placeholder="company-name"
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
containerStyle={styles.fieldContainer}
|
|
inputContainerStyle={[
|
|
styles.inputWrapper,
|
|
focusedField === 'tenancy' && styles.inputWrapperFocused,
|
|
]}
|
|
onFocus={() => setFocusedField('tenancy')}
|
|
onBlur={() => setFocusedField(null)}
|
|
leftIcon={
|
|
<Icon
|
|
name="business-outline"
|
|
size={20}
|
|
color={
|
|
focusedField === 'tenancy'
|
|
? colors.icon
|
|
: colors.textSecondary
|
|
}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{/* Email Field */}
|
|
<FormInput
|
|
label="Email Address"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
placeholder="name@company.com"
|
|
keyboardType="email-address"
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
containerStyle={styles.fieldContainer}
|
|
inputContainerStyle={[
|
|
styles.inputWrapper,
|
|
focusedField === 'email' && styles.inputWrapperFocused,
|
|
]}
|
|
onFocus={() => setFocusedField('email')}
|
|
onBlur={() => setFocusedField(null)}
|
|
leftIcon={
|
|
<Icon
|
|
name="mail-outline"
|
|
size={20}
|
|
color={
|
|
focusedField === 'email'
|
|
? colors.icon
|
|
: colors.textSecondary
|
|
}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{/* Password Field */}
|
|
<FormInput
|
|
label="Password"
|
|
value={password}
|
|
onChangeText={setPassword}
|
|
placeholder="••••••••"
|
|
secureTextEntry={!showPassword}
|
|
autoCapitalize="none"
|
|
autoCorrect={false}
|
|
containerStyle={styles.fieldContainer}
|
|
inputContainerStyle={[
|
|
styles.inputWrapper,
|
|
focusedField === 'password' && styles.inputWrapperFocused,
|
|
]}
|
|
onFocus={() => setFocusedField('password')}
|
|
onBlur={() => setFocusedField(null)}
|
|
leftIcon={
|
|
<Icon
|
|
name="lock-closed-outline"
|
|
size={20}
|
|
color={
|
|
focusedField === 'password'
|
|
? colors.icon
|
|
: colors.textSecondary
|
|
}
|
|
/>
|
|
}
|
|
rightIcon={
|
|
<TouchableOpacity
|
|
onPress={() => setShowPassword(!showPassword)}
|
|
style={styles.eyeIconButton}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Icon
|
|
name={showPassword ? 'eye-off-outline' : 'eye-outline'}
|
|
size={20}
|
|
color={colors.textMuted}
|
|
/>
|
|
</TouchableOpacity>
|
|
}
|
|
/>
|
|
|
|
{/* Sign In Button */}
|
|
<TouchableOpacity
|
|
style={[styles.button, loginLoading && styles.buttonDisabled]}
|
|
activeOpacity={0.85}
|
|
onPress={handleLogin}
|
|
disabled={loginLoading}
|
|
>
|
|
{loginLoading ? (
|
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
) : (
|
|
<Text style={styles.buttonText}>Sign In</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
</View>
|
|
</ImageBackground>
|
|
);
|
|
};
|