update register page ui and api

This commit is contained in:
Samandar Turgunboyev
2026-02-24 11:28:06 +05:00
parent 0c9e0811ea
commit 8edd45d1ad
18 changed files with 14795 additions and 313 deletions

1
.npmrc
View File

@@ -1 +1,2 @@
legacy-peer-deps=true
node-linker=hoisted

View File

@@ -36,4 +36,6 @@ export const API_URLS = {
Notification_List: '/api/notifications/',
Notification_Ready: (id: number) => `/api/notifications/${id}/read/`,
Notification_Mark_All_Read: '/api/notifications/read-all/',
Info: "/auth/get-person/",
Get_Director_Info: "/auth/get-director/"
};

View File

@@ -1,6 +1,14 @@
// app/(auth)/_layout.tsx
import { RegisterProvider } from '@/screens/auth/register/lib/useRegisterStore';
import { Slot } from 'expo-router';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function AuthLayout() {
return <Slot />;
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<RegisterProvider>
<Slot />
</RegisterProvider>
</GestureHandlerRootView>
)
}

View File

@@ -0,0 +1,15 @@
import RegisterFormScreen from '@/screens/auth/register/RegisterForm';
import React from 'react';
import { StyleSheet, View } from 'react-native';
export default function Index() {
return (
<View style={styles.safeArea}>
<RegisterFormScreen />
</View>
);
}
const styles = StyleSheet.create({
safeArea: { flex: 1, backgroundColor: '#0f172a' },
});

View File

@@ -1,21 +1,247 @@
import RegisterScreen from '@/screens/auth/register/RegisterScreen';
import AuthHeader from '@/components/ui/AuthHeader';
import { PersonType, useRegister } from '@/screens/auth/register/lib/useRegisterStore';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import { Building2, ChevronRight, ShieldCheck, User } from 'lucide-react-native';
import React from 'react';
import { ScrollView, StyleSheet, View } from 'react-native';
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 (
<View style={styles.safeArea}>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
<Animated.View style={{ transform: [{ scale }], marginBottom: 14 }}>
<TouchableOpacity
style={styles.card}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={1}
testID={`person-type-${item.key}`}
>
<RegisterScreen />
</ScrollView>
<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({
safeArea: { flex: 1, backgroundColor: '#0f172a' },
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)',
},
});

View File

@@ -28,10 +28,15 @@ interface Category {
export default function CategorySelectScreen() {
const router = useRouter();
const { t } = useTranslation();
const { phone, stir, person_type } = useLocalSearchParams<{
const { phone, stir, person_type, director_full_name, referal, first_name, last_name, middle_name } = useLocalSearchParams<{
phone: string;
stir: string;
person_type: 'band' | 'ytt';
referal: string;
director_full_name: string;
first_name: string;
last_name: string;
middle_name: string;
}>();
const [selected, setSelected] = useState<number | null>(null);
@@ -63,6 +68,10 @@ export default function CategorySelectScreen() {
stir: string;
person_type: string;
activate_types: number[];
director_full_name: string;
referal: string;
first_name: string;
last_name: string;
}) => auth_api.register(body),
onSuccess: async () => {
router.replace('/(auth)/register-confirm');
@@ -94,6 +103,8 @@ export default function CategorySelectScreen() {
setSelected(null);
};
const full_name = first_name.length > 0 ? first_name + ' ' + last_name + ' ' + middle_name : director_full_name;
return (
<View style={styles.safeArea}>
<AuthHeader />
@@ -138,6 +149,10 @@ export default function CategorySelectScreen() {
person_type,
phone: `998${phone}`,
stir,
referal: referal,
director_full_name: director_full_name,
first_name: full_name,
last_name: last_name,
});
}}
>

View File

@@ -208,5 +208,23 @@
"Bekor qilish": "Cancel",
"Barchasi o'qildi": "Mark All as Read",
"Hisobni o'chirish": "Delete account",
"Rostdan ham hisobingizni ochirmoqchimisiz?": "Are you sure you want to delete your account?"
"Rostdan ham hisobingizni ochirmoqchimisiz?": "Are you sure you want to delete your account?",
"Referal": "Referal",
"Faoliyat turingizni tanlang": "Select your type of activity",
"YATT": "Sole Proprietor",
"Yakka tartibdagi tadbirkor": "Individual entrepreneur",
"O'zini o'zi band qilgan": "Self-employed",
"O'z faoliyatini mustaqil yurituvchi": "Independently operating individual",
"Yuridik shaxs": "Legal entity",
"Tashkilot yoki korxona": "Organization or company",
"JSHSHR va passport ma'lumotlarini kiriting": "Enter JSHSHR and passport details",
"INN raqamini kiriting": "Enter INN number",
"JSHSHR": "JSHSHR",
"JSHSHR kiriting (14 raqam)": "Enter JSHSHR (14 digits)",
"Passport seriya va raqami": "Passport series and number",
"INN": "INN",
"INN kiriting (9 raqam)": "Enter INN (9 digits)",
"Referal kodi": "Referral code",
"Direktor JSHSHR": "Director JSHSHR",
"Direktor JSHSHR (14 raqam)": "Director JSHSHR (number 14)"
}

