PizzaV2
Кросплатформне замовлення піциСучасний додаток замовлення піци з авторизацією по телефону, OTP верифікацією, налаштовуваними розмірами (S/M/L), керуванням кошиком в реальному часі та відстеженням замовлень з бейджами статусу. Побудований з React Native 0.76 та TypeScript.
Створення сучасного додатку замовлення їжі означає одночасну обробку процесів авторизації по телефону, складної кастомізації продуктів та стану кошика в реальному часі — все це з наданням відшліфованого нативного відчуття. Більшість рішень виглядають громіздко, не мають належного OTP UX та не справляються з нюансами мобільного замовлення, такими як варіанти розмірів, керування кількістю та відстеження життєвого циклу замовлення.


Додаток замовлення піци в темній темі з безперебійною авторизацією по телефону, OTP верифікацією з автоматичним переходом між полями, каталогом з 10 піцами з варіантами розмірів S/M/L, керуванням кошиком в реальному часі та повною історією замовлень з бейджами статусу. Побудований з єдиної кодової бази React Native CLI для Android з двигуном Hermes.


React Navigation 7 керує захищеною маршрутизацією з трьома навігаторами: `AuthNavigator` (онбординг → вхід по телефону → OTP), `TabNavigator` (головна, історія, кошик, профіль) та вкладений стек для деталей піци. Стан керується через два провайдери React Context з хуками-захисниками. Повністю кастомний компонент `BottomNavigation` замінює стандартну панель вкладок з емодзі іконками, індикаторами активної крапки та бейджем кошика, синхронізованим з контекстом.


Згуртована темна дизайн-система з помаранчевим (#FF6B03) акцентом, 20px радіусом меж на картках, кнопками у формі пігулок та послідовними токенами відстаней. Плаваючі зображення продуктів накладаються на картки з від'ємними відступами. Сім бейджів статусу замовлення мають унікальні пари кольорів — готується (помаранчевий на темно-помаранчевому), доставлено (зелений на темно-зеленому), скасовано (червоний на темно-червоному) — на основі карти стилів з токенами.


### Авторизація по телефону та OTP Триетапний процес авторизації: онбординг → ввід телефону → 4-значний OTP. Окремі поля вводу з автоматичним переходом при введенні та відкатом при натисканні Backspace — відтворює нативну поведінку введення SMS коду. Динамічні стани кнопок відображають коректність введення. ### Каталог з пошуком Живий пошук фільтрує 10 піц за назвою або інгредієнтом в реальному часі. Плаваючі картки продуктів з накладеними зображеннями та кнопкою "+" для додавання в кошик. Порожній стан з дружнім повідомленням. ### Кастомізація розмірів Селектор розмірів S/M/L з вагою та ціною за кожен варіант. Середній розмір обраний за замовчуванням. Лічильник кількості з захистом від мінус 1. Загальна ціна розраховується з обраного варіанту × кількість. ### Незмінний кошик Кошик на основі контексту з патерном оновлення `map` + `filter`. Переповнення кількості автоматично видаляє товари при нулі. Лічильник бейджа синхронізується між екранами через кастомну панель вкладок. Похідні `totalCount` та `totalPrice` — без застарілого стану. ### Бейджі статусу замовлення Семистановий перелік (Завершено, Доставлено, В очікуванні, Готується, В дорозі, Прибуло, Скасовано) з картою кольорів на основі токенів. Кожен бейдж рендериться з семантичною міткою, кольором тексту та фоном — без умовних стилів. ### Кастомна панель вкладок Повна реалізація `BottomTabBarProps` з емодзі іконками, індикатором активної крапки та абсолютно позиціонованим бейджем кошика. Плаваючий дизайн з 30px радіусом меж та 80px висотою.
```ts import React, { useRef, useState } from 'react'; import { View, TextInput, Text, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity, } from 'react-native'; import { useRoute, RouteProp } from '@react-navigation/native'; import AppButton from '../components/AppButton'; import { useAuth } from '../context/AuthContext'; import { AuthStackParamList } from '../navigation/AuthNavigator'; type Route = RouteProp<AuthStackParamList, 'OtpVerification'>; const OTP_LENGTH = 4; const OtpVerificationScreen = () => { const [otp, setOtp] = useState<string[]>(Array(OTP_LENGTH).fill('')); const inputs = useRef<(TextInput | null)[]>([]); const { login } = useAuth(); const route = useRoute<Route>(); const phone = route.params?.phone ?? '+1 555 123 4567'; const handleChange = (text: string, index: number) => { if (text.length > 1) return; const newOtp = [...otp]; newOtp[index] = text; setOtp(newOtp); if (text && index < OTP_LENGTH - 1) { inputs.current[index + 1]?.focus(); } }; const handleKeyPress = (e: any, index: number) => { if (e.nativeEvent.key === 'Backspace' && !otp[index] && index > 0) { inputs.current[index - 1]?.focus(); } }; const isComplete = otp.every(d => d !== ''); return ( <KeyboardAvoidingView style={{ flex: 1, backgroundColor: '#0F0F0F' }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > <ScrollView contentContainerStyle={{ flexGrow: 1, alignItems: 'center', paddingHorizontal: 20, paddingVertical: 40, }} keyboardShouldPersistTaps="handled" > <Text style={{ color: 'white', fontSize: 40, fontWeight: 'bold', marginTop: 20 }}> SMS Code </Text> <Text style={{ color: '#9EA1AB', fontSize: 18, marginTop: 10, textAlign: 'center' }}> We sent a 4-digit code to {'\n'} <Text style={{ color: 'white' }}>{phone}</Text> </Text> <View style={{ flexDirection: 'row', gap: 12, marginTop: 40, marginBottom: 20 }}> {otp.map((digit, index) => ( <TextInput key={index} ref={ref => (inputs.current[index] = ref)} style={{ width: 75, height: 75, backgroundColor: '#121212', borderRadius: 15, borderWidth: 2, borderColor: digit ? '#FF6B00' : '#262626', fontSize: 26, fontWeight: 'bold', color: 'white', textAlign: 'center', }} value={digit} onChangeText={text => handleChange(text, index)} onKeyPress={e => handleKeyPress(e, index)} keyboardType="numeric" maxLength={1} selectionColor="#FF6B00" caretHidden /> ))} </View> <View style={{ flex: 1, minHeight: 40 }} /> <AppButton onPress={login} title="Verify" containerColor={isComplete ? '#FF6B00' : '#3A3A3A'} contentColor="white" /> </ScrollView> </KeyboardAvoidingView> ); }; ``` Екран верифікації OTP використовує масив `useRef` для імперативного керування фокусом між чотирма окремими полями `TextInput`. `handleChange` переміщує фокус при коректному введенні, а `handleKeyPress` відкатує при Backspace — відтворює нативний UX введення SMS коду. Кнопка Verify динамічно відображає стан завершення з помаранчевим (активна) або сірим (неактивна) стилем.









Хочете побачити більше?
Перегляньте повний каталог проєктів або завантажте код.