native_convex_CRM/app/features/login/login.screen.tsx

295 lines
10 KiB
TypeScript

import React, { useState, useEffect, useRef } 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, TENANCY_OPTIONS } from '@utils';
import { FormInput, FormPicker } from '@components';
import AsyncStorage from '@react-native-async-storage/async-storage';
import DeviceInfo from 'react-native-device-info';
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 scrollViewRef = useRef<ScrollView>(null);
const [localError, setLocalError] = useState('');
// const [deviceToken, setDeviceToken] = useState<string | null>(null);
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 fcmToken = await NotificationService.getFCMToken();
const deviceId = await DeviceInfo.getUniqueId();
console.log('fcm_token-', fcmToken);
console.log('device_id-', deviceId);
const payload: LoginRequest = {
email,
password,
fcm_token: fcmToken ?? '',
device_id: deviceId ?? '',
};
console.log('payload', payload)
// Build the full tenancy base URL and persist it in AsyncStorage
const cleanTenancy = tenancy.trim().replace(/^(https?:\/\/)?/, '');
const fullTenancy = `https://${cleanTenancy}`;
console.log('baseUrl set to-', fullTenancy);
try {
await AsyncStorage.setItem('base_url', fullTenancy);
await AsyncStorage.setItem('tenancy_name', cleanTenancy);
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));
};
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) {
const matched = TENANCY_OPTIONS.find(
opt =>
opt.value.trim().replace(/\/+$/, '').toLowerCase() ===
savedBaseUrl.trim().replace(/\/+$/, '').toLowerCase(),
);
setTenancy(matched ? matched.value : savedBaseUrl);
} else if (TENANCY_OPTIONS.length > 0) {
setTenancy(TENANCY_OPTIONS[0].value);
}
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) {
// Send FCM token to server immediately after login
// if (deviceToken && user_data.staffid) {
// dispatch(sendFcmToken({ id: user_data.staffid, fcm_token: deviceToken }));
// }
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' : 'height'}
style={styles.container}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
<ScrollView
ref={scrollViewRef}
contentContainerStyle={styles.scrollContainer}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
bounces={false}
keyboardDismissMode="none"
>
{/* 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 Environment Field */}
<FormPicker
label="Tenancy Name"
value={tenancy}
onValueChange={(val) => setTenancy(val)}
options={TENANCY_OPTIONS}
placeholder="Select Tenancy Environment"
/>
{/* 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');
setTimeout(() => scrollViewRef.current?.scrollToEnd({ animated: true }), 100);
}}
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');
setTimeout(() => scrollViewRef.current?.scrollToEnd({ animated: true }), 100);
}}
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>
);
};