View File

@@ -207,5 +207,23 @@
"Bekor qilish": "Отменить",
"Barchasi o'qildi": "Отметить все как прочитанное",
"Hisobni o'chirish": "Удалить аккаунт",
"Rostdan ham hisobingizni o'chirmoqchimisiz?": "Вы уверены, что хотите удалить свой аккаунт?"
"Rostdan ham hisobingizni o'chirmoqchimisiz?": "Вы уверены, что хотите удалить свой аккаунт?",
"Referal": "Реферал",
"Faoliyat turingizni tanlang": "Выберите тип деятельности",
"YATT": "ИП",
"Yakka tartibdagi tadbirkor": "Индивидуальный предприниматель",
"O'zini o'zi band qilgan": "Самозанятый",
"O'z faoliyatini mustaqil yurituvchi": "Лицо, ведущее самостоятельную деятельность",
"Yuridik shaxs": "Юридическое лицо",
"Tashkilot yoki korxona": "Организация или компания",
"JSHSHR va passport ma'lumotlarini kiriting": "Введите ПИНФЛ и паспортные данные",
"INN raqamini kiriting": "Введите номер ИНН",
"JSHSHR": "ПИНФЛ",
"JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ (14 цифр)",
"Passport seriya va raqami": "Серия и номер паспорта",
"INN": "ИНН",
"INN kiriting (9 raqam)": "Введите ИНН (9 цифр)",
"Referal kodi": "Реферальный код",
"Direktor JSHSHR": "Директор ПИНФЛ",
"Direktor JSHSHR kiriting (14 raqam)": "Введите ПИНФЛ директора (14 цифр)"
}

View File

@@ -207,5 +207,23 @@
"Bekor qilish": "Bekor qilish",
"Barchasi o'qildi": "Barchasi o'qildi",
"Hisobni o'chirish": "Hisobni o'chirish",
"Rostdan ham hisobingizni o'chirmoqchimisiz?": "Rostdan ham hisobingizni o'chirmoqchimisiz?"
"Rostdan ham hisobingizni o'chirmoqchimisiz?": "Rostdan ham hisobingizni o'chirmoqchimisiz?",
"Referal": "Referal",
"Faoliyat turingizni tanlang": "Faoliyat turingizni tanlang",
"YATT": "YATT",
"Yakka tartibdagi tadbirkor": "Yakka tartibdagi tadbirkor",
"O'zini o'zi band qilgan": "O'zini o'zi band qilgan",
"O'z faoliyatini mustaqil yurituvchi": "O'z faoliyatini mustaqil yurituvchi",
"Yuridik shaxs": "Yuridik shaxs",
"Tashkilot yoki korxona": "Tashkilot yoki korxona",
"JSHSHR va passport ma'lumotlarini kiriting": "JSHSHR va passport ma'lumotlarini kiriting",
"INN raqamini kiriting": "INN raqamini kiriting",
"JSHSHR": "JSHSHR",
"JSHSHR kiriting (14 raqam)": "JSHSHR kiriting (14 raqam)",
"Passport seriya va raqami": "Passport seriya va raqami",
"INN": "INN",
"INN kiriting (9 raqam)": "INN kiriting (9 raqam)",
"Referal kodi": "Referal kodi",
"Direktor JSHSHR": "Direktor JSHSHR",
"Direktor JSHSHR kiriting (14 raqam)": "Direktor JSHSHR kiriting (14 raqam)"
}

