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

73
screens/home/lib/api.ts Normal file
View File

@@ -0,0 +1,73 @@
import httpClient from '@/api/httpClient';
import { API_URLS } from '@/api/URLs';
import { AxiosResponse } from 'axios';
import {
businessAboutDetailRes,
businessAboutRes,
Categories,
CompanyBody,
CountryBody,
ProductBody,
States,
} from './types';
export const products_api = {
async getProducts(params: {
page?: number;
page_size?: number;
search?: string;
}): Promise<AxiosResponse<ProductBody>> {
const res = await httpClient.get(API_URLS.Get_Products, { params });
return res;
},
async getCompany(params: {
page?: number;
page_size?: number;
search?: string;
}): Promise<AxiosResponse<CompanyBody>> {
const res = await httpClient.get(API_URLS.Get_Company, { params });
return res;
},
async getCountry(params: {
page?: number;
page_size?: number;
search?: string;
}): Promise<AxiosResponse<CountryBody>> {
const res = await httpClient.get(API_URLS.Get_Countries, { params });
return res;
},
async getStates(): Promise<AxiosResponse<States>> {
const res = await httpClient.get(API_URLS.Get_States);
return res;
},
async getCategorys(params?: { parent: number }): Promise<AxiosResponse<Categories>> {
const res = await httpClient.get(API_URLS.Get_Categories, { params });
return res;
},
async getCategoryChild(id: number): Promise<AxiosResponse<Categories>> {
const res = await httpClient.get(API_URLS.Get_Categories_Child(id));
return res;
},
async businessAbout(params: {
country?: string;
district: string;
page?: number;
page_size?: number;
region?: string;
types?: number;
}): Promise<AxiosResponse<businessAboutRes>> {
const res = await httpClient.get(API_URLS.Business_About, { params });
return res;
},
async businessAboutDetail(id: number): Promise<AxiosResponse<businessAboutDetailRes>> {
const res = await httpClient.get(API_URLS.Business_About_Detail(id));
return res;
},
};

150
screens/home/lib/types.ts Normal file
View File

@@ -0,0 +1,150 @@
export interface ProductBody {
status: boolean;
data: {
links: {
previous: null | string;
next: null | string;
};
total_items: number;
total_pages: number;
page_size: number;
current_page: number;
results: ProductResponse[];
};
}
export interface ProductResponse {
id: number;
title: string;
description: string;
company: string;
files: {
id: number;
file: string;
}[];
category: { id: number; name: string; icon: string }[];
}
export interface CompanyBody {
status: boolean;
data: {
links: {
previous: null | string;
next: null | string;
};
total_items: number;
total_pages: number;
page_size: number;
current_page: number;
results: CompanyResponse[];
};
}
export interface CompanyResponse {
id: number;
company_name: string;
country_name: string;
region_name: string;
district_name: string;
product_service_company: {
id: number;
title: string;
description: string;
company: string;
files: [
{
id: number;
file: string;
},
];
category: [];
}[];
}
export interface CountryBody {
status: boolean;
data: {
links: {
previous: null | string;
next: null | string;
};
total_items: number;
total_pages: number;
page_size: number;
current_page: number;
results: CountryResponse[];
};
}
export interface CountryResponse {
id: number;
name: string;
companies: {
id: number;
company_name: string;
service_count: number;
}[];
}
export interface States {
status: boolean;
data: {
id: number;
name: string;
region: {
id: number;
name: string;
code: string;
districts: { id: number; name: string; code: string }[];
}[];
code: string;
}[];
}
export interface Categories {
status: boolean;
data: {
id: number;
name: string;
code: string;
external_id: null | string;
level: number;
is_leaf: boolean;
icon_name: null | string;
}[];
}
export interface businessAboutRes {
status: boolean;
data: {
links: {
previous: null | string;
next: null | string;
};
total_items: number;
total_pages: number;
page_size: number;
current_page: number;
results: { id: number; company_name: string }[];
};
}
export interface businessAboutDetailRes {
status: boolean;
data: businessAboutDetailResData;
}
export interface businessAboutDetailResData {
company_name: string;
director_full_name: string;
company_image: null | string;
phone: string;
product_service_company: {
title: string;
description: string;
category: { id: number; name: string; icon_name: string }[];
files: {
file: string;
}[];
}[];
}

View File

