561 lines
20 KiB
TypeScript
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;
|