12876
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import httpClient from '@/api/httpClient';
import { API_URLS } from '@/api/URLs';
import axios, { AxiosResponse } from 'axios';
import { AxiosResponse } from 'axios';
interface ConfirmBody {
status: boolean;
@@ -13,6 +13,310 @@ interface ConfirmBody {
};
}
export interface CompanyInfo {
id: number;
inn: string;
registration_authority: string;
registration_date: string;
registration_number: string;
name: string;
short_name: string;
opf_code: string;
opf_name: string;
oked_code: string;
vat_number: string;
oked_name: string;
area: string;
region: string;
soogu_code: string;
soogu_name: string;
small_businesses: string;
activity_state: number;
statutory_fund: string;
activity_state_detail: ActivityStateDetail;
business_type_detail: BusinessTypeDetail;
director: string;
email: string;
village_code: string;
email_status: number;
phones: string[];
soato_code: string;
soato_name: string;
address: string;
relevance_date: string;
court_relevance_date: string | null;
deal_relevance_date: string | null;
tax_mode: number;
trust: string;
score: number;
itpark: number;
leasing_count_all: number | null;
leasing_count_not_finish: number | null;
leasing_sum: number | null;
leasing_is_delay: number | null;
leasing_debt: number | null;
leasing_is_partner: number | null;
leasing_relevance_date: string | null;
is_bankrupt: number;
is_abuse_vat: number;
is_large_taxpayer: number;
vendor_rating: number | null;
developer_rating: number | null;
dishonest_executor: DishonestExecutor;
village_detail: VillageDetail;
company_billing_address: BillingAddress;
actual_date: string;
kfs: Kfs;
uuid: string;
connections: Connections;
courts: Courts;
director_uuid: string;
founders: Founder[];
deals: Deals;
licenses: Licenses;
leasing_guarantor_pinfl: string | null;
leasing_guarantor_inn: string | null;
buildings: Buildings;
cadastres: Cadastres;
liquidation_date: string | null;
liquidation_reason: string | null;
is_suppliers: number;
}
export interface ActivityStateDetail {
id: number;
group: string;
name: string;
name_en: string;
name_uz: string;
name_uz_kir: string;
}
export interface BusinessTypeDetail {
id: number;
external_id: number;
name: string;
name_uz: string;
name_en: string;
}
export interface DishonestExecutor {
is_dishonest_executor: number;
delete_date: string | null;
}
export interface VillageDetail {
code: number;
name: string;
}
export interface BillingAddress {
country_code: number;
region_code: number;
region_name: string;
region_name_en: string;
region_name_uz: string;
district_code: number;
district_name: string;
district_name_en: string;
district_name_uz: string;
sector_code: number;
street_name: string;
house: string | null;
flat: string | null;
postcode: string;
}
export interface Kfs {
code: number;
name: string;
name_ru: string;
name_uz_cyr: string;
name_uz_lat: string;
}
export interface Connections {
director: number;
founders: number;
entrepreneur: number;
all: number;
}
export interface Courts {
total: number;
current: number;
completed: number;
}
export interface Founder {
name: string;
percentage: number;
is_individual: number;
person_type: string;
id: number | null;
founder_uuid: string;
}
export interface Deals {
customer: DealSide;
provider: DealSide;
actual_date: string;
}
export interface DealSide {
rows: any[];
total: number;
summ: number | null;
}
export interface Licenses {
total: number;
relevance_date: string;
actual_date: string;
}
export interface Buildings {
total: number;
}
export interface Cadastres {
total: number;
relevance_date: string | null;
}
export interface GetInfoResponse {
status: boolean;
data: CompanyInfo | IndividualInfo;
}
export interface IndividualInfo {
uuid: string,
id: number,
lastname: string,
firstname: string,
middlename: string,
registered_at: string,
unregistered_at: null | string,
activities: {
code: number,
name_en: string,
name_uz: string,
name_ru: string
}[]
}
export interface GetDirectorInfoResponse {
status: boolean,
data: {
entity: {
name: {
rows: {
id: number,
inn: string,
name: string,
director: string,
email: string,
phones: string[],
founders: {
name: string,
percentage: number,
is_individual: number,
person_type: string,
id: number | null,
founder_uuid: string
}[],
activity_state: number,
registration_date: string,
oked_code: string,
oked_name: string,
statutory_fund: string,
address: string,
variant: null
}[],
total: number
},
inn: {
rows: [],
total: 0
},
director: {
rows: [],
total: 0
},
founder: {
rows: {
id: number,
inn: string,
name: string,
director: string,
email: string,
phones: string[],
founders:
{
name: string,
percentage: number,
is_individual: number,
person_type: string,
id: number | null,
founder_uuid: string
}[],
activity_state: number,
registration_date: string,
oked_code: string,
oked_name: string,
statutory_fund: string,
address: string,
variant: null
}[],
total: number
},
email: {
rows: [],
total: number
},
phone: {
rows: [],
total: number
}
},
entrepreneur: {
rows:
{
id: number,
pinfl: string,
entrepreneur: string,
email: string,
phone: string,
registration_date: string
}[],
total: number
},
trademark: {
rows: [],
total: number
}
}
}
export const auth_api = {
async login(body: { phone: string }) {
const res = await httpClient.post(API_URLS.LOGIN, body);
@@ -28,8 +332,13 @@ export const auth_api = {
return res;
},
async get_info(inn: string) {
const res = await axios.get(`https://devapi.goodsign.biz/v1/profile/${inn}`);
async get_info(body: { value: string, type: string, passport_series?: string, passport_number?: string }): Promise<AxiosResponse<GetInfoResponse>> {
const res = await httpClient.post(API_URLS.Info, body);
return res;
},
async get_director_info(body: { value: string }): Promise<AxiosResponse<GetDirectorInfoResponse>> {
const res = await httpClient.post(API_URLS.Get_Director_Info, body);
return res;
},
@@ -37,7 +346,11 @@ export const auth_api = {
phone: string;
stir: string;
person_type: string;
referal: string;
activate_types: number[];
director_full_name: string;
first_name: string;
last_name: string;
}) {
const res = await httpClient.post(API_URLS.Register, body);
return res;

View File

@@ -56,7 +56,7 @@ export default function LoginScreen() {
<TouchableOpacity
style={styles.registerButton}
onPress={() => router.push('/register')}
onPress={() => router.push('/(auth)/register')}
activeOpacity={0.8}
>
<LinearGradient

View File

@@ -0,0 +1,48 @@
import { useRouter } from 'expo-router';
import { ChevronLeft } from 'lucide-react-native';
import React from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export default function AuthHeader() {
const router = useRouter();
const insets = useSafeAreaInsets();
return (
<View style={[styles.container, { paddingTop: insets.top + 8 }]}>
<TouchableOpacity
style={styles.backButton}
onPress={() => {
if (router.canGoBack()) {
router.back();
}
}}
testID="auth-header-back"
>
<ChevronLeft size={22} color="#94a3b8" />
</TouchableOpacity>
<View style={{ width: 44 }} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 24,
paddingBottom: 8,
zIndex: 1000,
},
backButton: {
width: 44,
height: 44,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.15)',
},
});

View File

@@ -9,12 +9,18 @@ import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native';
export default function CategorySelectScreen() {
const router = useRouter();
const { phone, stir, person_type } = useLocalSearchParams<{
const { phone, stir, person_type, director_full_name, referal, first_name, last_name } = useLocalSearchParams<{
phone: string;
stir: string;
person_type: 'band' | 'ytt';
referal: string;
director_full_name: string;
first_name: string;
last_name: string;
}>();
console.log(referal);
const [selected, setSelected] = React.useState<number | null>(null);
const { data } = useQuery({
@@ -28,6 +34,10 @@ export default function CategorySelectScreen() {
stir: string;
person_type: string;
activate_types: number[];
referal: string;
director_full_name: string;
first_name: string;
last_name: string;
}) => auth_api.register(body),
onSuccess: () => router.replace('/'),
});
@@ -52,12 +62,16 @@ export default function CategorySelectScreen() {
disabled={!selected || isPending}
style={[styles.bottom, (!selected || isPending) && { opacity: 0.5 }]}
onPress={() => {
if (phone && stir && person_type && selected) {
if (phone && stir && person_type && selected && referal && director_full_name) {
mutate({
activate_types: [selected],
person_type: person_type,
phone: `998${phone}`,
stir: stir,
referal: String(referal),
director_full_name: String(director_full_name),
first_name: String(first_name),
last_name: String(last_name),
});
}
}}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,112 @@
import AuthHeader from '@/components/ui/AuthHeader';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
import { UserPlus } from 'lucide-react-native';
import { Building2, ChevronRight, ShieldCheck, User } from 'lucide-react-native';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import {
Animated,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import RegisterForm from './RegisterForm';
import { PersonType, useRegister } from './lib/useRegisterStore';
export default function RegisterScreen() {
const PERSON_TYPES: {
key: PersonType;
label: string;
description: string;
icon: React.ReactNode;
gradient: [string, string];
}[] = [
{
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'],
},
{
key: 'legal_entity',
label: 'Yuridik shaxs',
description: "Tashkilot yoki korxona",
icon: <Building2 size={28} color="#fff" />,
gradient: ['#f59e0b', '#d97706'],
},
];
function TypeCard({
item,
index,
onPress,
}: {
item: (typeof PERSON_TYPES)[number];
index: number;
onPress: () => void;
}) {
const scale = React.useRef(new Animated.Value(1)).current;
const handlePressIn = () => {
Animated.spring(scale, {
toValue: 0.96,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
Animated.spring(scale, {
toValue: 1,
friction: 4,
useNativeDriver: true,
}).start();
};
return (
<Animated.View style={{ transform: [{ scale }] }}>
<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}>{item.label}</Text>
<Text style={styles.cardDescription}>{item.description}</Text>
</View>
<ChevronRight size={20} color="#94a3b8" />
</TouchableOpacity>
</Animated.View>
);
}
export default function PersonTypeScreen() {
const router = useRouter();
const { t } = useTranslation();
const { setPersonType } = useRegister();
const handleSelect = (type: PersonType) => {
setPersonType(type);
router.push('/(auth)/register-form');
};
return (
<View style={styles.container}>
{/* Background Decorations */}
<LinearGradient
colors={['#0f172a', '#1e293b', '#334155']}
start={{ x: 0, y: 0 }}
@@ -24,119 +116,54 @@ export default function RegisterScreen() {
<View style={styles.decorCircle1} />
<View style={styles.decorCircle2} />
<AuthHeader />
<SafeAreaView style={{ flex: 1 }}>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* Header Section */}
<View style={styles.header}>
<View style={styles.iconContainer}>
<LinearGradient
colors={['#10b981', '#059669']}
style={styles.iconGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
<UserPlus size={32} color="#ffffff" />
</LinearGradient>
</View>
<Text style={styles.title}>{t("Ro'yxatdan o'tish")}</Text>
<Text style={styles.subtitle}>
{t('Tizimdan foydalanish uchun STIR raqami yoki JSHSHR kiritishingiz kerak.')}
<SafeAreaView style={styles.safeArea}>
<View style={styles.header}>
<Text style={styles.title}>Ro'yxatdan o'tish</Text>
<Text style={styles.subtitle}>
Faoliyat turingizni tanlang
</Text>
</View>
<View style={styles.cardList}>
{PERSON_TYPES.map((item, index) => (
<TypeCard
key={item.key}
item={item}
index={index}
onPress={() => handleSelect(item.key)}
/>
))}
</View>
<View style={styles.footer}>
<TouchableOpacity onPress={() => router.back()} testID="go-to-login">
<Text style={styles.footerText}>
Hisobingiz bormi? <Text style={styles.footerLink}>Kirish</Text>
</Text>
</View>
{/* Form Card */}
<View style={styles.card}>
<RegisterForm />
</View>
{/* Footer */}
<View style={styles.footer}>
<TouchableOpacity onPress={() => router.push('/')}>
<Text style={styles.footerText}>
{t('Hisobingiz bormi?')} <Text style={styles.footerLink}>{t('Kirish')}</Text>
</Text>
</TouchableOpacity>
</View>
</ScrollView>
</TouchableOpacity>
</View>
</SafeAreaView>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0f172a' },
// Header Navigatsiya qismi (LoginScreen kabi)
languageHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
container: {
flex: 1,
backgroundColor: '#0f172a',
},
safeArea: {
flex: 1,
paddingHorizontal: 24,
paddingVertical: 12,
zIndex: 1000,
},
backButton: {
width: 44,
height: 44,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.15)',
},
languageButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 12,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.15)',
},
languageText: {
fontSize: 14,
fontWeight: '600',
color: '#94a3b8',
},
// Scroll va Forma joylashuvi
scrollContent: {
flexGrow: 1,
paddingHorizontal: 24,
paddingBottom: 40,
paddingTop: 10,
justifyContent: 'center',
},
header: {
alignItems: 'center',
marginBottom: 32,
},
iconContainer: {
marginBottom: 20,
},
iconGradient: {
width: 72,
height: 72,
borderRadius: 22,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#10b981',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.3,
shadowRadius: 12,
elevation: 8,
marginTop: 40,
marginBottom: 36,
},
title: {
fontSize: 28,
fontWeight: '800',
fontWeight: '800' as const,
color: '#ffffff',
marginBottom: 10,
letterSpacing: 0.5,
@@ -146,61 +173,54 @@ const styles = StyleSheet.create({
color: '#94a3b8',
textAlign: 'center',
lineHeight: 22,
paddingHorizontal: 10,
},
cardList: {
gap: 14,
},
card: {
backgroundColor: '#ffffff',
borderRadius: 28,
padding: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.3,
shadowRadius: 20,
elevation: 10,
},
// Dropdown (LoginScreen bilan bir xil)
dropdown: {
position: 'absolute',
top: 55,
right: 0,
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 8,
minWidth: 180,
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.2,
shadowRadius: 20,
elevation: 15,
borderWidth: 1,
borderColor: '#f1f5f9',
},
dropdownOption: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 12,
marginBottom: 4,
backgroundColor: 'rgba(255, 255, 255, 0.06)',
borderRadius: 20,
padding: 18,
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
gap: 16,
},
dropdownOptionActive: { backgroundColor: '#eff6ff' },
dropdownOptionText: { fontSize: 14, fontWeight: '600', color: '#475569' },
dropdownOptionTextActive: { color: '#3b82f6' },
checkmark: {
width: 22,
height: 22,
borderRadius: 11,
backgroundColor: '#3b82f6',
cardIcon: {
width: 56,
height: 56,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
},
checkmarkText: { color: '#ffffff', fontSize: 12, fontWeight: 'bold' },
footer: { marginTop: 24, alignItems: 'center' },
footerText: { color: '#94a3b8', fontSize: 14 },
footerLink: { color: '#3b82f6', fontWeight: '700' },
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,

View File

@@ -0,0 +1,11 @@
import { create } from "zustand";
type State = {
referal: string;
setReferal: (referal: string) => void;
}
export const useRegisterForm = create<State>((set) => ({
referal: '',
setReferal: (referal) => set({ referal }),
}))

View File

@@ -0,0 +1,93 @@
import createContextHook from '@nkzw/create-context-hook';
import { useState } from 'react';
import { GetDirectorInfoResponse } from '../../login/lib/api';
export type PersonType = 'yatt' | 'band' | 'legal_entity' | null;
interface RegisterState {
personType: PersonType;
setPersonType: (type: PersonType) => void;
phone: string;
setPhone: (phone: string) => void;
referal: string;
setReferal: (referal: string) => void;
jshshr: string;
setJshshr: (jshshr: string) => void;
passportSeries: string;
setPassportSeries: (series: string) => void;
passportNumber: string;
setPassportNumber: (number: string) => void;
inn: string;
setInn: (inn: string) => void;
info: any;
setInfo: (info: any) => void;
reset_full: () => void;
reset: () => void;
directorJshshr: string;
setDirectorJshshr: (directorJshshr: string) => void;
directorInfo: GetDirectorInfoResponse | null;
setDirectorInfo: (directorInfo: GetDirectorInfoResponse | null) => void;
}
export const [RegisterProvider, useRegister] = createContextHook<RegisterState>(() => {
const [personType, setPersonType] = useState<PersonType>(null);
const [phone, setPhone] = useState<string>('');
const [referal, setReferal] = useState<string>('');
const [jshshr, setJshshr] = useState<string>('');
const [passportSeries, setPassportSeries] = useState<string>('');
const [passportNumber, setPassportNumber] = useState<string>('');
const [inn, setInn] = useState<string>('');
const [info, setInfo] = useState<any>(null);
const [directorJshshr, setDirectorJshshr] = useState<string>('');
const [directorInfo, setDirectorInfo] = useState<GetDirectorInfoResponse | null>(null);
const reset_full = () => {
setPersonType(null);
setPhone('');
setReferal('');
setJshshr('');
setPassportSeries('');
setPassportNumber('');
setInn('');
setInfo(null);
setDirectorJshshr('');
setDirectorInfo(null);
};
const reset = () => {
setPhone('');
setReferal('');
setJshshr('');
setPassportSeries('');
setPassportNumber('');
setInn('');
setInfo(null);
setDirectorJshshr('');
setDirectorInfo(null);
};
return {
personType,
setPersonType,
phone,
setPhone,
referal,
setReferal,
jshshr,
setJshshr,
passportSeries,
setPassportSeries,
passportNumber,
setPassportNumber,
inn,
setInn,
reset,
info,
setInfo,
reset_full,
directorJshshr,
setDirectorJshshr,
directorInfo,
setDirectorInfo,
};
});