@@ -0,0 +1,399 @@
import {
ArrowLeft,
Briefcase,
Building2,
CheckCircle2,
ChevronRight,
Globe,
MapPin,
Package,
Search,
SlidersHorizontal,
} from 'lucide-react-native';
import React, { useState } from 'react';
import {
FlatList,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
// --- TURLAR ---
type TabKey = 'products' | 'companies' | 'countries';
type FilterStep = 'filter' | 'items' | 'detail';
const FAKE_ITEMS = [
{ id: 1, company_name: 'Artel Electronics', industry: 'Maishiy texnika', country: 'UZ' },
{ id: 2, company_name: 'Toshkent City Mall', industry: 'Savdo', country: 'UZ' },
{ id: 3, company_name: 'Turkish Airlines', industry: 'Aviatsiya', country: 'TR' },
{ id: 4, company_name: 'Siemens AG', industry: 'Muhandislik', country: 'DE' },
];
export default function App() {
const [filterVisible, setFilterVisible] = useState(false);
const [activeTab, setActiveTab] = useState<TabKey>('products');
const [searchQuery, setSearchQuery] = useState('');
if (filterVisible) {
return <FilterUI back={() => setFilterVisible(false)} />;
}
return (
<SafeAreaView style={styles.container}>
{/* Search Header */}
<View style={styles.header}>
<View style={styles.searchContainer}>
<Search size={20} color="#94a3b8" style={styles.searchIcon} />
<TextInput
style={styles.searchInput}
placeholder={`${activeTab === 'products' ? 'Mahsulot' : activeTab === 'companies' ? 'Kompaniya' : 'Davlat'} bo'yicha...`}
value={searchQuery}
onChangeText={setSearchQuery}
placeholderTextColor="#94a3b8"
/>
</View>
<TouchableOpacity style={styles.filterButton} onPress={() => setFilterVisible(true)}>
<SlidersHorizontal size={22} color="#fff" />
</TouchableOpacity>
</View>
{/* Tabs */}
<View style={styles.tabWrapper}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.tabList}
>
{[
{
key: 'products',
label: 'Mahsulotlar',
icon: <Package size={18} color={activeTab === 'products' ? '#fff' : '#64748b'} />,
},
{
key: 'companies',
label: 'Kompaniyalar',
icon: <Building2 size={18} color={activeTab === 'companies' ? '#fff' : '#64748b'} />,
},
{
key: 'countries',
label: 'Davlatlar',
icon: <Globe size={18} color={activeTab === 'countries' ? '#fff' : '#64748b'} />,
},
].map((tab) => (
<TouchableOpacity
key={tab.key}
onPress={() => setActiveTab(tab.key as TabKey)}
style={[styles.tabItem, activeTab === tab.key && styles.activeTabItem]}
>
{tab.icon}
<Text style={[styles.tabText, activeTab === tab.key && styles.activeTabText]}>
{tab.label}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
{/* Main List */}
<FlatList
data={FAKE_ITEMS}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.listContent}
renderItem={({ item }) => (
<TouchableOpacity style={styles.card}>
<View style={styles.cardInfo}>
<Text style={styles.cardTitle}>{item.company_name}</Text>
<Text style={styles.cardSub}>{item.industry}</Text>
</View>
<ChevronRight size={20} color="#cbd5e1" />
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
}
// --- FILTER UI KOMPONENTI ---
function FilterUI({ back }: { back: () => void }) {
const [step, setStep] = useState<FilterStep>('filter');
const [selectedId, setSelectedId] = useState<number | null>(null);
const handleBack = () => {
if (step === 'items') setStep('filter');
else if (step === 'detail') setStep('items');
else back();
};
const renderFilter = () => (
<View style={{ flex: 1 }}>
<ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<MapPin size={20} color="#3b82f6" />
<Text style={styles.sectionLabel}>Hududni tanlang</Text>
</View>
<View style={styles.selectionCard}>
<TouchableOpacity style={styles.selectRow}>
<View>
<Text style={styles.selectLabel}>Davlat</Text>
<Text style={styles.selectValue}>O'zbekiston</Text>
</View>
<ChevronRight size={20} color="#cbd5e1" />
</TouchableOpacity>
<View style={styles.divider} />
<TouchableOpacity style={styles.selectRow}>
<View>
<Text style={styles.selectLabel}>Viloyat</Text>
<Text style={styles.selectValue}>Barchasi</Text>
</View>
<ChevronRight size={20} color="#cbd5e1" />
</TouchableOpacity>
</View>
</View>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Briefcase size={20} color="#3b82f6" />
<Text style={styles.sectionLabel}>Sanoat yo'nalishi</Text>
</View>
<View style={styles.industryGrid}>
{['Texnologiya', 'Tibbiyot', 'Talim', 'Qurilish'].map((item) => (
<TouchableOpacity key={item} style={styles.industryTag}>
<Text style={styles.industryTagText}>{item}</Text>
</TouchableOpacity>
))}
</View>
</View>
</ScrollView>
<View style={styles.footer}>
<TouchableOpacity onPress={() => setStep('items')} style={styles.applyButton}>
<Text style={styles.applyButtonText}>Natijalarni ko'rish ({FAKE_ITEMS.length})</Text>
<CheckCircle2 size={20} color="#fff" />
</TouchableOpacity>
</View>
</View>
);
const renderItems = () => (
<FlatList
data={FAKE_ITEMS}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{ padding: 16 }}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.resultCard}
onPress={() => {
setSelectedId(item.id);
setStep('detail');
}}
>
<View style={styles.iconCircle}>
<Building2 size={22} color="#3b82f6" />
</View>
<View style={{ flex: 1, marginLeft: 12 }}>
<Text style={styles.companyName}>{item.company_name}</Text>
<Text style={styles.industryText}>{item.industry}</Text>
</View>
<ChevronRight size={20} color="#cbd5e1" />
</TouchableOpacity>
)}
/>
);
const renderDetail = () => {
const item = FAKE_ITEMS.find((i) => i.id === selectedId);
return (
<View style={styles.detailCard}>
<Text style={styles.detailTitle}>{item?.company_name}</Text>
<View style={styles.badge}>
<Text style={styles.badgeText}>{item?.industry}</Text>
</View>
<Text style={styles.detailDesc}>
Bu yerda web-versiyadagi batafsil ma'lumotlar chiqadi.
</Text>
</View>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.navHeader}>
<TouchableOpacity onPress={handleBack} style={styles.backButton}>
<ArrowLeft size={24} color="#1e293b" />
</TouchableOpacity>
<Text style={styles.headerTitle}>
{step === 'filter' ? 'Filtrlash' : step === 'items' ? 'Natijalar' : 'Batafsil'}
</Text>
<View style={{ width: 40 }} />
</View>
{step === 'filter' && renderFilter()}
{step === 'items' && renderItems()}
{step === 'detail' && renderDetail()}
</SafeAreaView>
);
}
// --- STILLAR ---
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f8fafc' },
header: {
flexDirection: 'row',
padding: 16,
gap: 12,
alignItems: 'center',
backgroundColor: '#fff',
},
searchContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f1f5f9',
borderRadius: 12,
paddingHorizontal: 12,
height: 48,
},
searchIcon: { marginRight: 8 },
searchInput: { flex: 1, fontSize: 16, color: '#1e293b' },
filterButton: {
backgroundColor: '#3b82f6',
width: 48,
height: 48,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
elevation: 4,
},
tabWrapper: {
backgroundColor: '#fff',
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
tabList: { paddingHorizontal: 16, gap: 10 },
tabItem: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 10,
backgroundColor: '#f1f5f9',
gap: 8,
marginRight: 10,
},
activeTabItem: { backgroundColor: '#3b82f6' },
tabText: { fontSize: 14, fontWeight: '600', color: '#64748b' },
activeTabText: { color: '#fff' },
listContent: { padding: 16 },
card: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 16,
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
borderWidth: 1,
borderColor: '#f1f5f9',
},
cardInfo: { flex: 1 },
cardTitle: { fontSize: 16, fontWeight: '700', color: '#1e293b' },
cardSub: { fontSize: 13, color: '#94a3b8' },
navHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: '#fff',
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
backButton: { padding: 8, borderRadius: 12, backgroundColor: '#f1f5f9' },
headerTitle: { fontSize: 18, fontWeight: '700', color: '#1e293b' },
scrollContent: { padding: 16, paddingBottom: 100 },
section: { marginBottom: 24 },
sectionHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 12 },
sectionLabel: { fontSize: 15, fontWeight: '600', color: '#64748b' },
selectionCard: {
backgroundColor: '#fff',
borderRadius: 16,
paddingHorizontal: 16,
borderWidth: 1,
borderColor: '#e2e8f0',
},
selectRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 14,
},
selectLabel: { fontSize: 12, color: '#94a3b8' },
selectValue: { fontSize: 15, fontWeight: '600', color: '#1e293b' },
divider: { height: 1, backgroundColor: '#f1f5f9' },
industryGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
industryTag: {
paddingHorizontal: 16,
paddingVertical: 10,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#e2e8f0',
},
industryTagText: { fontSize: 14, color: '#475569' },
footer: {
position: 'absolute',
bottom: 0,
width: '100%',
padding: 20,
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#f1f5f9',
},
applyButton: {
height: 56,
backgroundColor: '#3b82f6',
borderRadius: 16,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 10,
},
applyButtonText: { color: '#fff', fontSize: 16, fontWeight: '700' },
resultCard: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 16,
marginBottom: 12,
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: '#f1f5f9',
},
iconCircle: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: '#eff6ff',
alignItems: 'center',
justifyContent: 'center',
},
companyName: { fontSize: 16, fontWeight: '700' },
industryText: { fontSize: 13, color: '#64748b' },
detailCard: { margin: 16, padding: 20, backgroundColor: '#fff', borderRadius: 20 },
detailTitle: { fontSize: 22, fontWeight: '800' },
badge: {
backgroundColor: '#eff6ff',
alignSelf: 'flex-start',
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 8,
marginVertical: 12,
},
badgeText: { color: '#3b82f6', fontWeight: '600' },
detailDesc: { lineHeight: 22, color: '#475569' },
});

