From 22c1688781961872c3f073cb65b0273fa13ee61b Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Wed, 18 Mar 2026 17:56:26 +0500 Subject: [PATCH] register update --- api/URLs.ts | 3 +- api/httpClient.ts | 1 - app.json | 2 +- app/(auth)/register.tsx | 247 +----- app/(auth)/select-category.tsx | 49 +- app/(dashboard)/_layout.tsx | 3 - components/NotificationProvider.tsx | 5 - components/ui/CategorySelect.tsx | 8 +- components/ui/FilterUI.tsx | 2 - components/ui/IndustrySelection.tsx | 8 +- components/ui/RefreshContext.tsx | 4 +- components/ui/gluestack-ui-provider/script.ts | 4 +- constants/crypto.ts | 41 + constants/formatText.ts | 55 ++ hooks/storage.native.ts | 10 +- hooks/useNotifications.ts | 1 - i18n/locales/en.json | 4 +- i18n/locales/ru.json | 4 +- i18n/locales/uz.json | 4 +- package-lock.json | 15 + package.json | 2 + screens/auth/confirm/ConfirmScreen.tsx | 4 +- screens/auth/login/lib/api.ts | 404 +++------ screens/auth/login/ui/LoginForm.tsx | 1 - .../auth/register-confirm/ConfirmScreen.tsx | 7 +- .../register/RegisterCategorySelection.tsx | 16 +- screens/auth/register/RegisterForm.tsx | 789 ++++++++---------- screens/auth/register/lib/useRegisterStore.ts | 14 +- screens/home/ui/HomeScreen.tsx | 1 - screens/profile/ui/EditServices.tsx | 2 - screens/profile/ui/RefferallsTab.tsx | 1 - 31 files changed, 631 insertions(+), 1080 deletions(-) create mode 100644 constants/crypto.ts create mode 100644 constants/formatText.ts diff --git a/api/URLs.ts b/api/URLs.ts index 93ce264..d525e40 100644 --- a/api/URLs.ts +++ b/api/URLs.ts @@ -37,5 +37,6 @@ export const API_URLS = { Notification_Ready: (id: number) => `/api/notifications/${id}/read/`, Notification_Mark_All_Read: '/api/notifications/read-all/', Info: "/auth/get-person/", - Get_Director_Info: "/auth/get-director/" + Get_Director_Info: "/auth/get-director/", + GetTokens: "/api/tokens/", }; diff --git a/api/httpClient.ts b/api/httpClient.ts index 1defde2..34395fe 100644 --- a/api/httpClient.ts +++ b/api/httpClient.ts @@ -82,7 +82,6 @@ httpClient.interceptors.response.use( // Original so'rovni qayta yuboramiz return httpClient(originalRequest); } catch (refreshError) { - console.error('Refresh token xatosi:', refreshError); // Refresh muvaffaqiyatsiz bo'lsa processQueue(refreshError); diff --git a/app.json b/app.json index 8b8ce6e..f1da3c7 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Info target", "slug": "infotarget", - "version": "1.0.2", + "version": "1.0.3", "orientation": "portrait", "icon": "./assets/images/logo.png", "scheme": "infotarget", diff --git a/app/(auth)/register.tsx b/app/(auth)/register.tsx index 70a5c40..4c2ea8d 100644 --- a/app/(auth)/register.tsx +++ b/app/(auth)/register.tsx @@ -1,247 +1,8 @@ -import AuthHeader from '@/components/ui/AuthHeader'; -import { PersonType, useRegister } from '@/screens/auth/register/lib/useRegisterStore'; -import { LinearGradient } from 'expo-linear-gradient'; -import { useRouter } from 'expo-router'; -import { Building2, ChevronRight, ShieldCheck, User } from 'lucide-react-native'; +import RegisterFormScreen from '@/screens/auth/register/RegisterForm'; import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { - Animated, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -const PERSON_TYPES: { - key: PersonType; - label: string; - description: string; - icon: React.ReactNode; - gradient: [string, string]; -}[] = [ - { - key: 'legal_entity', - label: 'Yuridik shaxs', - description: "Tashkilot yoki korxona", - icon: , - gradient: ['#f59e0b', '#d97706'], - }, - { - key: 'yatt', - label: 'YATT', - description: "Yakka tartibdagi tadbirkor", - icon: , - gradient: ['#10b981', '#059669'], - }, - { - key: 'band', - label: "O'zini o'zi band qilgan", - description: "O'z faoliyatini mustaqil yurituvchi", - icon: , - gradient: ['#3b82f6', '#2563eb'], - }, - ]; - -function TypeCard({ - item, - index, - onPress, -}: { - item: (typeof PERSON_TYPES)[number]; - index: number; - onPress: () => void; -}) { - const scale = React.useRef(new Animated.Value(1)).current; - const { t } = useTranslation() - - const handlePressIn = () => { - Animated.spring(scale, { - toValue: 0.96, - useNativeDriver: true, - }).start(); - }; - - const handlePressOut = () => { - Animated.spring(scale, { - toValue: 1, - friction: 4, - useNativeDriver: true, - }).start(); - }; +export default function Index() { return ( - - - - {item.icon} - - - {t(item.label)} - {t(item.description)} - - - - + ); -} - -export default function PersonTypeScreen() { - const router = useRouter(); - const { t } = useTranslation(); - const { setPersonType, reset } = useRegister(); - - const handleSelect = (type: PersonType) => { - setPersonType(type); - router.push('/(auth)/register-form'); - }; - - return ( - - - - - - - - {t("Ro'yxatdan o'tish")} - - {t("Faoliyat turingizni tanlang")} - - - - - {PERSON_TYPES.map((item, index) => ( - { handleSelect(item.key); reset() }} - /> - ))} - - - - router.back()} testID="go-to-login"> - - {t("Hisobingiz bormi?")} {t("Kirish")} - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0f172a', - }, - safeArea: { - flex: 1, - paddingHorizontal: 14, - }, - header: { - alignItems: 'center', - marginBottom: 20, - marginTop: 20, - }, - title: { - fontSize: 28, - fontWeight: '800' as const, - color: '#ffffff', - marginBottom: 10, - letterSpacing: 0.5, - }, - subtitle: { - fontSize: 15, - color: '#94a3b8', - textAlign: 'center', - lineHeight: 22, - }, - cardList: { - marginBottom: 14, - }, - card: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.06)', - borderRadius: 20, - padding: 18, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, 0.1)', - gap: 16, - }, - cardIcon: { - width: 56, - height: 56, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - }, - cardContent: { - flex: 1, - }, - cardTitle: { - fontSize: 17, - fontWeight: '700' as const, - color: '#ffffff', - marginBottom: 4, - }, - cardDescription: { - fontSize: 13, - color: '#94a3b8', - lineHeight: 18, - }, - footer: { - marginTop: 'auto' as const, - paddingBottom: 20, - alignItems: 'center', - }, - footerText: { - color: '#94a3b8', - fontSize: 14, - }, - footerLink: { - color: '#3b82f6', - fontWeight: '700' as const, - }, - decorCircle1: { - position: 'absolute', - top: -150, - right: -100, - width: 400, - height: 400, - borderRadius: 200, - backgroundColor: 'rgba(59, 130, 246, 0.1)', - }, - decorCircle2: { - position: 'absolute', - bottom: -100, - left: -150, - width: 350, - height: 350, - borderRadius: 175, - backgroundColor: 'rgba(16, 185, 129, 0.08)', - }, -}); +} \ No newline at end of file diff --git a/app/(auth)/select-category.tsx b/app/(auth)/select-category.tsx index 91d95f7..34798b2 100644 --- a/app/(auth)/select-category.tsx +++ b/app/(auth)/select-category.tsx @@ -11,13 +11,13 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActivityIndicator, - Alert, ScrollView, StyleSheet, Text, TouchableOpacity, - View, + View } from 'react-native'; +import { Toast } from 'toastify-react-native'; interface Category { id: number; @@ -28,15 +28,17 @@ interface Category { export default function CategorySelectScreen() { const router = useRouter(); const { t } = useTranslation(); - const { phone, stir, person_type, director_full_name, referal, first_name, last_name, middle_name } = useLocalSearchParams<{ + const { phone, stir, person_type, referal, director_full_name, first_name, last_name, district, company_name, address } = useLocalSearchParams<{ phone: string; stir: string; - person_type: 'band' | 'ytt'; + person_type: 'band' | 'ytt' | "legal_entity"; referal: string; director_full_name: string; first_name: string; last_name: string; - middle_name: string; + district: string + address: string; + company_name: string; }>(); const [selected, setSelected] = useState(null); @@ -69,22 +71,32 @@ export default function CategorySelectScreen() { person_type: string; activate_types: number[]; director_full_name: string; - referal: string; + referral: string; first_name: string; last_name: string; + district: number; + company_name: string; + address: number; }) => auth_api.register(body), onSuccess: async () => { router.replace('/(auth)/register-confirm'); await AsyncStorage.setItem('phone', phone); }, onError: (err: AxiosError) => { - const errMessage = (err.response?.data as { data: { stir: string[] } }).data.stir[0]; - const errMessageDetail = (err.response?.data as { data: { detail: string } }).data.detail; + const errMessage = (err.response?.data as any)?.data?.stir?.[0]; + const errMessageDetail = (err.response?.data as any)?.data?.detail; + const errMessageReffral = (err.response?.data as any).data.referral[0]; + const errMessageDetailData = (err.response?.data as any)?.data; - const errrAlert = errMessage ? errMessage : errMessageDetail; + const message = + errMessage || + errMessageReffral || + errMessageDetail || + errMessageDetailData || + t('Xatolik yuz berdi'); - Alert.alert(t('Xatolik yuz berdi'), errMessage || errrAlert || t('erroXatolik yuz berdi')); - }, + Toast.error(String(message)); + } }); const onCategoryPress = (cat: Category) => { @@ -103,8 +115,6 @@ export default function CategorySelectScreen() { setSelected(null); }; - const full_name = first_name.length > 0 ? first_name + ' ' + last_name + ' ' + middle_name : director_full_name; - return ( @@ -149,10 +159,13 @@ export default function CategorySelectScreen() { person_type, phone: `998${phone}`, stir, - referal: referal, - director_full_name: director_full_name, - first_name: full_name, - last_name: last_name, + referral: referal, + director_full_name, + first_name, + last_name, + district: Number(district), + address: Number(address), + company_name }); }} > @@ -179,7 +192,7 @@ const styles = StyleSheet.create({ container: { paddingHorizontal: 20, paddingTop: 16, - paddingBottom: 70, + paddingBottom: 90, gap: 12, }, diff --git a/app/(dashboard)/_layout.tsx b/app/(dashboard)/_layout.tsx index 794efbe..d724066 100644 --- a/app/(dashboard)/_layout.tsx +++ b/app/(dashboard)/_layout.tsx @@ -27,9 +27,6 @@ export default function TabsLayout() { }; }, []); - console.log(keyboard); - - useEffect(() => { Animated.loop( Animated.timing(rotateAnim, { diff --git a/components/NotificationProvider.tsx b/components/NotificationProvider.tsx index 8c1e811..89416ce 100644 --- a/components/NotificationProvider.tsx +++ b/components/NotificationProvider.tsx @@ -34,7 +34,6 @@ export async function registerForPushNotificationsAsync() { } if (finalStatus !== 'granted') { - console.log('Notification uchun ruxsat berilmadi!'); return; } @@ -43,10 +42,6 @@ export async function registerForPushNotificationsAsync() { projectId: '67d5a024-4eb7-44ec-8b18-6c9187bd1862', }) ).data; - - console.log('Push Token:', token); - } else { - console.log('Push notification faqat real qurilmalarda ishlaydi!'); } return token; diff --git a/components/ui/CategorySelect.tsx b/components/ui/CategorySelect.tsx index 9e81944..458f64d 100644 --- a/components/ui/CategorySelect.tsx +++ b/components/ui/CategorySelect.tsx @@ -1,9 +1,9 @@ import { useTheme } from '@/components/ThemeContext'; import { products_api } from '@/screens/home/lib/api'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { AxiosError } from 'axios'; import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import React, { Dispatch, SetStateAction, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ActivityIndicator, FlatList, @@ -12,6 +12,7 @@ import { TouchableOpacity, View, } from 'react-native'; +import { Toast } from 'toastify-react-native'; interface Category { id: number; @@ -44,6 +45,7 @@ export default function CategorySelect({ selectedCategories, setSelectedCategori const [currentCategories, setCurrentCategories] = useState([]); const [currentParentId, setCurrentParentId] = useState(null); const [history, setHistory] = useState([]); + const { t } = useTranslation() // Root categories const { isLoading: rootLoading, error: rootError } = useQuery({ @@ -66,8 +68,8 @@ export default function CategorySelect({ selectedCategories, setSelectedCategori setCurrentCategories(childCategories); setCurrentParentId(id); }, - onError: (err: AxiosError) => { - console.error('Child category loading error:', err); + onError: () => { + Toast.error(t("Xatolik yuz berdi")) }, }); diff --git a/components/ui/FilterUI.tsx b/components/ui/FilterUI.tsx index cd5f6b6..b303442 100644 --- a/components/ui/FilterUI.tsx +++ b/components/ui/FilterUI.tsx @@ -6,7 +6,6 @@ import BottomSheet, { BottomSheetScrollView, } from '@gorhom/bottom-sheet'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { AxiosError } from 'axios'; import { Image } from 'expo-image'; import { CheckIcon, ChevronRight, XIcon } from 'lucide-react-native'; import React, { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react'; @@ -75,7 +74,6 @@ export default function FilterUI({ back, onApply, setStep, setFiltered }: Filter setStep('items'); setFiltered(data.data.data.results); }, - onError: (error: AxiosError) => console.log(error), }); const handleApply = () => { diff --git a/components/ui/IndustrySelection.tsx b/components/ui/IndustrySelection.tsx index ce4cb99..c13d9aa 100644 --- a/components/ui/IndustrySelection.tsx +++ b/components/ui/IndustrySelection.tsx @@ -1,8 +1,8 @@ import { products_api } from '@/screens/home/lib/api'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { AxiosError } from 'axios'; import { ChevronLeft, ChevronRight } from 'lucide-react-native'; import React, { Dispatch, SetStateAction, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ActivityIndicator, FlatList, @@ -11,6 +11,7 @@ import { TouchableOpacity, View, } from 'react-native'; +import { Toast } from 'toastify-react-native'; import { useTheme } from '../ThemeContext'; interface Category { @@ -39,6 +40,7 @@ export default function CategorySelection({ selectedCategories, setSelectedCateg const [history, setHistory] = useState<{ parentId: number | null; categories: Category[] }[]>([]); const [currentParentId, setCurrentParentId] = useState(null); const { isDark } = useTheme(); + const { t } = useTranslation() const theme = { cardBg: isDark ? '#1e293b' : '#f8fafc', @@ -75,8 +77,8 @@ export default function CategorySelection({ selectedCategories, setSelectedCateg setCurrentCategories(childCategories); setCurrentParentId(id); }, - onError: (err: AxiosError) => { - console.error('Child yuklashda xato:', err); + onError: () => { + Toast.error(t("Xatolik yuz berdi")) }, }); diff --git a/components/ui/RefreshContext.tsx b/components/ui/RefreshContext.tsx index 74f71a7..901391c 100644 --- a/components/ui/RefreshContext.tsx +++ b/components/ui/RefreshContext.tsx @@ -18,9 +18,7 @@ export function RefreshProvider({ children }: { children: React.ReactNode }) { setRefreshing(true); try { await queryClient.refetchQueries(); - } catch (err) { - console.error('Global refresh error:', err); - } finally { + } catch (err) { } finally { setRefreshing(false); } }, [queryClient, refreshing]); diff --git a/components/ui/gluestack-ui-provider/script.ts b/components/ui/gluestack-ui-provider/script.ts index 732d136..75ea4a6 100644 --- a/components/ui/gluestack-ui-provider/script.ts +++ b/components/ui/gluestack-ui-provider/script.ts @@ -13,7 +13,5 @@ export const script = (mode: string) => { documentElement.classList.remove(theme === 'light' ? 'dark' : 'light'); documentElement.classList.add(theme); documentElement.style.colorScheme = theme; - } catch (e) { - console.error(e); - } + } catch (e) { } }; diff --git a/constants/crypto.ts b/constants/crypto.ts new file mode 100644 index 0000000..331009d --- /dev/null +++ b/constants/crypto.ts @@ -0,0 +1,41 @@ +import CryptoJS from 'crypto-js'; + +/** + * Backenddan kelgan shifrlangan tokenni ochish + */ +export const decryptToken = (encryptedBase64: string) => { + try { + // 1. Base64 dan WordArray ga + const encryptedData = CryptoJS.enc.Base64.parse(encryptedBase64); + + // 2. IV (dastlabki 16 bayt) + const iv = CryptoJS.lib.WordArray.create(encryptedData.words.slice(0, 4)); + + // 3. Ciphertext (qolgan qism) + const ciphertext = CryptoJS.lib.WordArray.create( + encryptedData.words.slice(4), + encryptedData.sigBytes - 16 + ); + + // 4. Maxfiy kalit + const key = CryptoJS.enc.Utf8.parse("12345678901234567890123456789012"); + + // 5. CipherParams yaratish + const cipherParams = CryptoJS.lib.CipherParams.create({ + ciphertext, + key, + iv + }); + + // 6. Dekodlash + const decrypted = CryptoJS.AES.decrypt(cipherParams, key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); + + return decrypted.toString(CryptoJS.enc.Utf8); + } catch (error) { + return null; + } +}; \ No newline at end of file diff --git a/constants/formatText.ts b/constants/formatText.ts new file mode 100644 index 0000000..1207ddf --- /dev/null +++ b/constants/formatText.ts @@ -0,0 +1,55 @@ +const latinToCyrillicMap = [ + ["O'", "Ў"], ["o'", "ў"], + ["G'", "Ғ"], ["g'", "ғ"], + ["Sh", "Ш"], ["sh", "ш"], + ["Ch", "Ч"], ["ch", "ч"], + ["A", "А"], ["B", "Б"], ["D", "Д"], ["E", "Е"], ["F", "Ф"], + ["G", "Г"], ["H", "Ҳ"], ["I", "И"], ["J", "Ж"], ["K", "К"], + ["L", "Л"], ["M", "М"], ["N", "Н"], ["O", "О"], ["P", "П"], + ["Q", "Қ"], ["R", "Р"], ["S", "С"], ["T", "Т"], ["U", "У"], + ["V", "В"], ["X", "Х"], ["Y", "Й"], ["Z", "З"], + ["a", "а"], ["b", "б"], ["d", "д"], ["e", "е"], ["f", "ф"], + ["g", "г"], ["h", "ҳ"], ["i", "и"], ["j", "ж"], ["k", "к"], + ["l", "л"], ["m", "м"], ["n", "н"], ["o", "о"], ["p", "п"], + ["q", "қ"], ["r", "р"], ["s", "с"], ["t", "т"], ["u", "у"], + ["v", "в"], ["x", "х"], ["y", "й"], ["z", "з"], + ["'", ""] // apostrofni olib tashlaymiz +]; + +export function formatText(str: string | null) { + if (!str) return null; + let result = str; + for (let [latin, cyrillic] of latinToCyrillicMap) { + const regex = new RegExp(latin, "g"); + result = result.replace(regex, cyrillic); + } + return result; +} + +const cyrillicToLatinMap = [ + ["Ў", "O'"], ["ў", "o'"], + ["Ғ", "G'"], ["ғ", "g'"], + ["Ш", "Sh"], ["ш", "sh"], + ["Ч", "Ch"], ["ч", "ch"], + ["ё", "yo"], ["Ё", "YO"], + ["А", "A"], ["Б", "B"], ["Д", "D"], ["Е", "E"], ["Ф", "F"], + ["Г", "G"], ["Ҳ", "H"], ["И", "I"], ["Ж", "J"], ["К", "K"], + ["Л", "L"], ["М", "M"], ["Н", "N"], ["О", "O"], ["П", "P"], + ["Қ", "Q"], ["Р", "R"], ["С", "S"], ["Т", "T"], ["У", "U"], + ["В", "V"], ["Х", "X"], ["Й", "Y"], ["З", "Z"], + ["а", "a"], ["б", "b"], ["д", "d"], ["е", "e"], ["ф", "f"], + ["г", "g"], ["ҳ", "h"], ["и", "i"], ["ж", "j"], ["к", "k"], + ["л", "l"], ["м", "m"], ["н", "n"], ["о", "o"], ["п", "p"], + ["қ", "q"], ["р", "r"], ["с", "s"], ["т", "t"], ["у", "u"], + ["в", "v"], ["х", "x"], ["й", "y"], ["з", "z"], +]; + +export function formatTextToLatin(str: string | null) { + if (!str) return null; + let result = str; + for (let [cyrillic, latin] of cyrillicToLatinMap) { + const regex = new RegExp(cyrillic, "g"); + result = result.replace(regex, latin); + } + return result; +} \ No newline at end of file diff --git a/hooks/storage.native.ts b/hooks/storage.native.ts index 5089c3c..b4ec181 100644 --- a/hooks/storage.native.ts +++ b/hooks/storage.native.ts @@ -19,10 +19,7 @@ export const deleteAsyncStorage = async (name: string) => { export const saveLang = async (lang: string) => { try { await AsyncStorage.setItem('lang', lang); - console.log(`Language saved: ${lang}`); - } catch (error) { - console.error('Failed to save language', error); - } + } catch (error) { } }; /** @@ -34,7 +31,6 @@ export const getLang = async (): Promise => { const lang = await AsyncStorage.getItem('lang'); return lang; } catch (error) { - console.error('Failed to get language', error); return null; } }; @@ -45,9 +41,7 @@ export const getLang = async (): Promise => { export const deleteLang = async () => { try { await AsyncStorage.removeItem('lang'); - } catch (error) { - console.error('Failed to delete language', error); - } + } catch (error) { } }; const ACCESS = 'access_token'; diff --git a/hooks/useNotifications.ts b/hooks/useNotifications.ts index bb5677d..d3b2a81 100644 --- a/hooks/useNotifications.ts +++ b/hooks/useNotifications.ts @@ -45,7 +45,6 @@ export function useNotifications() { // Foreground listener notificationListener.current = Notifications.addNotificationReceivedListener((notification) => { - console.log('Notification received:', notification); }); // User response listener diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 848cb0a..484e2e2 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -227,5 +227,7 @@ "Referal kodi": "Referral code", "Direktor JSHSHR": "Director JSHSHR", "Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)", - "Hozircha bildirishnomalar yo'q": "No notifications yet" + "Hozircha bildirishnomalar yo'q": "No notifications yet", + "Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak": "You must be self-employed or an individual entrepreneur", + "Sizning shaxsiy ma'lumotlaringiz topilmadi": "Your personal information was not found" } \ No newline at end of file diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index 23d6ca1..9dd4946 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -226,5 +226,7 @@ "Referal kodi": "Реферальный код", "Direktor JSHSHR": "Директор ПИНФЛ", "Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)", - "Hozircha bildirishnomalar yo'q": "Пока нет уведомлений" + "Hozircha bildirishnomalar yo'q": "Пока нет уведомлений", + "Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak": "Вы должны быть самозанятым или индивидуальным предпринимателем", + "Sizning shaxsiy ma'lumotlaringiz topilmadi": "Ваши личные данные не найдены" } \ No newline at end of file diff --git a/i18n/locales/uz.json b/i18n/locales/uz.json index 8e4214f..36ea769 100644 --- a/i18n/locales/uz.json +++ b/i18n/locales/uz.json @@ -226,5 +226,7 @@ "INN kiriting (9 raqam)": "INN kiriting (9 raqam)", "Referal kodi": "Referal kodi", "Direktor JSHSHR": "Direktor JSHSHR", - "Direktor JSHSHR kiriting (14 raqam)": "Direktor JSHSHR kiriting (14 raqam)" + "Direktor JSHSHR kiriting (14 raqam)": "Direktor JSHSHR kiriting (14 raqam)", + "Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak": "Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak", + "Sizning shaxsiy ma'lumotlaringiz topilmadi": "Sizning shaxsiy ma'lumotlaringiz topilmadi" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 50946c9..ccdac21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@react-navigation/native": "^7.1.8", "@tanstack/react-query": "^5.90.17", "axios": "^1.13.2", + "crypto-js": "^4.2.0", "expo": "~54.0.31", "expo-av": "~16.0.8", "expo-blur": "~15.0.8", @@ -75,6 +76,7 @@ "zustand": "^5.0.10" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/react": "~19.1.0", "babel-plugin-module-resolver": "^5.0.0", "eslint": "^9.25.0", @@ -6537,6 +6539,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -8569,6 +8578,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", diff --git a/package.json b/package.json index d411c01..6773171 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@react-navigation/native": "^7.1.8", "@tanstack/react-query": "^5.90.17", "axios": "^1.13.2", + "crypto-js": "^4.2.0", "expo": "~54.0.31", "expo-av": "~16.0.8", "expo-blur": "~15.0.8", @@ -80,6 +81,7 @@ "zustand": "^5.0.10" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/react": "~19.1.0", "babel-plugin-module-resolver": "^5.0.0", "eslint": "^9.25.0", diff --git a/screens/auth/confirm/ConfirmScreen.tsx b/screens/auth/confirm/ConfirmScreen.tsx index 7e3142e..868f32e 100644 --- a/screens/auth/confirm/ConfirmScreen.tsx +++ b/screens/auth/confirm/ConfirmScreen.tsx @@ -43,9 +43,7 @@ const ConfirmScreen = () => { const storedPhone = await AsyncStorage.getItem('phone'); if (storedPhone) setPhone(storedPhone); else setPhone(null); - } catch (error) { - console.log('AsyncStorage error:', error); - } + } catch (error) { } }; loadPhone(); }, []); diff --git a/screens/auth/login/lib/api.ts b/screens/auth/login/lib/api.ts index 66fda1d..23bc0a3 100644 --- a/screens/auth/login/lib/api.ts +++ b/screens/auth/login/lib/api.ts @@ -1,6 +1,6 @@ import httpClient from '@/api/httpClient'; import { API_URLS } from '@/api/URLs'; -import { AxiosResponse } from 'axios'; +import axios, { AxiosResponse } from 'axios'; interface ConfirmBody { status: boolean; @@ -13,307 +13,70 @@ interface ConfirmBody { }; } -export interface CompanyInfo { - id: number; - inn: string; - registration_authority: string; - registration_date: string; - registration_number: string; - name: string; - short_name: string; - opf_code: string; - opf_name: string; - oked_code: string; - vat_number: string; - oked_name: string; - area: string; - region: string; - soogu_code: string; - soogu_name: string; - small_businesses: string; - activity_state: number; - statutory_fund: string; - - activity_state_detail: ActivityStateDetail; - business_type_detail: BusinessTypeDetail; - - director: string; - email: string; - village_code: string; - email_status: number; - phones: string[]; - - soato_code: string; - soato_name: string; - address: string; - - relevance_date: string; - court_relevance_date: string | null; - deal_relevance_date: string | null; - - tax_mode: number; - trust: string; - score: number; - - itpark: number; - leasing_count_all: number | null; - leasing_count_not_finish: number | null; - leasing_sum: number | null; - leasing_is_delay: number | null; - leasing_debt: number | null; - leasing_is_partner: number | null; - leasing_relevance_date: string | null; - - is_bankrupt: number; - is_abuse_vat: number; - is_large_taxpayer: number; - - vendor_rating: number | null; - developer_rating: number | null; - - dishonest_executor: DishonestExecutor; - village_detail: VillageDetail; - company_billing_address: BillingAddress; - - actual_date: string; - kfs: Kfs; - - uuid: string; - - connections: Connections; - courts: Courts; - - director_uuid: string; - founders: Founder[]; - - deals: Deals; - licenses: Licenses; - - leasing_guarantor_pinfl: string | null; - leasing_guarantor_inn: string | null; - - buildings: Buildings; - cadastres: Cadastres; - - liquidation_date: string | null; - liquidation_reason: string | null; - - is_suppliers: number; -} -export interface ActivityStateDetail { - id: number; - group: string; - name: string; - name_en: string; - name_uz: string; - name_uz_kir: string; +export interface GetInfo { + VATRegCode: null | string + VATRegStatus: null | string + account: string + accountant: null | string + address: string + bankAccount: string + bankCode: string + director: string | null + directorPinfl: string | null + directorTin: null | string + fullName: string + fullname: string + isBudget: number + isItd: boolean + mfo: string + na1Code: null | string + na1Name: null | string + name: string + ns10Code: number + ns11Code: number + oked: null | string + peasantFarm: boolean + personalNum: string + privateNotary: boolean + regDate: null | string + selfEmployment: boolean + shortName: string + shortname: string + statusCode: null | string + statusName: null | string + tin: string } -export interface BusinessTypeDetail { - id: number; - external_id: number; - name: string; - name_uz: string; - name_en: string; +export interface GetDistrict { + districtId: string, + regionId: number, + districtCode: number, + name: string } -export interface DishonestExecutor { - is_dishonest_executor: number; - delete_date: string | null; +export interface GetRegion { + regionId: number, + name: string } -export interface VillageDetail { - code: number; - name: string; -} - -export interface BillingAddress { - country_code: number; - region_code: number; - region_name: string; - region_name_en: string; - region_name_uz: string; - - district_code: number; - district_name: string; - district_name_en: string; - district_name_uz: string; - - sector_code: number; - street_name: string; - house: string | null; - flat: string | null; - postcode: string; -} - -export interface Kfs { - code: number; - name: string; - name_ru: string; - name_uz_cyr: string; - name_uz_lat: string; -} - -export interface Connections { - director: number; - founders: number; - entrepreneur: number; - all: number; -} - -export interface Courts { - total: number; - current: number; - completed: number; -} - -export interface Founder { - name: string; - percentage: number; - is_individual: number; - person_type: string; - id: number | null; - founder_uuid: string; -} - -export interface Deals { - customer: DealSide; - provider: DealSide; - actual_date: string; -} - -export interface DealSide { - rows: any[]; - total: number; - summ: number | null; -} - -export interface Licenses { - total: number; - relevance_date: string; - actual_date: string; -} - -export interface Buildings { - total: number; -} - -export interface Cadastres { - total: number; - relevance_date: string | null; -} -export interface GetInfoResponse { - status: boolean; - data: CompanyInfo | IndividualInfo; -} - -export interface IndividualInfo { - uuid: string, - id: number, - lastname: string, - firstname: string, - middlename: string, - registered_at: string, - unregistered_at: null | string, - activities: { - code: number, - name_en: string, - name_uz: string, - name_ru: string - }[] - -} - -export interface GetDirectorInfoResponse { +export interface GetTokens { status: boolean, data: { - entity: { - name: { - rows: { - id: number, - inn: string, - name: string, - director: string, - email: string, - phones: string[], - founders: { - name: string, - percentage: number, - is_individual: number, - person_type: string, - id: number | null, - founder_uuid: string - }[], - activity_state: number, - registration_date: string, - oked_code: string, - oked_name: string, - statutory_fund: string, - address: string, - variant: null - }[], - total: number - }, - inn: { - rows: [], - total: 0 - }, - director: { - rows: [], - total: 0 - }, - founder: { - rows: { - id: number, - inn: string, - name: string, - director: string, - email: string, - phones: string[], - founders: - { - name: string, - percentage: number, - is_individual: number, - person_type: string, - id: number | null, - founder_uuid: string - }[], - activity_state: number, - registration_date: string, - oked_code: string, - oked_name: string, - statutory_fund: string, - address: string, - variant: null - }[], - total: number - }, - email: { - rows: [], - total: number - }, - phone: { - rows: [], - total: number - } + links: { + previous: null | string, + next: null | string }, - entrepreneur: { - rows: - { - id: number, - pinfl: string, - entrepreneur: string, - email: string, - phone: string, - registration_date: string - }[], - total: number - }, - trademark: { - rows: [], - total: number - } + total_items: number, + total_pages: number, + page_size: number, + current_page: number, + results: + { + id: number, + key: string, + value: string, + is_active: boolean + }[] } } @@ -332,25 +95,60 @@ export const auth_api = { return res; }, - async get_info(body: { value: string, type: string, passport_series?: string, passport_number?: string }): Promise> { - const res = await httpClient.post(API_URLS.Info, body); - return res; + async get_info(body: { value: string, token: string, tokenName: string }): Promise> { + try { + const res = await axios.get(`https://testapi3.didox.uz/v1/utils/info/${body.value}`, { + headers: { + "Accept-Language": "uz", + [body.tokenName]: body.token + } + }); + return res; + } catch (error) { + throw error; + } }, - async get_director_info(body: { value: string }): Promise> { - const res = await httpClient.post(API_URLS.Get_Director_Info, body); - return res; + async get_district(): Promise> { + try { + const res = await axios.get(`https://testapi3.didox.uz/v1/districts/all/`, { + headers: { + "Accept-Language": "uz", + "Partner-Authorization": `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzYsInN0YXR1cyI6IkFDVElWRSIsIm5hbWUiOiJcIkZFTElYIC0gSVRTXCIgTUNISiIsInJvbGUiOiJQQVJUTkVSIiwidGluIjoiMzA3NTA0MzQ4IiwiaWF0IjoxNzczMzkyMjcwfQ.Q-cIBl4Z784Fq5jdRUEVYF2iaUd7_5RD2sFsJeh-Xno ` + } + }); + return res; + } catch (error) { + throw error; + } + }, + + async get_region(): Promise> { + try { + const res = await axios.get(`https://testapi3.didox.uz/v1/regions/all/`, { + headers: { + "Accept-Language": "uz", + "Partner-Authorization": `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzYsInN0YXR1cyI6IkFDVElWRSIsIm5hbWUiOiJcIkZFTElYIC0gSVRTXCIgTUNISiIsInJvbGUiOiJQQVJUTkVSIiwidGluIjoiMzA3NTA0MzQ4IiwiaWF0IjoxNzczMzkyMjcwfQ.Q-cIBl4Z784Fq5jdRUEVYF2iaUd7_5RD2sFsJeh-Xno ` + } + }); + return res; + } catch (error) { + throw error; + } }, async register(body: { phone: string; stir: string; person_type: string; - referal: string; + referral: string; activate_types: number[]; director_full_name: string; first_name: string; last_name: string; + district: number; + address: number; + company_name: string; }) { const res = await httpClient.post(API_URLS.Register, body); return res; @@ -365,4 +163,10 @@ export const auth_api = { const res = await httpClient.post(API_URLS.Register_Resend, body); return res; }, + + async get_tokens(): Promise> { + const res = httpClient.get(API_URLS.GetTokens) + return res + }, + }; diff --git a/screens/auth/login/ui/LoginForm.tsx b/screens/auth/login/ui/LoginForm.tsx index 1f5e4e8..69e9ebc 100644 --- a/screens/auth/login/ui/LoginForm.tsx +++ b/screens/auth/login/ui/LoginForm.tsx @@ -19,7 +19,6 @@ export default function LoginForm() { const [focused, setFocused] = useState(false); const scaleAnim = useRef(new Animated.Value(1)).current; const { phone, setPhone, submit, loading, error } = UseLoginForm(); - console.log(error); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/screens/auth/register-confirm/ConfirmScreen.tsx b/screens/auth/register-confirm/ConfirmScreen.tsx index 97918fd..5862ea9 100644 --- a/screens/auth/register-confirm/ConfirmScreen.tsx +++ b/screens/auth/register-confirm/ConfirmScreen.tsx @@ -44,9 +44,7 @@ const RegisterConfirmScreen = () => { const storedPhone = await AsyncStorage.getItem('phone'); if (storedPhone) setPhone(storedPhone); else setPhone(null); - } catch (error) { - console.log('AsyncStorage error:', error); - } + } catch (error) { } }; loadPhone(); }, []); @@ -67,6 +65,7 @@ const RegisterConfirmScreen = () => { savedToken(res.data.data.token.access); await AsyncStorage.setItem('refresh_token', res.data.data.token.refresh); await login(res.data.data.token.access); + const pushToken = await registerForPushNotificationsAsync(); if (pushToken) { await commonRequests.registerDevice({ @@ -78,6 +77,8 @@ const RegisterConfirmScreen = () => { // Notification querylarni refetch queryClient.refetchQueries({ queryKey: ['notification-list'] }); queryClient.refetchQueries({ queryKey: ['notifications-list'] }); + + // Dashboardga yo‘naltirish router.replace('/(dashboard)'); }, onError: (err: any) => { diff --git a/screens/auth/register/RegisterCategorySelection.tsx b/screens/auth/register/RegisterCategorySelection.tsx index 124668c..a506739 100644 --- a/screens/auth/register/RegisterCategorySelection.tsx +++ b/screens/auth/register/RegisterCategorySelection.tsx @@ -9,7 +9,7 @@ import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native'; export default function CategorySelectScreen() { const router = useRouter(); - const { phone, stir, person_type, director_full_name, referal, first_name, last_name } = useLocalSearchParams<{ + const { phone, stir, person_type, director_full_name, referal, first_name, last_name, address, company_name } = useLocalSearchParams<{ phone: string; stir: string; person_type: 'band' | 'ytt'; @@ -17,10 +17,10 @@ export default function CategorySelectScreen() { director_full_name: string; first_name: string; last_name: string; + address: string; + company_name: string; }>(); - console.log(referal); - const [selected, setSelected] = React.useState(null); const { data } = useQuery({ @@ -34,10 +34,13 @@ export default function CategorySelectScreen() { stir: string; person_type: string; activate_types: number[]; - referal: string; + referral: string; director_full_name: string; first_name: string; last_name: string; + district: number; + company_name: string; + address: number; }) => auth_api.register(body), onSuccess: () => router.replace('/'), }); @@ -68,10 +71,13 @@ export default function CategorySelectScreen() { person_type: person_type, phone: `998${phone}`, stir: stir, - referal: String(referal), + referral: String(referal), director_full_name: String(director_full_name), first_name: String(first_name), last_name: String(last_name), + district: Number(address), + company_name: String(company_name), + address: Number(address), }); } }} diff --git a/screens/auth/register/RegisterForm.tsx b/screens/auth/register/RegisterForm.tsx index 47a5415..120cfad 100644 --- a/screens/auth/register/RegisterForm.tsx +++ b/screens/auth/register/RegisterForm.tsx @@ -1,27 +1,22 @@ import AuthHeader from '@/components/ui/AuthHeader'; +import { decryptToken } from '@/constants/crypto'; import { formatPhone, normalizeDigits } from '@/constants/formatPhone'; +import { formatText, formatTextToLatin } from '@/constants/formatText'; import { products_api } from '@/screens/home/lib/api'; -import BottomSheet, { - BottomSheetBackdrop, - BottomSheetFlatList, - BottomSheetTextInput, -} from '@gorhom/bottom-sheet'; +import BottomSheet, { BottomSheetBackdrop, BottomSheetFlatList, BottomSheetTextInput } from '@gorhom/bottom-sheet'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { AxiosError } from 'axios'; import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { useRouter } from 'expo-router'; import { - Building2, CheckIcon, + ChevronDown, Globe, Hash, Search, - ShieldCheck, - User, - UserPlus, + UserPlus } from 'lucide-react-native'; -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActivityIndicator, @@ -34,72 +29,78 @@ import { } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { auth_api } from '../login/lib/api'; +import { auth_api, GetInfo } from '../login/lib/api'; import PhonePrefix from '../login/ui/PhonePrefix'; -import { useRegister } from './lib/useRegisterStore'; +import { UseLoginForm } from '../login/ui/UseLoginForm'; -function getHeaderInfo(personType: string | null) { - switch (personType) { - case 'yatt': - return { - label: 'YATT', - icon: , - gradient: ['#10b981', '#059669'] as [string, string], - }; - case 'band': - return { - label: "O'zini o'zi band qilgan", - icon: , - gradient: ['#3b82f6', '#2563eb'] as [string, string], - }; - case 'legal_entity': - return { - label: 'Yuridik shaxs', - icon: , - gradient: ['#f59e0b', '#d97706'] as [string, string], - }; - default: - return { - label: "Ro'yxatdan o'tish", - icon: , - gradient: ['#10b981', '#059669'] as [string, string], - }; - } +interface CoordsData { + lat: number; + lon: number; + polygon: [number, number][][]; } export default function RegisterFormScreen() { const router = useRouter(); - const { - personType, - phone, - setPhone, - referal, - setReferal, - jshshr, - setJshshr, - passportSeries, - setPassportSeries, - passportNumber, - setPassportNumber, - inn, - setInn, - info, - setInfo, - directorInfo, - setDirectorInfo, - } = useRegister(); const { t } = useTranslation(); - - const [loading, setLoading] = React.useState(false); - const [directorJshshr, setDirectorJshshr] = React.useState(''); - const [directorLoading, setDirectorLoading] = React.useState(false); - const [directorInfoError, setErrorDirectorInfo] = useState(null); - - // Country select - const [selectedCountry, setSelectedCountry] = useState('UZ'); - const [countrySearch, setCountrySearch] = useState(''); + const { phone, setPhone } = UseLoginForm(); const countrySheetRef = useRef(null); const snapPoints = useMemo(() => ['60%', '90%'], []); + const [selectedCountry, setSelectedCountry] = useState('UZ'); + const [countrySearch, setCountrySearch] = useState(''); + + const [stir, setStir] = useState(''); + const [info, setInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [referal, setReferal] = useState(''); + const [error, setError] = useState(null) + const [district, setDistrict] = useState(null) + const [region, setRegion] = useState(null) + const [token, setTokens] = useState<{ name: string, value: string } | null>(null) + + const [directorTinInput, setDirectorTinInput] = useState(''); + + const { data } = useQuery({ + queryKey: ["tokens"], + queryFn: async () => auth_api.get_tokens(), + select(data) { + return data.data.data.results + }, + }) + + useEffect(() => { + if (data?.length) { + const token = data[0] + const tokenValue = decryptToken(token.value) + if (tokenValue) { + setTokens({ name: token.key, value: tokenValue }) + } + } + }, [data]) + + const { mutate } = useMutation({ + mutationFn: (stir: string) => auth_api.get_info({ value: stir, token: token?.value || "", tokenName: token?.name || "" }), + onSuccess: (res) => { + setInfo(res.data); + setLoading(false); + setError(null) + setDistrict(res.data.address) + }, + onError: () => { + setInfo(null); + setLoading(false); + setError("Foydalanuvchi topilmadi") + }, + }); + + const { data: districts } = useQuery({ + queryKey: ["discrit"], + queryFn: async () => auth_api.get_district(), + }) + + const { data: regions } = useQuery({ + queryKey: ["regions"], + queryFn: async () => auth_api.get_region(), + }) const { data: countryResponse, isLoading: countryLoading } = useQuery({ queryKey: ['country-detail'], @@ -107,6 +108,111 @@ export default function RegisterFormScreen() { select: (res) => res.data?.data || [], }); + const getRegionDistrictFromAddress = async (address: string) => { + try { + const encoded = encodeURIComponent(address + ", Uzbekistan"); + const res = await fetch( + `https://nominatim.openstreetmap.org/search?q=${encoded}&format=json&addressdetails=1&limit=1`, + { headers: { 'Accept-Language': 'uz', 'User-Agent': 'MyApp/1.0 (turgunboyevsamandar4@gamil.com)' } } + ); + const data = await res.json(); + + if (data.length > 0) { + const addr = data[0].address; + return { + district: addr.county || addr.district || addr.suburb, + region: addr.state, + }; + } + } catch (e) { } + return null; + }; + + useEffect(() => { + if (district) { + const dis = formatText(district)?.split(" ")[0].toLocaleUpperCase() + let reg = null + if (dis) { + reg = districts?.data.find((item) => item.name.includes(dis)) + }; + + if (reg) { + const region = regions?.data.find((item) => item.regionId == reg.regionId) + setRegion(region?.name || "") + } + } + }, [district]) + + + const [districtId, setDistrictId] = useState(null); + const [regionId, setRegionId] = useState(null); + + useEffect(() => { + if (!district || !countryResponse?.length) return; + + const resolve = async () => { + const geo = await getRegionDistrictFromAddress(district.split(" ")[0]); + + const searchRegion = geo?.region || region; + const searchDistrict = geo?.district || district + + for (const country of countryResponse) { + const regionName = formatTextToLatin(searchRegion)?.split(" ")[0]; + let foundRegion = null + if (regionName) { + foundRegion = country.region.find((r: any) => + formatTextToLatin(r.name)?.includes(regionName) + ); + } + + if (foundRegion) { + setRegionId(foundRegion.id); + setDistrictId(null); + return; + } + + for (const reg of country.region || []) { + const dis = formatTextToLatin(searchDistrict)?.split(" ")[0].toUpperCase(); + let foundDistrict = null + if (dis) { + foundDistrict = reg.districts.find((d: any) => { + return formatTextToLatin(d.name)?.toUpperCase().includes(dis.slice(0, 4)) + } + ); + }; + + if (foundDistrict) { + setDistrictId(foundDistrict.id); + setRegionId(null); + return; + } + } + } + }; + resolve(); + }, [district, countryResponse]); + + useEffect(() => { + if (info === null || (stir.length === 9 && info.name && info.fullName)) { + setError(null) + } else if (info?.name === null || info?.fullName === null) { + setError("Sizning shaxsiy ma'lumotlaringiz topilmadi") + } else if (!info?.selfEmployment && !info?.isItd) { + setError("Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak") + } + }, [info]) + + const hasDirectorTin = info?.directorPinfl && String(info.directorPinfl).length > 0; + + const isDirectorTinValid = !hasDirectorTin || directorTinInput === String(info.directorPinfl); + const hasValidName = Boolean(info?.name || info?.fullName); + + const filteredCountries = useMemo(() => { + if (!countrySearch.trim()) return countryResponse || []; + const q = countrySearch.toLowerCase().trim(); + return (countryResponse || []).filter((c: any) => c.name?.toLowerCase().includes(q)); + }, [countryResponse, countrySearch]); + const openCountrySheet = useCallback(() => { Keyboard.dismiss(); setTimeout(() => { @@ -114,10 +220,13 @@ export default function RegisterFormScreen() { }, 100); }, []); - const closeCountrySheet = useCallback(() => { - countrySheetRef.current?.close(); - setTimeout(() => setCountrySearch(''), 300); - }, []); + const selectedCountryName = useMemo(() => { + if (!selectedCountry) return t('Tanlang'); + return ( + countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name || + t('Tanlang') + ); + }, [selectedCountry, countryResponse, t]); const renderBackdrop = useCallback( (props: any) => ( @@ -132,223 +241,20 @@ export default function RegisterFormScreen() { [] ); - const selectedCountryName = useMemo(() => { - if (!selectedCountry) return t('Tanlang'); - return ( - countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name || - t('Tanlang') - ); - }, [selectedCountry, countryResponse, t]); + const closeCountrySheet = useCallback(() => { + countrySheetRef.current?.close(); + setTimeout(() => setCountrySearch(''), 300); + }, []); - const filteredCountries = useMemo(() => { - if (!countrySearch.trim()) return countryResponse || []; - const q = countrySearch.toLowerCase().trim(); - return (countryResponse || []).filter((c: any) => c.name?.toLowerCase().includes(q)); - }, [countryResponse, countrySearch]); - - const headerInfo = getHeaderInfo(personType); - - const isYattOrBand = personType === 'yatt' || personType === 'band'; - const isLegal = personType === 'legal_entity'; - - const { mutate: fetchInfo } = useMutation({ - mutationFn: (body: { - value: string; - type: string; - passport_series?: string; - passport_number?: string; - }) => auth_api.get_info(body), - onSuccess: (res) => { - setInfo(res.data.data); - setLoading(false); - setDirectorJshshr(''); - setDirectorInfo(null); - setErrorDirectorInfo(null); - }, - onError: () => { - setInfo(null); - setLoading(false); - setDirectorJshshr(''); - setDirectorInfo(null); - setErrorDirectorInfo(null); - }, - }); - - const { mutate: fetchDirectorInfo } = useMutation({ - mutationFn: (body: { value: string }) => auth_api.get_director_info(body), - onSuccess: (res) => { - const directorData = res.data; - const rows: Array<{ inn: string }> = directorData?.data?.entity?.name?.rows ?? []; - const innMatch = rows.some((row) => row.inn === inn); - - if (!innMatch) { - setDirectorInfo(null); - setErrorDirectorInfo(t("Bu direktor ko'rsatilgan tashkilotga tegishli emas")); - setDirectorLoading(false); - return; - } - - setDirectorInfo(directorData); - setDirectorLoading(false); - setErrorDirectorInfo(null); - }, - onError: (error: AxiosError) => { - const err = error.response?.data as { - status: boolean; - data: { - detail: string; - error: { message: string }; - status_code: number; - }; - }; - setDirectorInfo(null); - setErrorDirectorInfo(err?.data?.detail ?? t('Xatolik yuz berdi')); - setDirectorLoading(false); - }, - }); - - const handleJshshrChange = useCallback( - (text: string) => { - const v = normalizeDigits(text).slice(0, 14); - setJshshr(v); - if (v.length === 14) { - setLoading(true); - if (personType) { - fetchInfo({ value: v, type: personType }); - } - } - }, - [setJshshr, fetchInfo, personType] - ); - - const handleInnChange = useCallback( - (text: string) => { - const v = normalizeDigits(text).slice(0, 9); - setInn(v); - if (v.length === 9) { - setLoading(true); - if (personType) { - fetchInfo({ value: v, type: personType }); - } - } - }, - [setInn, fetchInfo, personType] - ); - - const handlePassportSeriesChange = useCallback( - (text: string) => { - const v = text - .toUpperCase() - .replace(/[^A-Z]/g, '') - .slice(0, 2); - setPassportSeries(v); - if (personType && passportNumber.length === 7 && jshshr.length === 14) { - setLoading(true); - fetchInfo({ - type: personType, - passport_number: passportNumber, - passport_series: v, - value: jshshr, - }); - } - }, - [setPassportSeries, passportNumber, jshshr, personType, fetchInfo] - ); - - const handlePassportNumberChange = useCallback( - (text: string) => { - const v = normalizeDigits(text).slice(0, 7); - setPassportNumber(v); - if (personType && passportSeries.length === 2 && jshshr.length === 14) { - setLoading(true); - fetchInfo({ - type: personType, - passport_number: v, - passport_series: passportSeries, - value: jshshr, - }); - } - }, - [setPassportNumber, passportSeries, jshshr, personType, fetchInfo] - ); - - const handleDirectorJshshrChange = useCallback( - (text: string) => { - const v = normalizeDigits(text).slice(0, 14); - setDirectorJshshr(v); - setDirectorInfo(null); - setErrorDirectorInfo(null); - if (v.length === 14) { - setDirectorLoading(true); - fetchDirectorInfo({ value: v }); - } - }, - [fetchDirectorInfo] - ); - - const hasValidName = Boolean(info?.name || info?.fullName); - const hasValidInfo = Boolean(info?.lastname || info?.firstname || info?.middlename); - - const isValid = (() => { - const phoneValid = phone.length === 9; - const referalValid = referal.length > 0; - const countryValid = Boolean(selectedCountry && selectedCountry !== 'all'); - - if (isYattOrBand) { - return ( - phoneValid && - referalValid && - countryValid && - jshshr.length === 14 && - passportSeries.length === 2 && - passportNumber.length === 7 && - info - ); - } - - if (isLegal) { - return ( - phoneValid && - referalValid && - countryValid && - inn.length === 9 && - info && - directorJshshr.length === 14 && - directorInfo - ); - } - - return false; - })(); - - const handleContinue = () => { - const directorFullName = isLegal - ? (directorInfo?.data?.entrepreneur?.rows[0]?.entrepreneur ?? '') - : `${info?.firstname ?? ''} ${info?.lastname ?? ''} ${info?.middlename ?? ''}`.trim(); - - const countryObj = countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry); - - router.push({ - pathname: '/(auth)/select-category', - params: { - phone, - stir: isLegal ? inn : jshshr, - person_type: personType ?? '', - passport_series: passportSeries, - passport_number: passportNumber, - director_full_name: directorFullName, - referal: referal, - first_name: info?.firstname ?? '', - last_name: info?.lastname ?? '', - middle_name: info?.middlename ?? '', - country: countryObj?.name ?? '', - country_id: selectedCountry, - }, - }); - }; + const valid = + phone.length === 9 && + (stir.length === 9 || stir.length === 14) && + info && + hasValidName && + isDirectorTinValid && + error === null; return ( - // ✅ Fragment — BottomSheet tashqarida bo'lishi uchun <> - {/* ✅ TouchableWithoutFeedback yo'q — onStartShouldSetResponder ishlatildi */} { Keyboard.dismiss(); - return false; // false — child elementlar (buttonlar) ham ishlaydi + return false; }} > - {/* ✅ SafeAreaView va ichki ScrollView yo'q — to'g'ridan View */} - {headerInfo.icon} + - {t(headerInfo.label)} - - {isYattOrBand - ? t("JSHSHR va passport ma'lumotlarini kiriting") - : t('INN raqamini kiriting')} - + {t('Ro\'yxatdan o\'tish')} - {/* ---- YATT / BAND ---- */} - {isYattOrBand && ( - <> - - {t('JSHSHR')} - - - - {loading && jshshr.length >= 14 && ( - - )} - - - - - {t('Passport seriya va raqami')} - - - - - - - - - - - )} - - {/* ---- LEGAL ENTITY: INN ---- */} - {isLegal && ( + - {t('INN')} - - - - {loading && inn.length >= 9 && ( - + {t('Davlat')} + + {countryLoading ? ( + + ) : ( + <> + + + {selectedCountryName} + + )} - + + - )} + - {/* ---- LEGAL ENTITY: Kompaniya ma'lumoti ---- */} - {isLegal && - info && - (hasValidName ? ( - - {t('Tashkilot')} - {info.fullName || info.name} - - ) : hasValidInfo ? ( - - {t('Tashkilot')} - {info.firstname} - {info.lastname} - {info.middlename} - - ) : ( - - {t('Tashkilot topilmadi')} - - ))} + + {t('STIR')} + + + { + const v = normalizeDigits(text).slice(0, 14); + setStir(v); - {/* ---- LEGAL ENTITY: Direktor JSHSHR ---- */} - {isLegal && info && ( - - {t('Direktor JSHSHR')} - - - - {directorLoading && directorJshshr.length >= 14 && ( - - )} - - - {directorInfo && ( - - {t('Direktor')} - - {directorInfo?.data?.entrepreneur?.rows[0]?.entrepreneur} - - - )} - - {directorInfoError && ( - - {directorInfoError} - - )} + if (v.length === 9 || v.length === 14) { + setLoading(true); + mutate(v); + setRegionId(null) + setDistrictId(null) + setRegion(null) + setDistrict(null) + } + }} + /> + {loading && } - )} + - {/* ---- Referal ---- */} {t('Referal')} @@ -553,7 +380,6 @@ export default function RegisterFormScreen() { - {/* ---- Telefon ---- */} {t('Telefon raqami')} @@ -563,38 +389,62 @@ export default function RegisterFormScreen() { placeholder="90 123 45 67" placeholderTextColor="#94a3b8" keyboardType="phone-pad" - style={styles.textInput} - onChangeText={(t) => setPhone(normalizeDigits(t).slice(0, 9))} - testID="phone-input" + style={{ flex: 1 }} + onChangeText={(t) => setPhone(normalizeDigits(t))} /> - {/* ---- YATT/BAND info ---- */} - {!isLegal && - info && - (hasValidName ? ( - - {info.fullName || info.name} + {hasDirectorTin && ( + + {t('Direktor STIR')} + + + setDirectorTinInput(normalizeDigits(t))} + /> - ) : hasValidInfo ? ( - - {info.firstname} - {info.lastname} - {info.middlename} - - ) : ( - - {t('Foydalanuvchi topilmadi')} - - ))} - {/* ---- Davom etish tugmasi ---- */} + {directorTinInput.length === 14 && !isDirectorTinValid && ( + {t('Direktor STIR noto‘g‘ri')} + )} + + )} + + {error !== null ? + {t(error)} + : info && hasValidName && + {info.fullName || info.name} + } + { + if (error === null) { + router.push({ + pathname: '/(auth)/select-category', + params: { + phone, + first_name: stir.length === 9 ? info?.director : info?.fullName, + last_name: stir.length === 9 ? info?.director : info?.fullName, + company_name: info?.name, + district: districtId !== null ? districtId : regionId, + address: districtId !== null ? districtId : regionId, + director_full_name: stir.length === 9 ? info?.director : info?.fullName, + stir, + referal, + person_type: stir.length === 9 ? 'legal_entity' : info?.selfEmployment ? 'band' : info?.isItd ? 'ytt' : 'ytt', + }, + }) + } + }} > {t('Davom etish')} @@ -602,10 +452,9 @@ export default function RegisterFormScreen() { - - + + - {/* ✅ BottomSheet KeyboardAwareScrollView TASHQARISIDA */} - + ); } const styles = StyleSheet.create({ - // ✅ KeyboardAwareScrollView uchun style keyboardScroll: { flex: 1, backgroundColor: '#0f172a', @@ -877,6 +725,7 @@ const styles = StyleSheet.create({ infoBox: { backgroundColor: '#f0fdf4', padding: 14, + marginTop: 10, borderRadius: 14, borderWidth: 1, borderColor: '#bbf7d0', @@ -948,4 +797,26 @@ const styles = StyleSheet.create({ backgroundColor: '#f1f5f9', borderColor: '#e2e8f0', }, + + info: { + padding: 12, + borderRadius: 12, + fontWeight: '700', + backgroundColor: '#f0fdf4', + }, + error: { + color: '#dc2626', + fontSize: 12, + marginTop: 4, + fontWeight: '600', + }, + notFound: { + backgroundColor: '#fef2f2', + padding: 12, + borderRadius: 12, + fontWeight: '700', + color: '#dc2626', + borderWidth: 1, + borderColor: '#fecaca', + }, }); \ No newline at end of file diff --git a/screens/auth/register/lib/useRegisterStore.ts b/screens/auth/register/lib/useRegisterStore.ts index 3927f30..e326f88 100644 --- a/screens/auth/register/lib/useRegisterStore.ts +++ b/screens/auth/register/lib/useRegisterStore.ts @@ -1,6 +1,6 @@ import createContextHook from '@nkzw/create-context-hook'; import { useState } from 'react'; -import { GetDirectorInfoResponse } from '../../login/lib/api'; +import { GetInfo } from '../../login/lib/api'; export type PersonType = 'yatt' | 'band' | 'legal_entity' | null; @@ -19,14 +19,14 @@ interface RegisterState { setPassportNumber: (number: string) => void; inn: string; setInn: (inn: string) => void; - info: any; - setInfo: (info: any) => void; + info: GetInfo | null; + setInfo: (info: GetInfo | null) => void; reset_full: () => void; reset: () => void; directorJshshr: string; setDirectorJshshr: (directorJshshr: string) => void; - directorInfo: GetDirectorInfoResponse | null; - setDirectorInfo: (directorInfo: GetDirectorInfoResponse | null) => void; + directorInfo: GetInfo | null; + setDirectorInfo: (directorInfo: GetInfo | null) => void; } export const [RegisterProvider, useRegister] = createContextHook(() => { @@ -37,9 +37,9 @@ export const [RegisterProvider, useRegister] = createContextHook( const [passportSeries, setPassportSeries] = useState(''); const [passportNumber, setPassportNumber] = useState(''); const [inn, setInn] = useState(''); - const [info, setInfo] = useState(null); + const [info, setInfo] = useState(null); const [directorJshshr, setDirectorJshshr] = useState(''); - const [directorInfo, setDirectorInfo] = useState(null); + const [directorInfo, setDirectorInfo] = useState(null); const reset_full = () => { setPersonType(null); diff --git a/screens/home/ui/HomeScreen.tsx b/screens/home/ui/HomeScreen.tsx index b7a3b27..714fb6e 100644 --- a/screens/home/ui/HomeScreen.tsx +++ b/screens/home/ui/HomeScreen.tsx @@ -57,7 +57,6 @@ export default function HomeScreen() { } await queryClient.refetchQueries(); } catch (err) { - console.error('Refresh error:', err); } finally { setRefreshing(false); } diff --git a/screens/profile/ui/EditServices.tsx b/screens/profile/ui/EditServices.tsx index e57c0e5..f5d357e 100644 --- a/screens/profile/ui/EditServices.tsx +++ b/screens/profile/ui/EditServices.tsx @@ -86,13 +86,11 @@ export default function EditService() { const { mutate, isPending } = useMutation({ mutationFn: (body: FormData) => user_api.update_service({ body, id: Number(id) }), onSuccess: (res) => { - console.log(res); queryClient.invalidateQueries({ queryKey: ['my_services'] }); queryClient.invalidateQueries({ queryKey: ['service_detail'] }); router.back(); }, onError: (err: any) => { - console.log(err); Alert.alert(t('Xatolik yuz berdi'), err?.message || t('Yangilashda xato yuz berdi')); }, }); diff --git a/screens/profile/ui/RefferallsTab.tsx b/screens/profile/ui/RefferallsTab.tsx index 64c7df2..2c352f8 100644 --- a/screens/profile/ui/RefferallsTab.tsx +++ b/screens/profile/ui/RefferallsTab.tsx @@ -80,7 +80,6 @@ export function ReferralsTab() { title: t('Referal linkni ulashish'), }); } catch (err) { - console.log('Share error:', err); } if (Platform.OS === 'android') {