added notification

This commit is contained in:
Samandar Turgunboyev
2025-09-04 10:06:46 +05:00
parent e51ff4f502
commit f55a3a50ed
54 changed files with 2502 additions and 643 deletions

View File

@@ -1,4 +1,6 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation } from '@tanstack/react-query';
@@ -17,9 +19,9 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
import LanguageSelector from 'screens/auth/select-language/SelectLang';
import ArrowLeft from 'svg/ArrowLeft';
import { RootStackParamList } from 'types/types';
@@ -36,27 +38,57 @@ const OTP_LENGTH = 4;
const Confirm = () => {
const navigation = useNavigation<VerificationCodeScreenNavigationProp>();
const { t } = useTranslation();
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const [code, setCode] = useState<string[]>(new Array(OTP_LENGTH).fill(''));
const [timer, setTimer] = useState(60);
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
const [canResend, setCanResend] = useState(false);
const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state);
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
onSuccess: async res => {
await AsyncStorage.setItem('token', res.data.accessToken);
navigation.navigate('Home');
setErrorConfirm(null);
console.log(res);
},
onError: (err: any) => {
setErrorConfirm(err?.response.data.message);
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
},
});
const { mutate: resendMutate } = useMutation({
mutationFn: (payload: resendPayload) => authApi.resendOtp(payload),
onSuccess: async res => {
onSuccess: async () => {
setTimer(60);
setCanResend(false);
setCode(new Array(OTP_LENGTH).fill(''));
@@ -64,17 +96,14 @@ const Confirm = () => {
setErrorConfirm(null);
},
onError: (err: any) => {
setErrorConfirm(err?.response.data.message);
setErrorConfirm(err?.response?.data?.message || 'Xato yuz berdi');
},
});
const openModal = useModalStore(state => state.openModal);
useEffect(() => {
let interval: NodeJS.Timeout | null = null;
if (timer > 0) {
interval = setInterval(() => {
setTimer(prevTimer => prevTimer - 1);
}, 1000);
interval = setInterval(() => setTimer(prev => prev - 1), 1000);
} else {
setCanResend(true);
if (interval) clearInterval(interval);
@@ -104,20 +133,21 @@ const Confirm = () => {
};
const handleResendCode = () => {
resendMutate({
phoneNumber: phoneNumber,
otpType: 'LOGIN',
});
resendMutate({ phoneNumber, otpType: 'LOGIN' });
};
const handleVerifyCode = () => {
const enteredCode = code.join('');
mutate({
phoneNumber: phoneNumber,
otp: String(enteredCode),
otpType: 'LOGIN',
});
// navigation.navigate('Home');
if (firebaseToken) {
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'LOGIN',
deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
};
return (
@@ -147,15 +177,13 @@ const Confirm = () => {
<Text style={styles.title}>{t('Tasdiqlash kodini kiriting')}</Text>
<Text style={styles.message}>
{phoneNumber} {t('raqamiga yuborilgan')} {OTP_LENGTH}{' '}
{t('xonali kodni kiriting.')}
{t('xonali kodni kiriting.')}{' '}
</Text>
<View style={styles.otpContainer}>
{code.map((digit, index) => (
<TextInput
key={index}
ref={ref => {
inputRefs.current[index] = ref;
}}
ref={ref => (inputRefs.current[index] = ref)}
style={styles.otpInput}
keyboardType="number-pad"
maxLength={1}
@@ -166,7 +194,7 @@ const Confirm = () => {
/>
))}
</View>
{errorConfirm !== null && (
{errorConfirm && (
<Text style={styles.errorText}>{errorConfirm}</Text>
)}
<View style={styles.resendContainer}>
@@ -210,10 +238,7 @@ const Confirm = () => {
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#f8f9fa',
},
safeArea: { flex: 1, backgroundColor: '#f8f9fa' },
errorText: {
color: 'red',
fontSize: 14,
@@ -221,33 +246,22 @@ const styles = StyleSheet.create({
marginTop: 10,
textAlign: 'center',
},
buttonTextDisabled: {
color: 'white',
},
buttonTextDisabled: { color: 'white' },
langContainer: {
width: '100%',
marginTop: 10,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
},
headerTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
content: {
width: '100%',
alignItems: 'center',
},
content: { width: '100%', alignItems: 'center' },
title: {
fontSize: 24,
fontWeight: 'bold',
@@ -280,24 +294,10 @@ const styles = StyleSheet.create({
color: '#333',
backgroundColor: '#fff',
},
resendContainer: {
marginBottom: 30,
marginTop: 20,
},
timerText: {
fontSize: 15,
color: '#999',
},
resendButton: {
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 8,
},
resendButtonText: {
fontSize: 15,
color: '#007bff',
fontWeight: 'bold',
},
resendContainer: { marginBottom: 30, marginTop: 20 },
timerText: { fontSize: 15, color: '#999' },
resendButton: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 8 },
resendButtonText: { fontSize: 15, color: '#007bff', fontWeight: 'bold' },
verifyButton: {
backgroundColor: '#007bff',
paddingVertical: 15,
@@ -311,11 +311,7 @@ const styles = StyleSheet.create({
shadowOpacity: 0.2,
shadowRadius: 4,
},
verifyButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: '600',
},
verifyButtonText: { color: '#fff', fontSize: 18, fontWeight: '600' },
});
export default Confirm;

View File

@@ -1,4 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation, useQuery } from '@tanstack/react-query';
@@ -6,7 +8,13 @@ import { authApi } from 'api/auth';
import { loginPayload } from 'api/auth/type';
import { Branch, branchApi } from 'api/branch';
import formatPhone from 'helpers/formatPhone';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import {
@@ -20,6 +28,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import { LoginFormType, loginSchema } from 'screens/auth/login/lib/form';
@@ -50,6 +59,36 @@ const Login = () => {
queryKey: ['branchList'],
queryFn: branchApi.branchList,
});
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
deviceType: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
deviceType: await DeviceInfo.getDeviceType(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: loginPayload) => authApi.login(payload),
@@ -84,6 +123,10 @@ const Login = () => {
passportSerial: `${data.passportSeriya.toUpperCase()}${
data.passportNumber
}`,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceName || '',
});
// navigation.navigate('Login-Confirm');
setUser({

View File

@@ -4,6 +4,7 @@ import { z } from 'zod';
export const FirstStepSchema = z.object({
firstName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
lastName: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
address: z.string().min(3, "Eng kamida 3ta belgi bo'lishi kerak"),
phoneNumber: z.string().min(12, 'Xato raqam kiritildi'),
branchId: z.number().min(1, 'Filialni tanlang'),
recommend: z.string().min(1, 'Majburiy maydon'),

View File

@@ -1,4 +1,6 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useMutation } from '@tanstack/react-query';
@@ -17,6 +19,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import { useModalStore } from 'screens/auth/registeration/lib/modalStore';
@@ -46,6 +49,34 @@ const Confirm = ({
const [errorConfirm, setErrorConfirm] = useState<string | null>(null);
const inputRefs = useRef<Array<TextInput | null>>([]);
const { phoneNumber } = useUserStore(state => state);
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: otpPayload) => authApi.verifyOtp(payload),
onSuccess: async res => {
@@ -54,6 +85,7 @@ const Confirm = ({
setErrorConfirm(null);
},
onError: (err: any) => {
console.dir(err);
setErrorConfirm(err?.response.data.message);
},
});
@@ -116,11 +148,16 @@ const Confirm = ({
const handleVerifyCode = () => {
const enteredCode = code.join('');
mutate({
phoneNumber: phoneNumber,
otp: String(enteredCode),
otpType: 'REGISTRATION',
});
if (firebaseToken) {
mutate({
phoneNumber,
otp: enteredCode,
otpType: 'REGISTRATION',
deviceId: firebaseToken.deviceId,
deviceName: firebaseToken.deviceName,
fcmToken: firebaseToken.fcmToken,
});
}
};
return (

View File

@@ -1,6 +1,8 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { getApp } from '@react-native-firebase/app';
import { getMessaging, getToken } from '@react-native-firebase/messaging';
import {
type RouteProp,
useNavigation,
@@ -27,6 +29,7 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import { SafeAreaView } from 'react-native-safe-area-context';
import Logo from 'screens/../../assets/bootsplash/logo_512.png';
import {
@@ -64,14 +67,43 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
queryFn: branchApi.branchList,
});
const [firebaseToken, setFirebseToken] = useState<{
fcmToken: string;
deviceId: string;
deviceName: string;
deviceType: string;
} | null>();
const app = getApp();
const messaging = getMessaging(app);
const getDeviceData = async () => {
try {
const fcmToken = await getToken(messaging);
return {
fcmToken,
deviceId: await DeviceInfo.getUniqueId(),
deviceName: await DeviceInfo.getDeviceName(),
deviceType: await DeviceInfo.getDeviceType(),
};
} catch (e) {
console.log('Xato:', e);
return null;
}
};
useEffect(() => {
getDeviceData().then(data => {
setFirebseToken(data);
});
}, []);
const { mutate, isPending } = useMutation({
mutationFn: (payload: registerPayload) => authApi.register(payload),
onSuccess: res => {
onNext();
},
onError: err => {
console.dir(err);
setError('Xatolik yuz berdi');
},
});
@@ -93,6 +125,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
resolver: zodResolver(FirstStepSchema),
defaultValues: {
firstName: '',
address: '',
lastName: '',
recommend: '',
},
@@ -104,7 +137,18 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
lastName: data.lastName,
phoneNumber: data.phoneNumber,
});
mutate(data);
mutate({
firstName: data.firstName,
lastName: data.lastName,
phoneNumber: data.phoneNumber,
recommend: data.recommend,
branchId: data.branchId,
address: data.address,
fcmToken: firebaseToken?.fcmToken || '',
deviceId: firebaseToken?.deviceId || '',
deviceType: firebaseToken?.deviceType || '',
deviceName: firebaseToken?.deviceId || '',
});
};
useEffect(() => {
@@ -143,6 +187,7 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
}).start();
}
};
return (
<ImageBackground
source={Logo}
@@ -314,6 +359,31 @@ const FirstStep = ({ onNext }: { onNext: () => void }) => {
</View>
)}
/>
<Controller
control={control}
name="address"
render={({ field: { onChange, value } }) => (
<View>
<Text style={RegisterStyle.label}>
{t('Manzilingizni kiriting')}
</Text>
<TextInput
style={RegisterStyle.input}
placeholder={t(
"Toshkent Shahri, Mirzo Ulug'bek tumani...",
)}
placeholderTextColor={'#D8DADC'}
onChangeText={onChange}
value={value}
/>
{errors.lastName && (
<Text style={RegisterStyle.errorText}>
{t(errors.lastName.message || '')}
</Text>
)}
</View>
)}
/>
<Controller
control={control}
name="recommend"

View File

@@ -35,6 +35,7 @@ import ArrowLeft from 'svg/ArrowLeft';
import Calendar from 'svg/Calendar';
import { RootStackParamList } from 'types/types';
import { SecondStepFormType, SecondStepSchema } from '../lib/form';
import { useUserStore } from '../lib/userstore';
import { RegisterStyle } from './styled';
interface FileData {
@@ -54,6 +55,7 @@ const SecondStep = () => {
const passportNumberRef = useRef<TextInput>(null);
const [checkboxAnimation] = useState(new Animated.Value(1));
const [inputValue, setInputValue] = useState('');
const { firstName, lastName } = useUserStore(state => state);
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>();
@@ -111,7 +113,7 @@ const SecondStep = () => {
const isoBirthDate = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
mutate({
fullName: data.passportSeriya.toUpperCase(),
fullName: `${firstName} ${lastName}`,
birthDate: isoBirthDate,
passportSerial: `${data.passportSeriya.toUpperCase()}${
data.passportNumber

View File

@@ -24,8 +24,6 @@ const SelectAuth = () => {
const navigation = useNavigation<LoginScreenNavigationProp>();
const { width } = useWindowDimensions();
const isSmallScreen = width < 360;
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
@@ -41,20 +39,15 @@ const SelectAuth = () => {
style={[
styles.logoImage,
{
width: isSmallScreen ? 120 : 250,
height: isSmallScreen ? 120 : 200,
borderRadius: 20000,
width: 180,
height: 180,
},
]}
/>
<Text
style={[styles.logoText, { fontSize: isSmallScreen ? 22 : 50 }]}
>
CPOST
</Text>
<Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
</View>
<Text style={[styles.title, { fontSize: isSmallScreen ? 20 : 24 }]}>
<Text style={[styles.title, { fontSize: 24 }]}>
{t('Royxatdan otganmisz')}
</Text>
@@ -110,17 +103,18 @@ const styles = StyleSheet.create({
},
logoWrapper: {
alignItems: 'center',
marginBottom: 40,
marginBottom: 20,
},
logoImage: {
resizeMode: 'stretch',
},
logoText: {
fontWeight: '700',
fontWeight: '500',
marginTop: 4,
color: '#28A7E8',
},
title: {
fontWeight: '600',
fontWeight: '500',
textAlign: 'center',
color: '#28A7E8',
marginBottom: 20,

View File

@@ -7,7 +7,6 @@ import {
StyleSheet,
Text,
TouchableOpacity,
useWindowDimensions,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
@@ -21,9 +20,6 @@ type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
const SelectLangPage = () => {
const navigation = useNavigation<NavigationProp>();
const { width } = useWindowDimensions();
const isSmallScreen = width < 380;
const selectLanguage = async (lang: 'uz' | 'ru') => {
await changeLanguage(lang);
@@ -45,24 +41,17 @@ const SelectLangPage = () => {
style={[
styles.logoImage,
{
width: isSmallScreen ? 120 : 250,
height: isSmallScreen ? 120 : 200,
borderRadius: 20000,
width: 180,
height: 180,
},
]}
/>
<Text
style={[styles.logoText, { fontSize: isSmallScreen ? 24 : 40 }]}
>
CPOST
</Text>
<Text style={[styles.logoText, { fontSize: 24 }]}>CPOST</Text>
</View>
<Text style={[styles.title, { fontSize: isSmallScreen ? 18 : 24 }]}>
<Text style={[styles.title, { fontSize: 24 }]}>
Tilni tanlang{' '}
<Text
style={[styles.title, { fontSize: isSmallScreen ? 14 : 18 }]}
>
<Text style={[styles.title, { fontSize: 18 }]}>
(Выберите язык)
</Text>
</Text>
@@ -118,11 +107,11 @@ const styles = StyleSheet.create({
marginBottom: 8,
},
logoText: {
fontWeight: '700',
fontWeight: '500',
color: '#28A7E8',
},
title: {
fontWeight: '600',
fontWeight: '500',
textAlign: 'center',
color: '#28A7E8',
marginBottom: 24,