first commit

This commit is contained in:
AyrisAI
2026-08-20 01:51:59 +03:00
commit 97b83c7fd4
109 changed files with 21215 additions and 0 deletions
@@ -0,0 +1,506 @@
import { useState, useEffect } from "react";
import { router } from "expo-router";
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
Text,
TextInput,
View,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { api } from "@/lib/api";
import { getActiveRestaurant } from "@/lib/active-restaurant";
import { aiStore } from "@/lib/ai-store";
import type { AiExtractedCategory, AiExtractedItem } from "@menulio/shared";
export default function OnboardingAiReviewStep() {
const [importData, setImportData] = useState(aiStore.getImportData());
const [categories, setCategories] = useState<AiExtractedCategory[]>(importData?.categories ?? []);
const [saving, setSaving] = useState(false);
const [newCatName, setNewCatName] = useState("");
const [showAddCat, setShowAddCat] = useState(false);
useEffect(() => {
const data = aiStore.getImportData();
if (!data) {
router.replace("/(onboarding)/scan");
return;
}
setImportData(data);
setCategories(data.categories);
}, []);
const totalItems = categories.reduce((acc, c) => acc + c.items.length, 0);
const lowConfidenceItems = categories.reduce(
(acc, c) => acc + c.items.filter((i) => i.confidence < 0.85).length,
0,
);
function updateItem(categoryId: string, itemId: string, field: "name" | "price" | "description", value: string) {
setCategories((prev) =>
prev.map((cat) => {
if (cat.id !== categoryId) return cat;
return {
...cat,
items: cat.items.map((itm) => {
if (itm.id !== itemId) return itm;
if (field === "price") {
const parsed = Number(value.replace(",", "."));
return { ...itm, price: Number.isNaN(parsed) ? 0 : parsed, confidence: 1 };
}
return { ...itm, [field]: value };
}),
};
}),
);
}
function deleteItem(categoryId: string, itemId: string) {
setCategories((prev) =>
prev.map((cat) => {
if (cat.id !== categoryId) return cat;
return {
...cat,
items: cat.items.filter((i) => i.id !== itemId),
};
}),
);
}
function addItem(categoryId: string) {
const newItem: AiExtractedItem = {
id: `custom-item-${Date.now()}`,
name: "Yeni Ürün",
price: 100,
description: "",
confidence: 1,
};
setCategories((prev) =>
prev.map((cat) => {
if (cat.id !== categoryId) return cat;
return { ...cat, items: [...cat.items, newItem] };
}),
);
}
function deleteCategory(categoryId: string) {
setCategories((prev) => prev.filter((c) => c.id !== categoryId));
}
function addCategory() {
if (!newCatName.trim()) return;
const newCat: AiExtractedCategory = {
id: `custom-cat-${Date.now()}`,
name: newCatName.trim(),
items: [],
};
setCategories((prev) => [...prev, newCat]);
setNewCatName("");
setShowAddCat(false);
}
async function onSaveAndContinue() {
if (categories.length === 0 || totalItems === 0) {
Alert.alert("Uyarı", "Lütfen en az bir kategori ve ürün ekleyin.");
return;
}
setSaving(true);
try {
const active = await getActiveRestaurant();
if (!active?.menuId) {
throw new Error("Menü bulunamadı.");
}
const payload = {
menuId: active.menuId,
categories: categories.map((cat) => ({
name: cat.name,
items: cat.items.map((itm) => ({
name: itm.name,
description: itm.description || null,
price: itm.price,
})),
})),
};
await api.post(`/ai-imports/${importData?.importId ?? "new"}/apply`, payload);
aiStore.clear();
Alert.alert("Başarılı 🎉", "Menünüz başarıyla aktarıldı!", [
{ text: "Menüye Git", onPress: () => router.replace("/menu") },
]);
} catch (err) {
Alert.alert("Hata", err instanceof Error ? err.message : "Menü kaydedilemedi.");
} finally {
setSaving(false);
}
}
return (
<View style={{ flex: 1, backgroundColor: "#FAF8F5" }}>
<ScrollView contentContainerStyle={{ padding: 20, paddingBottom: 110 }}>
{/* Header Badge */}
<View
style={{
alignSelf: "flex-start",
backgroundColor: "#F3EDE2",
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 99,
marginBottom: 12,
}}
>
<Text style={{ color: "#926E27", fontSize: 12, fontWeight: "700" }}>
ADIM 3 / 4 AI REVIEW & ONAY
</Text>
</View>
<Text style={{ fontSize: 26, fontWeight: "800", color: "#1C1917", marginBottom: 6 }}>
Çıkarılan Menüyü İnceleyin
</Text>
<Text style={{ fontSize: 13, color: "#78716C", marginBottom: 20 }}>
AI sonuçlarını kontrol edin, gerekiyorsa fiyat veya isimleri düzenleyin.
</Text>
{/* Summary Banner */}
<View
style={{
backgroundColor: "#FFFFFF",
borderRadius: 16,
padding: 16,
marginBottom: 24,
borderWidth: 1,
borderColor: "rgba(200, 169, 107, 0.25)",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.04,
shadowRadius: 8,
}}
>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<View>
<Text style={{ fontSize: 18, fontWeight: "700", color: "#1C1917" }}>
{totalItems} Ürün {categories.length} Kategori
</Text>
<Text style={{ fontSize: 12, color: "#78716C", marginTop: 2 }}>
Model: {importData?.model ?? "Menulio Vision"}
</Text>
</View>
<View
style={{
backgroundColor: "#ECFDF5",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 8,
flexDirection: "row",
alignItems: "center",
gap: 4,
}}
>
<Ionicons name="checkmark-circle" size={14} color="#059669" />
<Text style={{ color: "#059669", fontSize: 12, fontWeight: "700" }}>Hazır</Text>
</View>
</View>
{lowConfidenceItems > 0 ? (
<View
style={{
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: "#F5F5F4",
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
<Ionicons name="warning-outline" size={16} color="#D97706" />
<Text style={{ color: "#D97706", fontSize: 12, fontWeight: "600", flex: 1 }}>
{lowConfidenceItems} ürünün fiyatı veya ismi silik çıkmış olabilir, lütfen sarı etiketli ürünleri kontrol edin.
</Text>
</View>
) : null}
</View>
{/* Categories & Items List */}
{categories.map((category) => (
<View
key={category.id}
style={{
backgroundColor: "#FFFFFF",
borderRadius: 16,
padding: 16,
marginBottom: 20,
borderWidth: 1,
borderColor: "#E7E5E4",
}}
>
{/* Category Header */}
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 12,
paddingBottom: 10,
borderBottomWidth: 1.5,
borderBottomColor: "#C8A96B40",
}}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Ionicons name="restaurant-outline" size={18} color="#C8A96B" />
<Text style={{ fontSize: 18, fontWeight: "700", color: "#C8A96B" }}>
{category.name} ({category.items.length})
</Text>
</View>
<Pressable
onPress={() => deleteCategory(category.id)}
style={{ flexDirection: "row", alignItems: "center", gap: 4 }}
>
<Ionicons name="trash-outline" size={14} color="#EF4444" />
<Text style={{ color: "#EF4444", fontSize: 12, fontWeight: "600" }}>Kategoriyi Sil</Text>
</Pressable>
</View>
{/* Items in Category */}
<View style={{ gap: 12 }}>
{category.items.map((item) => {
const isLowConfidence = item.confidence < 0.85;
return (
<View
key={item.id}
style={{
backgroundColor: isLowConfidence ? "#FFFBEB" : "#FAFAFA",
borderWidth: 1,
borderColor: isLowConfidence ? "#FDE68A" : "#F4F4F5",
borderRadius: 12,
padding: 12,
}}
>
{isLowConfidence ? (
<View
style={{
alignSelf: "flex-start",
backgroundColor: "#FEF3C7",
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 6,
marginBottom: 6,
flexDirection: "row",
alignItems: "center",
gap: 4,
}}
>
<Ionicons name="warning-outline" size={12} color="#B45309" />
<Text style={{ color: "#B45309", fontSize: 11, fontWeight: "700" }}>
Fiyatı / İsmi Kontrol Edin (%{Math.round(item.confidence * 100)} Güven)
</Text>
</View>
) : null}
<View style={{ flexDirection: "row", gap: 8, alignItems: "center", marginBottom: 6 }}>
<TextInput
value={item.name}
onChangeText={(v) => updateItem(category.id, item.id, "name", v)}
placeholder="Ürün Adı"
style={{
flex: 1,
fontSize: 15,
fontWeight: "700",
color: "#1C1917",
paddingVertical: 4,
}}
/>
<View style={{ flexDirection: "row", alignItems: "center", gap: 4 }}>
<TextInput
value={String(item.price)}
onChangeText={(v) => updateItem(category.id, item.id, "price", v)}
keyboardType="numeric"
placeholder="Fiyat"
style={{
fontSize: 15,
fontWeight: "800",
color: "#C8A96B",
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 8,
paddingHorizontal: 8,
paddingVertical: 4,
minWidth: 64,
textAlign: "right",
}}
/>
<Text style={{ fontSize: 14, fontWeight: "700", color: "#1C1917" }}></Text>
</View>
<Pressable
onPress={() => deleteItem(category.id, item.id)}
style={{ padding: 4 }}
>
<Ionicons name="close-circle-outline" size={18} color="#9CA3AF" />
</Pressable>
</View>
<TextInput
value={item.description ?? ""}
onChangeText={(v) => updateItem(category.id, item.id, "description", v)}
placeholder="Açıklama (opsiyonel)"
style={{
fontSize: 12,
color: "#78716C",
paddingVertical: 2,
}}
/>
</View>
);
})}
<Pressable
onPress={() => addItem(category.id)}
style={{
paddingVertical: 10,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 6,
borderRadius: 8,
borderWidth: 1,
borderColor: "#E7E5E4",
borderStyle: "dashed",
}}
>
<Ionicons name="add" size={16} color="#78716C" />
<Text style={{ color: "#78716C", fontSize: 13, fontWeight: "600" }}>
Bu Kategoriye Ürün Ekle
</Text>
</Pressable>
</View>
</View>
))}
{/* Add New Category */}
{showAddCat ? (
<View
style={{
backgroundColor: "#FFFFFF",
borderRadius: 16,
padding: 16,
marginBottom: 20,
borderWidth: 1,
borderColor: "#C8A96B",
}}
>
<Text style={{ fontSize: 15, fontWeight: "700", color: "#1C1917", marginBottom: 8 }}>
Yeni Kategori Ekle
</Text>
<TextInput
value={newCatName}
onChangeText={setNewCatName}
placeholder="Örn: Başlangıçlar"
style={{
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 8,
padding: 10,
fontSize: 14,
marginBottom: 10,
}}
/>
<View style={{ flexDirection: "row", gap: 8, justifyContent: "flex-end" }}>
<Pressable
onPress={() => setShowAddCat(false)}
style={{ paddingVertical: 8, paddingHorizontal: 14, borderRadius: 8 }}
>
<Text style={{ color: "#78716C", fontWeight: "600" }}>Vazgeç</Text>
</Pressable>
<Pressable
onPress={addCategory}
style={{
backgroundColor: "#1C1917",
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 8,
}}
>
<Text style={{ color: "#FFFFFF", fontWeight: "700" }}>Ekle</Text>
</Pressable>
</View>
</View>
) : (
<Pressable
onPress={() => setShowAddCat(true)}
style={{
paddingVertical: 14,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
borderRadius: 12,
backgroundColor: "#FFFFFF",
borderWidth: 1.5,
borderColor: "#E7E5E4",
marginBottom: 20,
}}
>
<Ionicons name="add-circle-outline" size={18} color="#1C1917" />
<Text style={{ color: "#1C1917", fontSize: 14, fontWeight: "700" }}>
Yeni Kategori Ekle
</Text>
</Pressable>
)}
</ScrollView>
{/* Fixed Bottom Action Bar */}
<View
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
backgroundColor: "#FFFFFF",
borderTopWidth: 1,
borderTopColor: "#E7E5E4",
padding: 16,
paddingBottom: 28,
}}
>
<Pressable
onPress={onSaveAndContinue}
disabled={saving}
style={({ pressed }) => ({
backgroundColor: "#1C1917",
borderRadius: 14,
paddingVertical: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: pressed || saving ? 0.85 : 1,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 12,
elevation: 4,
})}
>
{saving ? (
<ActivityIndicator color="#FFFFFF" />
) : (
<>
<Ionicons name="checkmark-circle-outline" size={20} color="#FFFFFF" />
<Text style={{ color: "#FFFFFF", fontSize: 16, fontWeight: "700" }}>
Menüyü Onayla ve Kaydet ({totalItems} Ürün)
</Text>
</>
)}
</Pressable>
</View>
</View>
);
}
@@ -0,0 +1,193 @@
import { useState } from "react";
import { router } from "expo-router";
import { ActivityIndicator, Pressable, ScrollView, Text, TextInput, View } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { api } from "@/lib/api";
import { setActiveRestaurant } from "@/lib/active-restaurant";
interface CreateRestaurantResponse {
restaurant: { id: string; slug: string };
location: { id: string };
menu: { id: string };
}
export default function OnboardingRestaurantStep() {
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function onSubmit() {
setError(null);
setLoading(true);
try {
const { restaurant, location, menu } = await api.post<CreateRestaurantResponse>("/restaurants", {
name,
phone: phone || undefined,
address: address || undefined,
});
await setActiveRestaurant({
restaurantId: restaurant.id,
locationId: location.id,
menuId: menu.id,
slug: restaurant.slug,
});
router.replace("/(onboarding)/scan");
} catch (err) {
setError(err instanceof Error ? err.message : "Bir hata oluştu");
} finally {
setLoading(false);
}
}
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: "#FAF8F5", padding: 24, justifyContent: "center" }}>
<View style={{ maxWidth: 440, width: "100%", alignSelf: "center" }}>
{/* Step Badge */}
<View
style={{
alignSelf: "flex-start",
backgroundColor: "#F3EDE2",
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 99,
marginBottom: 16,
}}
>
<Text style={{ color: "#926E27", fontSize: 12, fontWeight: "700" }}>
ADIM 1 / 4 RESTORAN BİLGİLERİ
</Text>
</View>
<Text style={{ fontSize: 26, fontWeight: "800", color: "#1C1917", marginBottom: 6 }}>
Restoran Bilgilerini Ekle
</Text>
<Text style={{ fontSize: 14, color: "#78716C", marginBottom: 28, lineHeight: 20 }}>
Sadece isim zorunludur. Telefon ve adres bilgilerinizi daha sonra da düzenleyebilirsiniz.
</Text>
{error ? (
<View
style={{
backgroundColor: "#FEF2F2",
borderWidth: 1,
borderColor: "#FCA5A5",
borderRadius: 12,
padding: 12,
marginBottom: 16,
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
<Ionicons name="alert-circle" size={18} color="#DC2626" />
<Text style={{ color: "#DC2626", fontSize: 13, flex: 1 }}>{error}</Text>
</View>
) : null}
{/* Restaurant Name */}
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
paddingHorizontal: 14,
marginBottom: 12,
}}
>
<Ionicons name="storefront-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} />
<TextInput
placeholder="Restoran adı (Örn: Kebapçı Ahmet)"
placeholderTextColor="#A8A29E"
value={name}
onChangeText={setName}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }}
/>
</View>
{/* Phone */}
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
paddingHorizontal: 14,
marginBottom: 12,
}}
>
<Ionicons name="call-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} />
<TextInput
placeholder="Telefon numarası (opsiyonel)"
placeholderTextColor="#A8A29E"
keyboardType="phone-pad"
value={phone}
onChangeText={setPhone}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }}
/>
</View>
{/* Address */}
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#E7E5E4",
borderRadius: 12,
paddingHorizontal: 14,
marginBottom: 24,
}}
>
<Ionicons name="location-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} />
<TextInput
placeholder="Adres / Şehir (opsiyonel)"
placeholderTextColor="#A8A29E"
value={address}
onChangeText={setAddress}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }}
/>
</View>
{/* Submit */}
<Pressable
onPress={onSubmit}
disabled={loading || !name.trim()}
style={({ pressed }) => ({
backgroundColor: "#1C1917",
borderRadius: 12,
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: loading || !name.trim() ? 0.6 : pressed ? 0.9 : 1,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 3,
})}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<>
<Text style={{ color: "#fff", fontWeight: "700", fontSize: 15 }}>Devam Et</Text>
<Ionicons name="arrow-forward" size={16} color="#fff" />
</>
)}
</Pressable>
</View>
</ScrollView>
);
}
+302
View File
@@ -0,0 +1,302 @@
import { useState, useEffect } from "react";
import { router } from "expo-router";
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
Text,
View,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import * as ImagePicker from "expo-image-picker";
import { api } from "@/lib/api";
import { getActiveRestaurant } from "@/lib/active-restaurant";
import { aiStore } from "@/lib/ai-store";
import type { AiImportResponse } from "@menulio/shared";
export default function OnboardingScanStep() {
const [analyzing, setAnalyzing] = useState(false);
const [currentStage, setCurrentStage] = useState(0);
const [error, setError] = useState<string | null>(null);
const stages = [
{ title: "Fotoğraf okunuyor...", icon: "document-text-outline" as const },
{ title: "Kategoriler ve başlıklar bulunuyor...", icon: "list-outline" as const },
{ title: "Yemekler ve açıklamalar çıkarılıyor...", icon: "restaurant-outline" as const },
{ title: "Fiyatlar ve para birimi algılanıyor...", icon: "pricetag-outline" as const },
{ title: "Güven skorları hesaplanıyor...", icon: "shield-checkmark-outline" as const },
];
useEffect(() => {
let interval: NodeJS.Timeout;
if (analyzing) {
interval = setInterval(() => {
setCurrentStage((prev) => (prev < stages.length - 1 ? prev + 1 : prev));
}, 1200);
}
return () => clearInterval(interval);
}, [analyzing, stages.length]);
async function processImage(base64: string) {
setError(null);
setAnalyzing(true);
setCurrentStage(0);
try {
const active = await getActiveRestaurant();
if (!active?.menuId) {
throw new Error("Aktif restoran veya menü bulunamadı.");
}
const formattedImage = base64.startsWith("data:")
? base64
: `data:image/jpeg;base64,${base64}`;
const response = await api.post<AiImportResponse>(`/menus/${active.menuId}/import-ai`, {
image: formattedImage,
});
aiStore.setImportData(response);
router.push("/(onboarding)/ai-review");
} catch (err) {
setError(err instanceof Error ? err.message : "Fotoğraf analiz edilirken bir hata oluştu.");
} finally {
setAnalyzing(false);
}
}
async function takePhoto() {
try {
const permission = await ImagePicker.requestCameraPermissionsAsync();
if (!permission.granted) {
Alert.alert("İzin Gerekli", "Menü fotoğrafı çekebilmek için kamera erişimine izin vermelisiniz.");
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ["images"],
quality: 0.8,
base64: true,
});
if (!result.canceled && result.assets[0]?.base64) {
await processImage(result.assets[0].base64);
}
} catch (err) {
setError(err instanceof Error ? err.message : "Kamera açılamadı.");
}
}
async function pickFromGallery() {
try {
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permission.granted) {
Alert.alert("İzin Gerekli", "Menü görseli seçebilmek için galeri erişimine izin vermelisiniz.");
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"],
quality: 0.8,
base64: true,
});
if (!result.canceled && result.assets[0]?.base64) {
await processImage(result.assets[0].base64);
}
} catch (err) {
setError(err instanceof Error ? err.message : "Galeri açılamadı.");
}
}
if (analyzing) {
return (
<View style={{ flex: 1, backgroundColor: "#FAF8F5", justifyContent: "center", alignItems: "center", padding: 24 }}>
<View
style={{
backgroundColor: "#FFFFFF",
borderRadius: 24,
padding: 32,
alignItems: "center",
width: "100%",
maxWidth: 360,
shadowColor: "#000",
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.08,
shadowRadius: 24,
elevation: 8,
}}
>
<View
style={{
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: "#FDF4E6",
alignItems: "center",
justifyContent: "center",
marginBottom: 20,
}}
>
<Ionicons name="sparkles" size={32} color="#C8A96B" />
</View>
<Text style={{ fontSize: 20, fontWeight: "700", color: "#1C1917", marginBottom: 6, textAlign: "center" }}>
Menünüz Analiz Ediliyor
</Text>
<Text style={{ fontSize: 13, color: "#78716C", textAlign: "center", marginBottom: 24 }}>
Menulio AI menünüzü dijitalleştiriyor...
</Text>
<View style={{ width: "100%", gap: 14 }}>
{stages.map((stage, idx) => {
const isDone = idx < currentStage;
const isCurrent = idx === currentStage;
return (
<View key={idx} style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
<View
style={{
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: isDone ? "#ECFDF5" : isCurrent ? "#FDF4E6" : "#F5F5F4",
alignItems: "center",
justifyContent: "center",
}}
>
{isDone ? (
<Ionicons name="checkmark-circle" size={18} color="#10B981" />
) : isCurrent ? (
<ActivityIndicator size="small" color="#C8A96B" />
) : (
<Ionicons name={stage.icon} size={15} color="#A8A29E" />
)}
</View>
<Text
style={{
fontSize: 13,
fontWeight: isCurrent ? "700" : "500",
color: isDone ? "#10B981" : isCurrent ? "#C8A96B" : "#A8A29E",
flex: 1,
}}
>
{stage.title}
</Text>
</View>
);
})}
</View>
</View>
</View>
);
}
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: "#FAF8F5", padding: 24, justifyContent: "center" }}>
<View style={{ maxWidth: 440, width: "100%", alignSelf: "center" }}>
{/* Step Badge */}
<View
style={{
alignSelf: "flex-start",
backgroundColor: "#F3EDE2",
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 99,
marginBottom: 16,
}}
>
<Text style={{ color: "#926E27", fontSize: 12, fontWeight: "700" }}>
ADIM 2 / 4 AI MENU SCANNER
</Text>
</View>
<Text style={{ fontSize: 28, fontWeight: "800", color: "#1C1917", marginBottom: 8, letterSpacing: -0.5 }}>
Menü Fotoğrafını Yükleyin
</Text>
<Text style={{ fontSize: 14, color: "#78716C", lineHeight: 22, marginBottom: 32 }}>
Basılı menünüzün fotoğrafını çekin veya galeriden seçin. Menulio AI saniyeler içinde tüm yemekleri, kategorileri ve fiyatları dijitalleştirsin.
</Text>
{error ? (
<View
style={{
backgroundColor: "#FEF2F2",
borderWidth: 1,
borderColor: "#FCA5A5",
borderRadius: 12,
padding: 12,
marginBottom: 20,
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
<Ionicons name="alert-circle" size={18} color="#DC2626" />
<Text style={{ color: "#DC2626", fontSize: 13, flex: 1 }}>{error}</Text>
</View>
) : null}
{/* Primary CTA: Take Photo */}
<Pressable
onPress={takePhoto}
style={({ pressed }) => ({
backgroundColor: "#1C1917",
borderRadius: 16,
padding: 18,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 12,
marginBottom: 12,
opacity: pressed ? 0.9 : 1,
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 4,
})}
>
<Ionicons name="camera-outline" size={22} color="#FFFFFF" />
<Text style={{ color: "#FFFFFF", fontSize: 16, fontWeight: "700" }}>
Menü Fotoğrafı Çek
</Text>
</Pressable>
{/* Secondary CTA: Pick from Gallery */}
<Pressable
onPress={pickFromGallery}
style={({ pressed }) => ({
backgroundColor: "#FFFFFF",
borderWidth: 1.5,
borderColor: "#E7E5E4",
borderRadius: 16,
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 10,
marginBottom: 24,
opacity: pressed ? 0.85 : 1,
})}
>
<Ionicons name="images-outline" size={20} color="#1C1917" />
<Text style={{ color: "#1C1917", fontSize: 15, fontWeight: "600" }}>
Galeriden Seç
</Text>
</Pressable>
{/* Tertiary: Skip to manual */}
<Pressable
onPress={() => router.replace("/menu")}
style={{ padding: 12, alignItems: "center", flexDirection: "row", justifyContent: "center", gap: 6 }}
>
<Ionicons name="create-outline" size={16} color="#78716C" />
<Text style={{ color: "#78716C", fontSize: 14, fontWeight: "600", textDecorationLine: "underline" }}>
Fotoğrafım yok, menüyü elle oluşturacağım
</Text>
</Pressable>
</View>
</ScrollView>
);
}