fitst commit

This commit is contained in:
Samandar Turgunboyev
2026-01-28 18:26:50 +05:00
parent 166a55b1e9
commit 124798419b
196 changed files with 26627 additions and 421 deletions

6
app/(auth)/_layout.tsx Normal file
View File

@@ -0,0 +1,6 @@
// app/(auth)/_layout.tsx
import { Slot } from 'expo-router';
export default function AuthLayout() {
return <Slot />;
}

18
app/(auth)/confirm.tsx Normal file
View File

@@ -0,0 +1,18 @@
import ConfirmScreen from '@/screens/auth/confirm/ConfirmScreen';
import { ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function Confirm() {
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#0f172a',
}}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps="handled">
<ConfirmScreen />
</ScrollView>
</SafeAreaView>
);
}

39
app/(auth)/index.tsx Normal file
View File

@@ -0,0 +1,39 @@
import { useAuth } from '@/components/AuthProvider';
import LoginScreen from '@/screens/auth/login/ui/LoginScreens';
import { router } from 'expo-router';
import { useEffect } from 'react';
import { ActivityIndicator, ScrollView, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function Index() {
const { isAuthenticated, isLoading } = useAuth();
// Loading spinner
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#3b82f6" />
</View>
);
}
// Token bolsa dashboard-ga yonaltir
useEffect(() => {
if (isAuthenticated) {
router.replace('/(dashboard)');
}
}, [isAuthenticated]);
// Token yoq → login screen
if (!isAuthenticated) {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#0f172a' }}>
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
<LoginScreen />
</ScrollView>
</SafeAreaView>
);
}
return null;
}

View File

@@ -0,0 +1,18 @@
import RegisterConfirmScreen from '@/screens/auth/register-confirm/ConfirmScreen';
import { ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function RegisterConfirm() {
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: '#0f172a',
}}
>
<ScrollView contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps="handled">
<RegisterConfirmScreen />
</ScrollView>
</SafeAreaView>
);
}

22
app/(auth)/register.tsx Normal file
View File

@@ -0,0 +1,22 @@
import RegisterScreen from '@/screens/auth/register/RegisterScreen';
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function Index() {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<RegisterScreen />
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: { flex: 1, backgroundColor: '#0f172a' },
});

View File