View File

@@ -0,0 +1,52 @@
import { ArrowLeft, ChevronRight } from 'lucide-react-native';
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export function FilteredItems({ data, back }: any) {
return (
<SafeAreaView>
<View>
<TouchableOpacity onPress={back}>
<ArrowLeft size={24} color="#1e293b" />
</TouchableOpacity>
<Text>Natijalar ({data.length})</Text>
<View style={{ width: 40 }} />
</View>
<FlatList
data={data}
contentContainerStyle={{ padding: 16 }}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity style={styles.resultCard}>
<View style={styles.resultInfo}>
<Text style={styles.companyName}>{item.company_name}</Text>
<Text style={styles.industryText}>{item.industry || 'Sanoat turi'}</Text>
</View>
<View style={styles.goIcon}>
<ChevronRight size={18} color="#3b82f6" />
</View>
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
}
// Qo'shimcha stillar resultCard uchun
const styles = StyleSheet.create({
resultCard: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 16,
marginBottom: 12,
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: '#f1f5f9',
},
resultInfo: { flex: 1 },
companyName: { fontSize: 16, fontWeight: '700', color: '#1e293b' },
industryText: { fontSize: 13, color: '#64748b', marginTop: 4 },
goIcon: { backgroundColor: '#eff6ff', padding: 8, borderRadius: 10 },
});

