Files
cpost-mobile/src/screens/auth/registeration/ui/FirstStep.tsx
Samandar Turgunboyev 684d09e6b5 added login modal
2025-11-24 10:52:21 +05:00

561 lines
20 KiB
TypeScript

'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import {
type RouteProp,
useNavigation,
useRoute,
} from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation, useQuery } from '@tanstack/react-query';
import { authApi } from 'api/auth';
import { registerPayload } from 'api/auth/type';
import { Branch, branchApi } from 'api/branch';
import AppText from 'components/AppText';
import ErrorNotification from 'components/ErrorNotification';
import formatPhone from 'helpers/formatPhone';
import { useEffect, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
Animated,
ImageBackground,
Keyboard,
KeyboardAvoidingView,
ScrollView,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import {
FirstStepFormType,
FirstStepSchema,
} from 'screens/auth/registeration/lib/form';
import LanguageSelector from 'screens/auth/select-language/SelectLang';
import ArrowDown from 'svg/ArrowDown';
import ArrowLeft from 'svg/ArrowLeft';
import ArrowUp from 'svg/ArrowUp';
import Check from 'svg/Check';
import { RootStackParamList } from 'types/types';
import { useUserStore } from '../lib/userstore';
import { RegisterStyle } from './styled';
type LoginScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'Login'
>;
const recommended = [
{ label: 'Tanishim orqali', value: 'FRIEND' },
{ label: 'Telegram orqali', value: 'TELEGRAM' },
{ label: 'Instagram orqali', value: 'INSTAGRAM' },
{ label: 'Facebook orqali', value: 'FACEBOOK' },
];
const FirstStep = ({ onNext }: { onNext: () => void }) => {
const { t } = useTranslation();
const [filialDropdownVisible, setFilialDropdownVisible] = useState(false);
const [error, setError] = useState<string>('Xatolik yuz berdi');
const [visible, setVisible] = useState(false);
const { setUser } = useUserStore(state => state);
const { data: branchList } = useQuery({
queryKey: ['branchList'],
queryFn: branchApi.branchList,
});
const { mutate, isPending } = useMutation({
mutationFn: (payload: registerPayload) => authApi.register(payload),
onSuccess: res => {
onNext();
},
onError: (err: any) => {
setVisible(true);
setError(
err?.response?.data?.message ||
err?.response?.message ||
'Xatolik yuz berdi',
);
},
});
const [recommendedDropdownVisible, setRecommendedDropdownVisible] =
useState(false);
const [termsAccepted, setTermsAccepted] = useState(false);
const [checkboxAnimation] = useState(new Animated.Value(0));
const navigation = useNavigation<LoginScreenNavigationProp>();
const [rawPhone, setRawPhone] = useState('+998');
const route = useRoute<RouteProp<RootStackParamList, 'Register'>>();
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FirstStepFormType>({
resolver: zodResolver(FirstStepSchema),
defaultValues: {
firstName: '',
address: '',
lastName: '',
recommend: '',
},
});
// 🔑 Input ref'lar
const firstNameRef = useRef<TextInput>(null);
const lastNameRef = useRef<TextInput>(null);
const phoneRef = useRef<TextInput>(null);
const addressRef = useRef<TextInput>(null);
const onSubmit = (data: FirstStepFormType) => {
if (
errors.address ||
errors.branchId ||
errors.firstName ||
errors.lastName ||
errors.phoneNumber ||
errors.recommend
) {
const firstError =
errors.address?.message ||
errors.branchId?.message ||
errors.firstName?.message ||
errors.lastName?.message ||
errors.phoneNumber?.message ||
errors.recommend?.message ||
'Xatolik yuz berdi';
setError(firstError);
setVisible(true);
return;
}
setUser({
firstName: data.firstName,
lastName: data.lastName,
phoneNumber: data.phoneNumber,
});
mutate({
firstName: data.firstName,
lastName: data.lastName,
phoneNumber: data.phoneNumber,
recommend: data.recommend,
branchId: data.branchId,
address: data.address,
fcmToken: '',
deviceId: '',
deviceType: '',
deviceName: '',
});
};
const onErrorSubmit = (errors: any) => {
const firstError =
errors.address?.message ||
errors.branchId?.message ||
errors.firstName?.message ||
errors.lastName?.message ||
errors.phoneNumber?.message ||
errors.recommend?.message;
setError(
firstError ? "Ma'lumotlarni to'liq kiriting" : 'Xatolik yuz berdi',
);
setVisible(true);
};
useEffect(() => {
if (route.params?.termsAccepted) {
setTermsAccepted(true);
Animated.spring(checkboxAnimation, {
toValue: 1,
useNativeDriver: false,
tension: 100,
friction: 8,
}).start();
}
}, [route.params]);
const navigateToTerms = () => {
navigation.navigate('TermsAndConditions');
setTermsAccepted(true);
Animated.spring(checkboxAnimation, {
toValue: 1,
useNativeDriver: false,
tension: 100,
friction: 8,
}).start();
};
const toggleCheckbox = () => {
if (!termsAccepted) {
navigateToTerms();
} else {
setTermsAccepted(false);
Animated.spring(checkboxAnimation, {
toValue: 0,
useNativeDriver: false,
tension: 100,
friction: 8,
}).start();
}
};
return (
<ImageBackground
source={Logo}
style={RegisterStyle.background}
resizeMode="contain"
imageStyle={{
opacity: 0.1,
height: '100%',
width: '90%',
transform: [{ scale: 1 }],
}}
>
<SafeAreaView style={{ flex: 1 }}>
<View style={RegisterStyle.langContainer}>
<TouchableOpacity onPress={() => navigation.navigate('select-auth')}>
<ArrowLeft color="#000" />
</TouchableOpacity>
<LanguageSelector />
</View>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior="padding"
keyboardVerticalOffset={50}
>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<View style={RegisterStyle.scrollContainer}>
<View style={RegisterStyle.loginContainer}>
<AppText style={RegisterStyle.title}>
{t("Ro'yxatdan o'tish")}
</AppText>
<ErrorNotification
setVisible={setVisible}
error={error}
visible={visible}
/>
{/* Ism */}
<Controller
control={control}
name="firstName"
render={({ field: { onChange, value } }) => (
<View>
<AppText style={RegisterStyle.label}>{t('Ism')}</AppText>
<TextInput
ref={firstNameRef}
style={RegisterStyle.input}
placeholder={t('Ismingiz')}
onChangeText={onChange}
value={value}
placeholderTextColor={'#D8DADC'}
returnKeyType="next"
onSubmitEditing={() => lastNameRef.current?.focus()}
/>
</View>
)}
/>
{/* Familiya */}
<Controller
control={control}
name="lastName"
render={({ field: { onChange, value } }) => (
<View>
<AppText style={RegisterStyle.label}>
{t('Familiya')}
</AppText>
<TextInput
ref={lastNameRef}
style={RegisterStyle.input}
placeholder={t('Familiyangiz')}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
returnKeyType="next"
onSubmitEditing={() => phoneRef.current?.focus()}
/>
</View>
)}
/>
{/* Telefon raqami */}
<Controller
control={control}
name="phoneNumber"
render={({ field: { onChange } }) => {
const formatted = formatPhone(rawPhone);
return (
<View>
<AppText style={RegisterStyle.label}>
{t('Telefon raqami')}
</AppText>
<TextInput
ref={phoneRef}
keyboardType="numeric"
placeholder="+998 __ ___-__-__"
value={formatted}
onChangeText={text => {
const digits = text.replace(/\D/g, '').slice(0, 12);
const full = digits.startsWith('998')
? digits
: `998${digits}`;
setRawPhone(full);
onChange(full);
}}
style={RegisterStyle.input}
placeholderTextColor="#D8DADC"
maxLength={17}
returnKeyType="next"
onSubmitEditing={
() => setFilialDropdownVisible(true) // ❗ Branch select ochiladi
}
/>
</View>
);
}}
/>
{/* Filial (dropdown) */}
<Controller
control={control}
name="branchId"
render={({ field: { value } }) => (
<View style={{ position: 'relative' }}>
<AppText style={RegisterStyle.label}>
{t('Filial')}
</AppText>
<View style={RegisterStyle.input}>
<TouchableOpacity
style={RegisterStyle.selector}
onPress={() => {
setFilialDropdownVisible(prev => !prev);
Keyboard.dismiss();
}}
>
<AppText
style={
value
? { color: '#000' }
: RegisterStyle.selectedText
}
>
{branchList?.find(e => e.id === value)?.name ||
t('Filialni tanlang...')}
</AppText>
{filialDropdownVisible ? (
<ArrowUp color={'#000'} />
) : (
<ArrowDown color={'#000'} />
)}
</TouchableOpacity>
</View>
{filialDropdownVisible && (
<View
style={[RegisterStyle.dropdown, { maxHeight: 200 }]}
>
<ScrollView nestedScrollEnabled>
{branchList &&
branchList.map((item: Branch) => (
<TouchableOpacity
key={item.id}
style={RegisterStyle.dropdownItem}
onPress={() => {
setValue('branchId', item.id);
setFilialDropdownVisible(false);
// keyingi inputga focus
setTimeout(
() => addressRef.current?.focus(),
200,
);
}}
>
<AppText
style={RegisterStyle.dropdownItemText}
>
{item.name}
</AppText>
</TouchableOpacity>
))}
</ScrollView>
</View>
)}
</View>
)}
/>
{/* Manzil */}
<Controller
control={control}
name="address"
render={({ field: { onChange, value } }) => (
<View>
<AppText style={RegisterStyle.label}>
{t('Manzilingizni kiriting')}
</AppText>
<TextInput
ref={addressRef}
style={RegisterStyle.input}
placeholder={t(
"Toshkent Shahri, Mirzo Ulug'bek tumani...",
)}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
returnKeyType="done"
onSubmitEditing={
() => setRecommendedDropdownVisible(true) // ❗ recommend select ochiladi
}
/>
</View>
)}
/>
{/* Recommend (dropdown) */}
<Controller
control={control}
name="recommend"
render={({ field: { value } }) => (
<View style={{ position: 'relative' }}>
<AppText style={RegisterStyle.label}>
{t('Bizni qaerdan topdingiz?')}
</AppText>
<View style={RegisterStyle.input}>
<TouchableOpacity
style={RegisterStyle.selector}
onPress={() =>
setRecommendedDropdownVisible(prev => !prev)
}
>
<AppText
style={
value
? { color: '#000' }
: RegisterStyle.selectedText
}
>
{t(
recommended.find(e => e.value === value)?.label ||
'Bizni kim tavsiya qildi...',
)}
</AppText>
{recommendedDropdownVisible ? (
<ArrowUp color={'#000'} />
) : (
<ArrowDown color={'#000'} />
)}
</TouchableOpacity>
</View>
{recommendedDropdownVisible && (
<View
style={[RegisterStyle.dropdown, { maxHeight: 200 }]}
>
<ScrollView nestedScrollEnabled>
{recommended.map((item, index) => (
<TouchableOpacity
key={index}
style={RegisterStyle.dropdownItem}
onPress={() => {
setValue('recommend', item.value);
setRecommendedDropdownVisible(false);
}}
>
<AppText style={RegisterStyle.dropdownItemText}>
{t(item.label)}
</AppText>
</TouchableOpacity>
))}
</ScrollView>
</View>
)}
</View>
)}
/>
{/* Terms */}
<View style={RegisterStyle.termsContainer}>
<TouchableOpacity
style={RegisterStyle.checkboxContainer}
onPress={toggleCheckbox}
activeOpacity={0.7}
>
<Animated.View
style={[
RegisterStyle.checkbox,
termsAccepted && RegisterStyle.checkboxChecked,
{
transform: [
{
scale: checkboxAnimation.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 1.1, 1],
}),
},
],
},
]}
>
{termsAccepted && (
<Animated.View
style={{
opacity: checkboxAnimation,
transform: [
{
scale: checkboxAnimation,
},
],
}}
>
<Check color="#fff" />
</Animated.View>
)}
</Animated.View>
<View style={RegisterStyle.termsTextContainer}>
<AppText style={RegisterStyle.termsText}>
<AppText>{t('Foydalanish shartlari')} </AppText>
<AppText> {t('bilan tanishib chiqdim!')} </AppText>
</AppText>
</View>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={handleSubmit(onSubmit, onErrorSubmit)}
style={[
RegisterStyle.button,
(!termsAccepted || isPending) &&
RegisterStyle.buttonDisabled,
]}
disabled={!termsAccepted || isPending}
>
{isPending ? (
<ActivityIndicator color="#fff" />
) : (
<AppText
style={[
RegisterStyle.btnText,
(!termsAccepted || isPending) &&
RegisterStyle.buttonTextDisabled,
]}
>
{t('Davom etish')}
</AppText>
)}
</TouchableOpacity>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
</ImageBackground>
);
};
export default FirstStep;