register update
This commit is contained in:
@@ -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/",
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
2
app.json
2
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",
|
||||
|
||||
@@ -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: <Building2 size={28} color="#fff" />,
|
||||
gradient: ['#f59e0b', '#d97706'],
|
||||
},
|
||||
{
|
||||
key: 'yatt',
|
||||
label: 'YATT',
|
||||
description: "Yakka tartibdagi tadbirkor",
|
||||
icon: <User size={28} color="#fff" />,
|
||||
gradient: ['#10b981', '#059669'],
|
||||
},
|
||||
{
|
||||
key: 'band',
|
||||
label: "O'zini o'zi band qilgan",
|
||||
description: "O'z faoliyatini mustaqil yurituvchi",
|
||||
icon: <ShieldCheck size={28} color="#fff" />,
|
||||
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 (
|
||||
<Animated.View style={{ transform: [{ scale }], marginBottom: 14 }}>
|
||||
<TouchableOpacity
|
||||
style={styles.card}
|
||||
onPress={onPress}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
activeOpacity={1}
|
||||
testID={`person-type-${item.key}`}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={item.gradient}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.cardIcon}
|
||||
>
|
||||
{item.icon}
|
||||
</LinearGradient>
|
||||
<View style={styles.cardContent}>
|
||||
<Text style={styles.cardTitle}>{t(item.label)}</Text>
|
||||
<Text style={styles.cardDescription}>{t(item.description)}</Text>
|
||||
</View>
|
||||
<ChevronRight size={20} color="#94a3b8" />
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
<RegisterFormScreen />
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View style={styles.container}>
|
||||
<LinearGradient
|
||||
colors={['#0f172a', '#1e293b', '#334155']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={styles.decorCircle1} />
|
||||
<View style={styles.decorCircle2} />
|
||||
<AuthHeader />
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>{t("Ro'yxatdan o'tish")}</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{t("Faoliyat turingizni tanlang")}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.cardList} showsVerticalScrollIndicator={false}>
|
||||
{PERSON_TYPES.map((item, index) => (
|
||||
<TypeCard
|
||||
key={item.key}
|
||||
item={item}
|
||||
index={index}
|
||||
onPress={() => { handleSelect(item.key); reset() }}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity onPress={() => router.back()} testID="go-to-login">
|
||||
<Text style={styles.footerText}>
|
||||
{t("Hisobingiz bormi?")} <Text style={styles.footerLink}>{t("Kirish")}</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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)',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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<number | null>(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 (
|
||||
<View style={styles.safeArea}>
|
||||
<AuthHeader />
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@ export default function TabsLayout() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
console.log(keyboard);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Animated.loop(
|
||||
Animated.timing(rotateAnim, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Category[]>([]);
|
||||
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Root categories
|
||||
const { isLoading: rootLoading, error: rootError } = useQuery<CategoryResponse>({
|
||||
@@ -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"))
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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<number | null>(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"))
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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) { }
|
||||
};
|
||||
|
||||
41
constants/crypto.ts
Normal file
41
constants/crypto.ts
Normal file
@@ -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;
|
||||
}
|
||||
};
|
||||
55
constants/formatText.ts
Normal file
55
constants/formatText.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<string | null> => {
|
||||
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<string | null> => {
|
||||
export const deleteLang = async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('lang');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete language', error);
|
||||
}
|
||||
} catch (error) { }
|
||||
};
|
||||
|
||||
const ACCESS = 'access_token';
|
||||
|
||||
@@ -45,7 +45,6 @@ export function useNotifications() {
|
||||
|
||||
// Foreground listener
|
||||
notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
|
||||
console.log('Notification received:', notification);
|
||||
});
|
||||
|
||||
// User response listener
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "Ваши личные данные не найдены"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}, []);
|
||||
|
||||
@@ -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
|
||||
links: {
|
||||
previous: null | string,
|
||||
next: null | string
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
entrepreneur: {
|
||||
rows:
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
page_size: number,
|
||||
current_page: number,
|
||||
results:
|
||||
{
|
||||
id: number,
|
||||
pinfl: string,
|
||||
entrepreneur: string,
|
||||
email: string,
|
||||
phone: string,
|
||||
registration_date: string
|
||||
}[],
|
||||
total: number
|
||||
},
|
||||
trademark: {
|
||||
rows: [],
|
||||
total: 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<AxiosResponse<GetInfoResponse>> {
|
||||
const res = await httpClient.post(API_URLS.Info, body);
|
||||
async get_info(body: { value: string, token: string, tokenName: string }): Promise<AxiosResponse<GetInfo>> {
|
||||
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<AxiosResponse<GetDirectorInfoResponse>> {
|
||||
const res = await httpClient.post(API_URLS.Get_Director_Info, body);
|
||||
async get_district(): Promise<AxiosResponse<GetDistrict[]>> {
|
||||
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<AxiosResponse<GetRegion[]>> {
|
||||
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<AxiosResponse<GetTokens>> {
|
||||
const res = httpClient.get(API_URLS.GetTokens)
|
||||
return res
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<number | null>(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),
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -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: <User size={32} color="#ffffff" />,
|
||||
gradient: ['#10b981', '#059669'] as [string, string],
|
||||
};
|
||||
case 'band':
|
||||
return {
|
||||
label: "O'zini o'zi band qilgan",
|
||||
icon: <ShieldCheck size={32} color="#ffffff" />,
|
||||
gradient: ['#3b82f6', '#2563eb'] as [string, string],
|
||||
};
|
||||
case 'legal_entity':
|
||||
return {
|
||||
label: 'Yuridik shaxs',
|
||||
icon: <Building2 size={32} color="#ffffff" />,
|
||||
gradient: ['#f59e0b', '#d97706'] as [string, string],
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Ro'yxatdan o'tish",
|
||||
icon: <UserPlus size={32} color="#ffffff" />,
|
||||
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<string | null>(null);
|
||||
|
||||
// Country select
|
||||
const [selectedCountry, setSelectedCountry] = useState<string>('UZ');
|
||||
const [countrySearch, setCountrySearch] = useState<string>('');
|
||||
const { phone, setPhone } = UseLoginForm();
|
||||
const countrySheetRef = useRef<BottomSheet>(null);
|
||||
const snapPoints = useMemo(() => ['60%', '90%'], []);
|
||||
const [selectedCountry, setSelectedCountry] = useState<string>('UZ');
|
||||
const [countrySearch, setCountrySearch] = useState<string>('');
|
||||
|
||||
const [stir, setStir] = useState('');
|
||||
const [info, setInfo] = useState<GetInfo | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [referal, setReferal] = useState('');
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [district, setDistrict] = useState<string | null>(null)
|
||||
const [region, setRegion] = useState<string | null>(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<number | null>(null);
|
||||
const [regionId, setRegionId] = useState<number | null>(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 &&
|
||||
const valid =
|
||||
phone.length === 9 &&
|
||||
(stir.length === 9 || stir.length === 14) &&
|
||||
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,
|
||||
},
|
||||
});
|
||||
};
|
||||
hasValidName &&
|
||||
isDirectorTinValid &&
|
||||
error === null;
|
||||
|
||||
return (
|
||||
// ✅ Fragment — BottomSheet tashqarida bo'lishi uchun
|
||||
<>
|
||||
<KeyboardAwareScrollView
|
||||
enableOnAndroid
|
||||
@@ -356,12 +262,11 @@ export default function RegisterFormScreen() {
|
||||
extraScrollHeight={120}
|
||||
style={styles.keyboardScroll}
|
||||
>
|
||||
{/* ✅ TouchableWithoutFeedback yo'q — onStartShouldSetResponder ishlatildi */}
|
||||
<View
|
||||
style={styles.container}
|
||||
onStartShouldSetResponder={() => {
|
||||
Keyboard.dismiss();
|
||||
return false; // false — child elementlar (buttonlar) ham ishlaydi
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<LinearGradient
|
||||
@@ -375,168 +280,90 @@ export default function RegisterFormScreen() {
|
||||
|
||||
<AuthHeader />
|
||||
|
||||
{/* ✅ SafeAreaView va ichki ScrollView yo'q — to'g'ridan View */}
|
||||
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||||
<View style={styles.scrollContent}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.iconContainer}>
|
||||
<LinearGradient
|
||||
colors={headerInfo.gradient}
|
||||
colors={['#10b981', '#059669']}
|
||||
style={styles.iconGradient}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
>
|
||||
{headerInfo.icon}
|
||||
<UserPlus size={32} color="#fff" />
|
||||
</LinearGradient>
|
||||
</View>
|
||||
<Text style={styles.title}>{t(headerInfo.label)}</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{isYattOrBand
|
||||
? t("JSHSHR va passport ma'lumotlarini kiriting")
|
||||
: t('INN raqamini kiriting')}
|
||||
</Text>
|
||||
<Text style={styles.title}>{t('Ro\'yxatdan o\'tish')}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.card}>
|
||||
<View style={styles.formGap}>
|
||||
{/* ---- YATT / BAND ---- */}
|
||||
{isYattOrBand && (
|
||||
<View>
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Davlat')}</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.input,
|
||||
styles.inputDisabled,
|
||||
]}
|
||||
onPress={openCountrySheet}
|
||||
activeOpacity={0.7}
|
||||
testID="country-select"
|
||||
disabled
|
||||
>
|
||||
{countryLoading ? (
|
||||
<ActivityIndicator size="small" color="#3b82f6" style={{ flex: 1 }} />
|
||||
) : (
|
||||
<>
|
||||
<View>
|
||||
<Text style={styles.label}>{t('JSHSHR')}</Text>
|
||||
<View style={styles.input}>
|
||||
<Hash size={18} color="#94a3b8" />
|
||||
<TextInput
|
||||
value={jshshr}
|
||||
keyboardType="numeric"
|
||||
placeholder={t('JSHSHR kiriting (14 raqam)')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={handleJshshrChange}
|
||||
maxLength={14}
|
||||
testID="jshshr-input"
|
||||
<Image
|
||||
source={{ uri: `https://flagcdn.com/w320/${selectedCountry.toLowerCase()}.png` }}
|
||||
style={{ width: 30, height: 15 }}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
{loading && jshshr.length >= 14 && (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Passport seriya va raqami')}</Text>
|
||||
<View style={styles.passportRow}>
|
||||
<View style={[styles.input, styles.passportSeries]}>
|
||||
<TextInput
|
||||
value={passportSeries}
|
||||
placeholder="AA"
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={handlePassportSeriesChange}
|
||||
maxLength={2}
|
||||
autoCapitalize="characters"
|
||||
testID="passport-series-input"
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.input, styles.passportNumber]}>
|
||||
<TextInput
|
||||
value={passportNumber}
|
||||
keyboardType="numeric"
|
||||
placeholder="1234567"
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={handlePassportNumberChange}
|
||||
maxLength={7}
|
||||
testID="passport-number-input"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
styles.textInput,
|
||||
{ color: '#94a3b8' },
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{selectedCountryName}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown size={18} color={'#cbd5e1'} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ---- LEGAL ENTITY: INN ---- */}
|
||||
{isLegal && (
|
||||
<View>
|
||||
<Text style={styles.label}>{t('INN')}</Text>
|
||||
<Text style={styles.label}>{t('STIR')}</Text>
|
||||
<View style={styles.input}>
|
||||
<Hash size={18} color="#94a3b8" />
|
||||
<TextInput
|
||||
value={inn}
|
||||
value={stir}
|
||||
keyboardType="numeric"
|
||||
placeholder={t('INN kiriting (9 raqam)')}
|
||||
placeholder={t('STIR')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={handleInnChange}
|
||||
maxLength={9}
|
||||
testID="inn-input"
|
||||
style={{ flex: 1, color: "black" }}
|
||||
onChangeText={(text) => {
|
||||
const v = normalizeDigits(text).slice(0, 14);
|
||||
setStir(v);
|
||||
|
||||
if (v.length === 9 || v.length === 14) {
|
||||
setLoading(true);
|
||||
mutate(v);
|
||||
setRegionId(null)
|
||||
setDistrictId(null)
|
||||
setRegion(null)
|
||||
setDistrict(null)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{loading && inn.length >= 9 && (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
)}
|
||||
{loading && <ActivityIndicator size="small" />}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ---- LEGAL ENTITY: Kompaniya ma'lumoti ---- */}
|
||||
{isLegal &&
|
||||
info &&
|
||||
(hasValidName ? (
|
||||
<View style={styles.infoBox}>
|
||||
<Text style={styles.infoLabel}>{t('Tashkilot')}</Text>
|
||||
<Text style={styles.infoText}>{info.fullName || info.name}</Text>
|
||||
</View>
|
||||
) : hasValidInfo ? (
|
||||
<View style={[styles.infoBox, { flexDirection: 'row', gap: 5 }]}>
|
||||
<Text style={styles.infoLabel}>{t('Tashkilot')}</Text>
|
||||
<Text style={styles.infoText}>{info.firstname}</Text>
|
||||
<Text style={styles.infoText}>{info.lastname}</Text>
|
||||
<Text style={styles.infoText}>{info.middlename}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorText}>{t('Tashkilot topilmadi')}</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* ---- LEGAL ENTITY: Direktor JSHSHR ---- */}
|
||||
{isLegal && info && (
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Direktor JSHSHR')}</Text>
|
||||
<View style={styles.input}>
|
||||
<Hash size={18} color="#94a3b8" />
|
||||
<TextInput
|
||||
value={directorJshshr}
|
||||
keyboardType="numeric"
|
||||
placeholder={t('Direktor JSHSHR (14 raqam)')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.textInput}
|
||||
onChangeText={handleDirectorJshshrChange}
|
||||
maxLength={14}
|
||||
testID="director-jshshr-input"
|
||||
/>
|
||||
{directorLoading && directorJshshr.length >= 14 && (
|
||||
<ActivityIndicator size="small" color="#3b82f6" />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{directorInfo && (
|
||||
<View style={[styles.infoBox, { marginTop: 8 }]}>
|
||||
<Text style={styles.infoLabel}>{t('Direktor')}</Text>
|
||||
<Text style={styles.infoText}>
|
||||
{directorInfo?.data?.entrepreneur?.rows[0]?.entrepreneur}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{directorInfoError && (
|
||||
<View style={[styles.errorBox, { marginTop: 8 }]}>
|
||||
<Text style={styles.errorText}>{directorInfoError}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ---- Referal ---- */}
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Referal')}</Text>
|
||||
<View style={styles.input}>
|
||||
@@ -553,7 +380,6 @@ export default function RegisterFormScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ---- Telefon ---- */}
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Telefon raqami')}</Text>
|
||||
<View style={styles.input}>
|
||||
@@ -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))}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ---- YATT/BAND info ---- */}
|
||||
{!isLegal &&
|
||||
info &&
|
||||
(hasValidName ? (
|
||||
<View style={styles.infoBox}>
|
||||
<Text style={styles.infoText}>{info.fullName || info.name}</Text>
|
||||
{hasDirectorTin && (
|
||||
<View>
|
||||
<Text style={styles.label}>{t('Direktor STIR')}</Text>
|
||||
<View style={[styles.input, { backgroundColor: isDirectorTinValid ? '#f0fdf4' : '#f8fafc' }]}>
|
||||
<Hash size={18} color="#94a3b8" />
|
||||
<TextInput
|
||||
value={directorTinInput}
|
||||
keyboardType="numeric"
|
||||
placeholder={t('Direktor STIR')}
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={{ flex: 1 }}
|
||||
maxLength={14}
|
||||
onChangeText={(t) => setDirectorTinInput(normalizeDigits(t))}
|
||||
/>
|
||||
</View>
|
||||
) : hasValidInfo ? (
|
||||
<View style={[styles.infoBox, { flexDirection: 'row', gap: 5 }]}>
|
||||
<Text style={styles.infoText}>{info.firstname}</Text>
|
||||
<Text style={styles.infoText}>{info.lastname}</Text>
|
||||
<Text style={styles.infoText}>{info.middlename}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorText}>{t('Foydalanuvchi topilmadi')}</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* ---- Davom etish tugmasi ---- */}
|
||||
{directorTinInput.length === 14 && !isDirectorTinValid && (
|
||||
<Text style={styles.error}>{t('Direktor STIR noto‘g‘ri')}</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{error !== null ?
|
||||
<Text style={styles.notFound}>{t(error)}</Text>
|
||||
: info && hasValidName &&
|
||||
<Text style={styles.info}>{info.fullName || info.name}</Text>
|
||||
}
|
||||
|
||||
<TouchableOpacity
|
||||
disabled={!isValid}
|
||||
style={[styles.btn, !isValid && styles.disabled]}
|
||||
onPress={handleContinue}
|
||||
testID="continue-button"
|
||||
disabled={!valid}
|
||||
style={[styles.btn, !valid && styles.disabled]}
|
||||
onPress={() => {
|
||||
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',
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.btnText}>{t('Davom etish')}</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -602,10 +452,9 @@ export default function RegisterFormScreen() {
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
</View >
|
||||
</KeyboardAwareScrollView >
|
||||
|
||||
{/* ✅ BottomSheet KeyboardAwareScrollView TASHQARISIDA */}
|
||||
<BottomSheet
|
||||
ref={countrySheetRef}
|
||||
index={-1}
|
||||
@@ -682,13 +531,12 @@ export default function RegisterFormScreen() {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</BottomSheet>
|
||||
</BottomSheet >
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
});
|
||||
@@ -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<RegisterState>(() => {
|
||||
@@ -37,9 +37,9 @@ export const [RegisterProvider, useRegister] = createContextHook<RegisterState>(
|
||||
const [passportSeries, setPassportSeries] = useState<string>('');
|
||||
const [passportNumber, setPassportNumber] = useState<string>('');
|
||||
const [inn, setInn] = useState<string>('');
|
||||
const [info, setInfo] = useState<any>(null);
|
||||
const [info, setInfo] = useState<GetInfo | null>(null);
|
||||
const [directorJshshr, setDirectorJshshr] = useState<string>('');
|
||||
const [directorInfo, setDirectorInfo] = useState<GetDirectorInfoResponse | null>(null);
|
||||
const [directorInfo, setDirectorInfo] = useState<GetInfo | null>(null);
|
||||
|
||||
const reset_full = () => {
|
||||
setPersonType(null);
|
||||
|
||||
@@ -57,7 +57,6 @@ export default function HomeScreen() {
|
||||
}
|
||||
await queryClient.refetchQueries();
|
||||
} catch (err) {
|
||||
console.error('Refresh error:', err);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -80,7 +80,6 @@ export function ReferralsTab() {
|
||||
title: t('Referal linkni ulashish'),
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Share error:', err);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
|
||||
Reference in New Issue
Block a user