first commit
This commit is contained in:
@@ -0,0 +1,872 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Image,
|
||||
Linking,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { api } from "@/lib/api";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { getActiveRestaurant, clearActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant";
|
||||
import { getPublicMenuUrl, getPublicMenuDisplayUrl } from "@/lib/urls";
|
||||
|
||||
interface RestaurantDetail {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
logo_url: string | null;
|
||||
phone: string | null;
|
||||
address: string | null;
|
||||
}
|
||||
|
||||
interface DomainRow {
|
||||
id: string;
|
||||
hostname: string;
|
||||
is_custom: boolean;
|
||||
status: "pending" | "verified" | "failed";
|
||||
verified_at: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const THEME_OPTIONS = [
|
||||
{ key: "elegant", name: "Elegant Gold", color: "#C8A96B", desc: "Lüks altın & ipeksi krem" },
|
||||
{ key: "modern", name: "Modern Sapphire", color: "#2563EB", desc: "Kraliyet safiri & beyaz" },
|
||||
{ key: "dark", name: "Luxury Dark", color: "#F59E0B", desc: "Kehribar & gece siyahı" },
|
||||
{ key: "minimal", name: "Nordic Minimal", color: "#18181B", desc: "Sade grafit & kar beyazı" },
|
||||
{ key: "classic", name: "Classic Bistro", color: "#8B1E1E", desc: "Toskana bordo & rustik" },
|
||||
];
|
||||
|
||||
export default function AccountScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const [active, setActive] = useState<ActiveRestaurant | null>(null);
|
||||
const [userEmail, setUserEmail] = useState<string>("");
|
||||
const [userId, setUserId] = useState<string>("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Restaurant details state
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [address, setAddress] = useState("");
|
||||
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
||||
const [slug, setSlug] = useState("");
|
||||
const [savingRest, setSavingRest] = useState(false);
|
||||
|
||||
// Theme state
|
||||
const [selectedTheme, setSelectedTheme] = useState("elegant");
|
||||
const [savingTheme, setSavingTheme] = useState(false);
|
||||
|
||||
// Custom Domain state
|
||||
const [domains, setDomains] = useState<DomainRow[]>([]);
|
||||
const [newDomainInput, setNewDomainInput] = useState("");
|
||||
const [cnameTarget, setCnameTarget] = useState("cname.menul.io");
|
||||
const [addingDomain, setAddingDomain] = useState(false);
|
||||
const [verifyingDomainId, setVerifyingDomainId] = useState<string | null>(null);
|
||||
|
||||
const [copiedSlug, setCopiedSlug] = useState(false);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
const { data: authData } = await supabase.auth.getUser();
|
||||
if (authData.user) {
|
||||
setUserEmail(authData.user.email ?? "");
|
||||
setUserId(authData.user.id);
|
||||
}
|
||||
|
||||
const cached = await getActiveRestaurant();
|
||||
if (!cached) {
|
||||
router.replace("/(onboarding)/restaurant");
|
||||
return;
|
||||
}
|
||||
setActive(cached);
|
||||
|
||||
// Fetch restaurant details
|
||||
const rest = await api.get<RestaurantDetail>(`/restaurants/${cached.restaurantId}`);
|
||||
setName(rest.name);
|
||||
setPhone(rest.phone ?? "");
|
||||
setAddress(rest.address ?? "");
|
||||
setLogoUrl(rest.logo_url ?? null);
|
||||
setSlug(rest.slug);
|
||||
|
||||
// Fetch theme
|
||||
const themeRes = await api.get<{ themeKey: string }>(`/restaurants/${cached.restaurantId}/theme`).catch(() => ({ themeKey: "elegant" }));
|
||||
setSelectedTheme(themeRes.themeKey || "elegant");
|
||||
|
||||
// Fetch custom domains
|
||||
const domainRes = await api.get<{ domains: DomainRow[]; cnameTarget: string }>(`/restaurants/${cached.restaurantId}/domains`).catch(() => null);
|
||||
if (domainRes?.domains) {
|
||||
setDomains(domainRes.domains);
|
||||
if (domainRes.cnameTarget) setCnameTarget(domainRes.cnameTarget);
|
||||
}
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Hesap bilgileri yüklenemedi.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadData();
|
||||
}, [loadData]),
|
||||
);
|
||||
|
||||
async function handlePickLogo() {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
|
||||
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (!permission.granted) {
|
||||
Alert.alert("İzin Gerekli", "Restoran logosu seçebilmek için fotoğraf galerisi izni gereklidir.");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ["images"],
|
||||
allowsEditing: true,
|
||||
aspect: [1, 1],
|
||||
quality: 0.6,
|
||||
base64: true,
|
||||
});
|
||||
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
const asset = result.assets[0];
|
||||
const newLogo = asset.base64 ? `data:image/jpeg;base64,${asset.base64}` : asset.uri;
|
||||
setLogoUrl(newLogo);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveRestaurant() {
|
||||
if (!active || !name.trim()) return;
|
||||
setSavingRest(true);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
|
||||
try {
|
||||
await api.patch(`/restaurants/${active.restaurantId}`, {
|
||||
name: name.trim(),
|
||||
phone: phone.trim() || null,
|
||||
address: address.trim() || null,
|
||||
logoUrl: logoUrl || null,
|
||||
});
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
Alert.alert("Başarılı 🎉", "Restoran bilgileri ve logo güncellendi.");
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Güncellenemedi.");
|
||||
} finally {
|
||||
setSavingRest(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelectTheme(themeKey: string) {
|
||||
if (!active) return;
|
||||
setSelectedTheme(themeKey);
|
||||
setSavingTheme(true);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
|
||||
try {
|
||||
await api.put(`/restaurants/${active.restaurantId}/theme`, { themeKey });
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Tema güncellenemedi.");
|
||||
} finally {
|
||||
setSavingTheme(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddCustomDomain() {
|
||||
if (!active || !newDomainInput.trim()) {
|
||||
Alert.alert("Eksik Bilgi", "Lütfen bağlamak istediğiniz alan adını girin (Örn: menu.kebapciahmet.com)");
|
||||
return;
|
||||
}
|
||||
|
||||
setAddingDomain(true);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
|
||||
try {
|
||||
const res = await api.post<{ domain: DomainRow }>(`/restaurants/${active.restaurantId}/domains`, {
|
||||
hostname: newDomainInput.trim(),
|
||||
});
|
||||
setDomains((prev) => [res.domain, ...prev]);
|
||||
setNewDomainInput("");
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
Alert.alert(
|
||||
"Alan Adı Eklendi 🎉",
|
||||
`Lütfen DNS sağlayıcınızda (GoDaddy, Cloudflare vb.) ${res.domain.hostname} için CNAME kaydını ${cnameTarget} adresine yönlendirin.`,
|
||||
);
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Alan adı eklenemedi.");
|
||||
} finally {
|
||||
setAddingDomain(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVerifyDomain(domainId: string) {
|
||||
if (!active) return;
|
||||
setVerifyingDomainId(domainId);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
|
||||
try {
|
||||
const res = await api.post<{ verified: boolean; domain: DomainRow; message: string }>(
|
||||
`/restaurants/${active.restaurantId}/domains/${domainId}/verify`,
|
||||
);
|
||||
if (res.verified) {
|
||||
setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d)));
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
Alert.alert("Tebrikler! 🎉", res.message);
|
||||
} else {
|
||||
setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d)));
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {});
|
||||
Alert.alert("Doğrulanamadı", res.message);
|
||||
}
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Doğrulama başarısız.");
|
||||
} finally {
|
||||
setVerifyingDomainId(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteDomain(domainId: string, hostname: string) {
|
||||
if (!active) return;
|
||||
Alert.alert(
|
||||
"Alan Adını Kaldır",
|
||||
`"${hostname}" alan adı restoranınızdan kaldırılacak. Emin misiniz?`,
|
||||
[
|
||||
{ text: "Vazgeç", style: "cancel" },
|
||||
{
|
||||
text: "Kaldır",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
try {
|
||||
await api.delete(`/restaurants/${active.restaurantId}/domains/${domainId}`);
|
||||
setDomains((prev) => prev.filter((d) => d.id !== domainId));
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Kaldırılamadı.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
async function handleCopyUrl() {
|
||||
const url = getPublicMenuUrl(slug);
|
||||
await Clipboard.setStringAsync(url);
|
||||
setCopiedSlug(true);
|
||||
setTimeout(() => setCopiedSlug(false), 2000);
|
||||
}
|
||||
|
||||
function handleOpenMenu() {
|
||||
const webUrl = getPublicMenuUrl(slug);
|
||||
Linking.openURL(webUrl).catch(() => {
|
||||
Alert.alert("Bilgi", `Menü adresi: ${webUrl}`);
|
||||
});
|
||||
}
|
||||
|
||||
function handleSignOut() {
|
||||
Alert.alert(
|
||||
"Çıkış Yap",
|
||||
"Hesabınızdan çıkış yapmak istediğinize emin misiniz?",
|
||||
[
|
||||
{ text: "Vazgeç", style: "cancel" },
|
||||
{
|
||||
text: "Çıkış Yap",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
await clearActiveRestaurant();
|
||||
await supabase.auth.signOut();
|
||||
router.replace("/(auth)/login");
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: "#FAF8F5", alignItems: "center", justifyContent: "center" }}>
|
||||
<ActivityIndicator size="large" color="#C8A96B" />
|
||||
<Text style={{ marginTop: 12, color: "#78716C", fontSize: 14, fontWeight: "600" }}>
|
||||
Hesap bilgileri yükleniyor...
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: "#FAF8F5" }}>
|
||||
{/* Top App Header */}
|
||||
<View
|
||||
style={{
|
||||
paddingTop: Math.max(insets.top, 16),
|
||||
paddingHorizontal: 20,
|
||||
paddingBottom: 14,
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#E7E5E4",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: "#FAF8F5",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
}}
|
||||
>
|
||||
<Ionicons name="arrow-back" size={18} color="#1C1917" />
|
||||
</Pressable>
|
||||
<Text style={{ fontSize: 20, fontWeight: "800", color: "#1C1917" }}>
|
||||
Hesabım & Ayarlar
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
onPress={handleSignOut}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
backgroundColor: "#FEF2F2",
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: "#FEE2E2",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#DC2626", fontSize: 12, fontWeight: "700" }}>Çıkış Yap</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={{ padding: 20, paddingBottom: 60, gap: 24 }}>
|
||||
{/* Restaurant Profile Card with Logo Picker */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.04,
|
||||
shadowRadius: 12,
|
||||
elevation: 2,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 14 }}>
|
||||
{/* Logo Avatar Picker */}
|
||||
<Pressable
|
||||
onPress={handlePickLogo}
|
||||
style={{
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 32,
|
||||
backgroundColor: "#1C1917",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
borderWidth: 2,
|
||||
borderColor: "#C8A96B",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{logoUrl ? (
|
||||
<Image source={{ uri: logoUrl }} style={{ width: "100%", height: "100%" }} resizeMode="cover" />
|
||||
) : (
|
||||
<Text style={{ color: "#C8A96B", fontSize: 24, fontWeight: "800" }}>
|
||||
{name.charAt(0).toUpperCase() || userEmail.charAt(0).toUpperCase() || "M"}
|
||||
</Text>
|
||||
)}
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
alignItems: "center",
|
||||
paddingVertical: 2,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="camera" size={12} color="#FFFFFF" />
|
||||
</View>
|
||||
</Pressable>
|
||||
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 17, fontWeight: "800", color: "#1C1917" }}>
|
||||
{name || "Restoran Yöneticisi"}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, color: "#78716C", marginTop: 2 }}>{userEmail}</Text>
|
||||
<Pressable
|
||||
onPress={handlePickLogo}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
backgroundColor: "#FDF8F0",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 6,
|
||||
marginTop: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(200, 169, 107, 0.4)",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#926E27", fontSize: 11, fontWeight: "700" }}>
|
||||
📷 Logo Değiştir
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Live Menu URL & Quick Actions */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Dijital Menü Linki & Kısayollar
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
}}
|
||||
>
|
||||
<Ionicons name="globe-outline" size={18} color="#C8A96B" style={{ marginRight: 8 }} />
|
||||
<Text numberOfLines={1} style={{ flex: 1, fontSize: 13, fontWeight: "600", color: "#1C1917" }}>
|
||||
{getPublicMenuDisplayUrl(slug)}
|
||||
</Text>
|
||||
<Pressable onPress={handleCopyUrl} style={{ paddingHorizontal: 8, paddingVertical: 4 }}>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: copiedSlug ? "#10B981" : "#C8A96B" }}>
|
||||
{copiedSlug ? "Kopyalandı!" : "Kopyala"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: "row", gap: 10 }}>
|
||||
<Pressable
|
||||
onPress={() => router.push("/menu/qr")}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="qr-code-outline" size={16} color="#1C1917" />
|
||||
<Text style={{ fontSize: 13, fontWeight: "700", color: "#1C1917" }}>QR Kod</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={handleOpenMenu}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="open-outline" size={16} color="#1C1917" />
|
||||
<Text style={{ fontSize: 13, fontWeight: "700", color: "#1C1917" }}>Menüyü Aç</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Custom Domain Section */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Ionicons name="link-outline" size={20} color="#C8A96B" />
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Özel Alan Adı (Custom Domain)
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 12, color: "#78716C", lineHeight: 18 }}>
|
||||
Menünüzü kendi web sitenizin alt alan adında (Örn: <Text style={{ fontWeight: "700" }}>menu.restoraniniz.com</Text>) yayınlayın.
|
||||
</Text>
|
||||
|
||||
{/* Add Domain Input */}
|
||||
<View style={{ gap: 8 }}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 12,
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
value={newDomainInput}
|
||||
onChangeText={setNewDomainInput}
|
||||
placeholder="Örn: menu.kebapciahmet.com"
|
||||
placeholderTextColor="#A8A29E"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingVertical: 12,
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
color: "#1C1917",
|
||||
}}
|
||||
/>
|
||||
<Pressable
|
||||
onPress={handleAddCustomDomain}
|
||||
disabled={addingDomain || !newDomainInput.trim()}
|
||||
style={{
|
||||
backgroundColor: !newDomainInput.trim() ? "#D6D3D1" : "#1C1917",
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#FFFFFF", fontSize: 12, fontWeight: "700" }}>
|
||||
{addingDomain ? "Ekleniyor..." : "Bağla"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Existing Domains List */}
|
||||
{domains.length > 0 ? (
|
||||
<View style={{ gap: 10, marginTop: 4 }}>
|
||||
{domains.map((dom) => {
|
||||
const isVerified = dom.status === "verified";
|
||||
const isChecking = verifyingDomainId === dom.id;
|
||||
return (
|
||||
<View
|
||||
key={dom.id}
|
||||
style={{
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderRadius: 14,
|
||||
padding: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: isVerified ? "#BBF7D0" : "#FDE68A",
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<View style={{ flex: 1, marginRight: 8 }}>
|
||||
<Text style={{ fontSize: 14, fontWeight: "700", color: "#1C1917" }}>
|
||||
{dom.hostname}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 6, marginTop: 4 }}>
|
||||
<View
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: isVerified ? "#10B981" : "#F59E0B",
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
color: isVerified ? "#059669" : "#D97706",
|
||||
}}
|
||||
>
|
||||
{isVerified ? "Yayında & Doğrulandı ✓" : "DNS Doğrulaması Bekleniyor"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={{ flexDirection: "row", gap: 6 }}>
|
||||
{!isVerified ? (
|
||||
<Pressable
|
||||
onPress={() => handleVerifyDomain(dom.id)}
|
||||
disabled={isChecking}
|
||||
style={{
|
||||
backgroundColor: "#1C1917",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#FFFFFF", fontSize: 11, fontWeight: "700" }}>
|
||||
{isChecking ? "Kontrol..." : "Doğrula"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
<Pressable
|
||||
onPress={() => handleDeleteDomain(dom.id, dom.hostname)}
|
||||
style={{
|
||||
backgroundColor: "#FEF2F2",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<Ionicons name="trash-outline" size={14} color="#DC2626" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* CNAME instructions if pending */}
|
||||
{!isVerified ? (
|
||||
<View
|
||||
style={{
|
||||
marginTop: 10,
|
||||
padding: 10,
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 11, fontWeight: "700", color: "#44403C" }}>
|
||||
DNS Kaydı Talimatı:
|
||||
</Text>
|
||||
<Text style={{ fontSize: 11, color: "#78716C", marginTop: 2 }}>
|
||||
Tür: <Text style={{ fontWeight: "700", color: "#1C1917" }}>CNAME</Text> | Hedef:{" "}
|
||||
<Text style={{ fontWeight: "700", color: "#C8A96B" }}>{cnameTarget}</Text>
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Restaurant Details Form */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Ionicons name="business-outline" size={20} color="#C8A96B" />
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Restoran Bilgileri
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Restaurant Name */}
|
||||
<View>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#78716C", marginBottom: 6 }}>
|
||||
Restoran Adı
|
||||
</Text>
|
||||
<TextInput
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
placeholder="Restoran Adı"
|
||||
style={{
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
fontSize: 14,
|
||||
color: "#1C1917",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Phone */}
|
||||
<View>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#78716C", marginBottom: 6 }}>
|
||||
Telefon Numarası
|
||||
</Text>
|
||||
<TextInput
|
||||
value={phone}
|
||||
onChangeText={setPhone}
|
||||
placeholder="05xx xxx xx xx"
|
||||
keyboardType="phone-pad"
|
||||
style={{
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
fontSize: 14,
|
||||
color: "#1C1917",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Address */}
|
||||
<View>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#78716C", marginBottom: 6 }}>
|
||||
Adres / Lokasyon
|
||||
</Text>
|
||||
<TextInput
|
||||
value={address}
|
||||
onChangeText={setAddress}
|
||||
placeholder="Restoran adresi"
|
||||
multiline
|
||||
numberOfLines={2}
|
||||
style={{
|
||||
backgroundColor: "#FAF8F5",
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
fontSize: 14,
|
||||
color: "#1C1917",
|
||||
minHeight: 64,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Pressable
|
||||
onPress={handleSaveRestaurant}
|
||||
disabled={savingRest}
|
||||
style={({ pressed }) => ({
|
||||
backgroundColor: "#1C1917",
|
||||
paddingVertical: 14,
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
opacity: pressed || savingRest ? 0.85 : 1,
|
||||
marginTop: 4,
|
||||
})}
|
||||
>
|
||||
<Text style={{ color: "#FFFFFF", fontSize: 14, fontWeight: "700" }}>
|
||||
{savingRest ? "Kaydediliyor..." : "Restoran Bilgilerini Güncelle"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* Theme Selector */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Ionicons name="color-palette-outline" size={20} color="#C8A96B" />
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Menü Şablonu (Tema)
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 12, color: "#78716C" }}>
|
||||
Restoranınızın konseptine uygun tasarımı seçin. Seçtiğiniz tema QR menünüzde anında aktif olur.
|
||||
</Text>
|
||||
|
||||
<View style={{ gap: 10 }}>
|
||||
{THEME_OPTIONS.map((theme) => {
|
||||
const isSelected = selectedTheme === theme.key;
|
||||
return (
|
||||
<Pressable
|
||||
key={theme.key}
|
||||
onPress={() => handleSelectTheme(theme.key)}
|
||||
style={({ pressed }) => ({
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: 14,
|
||||
borderRadius: 14,
|
||||
backgroundColor: isSelected ? "#FAF8F5" : "#FAFAFA",
|
||||
borderWidth: 2,
|
||||
borderColor: isSelected ? theme.color : "#E7E5E4",
|
||||
opacity: pressed ? 0.9 : 1,
|
||||
})}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
||||
<View
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
backgroundColor: theme.color,
|
||||
}}
|
||||
/>
|
||||
<View>
|
||||
<Text style={{ fontSize: 14, fontWeight: "700", color: "#1C1917" }}>
|
||||
{theme.name}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 11, color: "#78716C", marginTop: 2 }}>
|
||||
{theme.desc}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
borderWidth: 2,
|
||||
borderColor: isSelected ? theme.color : "#D6D3D1",
|
||||
backgroundColor: isSelected ? theme.color : "transparent",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{isSelected ? <Ionicons name="checkmark" size={14} color="#FFFFFF" /> : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user