View File

@@ -0,0 +1,272 @@
// pages/home/ui/HomeScreen.tsx
import { useTheme } from '@/components/ThemeContext';
import CompanyList from '@/components/ui/CompanyList';
import CountriesList from '@/components/ui/CountriesList';
import FilteredItems from '@/components/ui/FilteredItems';
import FilterUI from '@/components/ui/FilterUI';
import ProductList from '@/components/ui/ProductList';
import SearchTabs from '@/components/ui/SearchTabs';
import { useTabSearch } from '@/hooks/useSearch';
import { TabKey } from '@/types';
import { useQueryClient } from '@tanstack/react-query';
import { Stack } from 'expo-router';
import { Filter, Search } from 'lucide-react-native';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import { RefreshControl } from 'react-native-gesture-handler';
function Loading() {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#3b82f6" />
</View>
);
}
export default function HomeScreen() {
const { isDark } = useTheme();
const [activeTab, setActiveTab] = useState<TabKey>('products');
const [step, setStep] = useState<'filter' | 'items'>('filter');
const [query, setQuery] = useState('');
const [showFilter, setShowFilter] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [filtered, setFiltered] = useState<{ id: number; company_name: string }[]>([]);
const { t } = useTranslation();
const queryClient = useQueryClient();
const { isLoading, error } = useTabSearch(activeTab, query);
const onRefresh = useCallback(async () => {
setRefreshing(true);
try {
if (activeTab === 'products') {
await queryClient.invalidateQueries({ queryKey: ['products-list'] });
}
await queryClient.refetchQueries();
} catch (err) {
console.error('Refresh error:', err);
} finally {
setRefreshing(false);
}
}, [queryClient, activeTab]);
const placeholderText = useMemo(() => {
switch (activeTab) {
case 'products':
return 'Mahsulot qidirish...';
case 'companies':
return 'Korxona qidirish...';
case 'countries':
return 'Davlat qidirish...';
default:
return 'Qidiruv...';
}
}, [activeTab]);
const RenderedView = useMemo(() => {
switch (activeTab) {
case 'products':
return <ProductList query={query} />;
case 'companies':
return <CompanyList query={query} />;
case 'countries':
return <CountriesList search={query} />;
}
}, [activeTab, query]);
if (showFilter && step === 'filter') {
return (
<FilterUI back={() => setShowFilter(false)} setStep={setStep} setFiltered={setFiltered} />
);
}
if (showFilter && step === 'items') {
return (
<FilteredItems
data={filtered}
back={() => {
setShowFilter(false);
setStep('filter');
}}
/>
);
}
return (
<ScrollView
style={[isDark ? styles.darkBg : styles.lightBg]}
contentContainerStyle={{ flexGrow: 1, paddingBottom: 60 }}
keyboardShouldPersistTaps="handled"
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={['#3b82f6']}
tintColor="#3b82f6"
/>
}
>
<Stack.Screen options={{ headerShown: false }} />
<View style={styles.content}>
{/* Qidiruv va filter */}
<View style={styles.searchSection}>
<View
style={[
styles.searchInputContainer,
isDark ? styles.darkSearchInput : styles.lightSearchInput,
]}
>
<Search size={20} color={isDark ? '#64748b' : '#94a3b8'} style={styles.searchIcon} />
<TextInput
style={[styles.searchInput, isDark ? styles.darkInputText : styles.lightInputText]}
placeholder={t(placeholderText)}
value={query}
onChangeText={setQuery}
placeholderTextColor={isDark ? '#64748b' : '#94a3b8'}
/>
</View>
<TouchableOpacity
style={styles.filterButton}
onPress={() => setShowFilter(true)}
activeOpacity={0.7}
>
<Filter size={20} color="#fff" />
</TouchableOpacity>
</View>
<SearchTabs value={activeTab} onChange={setActiveTab} />
{error && (
<View style={[styles.errorContainer, isDark ? styles.darkError : styles.lightError]}>
<Text style={styles.errorText}>{t("Ma'lumot yuklashda xatolik")}</Text>
<TouchableOpacity onPress={onRefresh} style={styles.retryButton}>
<Text style={styles.retryButtonText}>{t('Qayta urinish')}</Text>
</TouchableOpacity>
</View>
)}
{isLoading && !refreshing && <Loading />}
{!isLoading && RenderedView}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
darkBg: {
flex: 1,
backgroundColor: '#0f172a',
},
lightBg: {
flex: 1,
backgroundColor: '#f8fafc',
},
content: {
padding: 16,
maxWidth: 768,
width: '100%',
alignSelf: 'center',
gap: 20,
},
searchSection: {
flexDirection: 'row',
gap: 12,
alignItems: 'center',
},
searchInputContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
borderRadius: 16,
borderWidth: 1.5,
paddingHorizontal: 16,
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 8,
elevation: 2,
},
darkSearchInput: {
backgroundColor: '#1e293b',
borderColor: '#334155',
},
lightSearchInput: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
},
searchIcon: {
marginRight: 10,
},
searchInput: {
flex: 1,
paddingVertical: 14,
fontSize: 15,
fontWeight: '500',
},
darkInputText: {
color: '#f1f5f9',
},
lightInputText: {
color: '#0f172a',
},
filterButton: {
backgroundColor: '#3b82f6',
width: 48,
height: 48,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#3b82f6',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 4,
},
loadingContainer: {
padding: 60,
alignItems: 'center',
justifyContent: 'center',
},
errorContainer: {
padding: 32,
alignItems: 'center',
borderRadius: 16,
},
darkError: {
backgroundColor: '#1e293b',
},
lightError: {
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#fee2e2',
},
errorText: {
fontSize: 16,
fontWeight: '600',
color: '#f87171',
marginBottom: 12,
},
retryButton: {
backgroundColor: '#3b82f6',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
},
retryButtonText: {
color: '#ffffff',
fontWeight: '700',
fontSize: 14,
},
});