PizzaV2
Cross-platformowe zamawianie pizzyNowoczesna aplikacja zamawiania pizzy z autoryzacją telefoniczną, weryfikacją OTP, konfigurowalnymi rozmiarami (S/M/L), zarządzaniem koszykiem w czasie rzeczywistym i śledzeniem zamówień z badge'ami statusu. Zbudowana z React Native 0.76 i TypeScript.
Tworzenie nowoczesnej aplikacji do zamawiania jedzenia oznacza jednoczesne zarządzanie przepływami autoryzacji telefonicznej, złożoną personalizacją produktów i stanem koszyka w czasie rzeczywistym — wszystko to zapewniając dopracowane natywne wrażenia. Większość rozwiązań wydaje się nieporęczna, nie ma odpowiedniego UX dla OTP i nie radzi sobie z niuansami mobilnego zamawiania, takimi jak warianty rozmiarów, zarządzanie ilością i śledzenie cyklu życia zamówienia.


Aplikacja zamawiania pizzy w ciemnym motywie z płynną autoryzacją telefoniczną, weryfikacją OTP z automatycznym przejściem między polami, katalogiem 10 pizz z opcjami rozmiarów S/M/L, zarządzaniem koszykiem w czasie rzeczywistym i pełną historią zamówień z badge'ami statusu. Zbudowana z jednego kodu źródłowego React Native CLI dla Android z silnikiem Hermes.


React Navigation 7 kieruje chronionym routingiem z trzema nawigatorami: `AuthNavigator` (onboarding → logowanie telefoniczne → OTP), `TabNavigator` (strona główna, historia, koszyk, profil) i zagnieżdżony stos dla szczegółów pizzy. Stan jest zarządzany przez dwóch dostawców React Context z hookami strażniczymi. W pełni niestandardowy komponent `BottomNavigation` zastępuje domyślny pasek kart z ikonami emoji, wskaźnikiem aktywnej kropki i badge'em koszyka zsynchronizowanym z kontekstem.


Spójny ciemny system projektowania z pomarańczowym (#FF6B03) akcentem, 20px promieniem obręczy na kartkach, przyciskami w kształcie pigułek i spójnymi tokenami odstępów. Pływające obrazy produktów nakładają się na kartki z ujemnymi marginesami. Siedem badge'ów statusu zamówienia ma unikalne pary kolorów — gotowanie (pomarańczowy na ciemnopomarańczowym), dostarczono (zielony na ciemnozielonym), anulowano (czerwony na ciemnoczerwonym) — opartych na mapie stylów z tokenami.


### Autoryzacja telefoniczna i OTP Trzyetapowy przepływ autoryzacji: onboarding → wprowadzenie numeru → 4-cyfrowy OTP. Oddzielne pola wejściowe z automatycznym przejściem przy wprowadzeniu i cofnięciem przy Backspace — odtwarza natywne zachowanie wprowadzania kodu SMS. Dynamiczne stany przycisków odzwierciedlają kompletność wprowadzenia. ### Katalog z wyszukiwaniem Żywe wyszukiwanie filtruje 10 pizz po nazwie lub składniku w czasie rzeczywistym. Pływające kartki produktów z nakładającymi się obrazami i przyciskiem "+" do dodawania do koszyka. Pusty stan z przyjaznym komunikatem. ### Personalizacja rozmiarów Selektor rozmiarów S/M/L z wagą i ceną dla każdej opcji. Średni rozmiar domyślnie wybrany. Licznik ilości z ochroną przed ujemnymi wartościami. Całkowita cena obliczana z wybranej opcji × ilość. ### Niemodyfikowalny koszyk Koszyk oparty na kontekście ze wzorem aktualizacji `map` + `filter`. Przepełnienie ilości automatycznie usuwa produkty przy zerze. Badge licznika synchronizowany między ekranami przez niestandardowy pasek kart. Pochodne `totalCount` i `totalPrice` — bez przestarzałego stanu. ### Badge'e statusu zamówienia Siedmiostanowy wykaz (Zakończone, Dostarczone, Oczekiwanie, Gotowanie, W drodze, Przybyło, Anulowane) z mapą kolorów opartą na tokenach. Każdy badge renderuje się z semantyczną etykietą, kolorem tekstu i tłem — bez warunkowych stylów. ### Niestandardowy pasek kart Pełna implementacja `BottomTabBarProps` z ikonami emoji, wskaźnikiem aktywnej kropki i absolutnie pozycjonowanym badge'em koszyka. Pływający design z 30px promieniem obręczy i 80px wysokością.
```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> ); }; ``` Ekran weryfikacji OTP używa tablicy `useRef` do imperatywnego zarządzania fokusem między czterema oddzielnymi polami `TextInput`. `handleChange` przesuwa fokus przy prawidłowym wprowadzeniu, a `handleKeyPress` cofa przy Backspace — odtwarza natywne UX wprowadzania kodu SMS. Przycisk Verify dynamicznie odzwierciedla stan kompletności z pomarańczowym (aktywny) lub szarym (nieaktywnym) stylem.









Chcesz zobaczyć więcej?
Przegląd pełny katalog projektów lub pobierz kod źródłowy.