fitst commit
This commit is contained in:
6
app/(auth)/_layout.tsx
Normal file
6
app/(auth)/_layout.tsx
Normal 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
18
app/(auth)/confirm.tsx
Normal 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
39
app/(auth)/index.tsx
Normal 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 bo‘lsa dashboard-ga yo‘naltir
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
router.replace('/(dashboard)');
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// Token yo‘q → login screen
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: '#0f172a' }}>
|
||||
<ScrollView contentContainerStyle={{ flexGrow: 1 }}>
|
||||
<LoginScreen />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
18
app/(auth)/register-confirm.tsx
Normal file
18
app/(auth)/register-confirm.tsx
Normal 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
22
app/(auth)/register.tsx
Normal 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' },
|
||||
});
|
||||
263
app/(auth)/select-category.tsx
Normal file
263
app/(auth)/select-category.tsx
Normal 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('Yo‘nalishni 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)',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user