Compare commits
7 Commits
bdc205b538
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fee9213c59 | ||
|
|
22c1688781 | ||
|
|
a34cf75c57 | ||
|
|
382b214e3e | ||
|
|
c71651ec4b | ||
|
|
4d5cc84850 | ||
|
|
ab363ca3b9 |
@@ -28,7 +28,7 @@ export const API_URLS = {
|
|||||||
User_Update: 'auth/user-update/',
|
User_Update: 'auth/user-update/',
|
||||||
Employee_List: 'api/employee/',
|
Employee_List: 'api/employee/',
|
||||||
My_Ads: 'api/my-ads/',
|
My_Ads: 'api/my-ads/',
|
||||||
My_Ads_Detail: (id: number) => `api/my-ads/${id}`,
|
Ads_Detail: (id: number) => `api/my-ads/${id}/`,
|
||||||
My_Bonuses: 'api/cashback/',
|
My_Bonuses: 'api/cashback/',
|
||||||
My_Refferals: 'api/referral/',
|
My_Refferals: 'api/referral/',
|
||||||
Goverment_Service: '/api/goverment-service/',
|
Goverment_Service: '/api/goverment-service/',
|
||||||
@@ -37,5 +37,6 @@ export const API_URLS = {
|
|||||||
Notification_Ready: (id: number) => `/api/notifications/${id}/read/`,
|
Notification_Ready: (id: number) => `/api/notifications/${id}/read/`,
|
||||||
Notification_Mark_All_Read: '/api/notifications/read-all/',
|
Notification_Mark_All_Read: '/api/notifications/read-all/',
|
||||||
Info: "/auth/get-person/",
|
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
|
// Original so'rovni qayta yuboramiz
|
||||||
return httpClient(originalRequest);
|
return httpClient(originalRequest);
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
console.error('Refresh token xatosi:', refreshError);
|
|
||||||
|
|
||||||
// Refresh muvaffaqiyatsiz bo'lsa
|
// Refresh muvaffaqiyatsiz bo'lsa
|
||||||
processQueue(refreshError);
|
processQueue(refreshError);
|
||||||
|
|||||||
8
app.json
8
app.json
@@ -2,7 +2,7 @@
|
|||||||
"expo": {
|
"expo": {
|
||||||
"name": "Info target",
|
"name": "Info target",
|
||||||
"slug": "infotarget",
|
"slug": "infotarget",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/logo.png",
|
"icon": "./assets/images/logo.png",
|
||||||
"scheme": "infotarget",
|
"scheme": "infotarget",
|
||||||
@@ -11,12 +11,16 @@
|
|||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true,
|
"supportsTablet": true,
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"UIBackgroundModes": ["remote-notification"]
|
"UIBackgroundModes": [
|
||||||
|
"remote-notification"
|
||||||
|
],
|
||||||
|
"UIViewControllerBasedStatusBarAppearance": true
|
||||||
},
|
},
|
||||||
"bundleIdentifier": "com.felix.infotarget"
|
"bundleIdentifier": "com.felix.infotarget"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"useNextNotificationsApi": true,
|
"useNextNotificationsApi": true,
|
||||||
|
"softwareKeyboardLayoutMode": "resize",
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"backgroundColor": "#E6F4FE",
|
"backgroundColor": "#E6F4FE",
|
||||||
"foregroundImage": "./assets/images/logo.png"
|
"foregroundImage": "./assets/images/logo.png"
|
||||||
|
|||||||
@@ -1,247 +1,8 @@
|
|||||||
import AuthHeader from '@/components/ui/AuthHeader';
|
import RegisterFormScreen from '@/screens/auth/register/RegisterForm';
|
||||||
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 React from 'react';
|
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 (
|
return (
|
||||||
<Animated.View style={{ transform: [{ scale }], marginBottom: 14 }}>
|
<RegisterFormScreen />
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -28,15 +28,17 @@ interface Category {
|
|||||||
export default function CategorySelectScreen() {
|
export default function CategorySelectScreen() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
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;
|
phone: string;
|
||||||
stir: string;
|
stir: string;
|
||||||
person_type: 'band' | 'ytt';
|
person_type: 'band' | 'ytt' | "legal_entity";
|
||||||
referal: string;
|
referal: string;
|
||||||
director_full_name: string;
|
director_full_name: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
middle_name: string;
|
district: string
|
||||||
|
address: string;
|
||||||
|
company_name: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const [selected, setSelected] = useState<number | null>(null);
|
const [selected, setSelected] = useState<number | null>(null);
|
||||||
@@ -69,22 +71,32 @@ export default function CategorySelectScreen() {
|
|||||||
person_type: string;
|
person_type: string;
|
||||||
activate_types: number[];
|
activate_types: number[];
|
||||||
director_full_name: string;
|
director_full_name: string;
|
||||||
referal: string;
|
referral: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
district: number;
|
||||||
|
company_name: string;
|
||||||
|
address: number;
|
||||||
}) => auth_api.register(body),
|
}) => auth_api.register(body),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
router.replace('/(auth)/register-confirm');
|
router.replace('/(auth)/register-confirm');
|
||||||
await AsyncStorage.setItem('phone', phone);
|
await AsyncStorage.setItem('phone', phone);
|
||||||
},
|
},
|
||||||
onError: (err: AxiosError) => {
|
onError: (err: AxiosError) => {
|
||||||
const errMessage = (err.response?.data as { data: { stir: string[] } }).data.stir[0];
|
const errMessage = (err.response?.data as any)?.data?.stir?.[0];
|
||||||
const errMessageDetail = (err.response?.data as { data: { detail: string } }).data.detail;
|
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) => {
|
const onCategoryPress = (cat: Category) => {
|
||||||
@@ -103,8 +115,6 @@ export default function CategorySelectScreen() {
|
|||||||
setSelected(null);
|
setSelected(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const full_name = first_name.length > 0 ? first_name + ' ' + last_name + ' ' + middle_name : director_full_name;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.safeArea}>
|
<View style={styles.safeArea}>
|
||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
@@ -149,10 +159,13 @@ export default function CategorySelectScreen() {
|
|||||||
person_type,
|
person_type,
|
||||||
phone: `998${phone}`,
|
phone: `998${phone}`,
|
||||||
stir,
|
stir,
|
||||||
referal: referal,
|
referral: referal,
|
||||||
director_full_name: director_full_name,
|
director_full_name,
|
||||||
first_name: full_name,
|
first_name,
|
||||||
last_name: last_name,
|
last_name,
|
||||||
|
district: Number(district),
|
||||||
|
address: Number(address),
|
||||||
|
company_name
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -179,7 +192,7 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
paddingTop: 16,
|
paddingTop: 16,
|
||||||
paddingBottom: 70,
|
paddingBottom: 90,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { useHomeStore } from '@/screens/home/lib/hook';
|
|||||||
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||||
import { router, Tabs } from 'expo-router';
|
import { router, Tabs } from 'expo-router';
|
||||||
import { Home, Megaphone, PlusCircle, User } from 'lucide-react-native';
|
import { Home, Megaphone, PlusCircle, User } from 'lucide-react-native';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Animated, Easing, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { Animated, Easing, Keyboard, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
export default function TabsLayout() {
|
export default function TabsLayout() {
|
||||||
@@ -15,6 +15,17 @@ export default function TabsLayout() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setShowFilter, setStep } = useHomeStore();
|
const { setShowFilter, setStep } = useHomeStore();
|
||||||
const rotateAnim = useRef(new Animated.Value(0)).current;
|
const rotateAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
const [keyboard, setKeyboard] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const showSub = Keyboard.addListener('keyboardDidShow', () => setKeyboard(true));
|
||||||
|
const hideSub = Keyboard.addListener('keyboardDidHide', () => setKeyboard(false));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
showSub.remove();
|
||||||
|
hideSub.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Animated.loop(
|
Animated.loop(
|
||||||
@@ -52,9 +63,10 @@ export default function TabsLayout() {
|
|||||||
headerShadowVisible: false,
|
headerShadowVisible: false,
|
||||||
tabBarStyle: {
|
tabBarStyle: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
display: keyboard ? "none" : "flex",
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
bottom: 4,
|
bottom: 8,
|
||||||
height: 70,
|
height: 70,
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
paddingBottom: 12,
|
paddingBottom: 12,
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { useTheme } from '@/components/ThemeContext';
|
|
||||||
import { FilterProvider } from '@/components/ui/FilterContext';
|
import { FilterProvider } from '@/components/ui/FilterContext';
|
||||||
import { CustomHeader } from '@/components/ui/Header';
|
import { CustomHeader } from '@/components/ui/Header';
|
||||||
import CreateAdsScreens from '@/screens/create-ads/ui/CreateAdsScreens';
|
import CreateAdsScreens from '@/screens/create-ads/ui/CreateAdsScreens';
|
||||||
|
|
||||||
export default function CreateAnnouncements() {
|
export default function CreateAnnouncements() {
|
||||||
const { isDark } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<FilterProvider>
|
<FilterProvider>
|
||||||
<CustomHeader />
|
<CustomHeader />
|
||||||
|
|||||||
@@ -7,34 +7,21 @@ import { ProfileDataProvider } from '@/screens/profile/lib/ProfileDataContext';
|
|||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { View } from 'react-native';
|
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import ToastManager from "toastify-react-native";
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
useNotifications();
|
useNotifications();
|
||||||
const insets = useSafeAreaInsets();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SafeAreaView style={{ flex: 1, backgroundColor: "#000" }}>
|
||||||
{/* iOS status bar fon */}
|
<StatusBar style='light' backgroundColor='#000' />
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: insets.top,
|
|
||||||
backgroundColor: '#000',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* StatusBar */}
|
|
||||||
<StatusBar
|
|
||||||
style="light"
|
|
||||||
backgroundColor="#000" // Android
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stack screenOptions={{ headerShown: false }} />
|
<Stack screenOptions={{ headerShown: false }} />
|
||||||
</>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
@@ -43,6 +30,7 @@ export default function RootLayout() {
|
|||||||
<ProfileDataProvider>
|
<ProfileDataProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<AppContent />
|
<AppContent />
|
||||||
|
<ToastManager />
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</ProfileDataProvider>
|
</ProfileDataProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
|
|
||||||
export default function PersonalInfoScreen() {
|
export default function PersonalInfoScreen() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -76,7 +76,7 @@ export default function PersonalInfoScreen() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['get_me'] });
|
queryClient.invalidateQueries({ queryKey: ['get_me'] });
|
||||||
router.push('/profile/personal-info');
|
router.push('/profile/personal-info');
|
||||||
ToastAndroid.show(t("Ma'lumotlar yangilandi"), ToastAndroid.TOP);
|
Toast.success(t("Ma'lumotlar yangilandi"));
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Alert.alert(t('Xatolik yzu berdi'), t("Ma'lumotlarni yangilashda xatolik yuz berdi"));
|
Alert.alert(t('Xatolik yzu berdi'), t("Ma'lumotlarni yangilashda xatolik yuz berdi"));
|
||||||
|
|||||||
@@ -9,17 +9,16 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
|
||||||
Image,
|
Image,
|
||||||
Pressable,
|
Pressable,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
|
|
||||||
export default function PersonalInfoScreen() {
|
export default function PersonalInfoScreen() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -71,10 +70,10 @@ export default function PersonalInfoScreen() {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['get_me'] });
|
queryClient.invalidateQueries({ queryKey: ['get_me'] });
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setShowCategories(false);
|
setShowCategories(false);
|
||||||
ToastAndroid.show(t("Ma'lumotlar yangilandi"), ToastAndroid.TOP);
|
Toast.success(t("Ma'lumotlar yangilandi"));
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Alert.alert(t('Xatolik yuz berdi'), t('Yangilashda xatolik yuz berdi'));
|
Toast.error(t('Yangilashda xatolik yuz berdi'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
BIN
assets/announcements-video/video_en.mp4
Normal file
BIN
assets/announcements-video/video_en.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/announcements-video/video_ru.mp4
Normal file
BIN
assets/announcements-video/video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/announcements-video/video_uz.mp4
Normal file
BIN
assets/announcements-video/video_uz.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/goverment/video_ru.mp4
Normal file
BIN
assets/goverment/video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_en.mp4
Normal file
BIN
assets/manual/manual_video_en.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_ru.mp4
Normal file
BIN
assets/manual/manual_video_ru.mp4
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/manual/manual_video_uz.mp4
Normal file
BIN
assets/manual/manual_video_uz.mp4
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
|||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
import { router } from 'expo-router';
|
||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
type AuthContextType = {
|
type AuthContextType = {
|
||||||
@@ -32,6 +33,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
await AsyncStorage.removeItem('access_token');
|
await AsyncStorage.removeItem('access_token');
|
||||||
await AsyncStorage.removeItem('refresh_token');
|
await AsyncStorage.removeItem('refresh_token');
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
|
router.replace('/(auth)');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export async function registerForPushNotificationsAsync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (finalStatus !== 'granted') {
|
if (finalStatus !== 'granted') {
|
||||||
console.log('Notification uchun ruxsat berilmadi!');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,10 +42,6 @@ export async function registerForPushNotificationsAsync() {
|
|||||||
projectId: '67d5a024-4eb7-44ec-8b18-6c9187bd1862',
|
projectId: '67d5a024-4eb7-44ec-8b18-6c9187bd1862',
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
console.log('Push Token:', token);
|
|
||||||
} else {
|
|
||||||
console.log('Push notification faqat real qurilmalarda ishlaydi!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useTheme } from '@/components/ThemeContext';
|
import { useTheme } from '@/components/ThemeContext';
|
||||||
import { products_api } from '@/screens/home/lib/api';
|
import { products_api } from '@/screens/home/lib/api';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
|
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
|
||||||
import React, { Dispatch, SetStateAction, useState } from 'react';
|
import React, { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
FlatList,
|
FlatList,
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -44,6 +45,7 @@ export default function CategorySelect({ selectedCategories, setSelectedCategori
|
|||||||
const [currentCategories, setCurrentCategories] = useState<Category[]>([]);
|
const [currentCategories, setCurrentCategories] = useState<Category[]>([]);
|
||||||
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
||||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
// Root categories
|
// Root categories
|
||||||
const { isLoading: rootLoading, error: rootError } = useQuery<CategoryResponse>({
|
const { isLoading: rootLoading, error: rootError } = useQuery<CategoryResponse>({
|
||||||
@@ -66,8 +68,8 @@ export default function CategorySelect({ selectedCategories, setSelectedCategori
|
|||||||
setCurrentCategories(childCategories);
|
setCurrentCategories(childCategories);
|
||||||
setCurrentParentId(id);
|
setCurrentParentId(id);
|
||||||
},
|
},
|
||||||
onError: (err: AxiosError) => {
|
onError: () => {
|
||||||
console.error('Child category loading error:', err);
|
Toast.error(t("Xatolik yuz berdi"))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import BottomSheet, {
|
|||||||
BottomSheetScrollView,
|
BottomSheetScrollView,
|
||||||
} from '@gorhom/bottom-sheet';
|
} from '@gorhom/bottom-sheet';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { CheckIcon, ChevronRight, XIcon } from 'lucide-react-native';
|
import { CheckIcon, ChevronRight, XIcon } from 'lucide-react-native';
|
||||||
import React, { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react';
|
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');
|
setStep('items');
|
||||||
setFiltered(data.data.data.results);
|
setFiltered(data.data.data.results);
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => console.log(error),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { products_api } from '@/screens/home/lib/api';
|
import { products_api } from '@/screens/home/lib/api';
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
|
import { ChevronLeft, ChevronRight } from 'lucide-react-native';
|
||||||
import React, { Dispatch, SetStateAction, useState } from 'react';
|
import React, { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
FlatList,
|
FlatList,
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
import { useTheme } from '../ThemeContext';
|
import { useTheme } from '../ThemeContext';
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
@@ -39,6 +40,7 @@ export default function CategorySelection({ selectedCategories, setSelectedCateg
|
|||||||
const [history, setHistory] = useState<{ parentId: number | null; categories: Category[] }[]>([]);
|
const [history, setHistory] = useState<{ parentId: number | null; categories: Category[] }[]>([]);
|
||||||
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
const [currentParentId, setCurrentParentId] = useState<number | null>(null);
|
||||||
const { isDark } = useTheme();
|
const { isDark } = useTheme();
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
cardBg: isDark ? '#1e293b' : '#f8fafc',
|
cardBg: isDark ? '#1e293b' : '#f8fafc',
|
||||||
@@ -75,8 +77,8 @@ export default function CategorySelection({ selectedCategories, setSelectedCateg
|
|||||||
setCurrentCategories(childCategories);
|
setCurrentCategories(childCategories);
|
||||||
setCurrentParentId(id);
|
setCurrentParentId(id);
|
||||||
},
|
},
|
||||||
onError: (err: AxiosError) => {
|
onError: () => {
|
||||||
console.error('Child yuklashda xato:', err);
|
Toast.error(t("Xatolik yuz berdi"))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useTheme } from '@/components/ThemeContext';
|
import { useTheme } from '@/components/ThemeContext';
|
||||||
import { products_api } from '@/screens/home/lib/api';
|
import { products_api } from '@/screens/home/lib/api';
|
||||||
import { ProductResponse } from '@/screens/home/lib/types';
|
import { ProductResponse } from '@/screens/home/lib/types';
|
||||||
|
import { user_api } from '@/screens/profile/lib/api';
|
||||||
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||||
import { ResizeMode, Video } from 'expo-av';
|
import { ResizeMode, Video } from 'expo-av';
|
||||||
import { Info, Package, PlayCircle } from 'lucide-react-native';
|
import { Info, Package, PlayCircle } from 'lucide-react-native';
|
||||||
import React, { useCallback, useRef, useState } from 'react';
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
@@ -50,6 +51,17 @@ export default function ProductList({ query }: Props) {
|
|||||||
|
|
||||||
const allProducts = data?.pages.flatMap((p) => p.results) ?? [];
|
const allProducts = data?.pages.flatMap((p) => p.results) ?? [];
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: detail,
|
||||||
|
isLoading: loadingDetail,
|
||||||
|
isError: detailError,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['my_ads_id', selectedProduct],
|
||||||
|
queryFn: () => user_api.detail_service(Number(selectedProduct?.id)),
|
||||||
|
select: (res) => res.data.data,
|
||||||
|
enabled: !!selectedProduct,
|
||||||
|
});
|
||||||
|
|
||||||
const handlePresentModalPress = useCallback((product: ProductResponse) => {
|
const handlePresentModalPress = useCallback((product: ProductResponse) => {
|
||||||
setSelectedProduct(product);
|
setSelectedProduct(product);
|
||||||
setCurrentImageIndex(0);
|
setCurrentImageIndex(0);
|
||||||
@@ -181,12 +193,29 @@ export default function ProductList({ query }: Props) {
|
|||||||
style={styles.sheetContent}
|
style={styles.sheetContent}
|
||||||
contentContainerStyle={styles.sheetContentContainer}
|
contentContainerStyle={styles.sheetContentContainer}
|
||||||
>
|
>
|
||||||
{selectedProduct && (
|
{/* Loading holati */}
|
||||||
|
{loadingDetail && (
|
||||||
|
<View style={styles.center}>
|
||||||
|
<ActivityIndicator size="large" color="#3b82f6" />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error holati */}
|
||||||
|
{detailError && (
|
||||||
|
<View style={styles.center}>
|
||||||
|
<Text style={{ color: '#ef4444', fontWeight: '600' }}>
|
||||||
|
{t('Xatolik yuz berdi')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Detail mavjud bo‘lsa */}
|
||||||
|
{detail && (
|
||||||
<>
|
<>
|
||||||
<View style={styles.carouselWrapper}>
|
<View style={styles.carouselWrapper}>
|
||||||
<FlatList
|
<FlatList
|
||||||
nestedScrollEnabled={true}
|
nestedScrollEnabled={true}
|
||||||
data={selectedProduct.files || []}
|
data={detail.files || []}
|
||||||
renderItem={renderCarouselItem}
|
renderItem={renderCarouselItem}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
horizontal
|
horizontal
|
||||||
@@ -197,9 +226,9 @@ export default function ProductList({ query }: Props) {
|
|||||||
setCurrentImageIndex(index);
|
setCurrentImageIndex(index);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{selectedProduct.files.length > 1 && (
|
{detail.files.length > 1 && (
|
||||||
<View style={styles.pagination}>
|
<View style={styles.pagination}>
|
||||||
{selectedProduct.files.map((_, i) => (
|
{detail.files.map((_, i) => (
|
||||||
<View
|
<View
|
||||||
key={i}
|
key={i}
|
||||||
style={[
|
style={[
|
||||||
@@ -214,10 +243,10 @@ export default function ProductList({ query }: Props) {
|
|||||||
|
|
||||||
<View style={styles.sheetHeader}>
|
<View style={styles.sheetHeader}>
|
||||||
<Text style={[styles.sheetTitle, isDark ? styles.darkText : styles.lightText]}>
|
<Text style={[styles.sheetTitle, isDark ? styles.darkText : styles.lightText]}>
|
||||||
{selectedProduct.title}
|
{detail.title}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.sheetCompanyBadge}>
|
<View style={styles.sheetCompanyBadge}>
|
||||||
<Text style={styles.sheetCompanyText}>{selectedProduct.company}</Text>
|
<Text style={styles.sheetCompanyText}>{detail.company}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -230,10 +259,8 @@ export default function ProductList({ query }: Props) {
|
|||||||
{t("Batafsil ma'lumot")}
|
{t("Batafsil ma'lumot")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text style={[styles.sheetDescription, isDark ? styles.darkText : styles.lightText]}>
|
||||||
style={[styles.sheetDescription, isDark ? styles.darkText : styles.lightText]}
|
{detail.description || "Ma'lumot mavjud emas."}
|
||||||
>
|
|
||||||
{selectedProduct.description || "Ma'lumot mavjud emas."}
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
@@ -245,7 +272,6 @@ export default function ProductList({ query }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContainer: { gap: 0, paddingBottom: 20 },
|
|
||||||
card: {
|
card: {
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ export function RefreshProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
try {
|
try {
|
||||||
await queryClient.refetchQueries();
|
await queryClient.refetchQueries();
|
||||||
} catch (err) {
|
} catch (err) { } finally {
|
||||||
console.error('Global refresh error:', err);
|
|
||||||
} finally {
|
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
}, [queryClient, refreshing]);
|
}, [queryClient, refreshing]);
|
||||||
|
|||||||
@@ -13,7 +13,5 @@ export const script = (mode: string) => {
|
|||||||
documentElement.classList.remove(theme === 'light' ? 'dark' : 'light');
|
documentElement.classList.remove(theme === 'light' ? 'dark' : 'light');
|
||||||
documentElement.classList.add(theme);
|
documentElement.classList.add(theme);
|
||||||
documentElement.style.colorScheme = theme;
|
documentElement.style.colorScheme = theme;
|
||||||
} catch (e) {
|
} catch (e) { }
|
||||||
console.error(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) => {
|
export const saveLang = async (lang: string) => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.setItem('lang', lang);
|
await AsyncStorage.setItem('lang', lang);
|
||||||
console.log(`Language saved: ${lang}`);
|
} catch (error) { }
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save language', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +31,6 @@ export const getLang = async (): Promise<string | null> => {
|
|||||||
const lang = await AsyncStorage.getItem('lang');
|
const lang = await AsyncStorage.getItem('lang');
|
||||||
return lang;
|
return lang;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get language', error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -45,9 +41,7 @@ export const getLang = async (): Promise<string | null> => {
|
|||||||
export const deleteLang = async () => {
|
export const deleteLang = async () => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.removeItem('lang');
|
await AsyncStorage.removeItem('lang');
|
||||||
} catch (error) {
|
} catch (error) { }
|
||||||
console.error('Failed to delete language', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCESS = 'access_token';
|
const ACCESS = 'access_token';
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export function useNotifications() {
|
|||||||
|
|
||||||
// Foreground listener
|
// Foreground listener
|
||||||
notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
|
notificationListener.current = Notifications.addNotificationReceivedListener((notification) => {
|
||||||
console.log('Notification received:', notification);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// User response listener
|
// User response listener
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
"Keyingi": "Next",
|
"Keyingi": "Next",
|
||||||
"Xizmat sarlavhasi": "Service title",
|
"Xizmat sarlavhasi": "Service title",
|
||||||
"Yangilashda xato yuz berdi": "Error occurred while updating",
|
"Yangilashda xato yuz berdi": "Error occurred while updating",
|
||||||
"Xizmatni tahrirlash (1/2)": "Edit service (1/2)",
|
"Xizmatni tahrirlash": "Edit service",
|
||||||
"Xizmatni tahrirlash (2/2)": "Edit service (2/2)",
|
"Xizmatni tahrirlash (2/2)": "Edit service (2/2)",
|
||||||
"Tilni tanlang": "Select language",
|
"Tilni tanlang": "Select language",
|
||||||
"Rejimni tanlang": "Select mode",
|
"Rejimni tanlang": "Select mode",
|
||||||
@@ -226,5 +226,8 @@
|
|||||||
"INN kiriting (9 raqam)": "Enter INN (9 digits)",
|
"INN kiriting (9 raqam)": "Enter INN (9 digits)",
|
||||||
"Referal kodi": "Referral code",
|
"Referal kodi": "Referral code",
|
||||||
"Direktor JSHSHR": "Director JSHSHR",
|
"Direktor JSHSHR": "Director JSHSHR",
|
||||||
"Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)"
|
"Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
"Keyingi": "Далее",
|
"Keyingi": "Далее",
|
||||||
"Xizmat sarlavhasi": "Заголовок услуги",
|
"Xizmat sarlavhasi": "Заголовок услуги",
|
||||||
"Yangilashda xato yuz berdi": "Произошла ошибка при обновлении",
|
"Yangilashda xato yuz berdi": "Произошла ошибка при обновлении",
|
||||||
"Xizmatni tahrirlash (1/2)": "Редактирование услуги (1/2)",
|
"Xizmatni tahrirlash": "Редактирование услуги",
|
||||||
"Xizmatni tahrirlash (2/2)": "Редактирование услуги (2/2)",
|
"Xizmatni tahrirlash (2/2)": "Редактирование услуги (2/2)",
|
||||||
"Tilni tanlang": "Выберите язык",
|
"Tilni tanlang": "Выберите язык",
|
||||||
"Rejimni tanlang": "Выберите режим",
|
"Rejimni tanlang": "Выберите режим",
|
||||||
@@ -225,5 +225,8 @@
|
|||||||
"INN kiriting (9 raqam)": "Введите ИНН (9 цифр)",
|
"INN kiriting (9 raqam)": "Введите ИНН (9 цифр)",
|
||||||
"Referal kodi": "Реферальный код",
|
"Referal kodi": "Реферальный код",
|
||||||
"Direktor JSHSHR": "Директор ПИНФЛ",
|
"Direktor JSHSHR": "Директор ПИНФЛ",
|
||||||
"Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)"
|
"Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)",
|
||||||
|
"Hozircha bildirishnomalar yo'q": "Пока нет уведомлений",
|
||||||
|
"Siz o'zini o'zi band qilgan yoki yakka tartibdagi tadbirkorlik bo'lishingiz kerak": "Вы должны быть самозанятым или индивидуальным предпринимателем",
|
||||||
|
"Sizning shaxsiy ma'lumotlaringiz topilmadi": "Ваши личные данные не найдены"
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
"Yangi xizmat (2/2)": "Yangi xizmat (2/2)",
|
"Yangi xizmat (2/2)": "Yangi xizmat (2/2)",
|
||||||
"Keyingi": "Keyingi",
|
"Keyingi": "Keyingi",
|
||||||
"Xizmat sarlavhasi": "Xizmat sarlavhasi",
|
"Xizmat sarlavhasi": "Xizmat sarlavhasi",
|
||||||
"Xizmatni tahrirlash (1/2)": "Xizmatni tahrirlash (1/2)",
|
"Xizmatni tahrirlash": "Xizmatni tahrirlash",
|
||||||
"Xizmatni tahrirlash (2/2)": "Xizmatni tahrirlash (2/2)",
|
"Xizmatni tahrirlash (2/2)": "Xizmatni tahrirlash (2/2)",
|
||||||
"Tilni tanlang": "Tilni tanlang",
|
"Tilni tanlang": "Tilni tanlang",
|
||||||
"Rejimni tanlang": "Rejimni tanlang",
|
"Rejimni tanlang": "Rejimni tanlang",
|
||||||
@@ -177,6 +177,7 @@
|
|||||||
"Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.",
|
"Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.": "Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.",
|
||||||
"Qo'llanma video": "Qo'llanma video",
|
"Qo'llanma video": "Qo'llanma video",
|
||||||
"Bildirishnomalar": "Bildirishnomalar",
|
"Bildirishnomalar": "Bildirishnomalar",
|
||||||
|
"Hozircha bildirishnomalar yo'q": "Hozircha bildirishnomalar yo'q",
|
||||||
"Bildirishnomalarni yuklashda muammo bo'ldi": "Bildirishnomalarni yuklashda muammo bo'ldi",
|
"Bildirishnomalarni yuklashda muammo bo'ldi": "Bildirishnomalarni yuklashda muammo bo'ldi",
|
||||||
"Hozir": "Hozir",
|
"Hozir": "Hozir",
|
||||||
"daqiqa oldin": "daqiqa oldin",
|
"daqiqa oldin": "daqiqa oldin",
|
||||||
@@ -225,5 +226,7 @@
|
|||||||
"INN kiriting (9 raqam)": "INN kiriting (9 raqam)",
|
"INN kiriting (9 raqam)": "INN kiriting (9 raqam)",
|
||||||
"Referal kodi": "Referal kodi",
|
"Referal kodi": "Referal kodi",
|
||||||
"Direktor JSHSHR": "Direktor JSHSHR",
|
"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"
|
||||||
}
|
}
|
||||||
96
package-lock.json
generated
96
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"@tanstack/react-query": "^5.90.17",
|
"@tanstack/react-query": "^5.90.17",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"expo": "~54.0.31",
|
"expo": "~54.0.31",
|
||||||
"expo-av": "~16.0.8",
|
"expo-av": "~16.0.8",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
@@ -71,9 +72,11 @@
|
|||||||
"react-native-worklets": "^0.5.1",
|
"react-native-worklets": "^0.5.1",
|
||||||
"react-stately": "^3.39.0",
|
"react-stately": "^3.39.0",
|
||||||
"tailwind-variants": "^0.1.20",
|
"tailwind-variants": "^0.1.20",
|
||||||
|
"toastify-react-native": "^7.2.3",
|
||||||
"zustand": "^5.0.10"
|
"zustand": "^5.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
"babel-plugin-module-resolver": "^5.0.0",
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
@@ -6536,6 +6539,13 @@
|
|||||||
"@babel/types": "^7.28.2"
|
"@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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -8568,6 +8578,12 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/crypto-random-string": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
|
||||||
@@ -15396,6 +15412,73 @@
|
|||||||
"react-native-pager-view": ">= 6.0.0"
|
"react-native-pager-view": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-vector-icons": {
|
||||||
|
"version": "10.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz",
|
||||||
|
"integrity": "sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==",
|
||||||
|
"deprecated": "react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"yargs": "^16.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fa-upgrade.sh": "bin/fa-upgrade.sh",
|
||||||
|
"fa5-upgrade": "bin/fa5-upgrade.sh",
|
||||||
|
"fa6-upgrade": "bin/fa6-upgrade.sh",
|
||||||
|
"generate-icon": "bin/generate-icon.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs": {
|
||||||
|
"version": "16.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
|
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^20.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-vector-icons/node_modules/yargs-parser": {
|
||||||
|
"version": "20.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||||
|
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-web": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.21.2",
|
"version": "0.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
@@ -17202,6 +17285,19 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/toastify-react-native": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/toastify-react-native/-/toastify-react-native-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-ngmpTKlTo0IRddwSsNWK+YKbB2veqotHy7Zpil4eksoLAlq0RPSgdVOk5QDEDUONJQ4r7ljGYeRW68KBztirsg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-native-vector-icons": "*"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"@tanstack/react-query": "^5.90.17",
|
"@tanstack/react-query": "^5.90.17",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"expo": "~54.0.31",
|
"expo": "~54.0.31",
|
||||||
"expo-av": "~16.0.8",
|
"expo-av": "~16.0.8",
|
||||||
"expo-blur": "~15.0.8",
|
"expo-blur": "~15.0.8",
|
||||||
@@ -76,9 +77,11 @@
|
|||||||
"react-native-worklets": "^0.5.1",
|
"react-native-worklets": "^0.5.1",
|
||||||
"react-stately": "^3.39.0",
|
"react-stately": "^3.39.0",
|
||||||
"tailwind-variants": "^0.1.20",
|
"tailwind-variants": "^0.1.20",
|
||||||
|
"toastify-react-native": "^7.2.3",
|
||||||
"zustand": "^5.0.10"
|
"zustand": "^5.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
"babel-plugin-module-resolver": "^5.0.0",
|
"babel-plugin-module-resolver": "^5.0.0",
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
|
|||||||
@@ -101,14 +101,14 @@ export default function DashboardScreen() {
|
|||||||
|
|
||||||
// Announcement videos
|
// Announcement videos
|
||||||
const videos = {
|
const videos = {
|
||||||
uz: require('@/assets/announcements-video/video_uz.webm'),
|
uz: require('@/assets/announcements-video/video_uz.mp4'),
|
||||||
ru: require('@/assets/announcements-video/video_ru.webm'),
|
ru: require('@/assets/announcements-video/video_ru.mp4'),
|
||||||
en: require('@/assets/announcements-video/video_en.webm'),
|
en: require('@/assets/announcements-video/video_en.mp4'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Government videos: faqat RU mavjud
|
// Government videos: faqat RU mavjud
|
||||||
const govermentVideos: Partial<Record<'uz' | 'ru' | 'en', any>> = {
|
const govermentVideos: Partial<Record<'uz' | 'ru' | 'en', any>> = {
|
||||||
ru: require('@/assets/goverment/video_ru.webm'),
|
ru: require('@/assets/goverment/video_ru.mp4'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update selected language
|
// Update selected language
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { Toast } from 'toastify-react-native';
|
||||||
import { auth_api } from '../login/lib/api';
|
import { auth_api } from '../login/lib/api';
|
||||||
import useTokenStore from '../login/lib/hook';
|
import useTokenStore from '../login/lib/hook';
|
||||||
import ConfirmForm from './ConfirmForm';
|
import ConfirmForm from './ConfirmForm';
|
||||||
@@ -44,9 +43,7 @@ const ConfirmScreen = () => {
|
|||||||
const storedPhone = await AsyncStorage.getItem('phone');
|
const storedPhone = await AsyncStorage.getItem('phone');
|
||||||
if (storedPhone) setPhone(storedPhone);
|
if (storedPhone) setPhone(storedPhone);
|
||||||
else setPhone(null);
|
else setPhone(null);
|
||||||
} catch (error) {
|
} catch (error) { }
|
||||||
console.log('AsyncStorage error:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
loadPhone();
|
loadPhone();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -98,11 +95,11 @@ const ConfirmScreen = () => {
|
|||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
setResendTimer(60);
|
setResendTimer(60);
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
ToastAndroid.show(t('Kod qayta yuborildi'), ToastAndroid.SHORT);
|
Toast.info(t('Kod qayta yuborildi'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Alert.alert(t('Xatolik yuz berdi'), t('Kodni qayta yuborishda xatolik yuz berdi'));
|
Toast.error(t('Kodni qayta yuborishda xatolik yuz berdi'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,10 +127,10 @@ const ConfirmScreen = () => {
|
|||||||
<View style={styles.decorCircle1} />
|
<View style={styles.decorCircle1} />
|
||||||
<View style={styles.decorCircle2} />
|
<View style={styles.decorCircle2} />
|
||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
|
||||||
<KeyboardAwareScrollView
|
<KeyboardAwareScrollView
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
enableOnAndroid
|
||||||
|
extraScrollHeight={120}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
@@ -195,7 +192,6 @@ const ConfirmScreen = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View> */}
|
</View> */}
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
</SafeAreaView>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import httpClient from '@/api/httpClient';
|
import httpClient from '@/api/httpClient';
|
||||||
import { API_URLS } from '@/api/URLs';
|
import { API_URLS } from '@/api/URLs';
|
||||||
import { AxiosResponse } from 'axios';
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
interface ConfirmBody {
|
interface ConfirmBody {
|
||||||
status: boolean;
|
status: boolean;
|
||||||
@@ -13,307 +13,70 @@ interface ConfirmBody {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyInfo {
|
export interface GetInfo {
|
||||||
id: number;
|
VATRegCode: null | string
|
||||||
inn: string;
|
VATRegStatus: null | string
|
||||||
registration_authority: string;
|
account: string
|
||||||
registration_date: string;
|
accountant: null | string
|
||||||
registration_number: string;
|
address: string
|
||||||
name: string;
|
bankAccount: string
|
||||||
short_name: string;
|
bankCode: string
|
||||||
opf_code: string;
|
director: string | null
|
||||||
opf_name: string;
|
directorPinfl: string | null
|
||||||
oked_code: string;
|
directorTin: null | string
|
||||||
vat_number: string;
|
fullName: string
|
||||||
oked_name: string;
|
fullname: string
|
||||||
area: string;
|
isBudget: number
|
||||||
region: string;
|
isItd: boolean
|
||||||
soogu_code: string;
|
mfo: string
|
||||||
soogu_name: string;
|
na1Code: null | string
|
||||||
small_businesses: string;
|
na1Name: null | string
|
||||||
activity_state: number;
|
name: string
|
||||||
statutory_fund: string;
|
ns10Code: number
|
||||||
|
ns11Code: number
|
||||||
activity_state_detail: ActivityStateDetail;
|
oked: null | string
|
||||||
business_type_detail: BusinessTypeDetail;
|
peasantFarm: boolean
|
||||||
|
personalNum: string
|
||||||
director: string;
|
privateNotary: boolean
|
||||||
email: string;
|
regDate: null | string
|
||||||
village_code: string;
|
selfEmployment: boolean
|
||||||
email_status: number;
|
shortName: string
|
||||||
phones: string[];
|
shortname: string
|
||||||
|
statusCode: null | string
|
||||||
soato_code: string;
|
statusName: null | string
|
||||||
soato_name: string;
|
tin: 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 BusinessTypeDetail {
|
export interface GetDistrict {
|
||||||
id: number;
|
districtId: string,
|
||||||
external_id: number;
|
regionId: number,
|
||||||
name: string;
|
districtCode: number,
|
||||||
name_uz: string;
|
name: string
|
||||||
name_en: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DishonestExecutor {
|
export interface GetRegion {
|
||||||
is_dishonest_executor: number;
|
regionId: number,
|
||||||
delete_date: string | null;
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VillageDetail {
|
export interface GetTokens {
|
||||||
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 {
|
|
||||||
status: boolean,
|
status: boolean,
|
||||||
data: {
|
data: {
|
||||||
entity: {
|
links: {
|
||||||
name: {
|
previous: null | string,
|
||||||
rows: {
|
next: null | string
|
||||||
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: {
|
total_items: number,
|
||||||
rows: [],
|
total_pages: number,
|
||||||
total: 0
|
page_size: number,
|
||||||
},
|
current_page: number,
|
||||||
director: {
|
results:
|
||||||
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:
|
|
||||||
{
|
{
|
||||||
id: number,
|
id: number,
|
||||||
pinfl: string,
|
key: string,
|
||||||
entrepreneur: string,
|
value: string,
|
||||||
email: string,
|
is_active: boolean
|
||||||
phone: string,
|
}[]
|
||||||
registration_date: string
|
|
||||||
}[],
|
|
||||||
total: number
|
|
||||||
},
|
|
||||||
trademark: {
|
|
||||||
rows: [],
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,25 +95,60 @@ export const auth_api = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
async get_info(body: { value: string, type: string, passport_series?: string, passport_number?: string }): Promise<AxiosResponse<GetInfoResponse>> {
|
async get_info(body: { value: string, token: string, tokenName: string }): Promise<AxiosResponse<GetInfo>> {
|
||||||
const res = await httpClient.post(API_URLS.Info, body);
|
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;
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async get_director_info(body: { value: string }): Promise<AxiosResponse<GetDirectorInfoResponse>> {
|
async get_district(body: { token: string, tokenName: string }): Promise<AxiosResponse<GetDistrict[]>> {
|
||||||
const res = await httpClient.post(API_URLS.Get_Director_Info, body);
|
try {
|
||||||
|
const res = await axios.get(`https://testapi3.didox.uz/v1/districts/all/`, {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": "uz",
|
||||||
|
[body.tokenName]: body.token
|
||||||
|
}
|
||||||
|
});
|
||||||
return res;
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_region(body: { token: string, tokenName: string }): Promise<AxiosResponse<GetRegion[]>> {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`https://testapi3.didox.uz/v1/regions/all/`, {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": "uz",
|
||||||
|
[body.tokenName]: body.token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async register(body: {
|
async register(body: {
|
||||||
phone: string;
|
phone: string;
|
||||||
stir: string;
|
stir: string;
|
||||||
person_type: string;
|
person_type: string;
|
||||||
referal: string;
|
referral: string;
|
||||||
activate_types: number[];
|
activate_types: number[];
|
||||||
director_full_name: string;
|
director_full_name: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
district: number;
|
||||||
|
address: number;
|
||||||
|
company_name: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await httpClient.post(API_URLS.Register, body);
|
const res = await httpClient.post(API_URLS.Register, body);
|
||||||
return res;
|
return res;
|
||||||
@@ -365,4 +163,10 @@ export const auth_api = {
|
|||||||
const res = await httpClient.post(API_URLS.Register_Resend, body);
|
const res = await httpClient.post(API_URLS.Register_Resend, body);
|
||||||
return res;
|
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 [focused, setFocused] = useState(false);
|
||||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||||
const { phone, setPhone, submit, loading, error } = UseLoginForm();
|
const { phone, setPhone, submit, loading, error } = UseLoginForm();
|
||||||
console.log(error);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
import { auth_api } from '../login/lib/api';
|
import { auth_api } from '../login/lib/api';
|
||||||
import useTokenStore from '../login/lib/hook';
|
import useTokenStore from '../login/lib/hook';
|
||||||
import ConfirmForm from './ConfirmForm';
|
import ConfirmForm from './ConfirmForm';
|
||||||
@@ -44,9 +44,7 @@ const RegisterConfirmScreen = () => {
|
|||||||
const storedPhone = await AsyncStorage.getItem('phone');
|
const storedPhone = await AsyncStorage.getItem('phone');
|
||||||
if (storedPhone) setPhone(storedPhone);
|
if (storedPhone) setPhone(storedPhone);
|
||||||
else setPhone(null);
|
else setPhone(null);
|
||||||
} catch (error) {
|
} catch (error) { }
|
||||||
console.log('AsyncStorage error:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
loadPhone();
|
loadPhone();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -67,6 +65,7 @@ const RegisterConfirmScreen = () => {
|
|||||||
savedToken(res.data.data.token.access);
|
savedToken(res.data.data.token.access);
|
||||||
await AsyncStorage.setItem('refresh_token', res.data.data.token.refresh);
|
await AsyncStorage.setItem('refresh_token', res.data.data.token.refresh);
|
||||||
await login(res.data.data.token.access);
|
await login(res.data.data.token.access);
|
||||||
|
|
||||||
const pushToken = await registerForPushNotificationsAsync();
|
const pushToken = await registerForPushNotificationsAsync();
|
||||||
if (pushToken) {
|
if (pushToken) {
|
||||||
await commonRequests.registerDevice({
|
await commonRequests.registerDevice({
|
||||||
@@ -78,6 +77,8 @@ const RegisterConfirmScreen = () => {
|
|||||||
// Notification querylarni refetch
|
// Notification querylarni refetch
|
||||||
queryClient.refetchQueries({ queryKey: ['notification-list'] });
|
queryClient.refetchQueries({ queryKey: ['notification-list'] });
|
||||||
queryClient.refetchQueries({ queryKey: ['notifications-list'] });
|
queryClient.refetchQueries({ queryKey: ['notifications-list'] });
|
||||||
|
|
||||||
|
// Dashboardga yo‘naltirish
|
||||||
router.replace('/(dashboard)');
|
router.replace('/(dashboard)');
|
||||||
},
|
},
|
||||||
onError: (err: any) => {
|
onError: (err: any) => {
|
||||||
@@ -93,7 +94,7 @@ const RegisterConfirmScreen = () => {
|
|||||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
setResendTimer(60);
|
setResendTimer(60);
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
ToastAndroid.show(t('Kod qayta yuborildi'), ToastAndroid.SHORT);
|
Toast.info(t('Kod qayta yuborildi'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native';
|
|||||||
|
|
||||||
export default function CategorySelectScreen() {
|
export default function CategorySelectScreen() {
|
||||||
const router = useRouter();
|
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;
|
phone: string;
|
||||||
stir: string;
|
stir: string;
|
||||||
person_type: 'band' | 'ytt';
|
person_type: 'band' | 'ytt';
|
||||||
@@ -17,10 +17,10 @@ export default function CategorySelectScreen() {
|
|||||||
director_full_name: string;
|
director_full_name: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
address: string;
|
||||||
|
company_name: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
console.log(referal);
|
|
||||||
|
|
||||||
const [selected, setSelected] = React.useState<number | null>(null);
|
const [selected, setSelected] = React.useState<number | null>(null);
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
@@ -34,10 +34,13 @@ export default function CategorySelectScreen() {
|
|||||||
stir: string;
|
stir: string;
|
||||||
person_type: string;
|
person_type: string;
|
||||||
activate_types: number[];
|
activate_types: number[];
|
||||||
referal: string;
|
referral: string;
|
||||||
director_full_name: string;
|
director_full_name: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
district: number;
|
||||||
|
company_name: string;
|
||||||
|
address: number;
|
||||||
}) => auth_api.register(body),
|
}) => auth_api.register(body),
|
||||||
onSuccess: () => router.replace('/'),
|
onSuccess: () => router.replace('/'),
|
||||||
});
|
});
|
||||||
@@ -68,10 +71,13 @@ export default function CategorySelectScreen() {
|
|||||||
person_type: person_type,
|
person_type: person_type,
|
||||||
phone: `998${phone}`,
|
phone: `998${phone}`,
|
||||||
stir: stir,
|
stir: stir,
|
||||||
referal: String(referal),
|
referral: String(referal),
|
||||||
director_full_name: String(director_full_name),
|
director_full_name: String(director_full_name),
|
||||||
first_name: String(first_name),
|
first_name: String(first_name),
|
||||||
last_name: String(last_name),
|
last_name: String(last_name),
|
||||||
|
district: Number(address),
|
||||||
|
company_name: String(company_name),
|
||||||
|
address: Number(address),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,106 +1,108 @@
|
|||||||
import AuthHeader from '@/components/ui/AuthHeader';
|
import AuthHeader from '@/components/ui/AuthHeader';
|
||||||
|
import { decryptToken } from '@/constants/crypto';
|
||||||
import { formatPhone, normalizeDigits } from '@/constants/formatPhone';
|
import { formatPhone, normalizeDigits } from '@/constants/formatPhone';
|
||||||
|
import { formatText, formatTextToLatin } from '@/constants/formatText';
|
||||||
import { products_api } from '@/screens/home/lib/api';
|
import { products_api } from '@/screens/home/lib/api';
|
||||||
import BottomSheet, {
|
import BottomSheet, { BottomSheetBackdrop, BottomSheetFlatList, BottomSheetTextInput } from '@gorhom/bottom-sheet';
|
||||||
BottomSheetBackdrop,
|
|
||||||
BottomSheetFlatList,
|
|
||||||
BottomSheetTextInput,
|
|
||||||
} from '@gorhom/bottom-sheet';
|
|
||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import {
|
import {
|
||||||
Building2,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
ChevronDown,
|
||||||
Globe,
|
Globe,
|
||||||
Hash,
|
Hash,
|
||||||
Search,
|
Search,
|
||||||
ShieldCheck,
|
UserPlus
|
||||||
User,
|
|
||||||
UserPlus,
|
|
||||||
} from 'lucide-react-native';
|
} 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 { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
KeyboardAvoidingView,
|
Keyboard,
|
||||||
Platform,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
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 PhonePrefix from '../login/ui/PhonePrefix';
|
||||||
import { useRegister } from './lib/useRegisterStore';
|
import { UseLoginForm } from '../login/ui/UseLoginForm';
|
||||||
|
|
||||||
function getHeaderInfo(personType: string | null) {
|
interface CoordsData {
|
||||||
switch (personType) {
|
lat: number;
|
||||||
case 'yatt':
|
lon: number;
|
||||||
return {
|
polygon: [number, number][][];
|
||||||
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],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RegisterFormScreen() {
|
export default function RegisterFormScreen() {
|
||||||
const router = useRouter();
|
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 { t } = useTranslation();
|
||||||
|
const { phone, setPhone } = UseLoginForm();
|
||||||
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 countrySheetRef = useRef<BottomSheet>(null);
|
const countrySheetRef = useRef<BottomSheet>(null);
|
||||||
const snapPoints = useMemo(() => ['60%', '90%'], []);
|
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({ token: token?.value || "", tokenName: token?.name || "" }),
|
||||||
|
enabled: !!token,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: regions } = useQuery({
|
||||||
|
queryKey: ["regions"],
|
||||||
|
queryFn: async () => auth_api.get_region({ token: token?.value || "", tokenName: token?.name || "" }),
|
||||||
|
enabled: !!token,
|
||||||
|
})
|
||||||
|
|
||||||
const { data: countryResponse, isLoading: countryLoading } = useQuery({
|
const { data: countryResponse, isLoading: countryLoading } = useQuery({
|
||||||
queryKey: ['country-detail'],
|
queryKey: ['country-detail'],
|
||||||
@@ -108,16 +110,125 @@ export default function RegisterFormScreen() {
|
|||||||
select: (res) => res.data?.data || [],
|
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(() => {
|
const openCountrySheet = useCallback(() => {
|
||||||
|
Keyboard.dismiss();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
countrySheetRef.current?.snapToIndex(0);
|
countrySheetRef.current?.snapToIndex(0);
|
||||||
}, 100);
|
}, 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeCountrySheet = useCallback(() => {
|
const selectedCountryName = useMemo(() => {
|
||||||
countrySheetRef.current?.close();
|
if (!selectedCountry) return t('Tanlang');
|
||||||
setTimeout(() => setCountrySearch(''), 300);
|
return (
|
||||||
}, []);
|
countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name ||
|
||||||
|
t('Tanlang')
|
||||||
|
);
|
||||||
|
}, [selectedCountry, countryResponse, t]);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
const renderBackdrop = useCallback(
|
||||||
(props: any) => (
|
(props: any) => (
|
||||||
@@ -132,236 +243,34 @@ export default function RegisterFormScreen() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedCountryName = useMemo(() => {
|
const closeCountrySheet = useCallback(() => {
|
||||||
if (!selectedCountry) return t('Tanlang');
|
countrySheetRef.current?.close();
|
||||||
return (
|
setTimeout(() => setCountrySearch(''), 300);
|
||||||
countryResponse?.find((c: any) => c.flag?.toUpperCase() === selectedCountry)?.name ||
|
}, []);
|
||||||
t('Tanlang')
|
|
||||||
);
|
|
||||||
}, [selectedCountry, countryResponse, t]);
|
|
||||||
|
|
||||||
const filteredCountries = useMemo(() => {
|
const valid =
|
||||||
if (!countrySearch.trim()) return countryResponse || [];
|
phone.length === 9 &&
|
||||||
const q = countrySearch.toLowerCase().trim();
|
(stir.length === 9 || stir.length === 14) &&
|
||||||
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);
|
|
||||||
// INN o'zgarganda director ma'lumotlarini tozalash
|
|
||||||
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;
|
|
||||||
|
|
||||||
// -------------------------------------------------------
|
|
||||||
// INN TEKSHIRUVI
|
|
||||||
// Director response strukturasi:
|
|
||||||
// data.entity.name.rows = [{ inn: "123456789", ... }, ...]
|
|
||||||
// Shu rows ichida joriy inn bor-yo'qligini tekshiramiz
|
|
||||||
// -------------------------------------------------------
|
|
||||||
const rows: Array<{ inn: string }> = directorData?.data?.entity?.name?.rows ?? [];
|
|
||||||
|
|
||||||
const innMatch = rows.some((row) => row.inn === inn);
|
|
||||||
|
|
||||||
if (!innMatch) {
|
|
||||||
// Director bu tashkilotga tegishli emas
|
|
||||||
setDirectorInfo(null);
|
|
||||||
setErrorDirectorInfo(t("Bu direktor ko'rsatilgan tashkilotga tegishli emas"));
|
|
||||||
setDirectorLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// INN mos keldi — director ma'lumotini saqlash
|
|
||||||
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 &&
|
info &&
|
||||||
directorJshshr.length === 14 &&
|
hasValidName &&
|
||||||
directorInfo // faqat INN mos kelganda to'ldiriladi
|
isDirectorTinValid &&
|
||||||
);
|
error === null;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const handleContinue = () => {
|
|
||||||
// Director to'liq ismi directorInfo dan olinadi
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<>
|
||||||
<View style={styles.container}>
|
<KeyboardAwareScrollView
|
||||||
|
enableOnAndroid
|
||||||
|
enableAutomaticScroll
|
||||||
|
extraScrollHeight={120}
|
||||||
|
style={styles.keyboardScroll}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={styles.container}
|
||||||
|
onStartShouldSetResponder={() => {
|
||||||
|
Keyboard.dismiss();
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={['#0f172a', '#1e293b', '#334155']}
|
colors={['#0f172a', '#1e293b', '#334155']}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
@@ -374,38 +283,25 @@ export default function RegisterFormScreen() {
|
|||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
|
|
||||||
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||||||
<KeyboardAvoidingView
|
<View style={styles.scrollContent}>
|
||||||
style={{ flex: 1 }}
|
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
||||||
>
|
|
||||||
<ScrollView
|
|
||||||
contentContainerStyle={styles.scrollContent}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
keyboardShouldPersistTaps="handled"
|
|
||||||
>
|
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.iconContainer}>
|
<View style={styles.iconContainer}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={headerInfo.gradient}
|
colors={['#10b981', '#059669']}
|
||||||
style={styles.iconGradient}
|
style={styles.iconGradient}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 1 }}
|
end={{ x: 1, y: 1 }}
|
||||||
>
|
>
|
||||||
{headerInfo.icon}
|
<UserPlus size={32} color="#fff" />
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.title}>{t(headerInfo.label)}</Text>
|
<Text style={styles.title}>{t('Ro\'yxatdan o\'tish')}</Text>
|
||||||
<Text style={styles.subtitle}>
|
|
||||||
{isYattOrBand
|
|
||||||
? t("JSHSHR va passport ma'lumotlarini kiriting")
|
|
||||||
: t('INN raqamini kiriting')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<View style={styles.formGap}>
|
<View style={styles.formGap}>
|
||||||
{/* ---- Davlat (Country) ---- */}
|
<View>
|
||||||
{/* <View>
|
<View>
|
||||||
<Text style={styles.label}>{t('Davlat')}</Text>
|
<Text style={styles.label}>{t('Davlat')}</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
@@ -429,7 +325,6 @@ export default function RegisterFormScreen() {
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.textInput,
|
styles.textInput,
|
||||||
// { color: selectedCountry === 'all' ? '#94a3b8' : '#1e293b' },
|
|
||||||
{ color: '#94a3b8' },
|
{ color: '#94a3b8' },
|
||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@@ -440,147 +335,37 @@ export default function RegisterFormScreen() {
|
|||||||
)}
|
)}
|
||||||
<ChevronDown size={18} color={'#cbd5e1'} />
|
<ChevronDown size={18} color={'#cbd5e1'} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View> */}
|
</View>
|
||||||
{/* ---- YATT / BAND ---- */}
|
</View>
|
||||||
{isYattOrBand && (
|
|
||||||
<>
|
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>{t('JSHSHR')}</Text>
|
<Text style={styles.label}>{t('STIR')}</Text>
|
||||||
<View style={styles.input}>
|
<View style={styles.input}>
|
||||||
<Hash size={18} color="#94a3b8" />
|
<Hash size={18} color="#94a3b8" />
|
||||||
<TextInput
|
<TextInput
|
||||||
value={jshshr}
|
value={stir}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
placeholder={t('JSHSHR kiriting (14 raqam)')}
|
placeholder={t('STIR')}
|
||||||
placeholderTextColor="#94a3b8"
|
placeholderTextColor="#94a3b8"
|
||||||
style={styles.textInput}
|
style={{ flex: 1, color: "black" }}
|
||||||
onChangeText={handleJshshrChange}
|
onChangeText={(text) => {
|
||||||
maxLength={14}
|
const v = normalizeDigits(text).slice(0, 14);
|
||||||
testID="jshshr-input"
|
setStir(v);
|
||||||
|
|
||||||
|
if (v.length === 9 || v.length === 14) {
|
||||||
|
setLoading(true);
|
||||||
|
mutate(v);
|
||||||
|
setRegionId(null)
|
||||||
|
setDistrictId(null)
|
||||||
|
setRegion(null)
|
||||||
|
setDistrict(null)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{loading && jshshr.length >= 14 && (
|
{loading && <ActivityIndicator size="small" />}
|
||||||
<ActivityIndicator size="small" color="#3b82f6" />
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---- LEGAL ENTITY: INN ---- */}
|
|
||||||
{isLegal && (
|
|
||||||
<View>
|
|
||||||
<Text style={styles.label}>{t('INN')}</Text>
|
|
||||||
<View style={styles.input}>
|
|
||||||
<Hash size={18} color="#94a3b8" />
|
|
||||||
<TextInput
|
|
||||||
value={inn}
|
|
||||||
keyboardType="numeric"
|
|
||||||
placeholder={t('INN kiriting (9 raqam)')}
|
|
||||||
placeholderTextColor="#94a3b8"
|
|
||||||
style={styles.textInput}
|
|
||||||
onChangeText={handleInnChange}
|
|
||||||
maxLength={9}
|
|
||||||
testID="inn-input"
|
|
||||||
/>
|
|
||||||
{loading && inn.length >= 9 && (
|
|
||||||
<ActivityIndicator size="small" color="#3b82f6" />
|
|
||||||
)}
|
|
||||||
</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 — faqat info kelganda ko'rinadi ---- */}
|
|
||||||
{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>
|
|
||||||
|
|
||||||
{/* ---- Direktor ma'lumoti (INN mos kelgan holda) ---- */}
|
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---- Xato: INN mos kelmasa yoki server xatosi ---- */}
|
|
||||||
{directorInfoError && (
|
|
||||||
<View style={[styles.errorBox, { marginTop: 8 }]}>
|
|
||||||
<Text style={styles.errorText}>{directorInfoError}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ---- Referal ---- */}
|
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>{t('Referal')}</Text>
|
<Text style={styles.label}>{t('Referal')}</Text>
|
||||||
<View style={styles.input}>
|
<View style={styles.input}>
|
||||||
@@ -591,12 +376,12 @@ export default function RegisterFormScreen() {
|
|||||||
placeholderTextColor="#94a3b8"
|
placeholderTextColor="#94a3b8"
|
||||||
style={styles.textInput}
|
style={styles.textInput}
|
||||||
onChangeText={setReferal}
|
onChangeText={setReferal}
|
||||||
|
maxLength={9}
|
||||||
testID="referal-input"
|
testID="referal-input"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ---- Telefon ---- */}
|
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.label}>{t('Telefon raqami')}</Text>
|
<Text style={styles.label}>{t('Telefon raqami')}</Text>
|
||||||
<View style={styles.input}>
|
<View style={styles.input}>
|
||||||
@@ -606,48 +391,72 @@ export default function RegisterFormScreen() {
|
|||||||
placeholder="90 123 45 67"
|
placeholder="90 123 45 67"
|
||||||
placeholderTextColor="#94a3b8"
|
placeholderTextColor="#94a3b8"
|
||||||
keyboardType="phone-pad"
|
keyboardType="phone-pad"
|
||||||
style={styles.textInput}
|
style={{ flex: 1 }}
|
||||||
onChangeText={(t) => setPhone(normalizeDigits(t).slice(0, 9))}
|
onChangeText={(t) => setPhone(normalizeDigits(t))}
|
||||||
testID="phone-input"
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ---- YATT/BAND info ---- */}
|
{hasDirectorTin && (
|
||||||
{!isLegal &&
|
<View>
|
||||||
info &&
|
<Text style={styles.label}>{t('Direktor STIR')}</Text>
|
||||||
(hasValidName ? (
|
<View style={[styles.input, { backgroundColor: isDirectorTinValid ? '#f0fdf4' : '#f8fafc' }]}>
|
||||||
<View style={styles.infoBox}>
|
<Hash size={18} color="#94a3b8" />
|
||||||
<Text style={styles.infoText}>{info.fullName || info.name}</Text>
|
<TextInput
|
||||||
|
value={directorTinInput}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder={t('Direktor STIR')}
|
||||||
|
placeholderTextColor="#94a3b8"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
maxLength={14}
|
||||||
|
onChangeText={(t) => setDirectorTinInput(normalizeDigits(t))}
|
||||||
|
/>
|
||||||
</View>
|
</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
|
<TouchableOpacity
|
||||||
disabled={!isValid}
|
disabled={!valid}
|
||||||
style={[styles.btn, !isValid && styles.disabled]}
|
style={[styles.btn, !valid && styles.disabled]}
|
||||||
onPress={handleContinue}
|
onPress={() => {
|
||||||
testID="continue-button"
|
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>
|
<Text style={styles.btnText}>{t('Davom etish')}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</View>
|
||||||
</KeyboardAvoidingView>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
</View >
|
||||||
|
</KeyboardAwareScrollView >
|
||||||
|
|
||||||
{/* ---- Country BottomSheet ---- */}
|
|
||||||
<BottomSheet
|
<BottomSheet
|
||||||
ref={countrySheetRef}
|
ref={countrySheetRef}
|
||||||
index={-1}
|
index={-1}
|
||||||
@@ -666,7 +475,6 @@ export default function RegisterFormScreen() {
|
|||||||
<Text style={styles.sheetTitle}>{t('Davlat')}</Text>
|
<Text style={styles.sheetTitle}>{t('Davlat')}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Search input */}
|
|
||||||
<View style={styles.searchContainer}>
|
<View style={styles.searchContainer}>
|
||||||
<Search size={16} color="#94a3b8" />
|
<Search size={16} color="#94a3b8" />
|
||||||
<BottomSheetTextInput
|
<BottomSheetTextInput
|
||||||
@@ -726,15 +534,19 @@ export default function RegisterFormScreen() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BottomSheet >
|
</BottomSheet >
|
||||||
</View>
|
</>
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
keyboardScroll: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#0f172a',
|
||||||
|
},
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#0f172a',
|
backgroundColor: '#0f172a',
|
||||||
|
minHeight: '100%',
|
||||||
},
|
},
|
||||||
scrollContent: {
|
scrollContent: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@@ -915,6 +727,7 @@ const styles = StyleSheet.create({
|
|||||||
infoBox: {
|
infoBox: {
|
||||||
backgroundColor: '#f0fdf4',
|
backgroundColor: '#f0fdf4',
|
||||||
padding: 14,
|
padding: 14,
|
||||||
|
marginTop: 10,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#bbf7d0',
|
borderColor: '#bbf7d0',
|
||||||
@@ -964,15 +777,6 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '800' as const,
|
fontWeight: '800' as const,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
footer: {
|
|
||||||
marginTop: 20,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
footerText: {
|
|
||||||
color: '#94a3b8',
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '600' as const,
|
|
||||||
},
|
|
||||||
decorCircle1: {
|
decorCircle1: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: -150,
|
top: -150,
|
||||||
@@ -992,7 +796,29 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: 'rgba(16, 185, 129, 0.08)',
|
backgroundColor: 'rgba(16, 185, 129, 0.08)',
|
||||||
},
|
},
|
||||||
inputDisabled: {
|
inputDisabled: {
|
||||||
backgroundColor: '#f1f5f9', // disabled bo'lganda fon rangi
|
backgroundColor: '#f1f5f9',
|
||||||
borderColor: '#e2e8f0', // disabled bo'lganda border rangi
|
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 createContextHook from '@nkzw/create-context-hook';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { GetDirectorInfoResponse } from '../../login/lib/api';
|
import { GetInfo } from '../../login/lib/api';
|
||||||
|
|
||||||
export type PersonType = 'yatt' | 'band' | 'legal_entity' | null;
|
export type PersonType = 'yatt' | 'band' | 'legal_entity' | null;
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ interface RegisterState {
|
|||||||
setPassportNumber: (number: string) => void;
|
setPassportNumber: (number: string) => void;
|
||||||
inn: string;
|
inn: string;
|
||||||
setInn: (inn: string) => void;
|
setInn: (inn: string) => void;
|
||||||
info: any;
|
info: GetInfo | null;
|
||||||
setInfo: (info: any) => void;
|
setInfo: (info: GetInfo | null) => void;
|
||||||
reset_full: () => void;
|
reset_full: () => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
directorJshshr: string;
|
directorJshshr: string;
|
||||||
setDirectorJshshr: (directorJshshr: string) => void;
|
setDirectorJshshr: (directorJshshr: string) => void;
|
||||||
directorInfo: GetDirectorInfoResponse | null;
|
directorInfo: GetInfo | null;
|
||||||
setDirectorInfo: (directorInfo: GetDirectorInfoResponse | null) => void;
|
setDirectorInfo: (directorInfo: GetInfo | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const [RegisterProvider, useRegister] = createContextHook<RegisterState>(() => {
|
export const [RegisterProvider, useRegister] = createContextHook<RegisterState>(() => {
|
||||||
@@ -37,9 +37,9 @@ export const [RegisterProvider, useRegister] = createContextHook<RegisterState>(
|
|||||||
const [passportSeries, setPassportSeries] = useState<string>('');
|
const [passportSeries, setPassportSeries] = useState<string>('');
|
||||||
const [passportNumber, setPassportNumber] = useState<string>('');
|
const [passportNumber, setPassportNumber] = useState<string>('');
|
||||||
const [inn, setInn] = 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 [directorJshshr, setDirectorJshshr] = useState<string>('');
|
||||||
const [directorInfo, setDirectorInfo] = useState<GetDirectorInfoResponse | null>(null);
|
const [directorInfo, setDirectorInfo] = useState<GetInfo | null>(null);
|
||||||
|
|
||||||
const reset_full = () => {
|
const reset_full = () => {
|
||||||
setPersonType(null);
|
setPersonType(null);
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ import React, { useCallback, useRef, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Image,
|
Image,
|
||||||
KeyboardAvoidingView,
|
Keyboard,
|
||||||
Linking,
|
Linking,
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
TouchableWithoutFeedback,
|
||||||
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import OneClick from '@/assets/images/one_click.png';
|
import OneClick from '@/assets/images/one_click.png';
|
||||||
import PAYME from '@/assets/images/Payme_NEW.png';
|
import PAYME from '@/assets/images/Payme_NEW.png';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { KeyboardAwareScrollView, } from 'react-native-keyboard-aware-scroll-view';
|
||||||
import { price_calculation } from '../lib/api';
|
import { price_calculation } from '../lib/api';
|
||||||
import { CreateAdsResponse } from '../lib/types';
|
import { CreateAdsResponse } from '../lib/types';
|
||||||
import StepFour from './StepFour';
|
import StepFour from './StepFour';
|
||||||
@@ -185,13 +186,16 @@ export default function CreateAdsScreens() {
|
|||||||
onSuccess: async (res, variables) => {
|
onSuccess: async (res, variables) => {
|
||||||
if (variables.paymentType === 'payme') {
|
if (variables.paymentType === 'payme') {
|
||||||
await Linking.openURL(res.data.url);
|
await Linking.openURL(res.data.url);
|
||||||
router.push('/(dashboard)/announcements');
|
bottomSheetModalRef.current?.dismiss();
|
||||||
|
router.push('/profile/my-ads');
|
||||||
} else {
|
} else {
|
||||||
router.push('/(dashboard)/announcements');
|
bottomSheetModalRef.current?.dismiss();
|
||||||
|
router.push('/profile/my-ads')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err: AxiosError) => {
|
||||||
Alert.alert('Xatolik yuz berdi', err.message);
|
const errMessage = (err.response?.data as { referral_amount: string }).referral_amount
|
||||||
|
Alert.alert(t('Xatolik yuz berdi'), errMessage || err.message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,11 +210,19 @@ export default function CreateAdsScreens() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAwareScrollView
|
||||||
behavior="padding"
|
showsVerticalScrollIndicator={false}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
style={[styles.safeArea, isDark ? styles.darkBg : styles.lightBg]}
|
style={[styles.safeArea, isDark ? styles.darkBg : styles.lightBg]}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={[styles.container, { paddingBottom: 90 }]}>
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<View
|
||||||
|
style={[styles.container, { marginBottom: Keyboard.isVisible() ? 10 : 90 }]}
|
||||||
|
onStartShouldSetResponder={() => {
|
||||||
|
Keyboard.dismiss();
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Image
|
<Image
|
||||||
source={OneClick}
|
source={OneClick}
|
||||||
style={{ width: 180, height: 56, marginBottom: 10 }}
|
style={{ width: 180, height: 56, marginBottom: 10 }}
|
||||||
@@ -273,11 +285,8 @@ export default function CreateAdsScreens() {
|
|||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
{/* FOOTER */}
|
|
||||||
|
|
||||||
{/* PAYMENT BOTTOM SHEET */}
|
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
index={0}
|
index={0}
|
||||||
@@ -321,7 +330,7 @@ export default function CreateAdsScreens() {
|
|||||||
</View>
|
</View>
|
||||||
</BottomSheetScrollView>
|
</BottomSheetScrollView>
|
||||||
</BottomSheetModal>
|
</BottomSheetModal>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAwareScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,10 +234,10 @@ const StepThree = forwardRef(({ formData, updateForm, data }: StepProps, ref) =>
|
|||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
||||||
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
<Text style={[styles.sectionTitle, { color: theme.text }]}>
|
||||||
{t('Reklama joylashtirish kompaniyasi')}
|
{t('Reklama joylashtirish kompaniyasi')}
|
||||||
</Text>
|
</Text>
|
||||||
|
<View style={{ flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 10 }}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.selectAllButton,
|
styles.selectAllButton,
|
||||||
@@ -330,7 +330,7 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
pickerText: { fontSize: 16 },
|
pickerText: { fontSize: 16 },
|
||||||
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 12 },
|
sectionTitle: { fontSize: 16, fontWeight: '700', marginVertical: 12, },
|
||||||
companyItem: {
|
companyItem: {
|
||||||
width: 55,
|
width: 55,
|
||||||
height: 55,
|
height: 55,
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import CategorySelection from '@/components/ui/IndustrySelection';
|
|||||||
import { XIcon } from 'lucide-react-native';
|
import { XIcon } from 'lucide-react-native';
|
||||||
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FlatList, StyleSheet, Text, ToastAndroid, TouchableOpacity, View } from 'react-native';
|
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
|
|
||||||
type StepProps = {
|
type StepProps = {
|
||||||
formData: any;
|
formData: any;
|
||||||
@@ -36,7 +37,7 @@ const StepTwo = forwardRef(({ formData, updateForm }: StepProps, ref) => {
|
|||||||
const validate = () => {
|
const validate = () => {
|
||||||
if (selectedCategories.length === 0) {
|
if (selectedCategories.length === 0) {
|
||||||
setError('Iltimos, kompaniyalarni tanlang');
|
setError('Iltimos, kompaniyalarni tanlang');
|
||||||
ToastAndroid.show(t('Iltimos, kompaniyalarni tanlang'), ToastAndroid.TOP);
|
Toast.info(t('Iltimos, kompaniyalarni tanlang'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import { useTheme } from "@/components/ThemeContext";
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import * as WebBrowser from "expo-web-browser";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
FlatList,
|
FlatList,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { RefreshControl } from "react-native-gesture-handler";
|
import { RefreshControl } from "react-native-gesture-handler";
|
||||||
import * as WebBrowser from "expo-web-browser";
|
import { Toast } from "toastify-react-native";
|
||||||
import { eservices_api } from "../lib/api";
|
import { eservices_api } from "../lib/api";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const dark = {
|
const dark = {
|
||||||
bg: "#0f172a",
|
bg: "#0f172a",
|
||||||
@@ -39,11 +39,14 @@ export default function EServicesCategoryScreen() {
|
|||||||
|
|
||||||
const handleOpenBrowser = async (fileUrl: string) => {
|
const handleOpenBrowser = async (fileUrl: string) => {
|
||||||
try {
|
try {
|
||||||
await WebBrowser.openBrowserAsync(fileUrl);
|
await WebBrowser.openBrowserAsync(fileUrl, {
|
||||||
|
dismissButtonStyle: 'close',
|
||||||
|
presentationStyle: WebBrowser.WebBrowserPresentationStyle.FULL_SCREEN,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
|
Toast.error(t("Xatolik yuz berdi"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useTheme } from "@/components/ThemeContext";
|
|||||||
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
|
import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
|
import * as WebBrowser from "expo-web-browser";
|
||||||
import { ChevronLeft } from "lucide-react-native";
|
import { ChevronLeft } from "lucide-react-native";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -10,14 +11,13 @@ import {
|
|||||||
FlatList,
|
FlatList,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import * as WebBrowser from "expo-web-browser";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RefreshControl } from "react-native-gesture-handler";
|
import { RefreshControl } from "react-native-gesture-handler";
|
||||||
|
import { Toast } from "toastify-react-native";
|
||||||
import { eservices_api } from "../lib/api";
|
import { eservices_api } from "../lib/api";
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
||||||
@@ -74,11 +74,10 @@ export default function EServicesScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenBrowser = async (fileUrl: string) => {
|
const handleOpenBrowser = async (fileUrl: string) => {
|
||||||
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
|
|
||||||
try {
|
try {
|
||||||
await WebBrowser.openBrowserAsync(fileUrl);
|
await WebBrowser.openBrowserAsync(fileUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ToastAndroid.show(t("Xatolik yuz berdi"), ToastAndroid.TOP);
|
Toast.error(t("Xatolik yuz berdi"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export default function HomeScreen() {
|
|||||||
}
|
}
|
||||||
await queryClient.refetchQueries();
|
await queryClient.refetchQueries();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Refresh error:', err);
|
|
||||||
} finally {
|
} finally {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ export const user_api = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
async my_ads_detail(id: number): Promise<AxiosResponse<{ status: boolean; data: MyAdsDataRes }>> {
|
|
||||||
const res = await httpClient.get(API_URLS.My_Ads_Detail(id));
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
async my_bonuses(params: {
|
async my_bonuses(params: {
|
||||||
page: number;
|
page: number;
|
||||||
@@ -109,6 +105,11 @@ export const user_api = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async ads_detail(id: number): Promise<AxiosResponse<{ status: boolean; data: MyAdsDataRes }>> {
|
||||||
|
const res = await httpClient.get(API_URLS.Ads_Detail(id));
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
async my_referrals(params: { page: number; page_size: number }) {
|
async my_referrals(params: { page: number; page_size: number }) {
|
||||||
const res = await httpClient.get(API_URLS.My_Refferals, { params });
|
const res = await httpClient.get(API_URLS.My_Refferals, { params });
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { formatPhone, normalizeDigits } from '@/constants/formatPhone';
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { ArrowLeft } from 'lucide-react-native';
|
import { ArrowLeft, Check } from 'lucide-react-native';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
ToastAndroid,
|
View
|
||||||
View,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
import { user_api } from '../lib/api';
|
import { user_api } from '../lib/api';
|
||||||
|
|
||||||
export default function AddEmployee() {
|
export default function AddEmployee() {
|
||||||
@@ -60,14 +60,14 @@ export default function AddEmployee() {
|
|||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!firstName.trim() || !lastName.trim() || !phoneNumber.trim()) {
|
if (!firstName.trim() || !lastName.trim() || !phoneNumber.trim()) {
|
||||||
ToastAndroid.show(t("Barcha maydonlarni to'ldiring"), ToastAndroid.SHORT);
|
Toast.info(t("Barcha maydonlarni to'ldiring"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate({
|
mutate({
|
||||||
first_name: firstName.trim(),
|
first_name: firstName.trim(),
|
||||||
last_name: lastName.trim(),
|
last_name: lastName.trim(),
|
||||||
phone: phoneNumber.trim(),
|
phone: `998${phoneNumber.trim()}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,12 +77,12 @@ export default function AddEmployee() {
|
|||||||
<Pressable onPress={() => router.push('/profile/employees')}>
|
<Pressable onPress={() => router.push('/profile/employees')}>
|
||||||
<ArrowLeft color={theme.text} />
|
<ArrowLeft color={theme.text} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Text style={[styles.headerTitle, { color: theme.text }]}>{t("Yangi xodim qo'shish")}</Text>
|
<Text style={[styles.headerTitle, { color: theme.text }]}>{t("Xodim qo'shish")}</Text>
|
||||||
<Pressable onPress={handleSave} disabled={isPending}>
|
<Pressable onPress={handleSave} disabled={isPending}>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<ActivityIndicator size={'small'} />
|
<ActivityIndicator size={'small'} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.saveButton, { color: theme.primary }]}>{t('Saqlash')}</Text>
|
<Check size={26} color={theme.primary} />
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function AnnouncementsTab() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<MyAdsDataRes | null>(null);
|
const [selectedAnnouncement, setSelectedAnnouncement] = useState<number | null>(null);
|
||||||
const [sheetOpen, setSheetOpen] = useState(false); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const [sheetOpen, setSheetOpen] = useState(false); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||||
|
|
||||||
const { data, isLoading, isError, fetchNextPage, hasNextPage, refetch } = useInfiniteQuery({
|
const { data, isLoading, isError, fetchNextPage, hasNextPage, refetch } = useInfiniteQuery({
|
||||||
@@ -82,14 +82,14 @@ export function AnnouncementsTab() {
|
|||||||
isLoading: loadingDetail,
|
isLoading: loadingDetail,
|
||||||
isError: detailError,
|
isError: detailError,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['my_ads_id', selectedAnnouncement?.id],
|
queryKey: ['my_ads_id', selectedAnnouncement],
|
||||||
queryFn: () => user_api.my_ads_detail(selectedAnnouncement?.id!),
|
queryFn: () => user_api.ads_detail(Number(selectedAnnouncement)),
|
||||||
select: (res) => res.data.data,
|
select: (res) => res.data.data,
|
||||||
enabled: !!selectedAnnouncement && sheetOpen,
|
enabled: !!selectedAnnouncement,
|
||||||
});
|
});
|
||||||
|
|
||||||
const openSheet = (item: MyAdsDataRes) => {
|
const openSheet = (item: MyAdsDataRes) => {
|
||||||
setSelectedAnnouncement(item);
|
setSelectedAnnouncement(item.id);
|
||||||
setSheetOpen(true);
|
setSheetOpen(true);
|
||||||
requestAnimationFrame(() => bottomSheetRef.current?.present());
|
requestAnimationFrame(() => bottomSheetRef.current?.present());
|
||||||
};
|
};
|
||||||
@@ -176,7 +176,7 @@ export function AnnouncementsTab() {
|
|||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.center, { backgroundColor: theme.background }]}>
|
<View style={[styles.center, { backgroundColor: theme.background }]}>
|
||||||
<Text style={[styles.error, { color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
<Text style={[{ color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ export function AnnouncementsTab() {
|
|||||||
<FlatList
|
<FlatList
|
||||||
data={allAds}
|
data={allAds}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
contentContainerStyle={styles.list}
|
contentContainerStyle={[styles.list, { flexGrow: 1 }]}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.primary} />
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={theme.primary} />
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ export function AnnouncementsTab() {
|
|||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.card, { backgroundColor: theme.cardBg }]}
|
style={[styles.card, { backgroundColor: theme.cardBg }]}
|
||||||
onPress={() => openSheet(item)}
|
onPress={() => { openSheet(item); setSelectedAnnouncement(item.id) }}
|
||||||
>
|
>
|
||||||
{item.files?.[0]?.file && (
|
{item.files?.[0]?.file && (
|
||||||
<Image source={{ uri: item.files[0].file }} style={styles.cardImage} />
|
<Image source={{ uri: item.files[0].file }} style={styles.cardImage} />
|
||||||
@@ -243,6 +243,14 @@ export function AnnouncementsTab() {
|
|||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
|
ListEmptyComponent={() => (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Megaphone size={48} color={theme.textSecondary} />
|
||||||
|
<Text style={[styles.emptyTitle, { color: theme.text }]}>
|
||||||
|
{t('Hozircha hech qanday eʼlon mavjud emas')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
@@ -253,14 +261,13 @@ export function AnnouncementsTab() {
|
|||||||
backgroundStyle={{ backgroundColor: theme.sheetBg }}
|
backgroundStyle={{ backgroundColor: theme.sheetBg }}
|
||||||
handleIndicatorStyle={{ backgroundColor: theme.indicator }}
|
handleIndicatorStyle={{ backgroundColor: theme.indicator }}
|
||||||
onDismiss={() => {
|
onDismiss={() => {
|
||||||
setSheetOpen(false);
|
setSelectedAnnouncement(null); // shu yetarli
|
||||||
setSelectedAnnouncement(null);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BottomSheetScrollView contentContainerStyle={styles.sheet}>
|
<BottomSheetScrollView contentContainerStyle={styles.sheet}>
|
||||||
{loadingDetail && <ActivityIndicator size={'large'} />}
|
{loadingDetail && <ActivityIndicator size={'large'} />}
|
||||||
{detailError && (
|
{detailError && (
|
||||||
<Text style={[styles.error, { color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
<Text style={[{ color: theme.error }]}>{t('Xatolik yuz berdi')}</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{detail && (
|
{detail && (
|
||||||
@@ -374,7 +381,7 @@ export function AnnouncementsTab() {
|
|||||||
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
||||||
{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' },
|
{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' },
|
||||||
]}
|
]}
|
||||||
onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'payme' })}
|
onPress={() => sendPayment({ id: selectedAnnouncement!, type: 'payme' })}
|
||||||
>
|
>
|
||||||
<Image source={PAYME} style={{ width: 80, height: 80 }} />
|
<Image source={PAYME} style={{ width: 80, height: 80 }} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -384,7 +391,7 @@ export function AnnouncementsTab() {
|
|||||||
styles.paymentItem,
|
styles.paymentItem,
|
||||||
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
isDark ? styles.darkPaymentItem : styles.lightPaymentItem,
|
||||||
]}
|
]}
|
||||||
onPress={() => sendPayment({ id: selectedAnnouncement?.id!, type: 'referral' })}
|
onPress={() => sendPayment({ id: selectedAnnouncement!, type: 'referral' })}
|
||||||
>
|
>
|
||||||
<Text style={[styles.paymentText, isDark ? styles.darkText : styles.lightText]}>
|
<Text style={[styles.paymentText, isDark ? styles.darkText : styles.lightText]}>
|
||||||
{t('Referal orqali')}
|
{t('Referal orqali')}
|
||||||
@@ -498,6 +505,22 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
|
|
||||||
loading: {},
|
emptyContainer: {
|
||||||
error: {},
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 60,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyDesc: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 6,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ const styles = StyleSheet.create({
|
|||||||
padding: 16,
|
padding: 16,
|
||||||
gap: 16,
|
gap: 16,
|
||||||
paddingBottom: 30,
|
paddingBottom: 30,
|
||||||
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
@@ -236,6 +237,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '600' as const,
|
fontWeight: '600' as const,
|
||||||
},
|
},
|
||||||
emptyContainer: {
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 80,
|
paddingVertical: 80,
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
ToastAndroid,
|
View
|
||||||
View,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
import { user_api } from '../lib/api';
|
import { user_api } from '../lib/api';
|
||||||
|
|
||||||
type FormType = {
|
type FormType = {
|
||||||
@@ -44,12 +44,12 @@ export default function CreateReferrals() {
|
|||||||
is_agent: boolean;
|
is_agent: boolean;
|
||||||
}) => user_api.create_referral(body),
|
}) => user_api.create_referral(body),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
ToastAndroid.show(t('Referral yaratildi'), ToastAndroid.SHORT);
|
Toast.success(t('Referral yaratildi'));
|
||||||
queryClient.refetchQueries({ queryKey: ['my_referrals'] });
|
queryClient.refetchQueries({ queryKey: ['my_referrals'] });
|
||||||
router.back();
|
router.back();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
ToastAndroid.show(t('Xatolik yuz berdi'), ToastAndroid.SHORT);
|
Toast.error(t('Xatolik yuz berdi'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,8 +60,6 @@ export default function CreateReferrals() {
|
|||||||
const validate = () => {
|
const validate = () => {
|
||||||
const e: any = {};
|
const e: any = {};
|
||||||
|
|
||||||
if (!form.code || form.code.length !== 9)
|
|
||||||
e.code = 'Kod aynan 9 ta belgidan iborat bo‘lishi kerak';
|
|
||||||
if (!form.description || form.description.length < 5)
|
if (!form.description || form.description.length < 5)
|
||||||
e.description = 'Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak';
|
e.description = 'Tavsif kamida 5 ta belgidan iborat bo‘lishi kerak';
|
||||||
|
|
||||||
|
|||||||
@@ -86,13 +86,11 @@ export default function EditService() {
|
|||||||
const { mutate, isPending } = useMutation({
|
const { mutate, isPending } = useMutation({
|
||||||
mutationFn: (body: FormData) => user_api.update_service({ body, id: Number(id) }),
|
mutationFn: (body: FormData) => user_api.update_service({ body, id: Number(id) }),
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
console.log(res);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['my_services'] });
|
queryClient.invalidateQueries({ queryKey: ['my_services'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['service_detail'] });
|
queryClient.invalidateQueries({ queryKey: ['service_detail'] });
|
||||||
router.back();
|
router.back();
|
||||||
},
|
},
|
||||||
onError: (err: any) => {
|
onError: (err: any) => {
|
||||||
console.log(err);
|
|
||||||
Alert.alert(t('Xatolik yuz berdi'), err?.message || t('Yangilashda xato yuz berdi'));
|
Alert.alert(t('Xatolik yuz berdi'), err?.message || t('Yangilashda xato yuz berdi'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -151,7 +149,7 @@ export default function EditService() {
|
|||||||
<ArrowLeft color={isDark ? '#fff' : '#0f172a'} />
|
<ArrowLeft color={isDark ? '#fff' : '#0f172a'} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Text style={[styles.headerTitle, { color: isDark ? '#fff' : '#0f172a' }]}>
|
<Text style={[styles.headerTitle, { color: isDark ? '#fff' : '#0f172a' }]}>
|
||||||
{step === 1 ? t('Xizmatni tahrirlash (1/2)') : t('Xizmatni tahrirlash (2/2)')}
|
{t('Xizmatni tahrirlash')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export function EmployeesTab() {
|
|||||||
data={allEmployees}
|
data={allEmployees}
|
||||||
keyExtractor={(item) => item.phone}
|
keyExtractor={(item) => item.phone}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
contentContainerStyle={styles.list}
|
contentContainerStyle={[styles.list, { flexGrow: 1 }]}
|
||||||
onEndReached={() => {
|
onEndReached={() => {
|
||||||
if (hasNextPage && !isFetchingNextPage) {
|
if (hasNextPage && !isFetchingNextPage) {
|
||||||
fetchNextPage();
|
fetchNextPage();
|
||||||
@@ -176,7 +176,12 @@ const styles = StyleSheet.create({
|
|||||||
infoContainer: { flex: 1, gap: 4 },
|
infoContainer: { flex: 1, gap: 4 },
|
||||||
name: { fontSize: 17, fontWeight: '700' },
|
name: { fontSize: 17, fontWeight: '700' },
|
||||||
phone: { fontSize: 15, fontWeight: '500' },
|
phone: { fontSize: 15, fontWeight: '500' },
|
||||||
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16 },
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 60,
|
||||||
|
},
|
||||||
emptyText: { fontSize: 17, fontWeight: '600' },
|
emptyText: { fontSize: 17, fontWeight: '600' },
|
||||||
emptyButton: {
|
emptyButton: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ export function ManualTab() {
|
|||||||
|
|
||||||
/** 🔹 Video manbalari (SELECT ga bog‘liq) */
|
/** 🔹 Video manbalari (SELECT ga bog‘liq) */
|
||||||
const videos = {
|
const videos = {
|
||||||
uz: require('@/assets/manual/manual_video_uz.webm'),
|
uz: require('@/assets/manual/manual_video_uz.mp4'),
|
||||||
ru: require('@/assets/manual/manual_video_ru.webm'),
|
ru: require('@/assets/manual/manual_video_ru.mp4'),
|
||||||
en: require('@/assets/manual/manual_video_en.webm'),
|
en: require('@/assets/manual/manual_video_en.mp4'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const player = useVideoPlayer(videos[selectedLang], (player) => {
|
const player = useVideoPlayer(videos[selectedLang], (player) => {
|
||||||
@@ -158,8 +158,6 @@ export function ManualTab() {
|
|||||||
[imageLang]
|
[imageLang]
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedLanguage = languages.find((l) => l.code === selectedLang);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// listener qo'shish
|
// listener qo'shish
|
||||||
const subscription = player.addListener('playingChange', (state) => {
|
const subscription = player.addListener('playingChange', (state) => {
|
||||||
@@ -350,7 +348,7 @@ const styles = StyleSheet.create({
|
|||||||
container: { flex: 1 },
|
container: { flex: 1 },
|
||||||
hero: { padding: 20 },
|
hero: { padding: 20 },
|
||||||
topHeader: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
topHeader: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
||||||
headerTitle: { fontSize: 22, fontWeight: '700', marginHorizontal: 16 },
|
headerTitle: { fontSize: 18, fontWeight: '700', marginHorizontal: 16 },
|
||||||
subtitle: { fontSize: 16, marginTop: 5, fontWeight: '500', marginHorizontal: 16 },
|
subtitle: { fontSize: 16, marginTop: 5, fontWeight: '500', marginHorizontal: 16 },
|
||||||
|
|
||||||
section: { marginBottom: 28 },
|
section: { marginBottom: 28 },
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default function MyServicesScreen() {
|
|||||||
<FlatList
|
<FlatList
|
||||||
data={allServices}
|
data={allServices}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
keyExtractor={(item) => item.id.toString()}
|
||||||
contentContainerStyle={styles.list}
|
contentContainerStyle={[styles.list, { flexGrow: 1 }]}
|
||||||
onEndReached={() => hasNextPage && fetchNextPage()}
|
onEndReached={() => hasNextPage && fetchNextPage()}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
@@ -269,7 +269,7 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
},
|
},
|
||||||
categoryText: { fontSize: 13, fontWeight: '500' as const },
|
categoryText: { fontSize: 13, fontWeight: '500' as const },
|
||||||
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16 },
|
emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 80, gap: 16, flex: 1 },
|
||||||
emptyText: { fontSize: 17, fontWeight: '600' as const },
|
emptyText: { fontSize: 17, fontWeight: '600' as const },
|
||||||
emptyButton: {
|
emptyButton: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -91,12 +91,47 @@ export function NotificationTab() {
|
|||||||
<Text style={[styles.headerTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
<Text style={[styles.headerTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
||||||
{t('Bildirishnomalar')}
|
{t('Bildirishnomalar')}
|
||||||
</Text>
|
</Text>
|
||||||
|
</View>
|
||||||
|
<FlatList
|
||||||
|
data={notifications}
|
||||||
|
keyExtractor={(item) => item.id.toString()}
|
||||||
|
contentContainerStyle={styles.listContent}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
ListHeaderComponent={() => {
|
||||||
|
if (notifications.length === 0) {
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyHeader}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.emptyTitle,
|
||||||
|
{ color: isDark ? '#f1f5f9' : '#0f172a' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{t("Hozircha bildirishnomalar yo'q")}
|
||||||
|
</Text>
|
||||||
|
|
||||||
{notifications.some((n) => !n.is_read) && (
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.emptyDesc,
|
||||||
|
{ color: isDark ? '#94a3b8' : '#64748b' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{t("Yangi xabarlar shu yerda paydo bo‘ladi")}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifications.some((n) => !n.is_read)) {
|
||||||
|
return (
|
||||||
|
<View style={styles.headerActions}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.markAllButton,
|
styles.markAllButton,
|
||||||
{ backgroundColor: isDark ? '#1e293b' : '#e0f2fe', borderColor: '#3b82f6' },
|
{
|
||||||
|
backgroundColor: isDark ? '#1e293b' : '#e0f2fe',
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
onPress={() => markAllAsRead()}
|
onPress={() => markAllAsRead()}
|
||||||
disabled={isMarkingAllRead}
|
disabled={isMarkingAllRead}
|
||||||
@@ -109,14 +144,24 @@ export function NotificationTab() {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
);
|
||||||
data={notifications}
|
}
|
||||||
keyExtractor={(item) => item.id.toString()}
|
|
||||||
contentContainerStyle={styles.listContent}
|
return (
|
||||||
showsVerticalScrollIndicator={false}
|
<View style={styles.allReadContainer}>
|
||||||
renderItem={({ item, index }) => <NotificationCard item={item} />}
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.allReadText,
|
||||||
|
{ color: isDark ? '#94a3b8' : '#64748b' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{t("Barcha bildirishnomalar o‘qilgan")}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderItem={({ item }) => <NotificationCard item={item} />}
|
||||||
onEndReached={() => {
|
onEndReached={() => {
|
||||||
if (hasNextPage && !isFetchingNextPage) {
|
if (hasNextPage && !isFetchingNextPage) {
|
||||||
fetchNextPage();
|
fetchNextPage();
|
||||||
@@ -130,6 +175,15 @@ export function NotificationTab() {
|
|||||||
}
|
}
|
||||||
refreshing={isLoading}
|
refreshing={isLoading}
|
||||||
onRefresh={refetch}
|
onRefresh={refetch}
|
||||||
|
ListEmptyComponent={() =>
|
||||||
|
!isLoading && (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<Text style={[styles.emptyTitle, { color: isDark ? '#f1f5f9' : '#0f172a' }]}>
|
||||||
|
{t("Hozircha bildirishnomalar yo'q")}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -266,6 +320,7 @@ const styles = StyleSheet.create({
|
|||||||
listContent: {
|
listContent: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
paddingBottom: 32,
|
paddingBottom: 32,
|
||||||
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Card Styles */
|
/* Card Styles */
|
||||||
@@ -292,6 +347,38 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
headerActions: {
|
||||||
|
marginBottom: 16,
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyHeader: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 60,
|
||||||
|
},
|
||||||
|
|
||||||
|
allReadContainer: {
|
||||||
|
marginBottom: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
allReadText: {
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 8,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyDesc: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -407,7 +494,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
minWidth: 80,
|
width: "auto",
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
@@ -415,4 +502,10 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 30,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
gap: 16,
|
gap: 16,
|
||||||
|
flexGrow: 1
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
backgroundColor: '#1e293b',
|
backgroundColor: '#1e293b',
|
||||||
@@ -372,6 +373,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '500' as const,
|
fontWeight: '500' as const,
|
||||||
},
|
},
|
||||||
emptyContainer: {
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 80,
|
paddingVertical: 80,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useTheme } from '@/components/ThemeContext';
|
|||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import * as Clipboard from 'expo-clipboard';
|
import * as Clipboard from 'expo-clipboard';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import { ArrowLeft, CopyIcon, HandCoins, Plus, Users } from 'lucide-react-native';
|
import { ArrowLeft, CopyIcon, Gift, HandCoins, Plus, Users } from 'lucide-react-native';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@@ -14,11 +14,11 @@ import {
|
|||||||
Share,
|
Share,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
ToastAndroid,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
import { Toast } from 'toastify-react-native';
|
||||||
import { user_api } from '../lib/api';
|
import { user_api } from '../lib/api';
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
@@ -68,7 +68,7 @@ export function ReferralsTab() {
|
|||||||
|
|
||||||
// Clipboard + Share funksiyasi
|
// Clipboard + Share funksiyasi
|
||||||
const handleCopyAndShare = async (code: string) => {
|
const handleCopyAndShare = async (code: string) => {
|
||||||
const referralLink = `https://t.me/infotargetbot/join?startapp=${code}`;
|
const referralLink = `${code}`;
|
||||||
|
|
||||||
// Clipboard-ga nusxa olish
|
// Clipboard-ga nusxa olish
|
||||||
await Clipboard.setStringAsync(referralLink);
|
await Clipboard.setStringAsync(referralLink);
|
||||||
@@ -80,11 +80,10 @@ export function ReferralsTab() {
|
|||||||
title: t('Referal linkni ulashish'),
|
title: t('Referal linkni ulashish'),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Share error:', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
ToastAndroid.show(t('Refferal kopiya qilindi'), ToastAndroid.SHORT);
|
Toast.success(t('Refferal kopiya qilindi'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,11 +152,22 @@ export function ReferralsTab() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={() => (
|
||||||
<Text style={{ textAlign: 'center', color: theme.subText }}>
|
<View style={styles.emptyContainer}>
|
||||||
{t('Refferallar topilmadi')}
|
<View
|
||||||
|
style={[
|
||||||
|
styles.emptyIconWrapper,
|
||||||
|
{ backgroundColor: theme.cardBg }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Gift size={40} color={theme.primary} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={[styles.emptyTitle, { color: theme.text }]}>
|
||||||
|
{t("Refferallar mavjud emas")}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
</View>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -175,7 +185,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
headerTitle: { fontSize: 18, fontWeight: '700' },
|
headerTitle: { fontSize: 18, fontWeight: '700' },
|
||||||
|
|
||||||
list: { padding: 16, gap: 12, paddingBottom: 30 },
|
list: { padding: 16, gap: 12, paddingBottom: 30, flexGrow: 1 },
|
||||||
|
|
||||||
card: {
|
card: {
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
@@ -217,4 +227,46 @@ const styles = StyleSheet.create({
|
|||||||
amount: {
|
amount: {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 30,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyIconWrapper: {
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 40,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 8,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyDesc: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyButton: {
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyButtonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user