Alle Projekte

PizzaV2

Plattformübergreifende Pizza-Bestellung

Moderner Pizza-Bestellungs-App mit Telefonauthentifizierung, OTP-Verifizierung, anpassbaren Größen (S/M/L), Echtzeit-Warenkorbverwaltung und Bestellverfolgung mit Status-Badges. Erstellt mit React Native 0.76 und TypeScript.

iOS · Android
React NativeTypeScriptReact Navigation 7
Das Problem

Die Erstellung einer modernen Food-Bestellungs-App bedeutet die gleichzeitige Bewältigung von Telefonauthentifizierungsabläufen, komplexer Produktanpassung und Echtzeit-Warenkorbzustand — und das bei einem polierten nativen Erlebnis. Die meisten Lösungen wirken klobig, haben kein ordentliches OTP-UX und bewältigen die Nuancen der mobilen Bestellung wie Größenvarianten, Mengenverwaltung und Bestellungs-Lebenszyklus-Verfolgung nicht.

Die Lösung

Eine dunkel getheme Pizza-Bestellungs-App mit nahtloser Telefonauthentifizierung, OTP-Verifizierung mit automatisch weiterschaltenden Eingabefeldern, einem durchsuchbaren Katalog von 10 Pizzen mit S/M/L-Größenoptionen, Echtzeit-Warenkorbverwaltung und vollständiger Bestellhistorie mit Status-Badges. Erstellt aus einer einzigen React Native CLI-Codebasis für Android mit Hermes-Engine.

Architektur

React Navigation 7 steuert auth-geschützte Weiterleitung mit drei Navigatoren: `AuthNavigator` (Onboarding → Telefoneingabe → OTP), `TabNavigator` (Startseite, Verlauf, Warenkorb, Profil) und verschachtelter Stack für den Pizza-Detailfluss. Zustand wird über zwei React Context-Anbieter mit Guard-Clause-Hooks verwaltet. Eine vollständig benutzerdefinierte `BottomNavigation`-Komponente ersetzt die Standard-Tab-Leiste mit Emoji-Icons, aktivem Punkt-Indikator und einem mit Kontext synchronisierten Warenkorb-Badge.

Design-System

Ein kohärentes dunkles Design-System mit orangefarbenem (#FF6B03) Akzent, 20px Border-Radius auf Karten, pillenförmigen Buttons und konsistenten Abstandstokens. Schwebende Produktbilder überlappen Karten mit negativen Margen. Sieben Bestellstatus-Badges haben jeweils einzigartige Farbpaare — Kochen (Orange auf Dunkelorange), Geliefert (Grün auf Dunkelgrün), Storniert (Rot auf Dunkelrot) — basierend auf einer tokenbasierten Stilkarte.

Hauptfunktionen

### Telefonauth & OTP Dreistufiger Auth-Ablauf: Onboarding → telefoneingabe → 4-stelliger OTP. Einzelne Eingabefelder mit automatischem Weiterschalten bei Eingabe und Rückwärts-Rewind bei Backspace — repliziert natives SMS-Code-Eingabe-UX. Dynamische Button-Zustände spiegeln die Eingabevollständigkeit wider. ### Durchsuchbarer Katalog Live-Suche filtert 10 Pizzen nach Name oder Zutat in Echtzeit. Schwebende Produktkarten mit überlappenden Bildern und einem „+"-Hinzufügen-Button. Leerer Zustand mit freundlichem Fallback. ### Größenanpassung S/M/L-Größenauswahler mit Gewicht und Preis pro Option. Mittlere Größe standardmäßig ausgewählt. Mengenzähler mit min-1-Schutz. Gesamtpreis abgeleitet von ausgewählter Option × Menge. ### Unveränderlicher Warenkorb Kontextgesteuerter Warenkorb mit `map` + `filter` unveränderlichem Aktualisierungsmuster. Mengenüberlauf entfernt Produkte automatisch bei null. Badge-Zähler synchronisiert über Bildschirme via benutzerdefinierte Tab-Leiste. Abgeleitete `totalCount` und `totalPrice` — kein veralteter Zustand. ### Bestellstatus-Badges Sieben-Stati-Enum (Abgeschlossen, Geliefert, Ausstehend, Kochend, Unterwegs, Angekommen, Storniert) mit tokenbasierter Farbkarte. Jedes Badge rendert mit semantischer Bezeichnung, Textfarbe und Hintergrund — keine bedingte Gestaltung. ### Benutzerdefinierte Tab-Leiste Vollständige Neuimplementierung von `BottomTabBarProps` mit Emoji-Icons, aktivem Punkt-Indikator und absolut positioniertem Warenkorb-Badge. Schwebendes Design mit 30px Border-Radius und 80px Höhe.

Galerie

Unter der Haube

Code
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>
  );
};

Möchten Sie mehr sehen?

Durchstöbern Sie den vollständigen Projektkatalog oder laden Sie den Quellcode herunter.