@@ -0,0 +1,263 @@
import AuthHeader from '@/components/ui/AuthHeader';
import { auth_api } from '@/screens/auth/login/lib/api';
import { products_api } from '@/screens/home/lib/api';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useMutation, useQuery } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { LinearGradient } from 'expo-linear-gradient';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
import { ChevronLeft } from 'lucide-react-native';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
Alert,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
interface Category {
id: number;
name: string;
is_leaf: boolean;
}
export default function CategorySelectScreen() {
const router = useRouter();
const { t } = useTranslation();
const { phone, stir, person_type } = useLocalSearchParams<{
phone: string;
stir: string;
person_type: 'band' | 'ytt';
}>();
const [selected, setSelected] = useState<number | null>(null);
const [categories, setCategories] = useState<Category[]>([]);
const [history, setHistory] = useState<Category[][]>([]);
/** ROOT categories */
const { isLoading } = useQuery({
queryKey: ['categories-root'],
queryFn: () => products_api.getCategorys(),
select: (res) => {
setCategories(res.data.data);
},
});
/** CHILD categories */
const childMutation = useMutation({
mutationFn: (id: number) => products_api.getCategorys({ parent: id }),
onSuccess: (res) => {
setHistory((prev) => [...prev, categories]);
setCategories(res.data.data);
},
});
/** REGISTER */
const registerMutation = useMutation({
mutationFn: (body: {
phone: string;
stir: string;
person_type: string;
activate_types: number[];
}) => auth_api.register(body),
onSuccess: async () => {
router.replace('/(auth)/register-confirm');
await AsyncStorage.setItem('phone', phone);
},
onError: (err: AxiosError) => {
const errMessage = (err.response?.data as { data: { stir: string[] } }).data.stir[0];
const errMessageDetail = (err.response?.data as { data: { detail: string } }).data.detail;
const errrAlert = errMessage ? errMessage : errMessageDetail;
Alert.alert(t('Xatolik yuz berdi'), errMessage || errrAlert || t('erroXatolik yuz berdi'));
},
});
const onCategoryPress = (cat: Category) => {
if (cat.is_leaf) {
setSelected(cat.id);
} else {
childMutation.mutate(cat.id);
}
};
const goBack = () => {
if (history.length === 0) return;
const prev = history[history.length - 1];
setCategories(prev);
setHistory((h) => h.slice(0, -1));
setSelected(null);
};
return (
<SafeAreaView style={styles.safeArea}>
<AuthHeader />
<Stack.Screen options={{ title: t('Yonalishni tanlang') }} />
<LinearGradient colors={['#0f172a', '#1e293b']} style={StyleSheet.absoluteFill} />
<ScrollView contentContainerStyle={styles.container}>
{history.length > 0 && (
<TouchableOpacity onPress={goBack} style={styles.backBtn}>
<ChevronLeft size={20} color="#3b82f6" />
<Text style={styles.backText}>{t('Orqaga')}</Text>
</TouchableOpacity>
)}
<Text style={styles.title}>{t("Yo'nalishni tanlang")}</Text>
{isLoading || childMutation.isPending ? (
<ActivityIndicator color="#3b82f6" />
) : (
categories.map((c) => {
const active = selected === c.id;
return (
<TouchableOpacity
key={c.id}
style={[styles.item, active && styles.itemActive]}
onPress={() => onCategoryPress(c)}
>
<Text style={[styles.text, active && styles.textActive]}>{c.name}</Text>
</TouchableOpacity>
);
})
)}
</ScrollView>
<TouchableOpacity
disabled={!selected || registerMutation.isPending}
style={[styles.bottom, (!selected || registerMutation.isPending) && styles.bottomDisabled]}
onPress={() => {
if (!selected) return;
registerMutation.mutate({
activate_types: [selected],
person_type,
phone: `998${phone}`,
stir,
});
}}
>
<Text style={styles.bottomText} disabled={registerMutation.isPending}>
{t('Tadiqlash')}
</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: '700',
color: '#ffffff',
marginBottom: 12,
},
safeArea: {
flex: 1,
backgroundColor: '#0f172a',
},
container: {
paddingHorizontal: 20,
paddingTop: 16,
paddingBottom: 70,
gap: 12,
},
backBtn: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
marginBottom: 12,
},
backText: {
fontSize: 14,
color: '#3b82f6',
fontWeight: '600',
},
item: {
paddingVertical: 18,
paddingHorizontal: 18,
borderRadius: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: 'rgba(255,255,255,0.06)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.12)',
},
itemActive: {
backgroundColor: 'rgba(59,130,246,0.15)',
borderColor: 'rgba(59,130,246,0.6)',
},
text: {
fontSize: 15,
fontWeight: '600',
color: '#cbd5f5',
},
textActive: {
color: '#ffffff',
fontWeight: '800',
},
arrow: {
fontSize: 18,
color: '#94a3b8',
},
bottom: {
position: 'absolute',
bottom: 20,
left: 16,
right: 16,
height: 54,
borderRadius: 16,
backgroundColor: '#3b82f6',
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.35,
shadowRadius: 12,
elevation: 10,
},
bottomDisabled: {
backgroundColor: '#64748b',
},
bottomText: {
color: '#ffffff',
fontWeight: '800',
fontSize: 16,
},
decorCircle1: {
position: 'absolute',
top: -120,
right: -80,
width: 300,
height: 300,
borderRadius: 150,
backgroundColor: 'rgba(59,130,246,0.12)',
},
decorCircle2: {
position: 'absolute',
bottom: -120,
left: -100,
width: 280,
height: 280,
borderRadius: 140,
backgroundColor: 'rgba(16,185,129,0.1)',
},
});