feat: redesign web menu layout, add header cover banner & dynamic header layout, refine mobile inputs and theme selector

This commit is contained in:
AyrisAI
2026-08-20 15:07:13 +03:00
parent 6fb91c94ff
commit 0836fc8539
17 changed files with 311 additions and 553 deletions
+2
View File
@@ -13,3 +13,5 @@ build/
supabase/.branches/ supabase/.branches/
supabase/.temp/ supabase/.temp/
coverage/ coverage/
*.ipa
*.apk
+24 -28
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { Link, router } from "expo-router"; import { Link, router } from "expo-router";
import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native"; import { ActivityIndicator, Image, Pressable, Text, TextInput, View } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { supabase } from "@/lib/supabase"; import { supabase } from "@/lib/supabase";
@@ -30,28 +30,24 @@ export default function LoginScreen() {
<View style={{ maxWidth: 400, width: "100%", alignSelf: "center" }}> <View style={{ maxWidth: 400, width: "100%", alignSelf: "center" }}>
{/* Brand Icon Header */} {/* Brand Icon Header */}
<View style={{ alignItems: "center", marginBottom: 28 }}> <View style={{ alignItems: "center", marginBottom: 28 }}>
<View <Image
source={require("../../../assets/icon.png")}
style={{ style={{
width: 64, width: 72,
height: 64, height: 72,
borderRadius: 32, borderRadius: 18,
backgroundColor: "#1C1917",
alignItems: "center",
justifyContent: "center",
marginBottom: 14, marginBottom: 14,
shadowColor: "#000", shadowColor: "#000",
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15, shadowOpacity: 0.12,
shadowRadius: 10, shadowRadius: 8,
elevation: 4,
}} }}
> resizeMode="contain"
<Ionicons name="restaurant-outline" size={30} color="#C8A96B" /> />
</View> <Text style={{ fontSize: 28, fontWeight: "800", color: "#1C1917", letterSpacing: -0.5 }}>
<Text style={{ fontSize: 26, fontWeight: "800", color: "#1C1917", letterSpacing: -0.5 }}> MenuL.io
Menulio&apos;ya Giriş Yap
</Text> </Text>
<Text style={{ fontSize: 13, color: "#78716C", marginTop: 4 }}> <Text style={{ fontSize: 13, color: "#57534E", marginTop: 4, fontWeight: "500" }}>
AI destekli dijital restoran menü yöneticisi AI destekli dijital restoran menü yöneticisi
</Text> </Text>
</View> </View>
@@ -81,22 +77,22 @@ export default function LoginScreen() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 12, marginBottom: 12,
}} }}
> >
<Ionicons name="mail-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="mail-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="E-posta adresiniz" placeholder="E-posta adresiniz"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
autoCapitalize="none" autoCapitalize="none"
keyboardType="email-address" keyboardType="email-address"
value={email} value={email}
onChangeText={setEmail} onChangeText={setEmail}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
</View> </View>
@@ -106,27 +102,27 @@ export default function LoginScreen() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 20, marginBottom: 20,
}} }}
> >
<Ionicons name="lock-closed-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="lock-closed-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="Şifreniz" placeholder="Şifreniz"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
<Pressable onPress={() => setShowPassword(!showPassword)} style={{ padding: 4 }}> <Pressable onPress={() => setShowPassword(!showPassword)} style={{ padding: 4 }}>
<Ionicons <Ionicons
name={showPassword ? "eye-off-outline" : "eye-outline"} name={showPassword ? "eye-off-outline" : "eye-outline"}
size={18} size={18}
color="#A8A29E" color="#57534E"
/> />
</Pressable> </Pressable>
</View> </View>
+24 -28
View File
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { Link, router } from "expo-router"; import { Link, router } from "expo-router";
import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native"; import { ActivityIndicator, Image, Pressable, Text, TextInput, View } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { supabase } from "@/lib/supabase"; import { supabase } from "@/lib/supabase";
@@ -35,28 +35,24 @@ export default function RegisterScreen() {
<View style={{ maxWidth: 400, width: "100%", alignSelf: "center" }}> <View style={{ maxWidth: 400, width: "100%", alignSelf: "center" }}>
{/* Brand Icon Header */} {/* Brand Icon Header */}
<View style={{ alignItems: "center", marginBottom: 28 }}> <View style={{ alignItems: "center", marginBottom: 28 }}>
<View <Image
source={require("../../../assets/icon.png")}
style={{ style={{
width: 64, width: 72,
height: 64, height: 72,
borderRadius: 32, borderRadius: 18,
backgroundColor: "#1C1917",
alignItems: "center",
justifyContent: "center",
marginBottom: 14, marginBottom: 14,
shadowColor: "#000", shadowColor: "#000",
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15, shadowOpacity: 0.12,
shadowRadius: 10, shadowRadius: 8,
elevation: 4,
}} }}
> resizeMode="contain"
<Ionicons name="sparkles" size={28} color="#C8A96B" /> />
</View> <Text style={{ fontSize: 28, fontWeight: "800", color: "#1C1917", letterSpacing: -0.5 }}>
<Text style={{ fontSize: 26, fontWeight: "800", color: "#1C1917", letterSpacing: -0.5 }}> MenuL.io
Hesap Oluştur
</Text> </Text>
<Text style={{ fontSize: 13, color: "#78716C", marginTop: 4 }}> <Text style={{ fontSize: 13, color: "#57534E", marginTop: 4, fontWeight: "500" }}>
Dakikalar içinde restoranınızı ve QR menünüzü hazırlayın Dakikalar içinde restoranınızı ve QR menünüzü hazırlayın
</Text> </Text>
</View> </View>
@@ -86,22 +82,22 @@ export default function RegisterScreen() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 12, marginBottom: 12,
}} }}
> >
<Ionicons name="mail-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="mail-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="E-posta adresiniz" placeholder="E-posta adresiniz"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
autoCapitalize="none" autoCapitalize="none"
keyboardType="email-address" keyboardType="email-address"
value={email} value={email}
onChangeText={setEmail} onChangeText={setEmail}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
</View> </View>
@@ -111,27 +107,27 @@ export default function RegisterScreen() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 20, marginBottom: 20,
}} }}
> >
<Ionicons name="lock-closed-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="lock-closed-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="Güçlü bir şifre belirleyin" placeholder="Güçlü bir şifre belirleyin"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
secureTextEntry={!showPassword} secureTextEntry={!showPassword}
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
<Pressable onPress={() => setShowPassword(!showPassword)} style={{ padding: 4 }}> <Pressable onPress={() => setShowPassword(!showPassword)} style={{ padding: 4 }}>
<Ionicons <Ionicons
name={showPassword ? "eye-off-outline" : "eye-outline"} name={showPassword ? "eye-off-outline" : "eye-outline"}
size={18} size={18}
color="#A8A29E" color="#57534E"
/> />
</Pressable> </Pressable>
</View> </View>
+15 -15
View File
@@ -96,20 +96,20 @@ export default function OnboardingRestaurantStep() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 12, marginBottom: 12,
}} }}
> >
<Ionicons name="storefront-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="storefront-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="Restoran adı (Örn: Kebapçı Ahmet)" placeholder="Restoran adı (Örn: Kebapçı Ahmet)"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
value={name} value={name}
onChangeText={setName} onChangeText={setName}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
</View> </View>
@@ -119,21 +119,21 @@ export default function OnboardingRestaurantStep() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 12, marginBottom: 12,
}} }}
> >
<Ionicons name="call-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="call-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="Telefon numarası (opsiyonel)" placeholder="Telefon numarası (opsiyonel)"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
keyboardType="phone-pad" keyboardType="phone-pad"
value={phone} value={phone}
onChangeText={setPhone} onChangeText={setPhone}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
</View> </View>
@@ -143,20 +143,20 @@ export default function OnboardingRestaurantStep() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FFFFFF", backgroundColor: "#FFFFFF",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
marginBottom: 24, marginBottom: 24,
}} }}
> >
<Ionicons name="location-outline" size={18} color="#A8A29E" style={{ marginRight: 10 }} /> <Ionicons name="location-outline" size={18} color="#57534E" style={{ marginRight: 10 }} />
<TextInput <TextInput
placeholder="Adres / Şehir (opsiyonel)" placeholder="Adres / Şehir (opsiyonel)"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
value={address} value={address}
onChangeText={setAddress} onChangeText={setAddress}
style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917" }} style={{ flex: 1, paddingVertical: 14, fontSize: 15, color: "#1C1917", fontWeight: "500" }}
/> />
</View> </View>
+15 -88
View File
@@ -560,8 +560,8 @@ export default function AccountScreen() {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FAF8F5", backgroundColor: "#FAF8F5",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 12, paddingHorizontal: 12,
}} }}
@@ -570,7 +570,7 @@ export default function AccountScreen() {
value={newDomainInput} value={newDomainInput}
onChangeText={setNewDomainInput} onChangeText={setNewDomainInput}
placeholder="Örn: menu.kebapciahmet.com" placeholder="Örn: menu.kebapciahmet.com"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
style={{ style={{
@@ -728,15 +728,17 @@ export default function AccountScreen() {
value={name} value={name}
onChangeText={setName} onChangeText={setName}
placeholder="Restoran Adı" placeholder="Restoran Adı"
placeholderTextColor="#57534E"
style={{ style={{
backgroundColor: "#FAF8F5", backgroundColor: "#FAF8F5",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 12, paddingVertical: 12,
fontSize: 14, fontSize: 14,
color: "#1C1917", color: "#1C1917",
fontWeight: "500",
}} }}
/> />
</View> </View>
@@ -750,16 +752,18 @@ export default function AccountScreen() {
value={phone} value={phone}
onChangeText={setPhone} onChangeText={setPhone}
placeholder="05xx xxx xx xx" placeholder="05xx xxx xx xx"
placeholderTextColor="#57534E"
keyboardType="phone-pad" keyboardType="phone-pad"
style={{ style={{
backgroundColor: "#FAF8F5", backgroundColor: "#FAF8F5",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 12, paddingVertical: 12,
fontSize: 14, fontSize: 14,
color: "#1C1917", color: "#1C1917",
fontWeight: "500",
}} }}
/> />
</View> </View>
@@ -773,17 +777,19 @@ export default function AccountScreen() {
value={address} value={address}
onChangeText={setAddress} onChangeText={setAddress}
placeholder="Restoran adresi" placeholder="Restoran adresi"
placeholderTextColor="#57534E"
multiline multiline
numberOfLines={2} numberOfLines={2}
style={{ style={{
backgroundColor: "#FAF8F5", backgroundColor: "#FAF8F5",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 10, borderRadius: 10,
paddingHorizontal: 14, paddingHorizontal: 14,
paddingVertical: 12, paddingVertical: 12,
fontSize: 14, fontSize: 14,
color: "#1C1917", color: "#1C1917",
fontWeight: "500",
minHeight: 64, minHeight: 64,
}} }}
/> />
@@ -806,85 +812,6 @@ export default function AccountScreen() {
</Text> </Text>
</Pressable> </Pressable>
</View> </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> </ScrollView>
</View> </View>
); );
@@ -101,12 +101,12 @@ export function AddCategoryModal({
value={name} value={name}
onChangeText={setName} onChangeText={setName}
placeholder="Örn: Tatlılar, Başlangıçlar, Sıcak İçecekler" placeholder="Örn: Tatlılar, Başlangıçlar, Sıcak İçecekler"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
autoFocus autoFocus
style={{ style={{
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
padding: 14, padding: 14,
fontSize: 15, fontSize: 15,
@@ -218,11 +218,11 @@ export function AddItemModal({
value={name} value={name}
onChangeText={setName} onChangeText={setName}
placeholder="Örn: Mercimek Çorbası" placeholder="Örn: Mercimek Çorbası"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
style={{ style={{
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
padding: 14, padding: 14,
fontSize: 15, fontSize: 15,
@@ -241,8 +241,8 @@ export function AddItemModal({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
}} }}
@@ -251,7 +251,7 @@ export function AddItemModal({
value={price} value={price}
onChangeText={setPrice} onChangeText={setPrice}
placeholder="120" placeholder="120"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
keyboardType="decimal-pad" keyboardType="decimal-pad"
style={{ style={{
flex: 1, flex: 1,
@@ -273,13 +273,13 @@ export function AddItemModal({
value={description} value={description}
onChangeText={setDescription} onChangeText={setDescription}
placeholder="Kısa içerik veya porsiyon açıklaması..." placeholder="Kısa içerik veya porsiyon açıklaması..."
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
multiline multiline
numberOfLines={3} numberOfLines={3}
style={{ style={{
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
padding: 14, padding: 14,
fontSize: 14, fontSize: 14,
@@ -311,11 +311,11 @@ export function ItemDetailSheet({
value={name} value={name}
onChangeText={setName} onChangeText={setName}
placeholder="Örn: Izgara Levrek" placeholder="Örn: Izgara Levrek"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
style={{ style={{
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
padding: 14, padding: 14,
fontSize: 15, fontSize: 15,
@@ -335,8 +335,8 @@ export function ItemDetailSheet({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
paddingHorizontal: 14, paddingHorizontal: 14,
}} }}
@@ -345,7 +345,7 @@ export function ItemDetailSheet({
value={price} value={price}
onChangeText={setPrice} onChangeText={setPrice}
placeholder="0.00" placeholder="0.00"
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
keyboardType="decimal-pad" keyboardType="decimal-pad"
style={{ style={{
flex: 1, flex: 1,
@@ -368,13 +368,13 @@ export function ItemDetailSheet({
value={description} value={description}
onChangeText={setDescription} onChangeText={setDescription}
placeholder="İçindekiler, porsiyon bilgisi, pişirme tarzı..." placeholder="İçindekiler, porsiyon bilgisi, pişirme tarzı..."
placeholderTextColor="#A8A29E" placeholderTextColor="#57534E"
multiline multiline
numberOfLines={3} numberOfLines={3}
style={{ style={{
backgroundColor: "#FAFAFA", backgroundColor: "#FAFAFA",
borderWidth: 1, borderWidth: 1.5,
borderColor: "#E7E5E4", borderColor: "#D6D3D1",
borderRadius: 12, borderRadius: 12,
padding: 14, padding: 14,
fontSize: 14, fontSize: 14,
@@ -285,7 +285,7 @@ export function ThemeSelectorSheet({
Menü Şablonları & Canlı Önizleme Menü Şablonları & Canlı Önizleme
</Text> </Text>
<Text style={{ fontSize: 12, color: "#78716C", marginTop: 2 }}> <Text style={{ fontSize: 12, color: "#78716C", marginTop: 2 }}>
5 lüks şablonu inceleyin, demolarını paylaşın veya menünüze uygulayın Şablonları inceleyin, demolarını görün veya menünüze uygulayın
</Text> </Text>
</View> </View>
@@ -323,61 +323,21 @@ export function ThemeSelectorSheet({
elevation: isSelected ? 3 : 1, elevation: isSelected ? 3 : 1,
}} }}
> >
{/* Top Row: Color Pip, Name, Category Badge & Active Check */} {/* Top Row: Theme Name */}
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}> <View style={{ marginBottom: 6 }}>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}> <Text style={{ fontSize: 17, fontWeight: "800", color: "#1C1917" }}>
<View {theme.name}
style={{
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: theme.color,
borderWidth: 2,
borderColor: "#FFFFFF",
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
}}
/>
<View>
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
{theme.name}
</Text>
</View>
</View>
<View
style={{
backgroundColor: theme.badgeBg,
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 6,
}}
>
<Text style={{ fontSize: 11, fontWeight: "700", color: theme.badgeText }}>
{theme.category}
</Text>
</View>
</View>
{/* Description */}
<Text style={{ fontSize: 13, color: "#57534E", lineHeight: 18, marginBottom: 8 }}>
{theme.desc}
</Text>
{/* Meta details */}
<View style={{ flexDirection: "row", alignItems: "center", gap: 6, marginBottom: 14 }}>
<Ionicons name="sparkles" size={13} color={theme.color} />
<Text style={{ fontSize: 11, color: "#78716C", fontWeight: "600" }}>
Renk: <Text style={{ color: "#1C1917", fontWeight: "700" }}>{theme.accent}</Text> {theme.suitableFor}
</Text> </Text>
</View> </View>
{/* Action Bar: Canlı Demo, Paylaş, Uygula */} {/* Description */}
<View style={{ flexDirection: "row", gap: 8, borderTopWidth: 1, borderTopColor: "#F5F5F4", paddingTop: 12 }}> <Text style={{ fontSize: 13, color: "#57534E", lineHeight: 18, marginBottom: 12 }}>
{/* Canlı Demo Aç */} {theme.desc}
</Text>
{/* Action Bar: Canlı Demo & Şablon Seç */}
<View style={{ flexDirection: "row", gap: 10, borderTopWidth: 1, borderTopColor: "#F5F5F4", paddingTop: 12 }}>
{/* Canlı Demo Önizle */}
<Pressable <Pressable
onPress={() => handleOpenDemo(theme.key)} onPress={() => handleOpenDemo(theme.key)}
style={({ pressed }) => ({ style={({ pressed }) => ({
@@ -385,43 +345,25 @@ export function ThemeSelectorSheet({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
gap: 4, gap: 6,
backgroundColor: "#F5F5F4", backgroundColor: "#F5F5F4",
paddingVertical: 10, paddingVertical: 12,
borderRadius: 10, borderRadius: 12,
opacity: pressed ? 0.8 : 1, opacity: pressed ? 0.8 : 1,
})} })}
> >
<Ionicons name="eye-outline" size={15} color="#1C1917" /> <Ionicons name="eye-outline" size={17} color="#1C1917" />
<Text style={{ fontSize: 12, fontWeight: "700", color: "#1C1917" }}> <Text style={{ fontSize: 13, fontWeight: "700", color: "#1C1917" }}>
Demo Menü Demo Önizle
</Text> </Text>
</Pressable> </Pressable>
{/* Demo Linkini Gönder / Paylaş */}
<Pressable
onPress={() => handleShareDemo(theme)}
style={({ pressed }) => ({
width: 40,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#FAF8F5",
borderWidth: 1,
borderColor: "#E7E5E4",
paddingVertical: 10,
borderRadius: 10,
opacity: pressed ? 0.8 : 1,
})}
>
<Ionicons name="share-outline" size={16} color="#78716C" />
</Pressable>
{/* Bu Şablonu Uygula / Aktif Rozeti */} {/* Bu Şablonu Uygula / Aktif Rozeti */}
<Pressable <Pressable
onPress={() => handleApplyTheme(theme.key)} onPress={() => handleApplyTheme(theme.key)}
disabled={isSelected || isApplying} disabled={isSelected || isApplying}
style={({ pressed }) => ({ style={({ pressed }) => ({
flex: 1.3, flex: 1,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@@ -429,8 +371,8 @@ export function ThemeSelectorSheet({
backgroundColor: isSelected ? "#ECFDF5" : theme.color, backgroundColor: isSelected ? "#ECFDF5" : theme.color,
borderWidth: isSelected ? 1 : 0, borderWidth: isSelected ? 1 : 0,
borderColor: "#A7F3D0", borderColor: "#A7F3D0",
paddingVertical: 10, paddingVertical: 12,
borderRadius: 10, borderRadius: 12,
opacity: pressed ? 0.85 : 1, opacity: pressed ? 0.85 : 1,
})} })}
> >
@@ -438,15 +380,15 @@ export function ThemeSelectorSheet({
<ActivityIndicator size="small" color="#FFFFFF" /> <ActivityIndicator size="small" color="#FFFFFF" />
) : isSelected ? ( ) : isSelected ? (
<> <>
<Ionicons name="checkmark-circle" size={16} color="#059669" /> <Ionicons name="checkmark-circle" size={17} color="#059669" />
<Text style={{ fontSize: 12, fontWeight: "800", color: "#059669" }}> <Text style={{ fontSize: 13, fontWeight: "800", color: "#059669" }}>
Aktif Şablon Aktif Şablon
</Text> </Text>
</> </>
) : ( ) : (
<> <>
<Ionicons name="checkmark" size={15} color="#FFFFFF" /> <Ionicons name="checkmark" size={16} color="#FFFFFF" />
<Text style={{ fontSize: 12, fontWeight: "800", color: "#FFFFFF" }}> <Text style={{ fontSize: 13, fontWeight: "800", color: "#FFFFFF" }}>
Şablonu Seç Şablonu Seç
</Text> </Text>
</> </>
+1 -1
View File
@@ -20,7 +20,7 @@ export default async function DemoMenuPage({ searchParams }: PageProps) {
restaurant={DEMO_RESTAURANT} restaurant={DEMO_RESTAURANT}
categories={DEMO_CATEGORIES} categories={DEMO_CATEGORIES}
initialThemeKey={theme} initialThemeKey={theme}
allowThemeSwitching={true} allowThemeSwitching={false}
/> />
); );
} }
+3 -2
View File
@@ -74,7 +74,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
let restaurant: any = null; let restaurant: any = null;
const { data: bySlug } = await supabase const { data: bySlug } = await supabase
.from("restaurants") .from("restaurants")
.select("id, name, slug, logo_url, phone, address") .select("id, name, slug, logo_url, cover_url, phone, address")
.eq("slug", slug) .eq("slug", slug)
.maybeSingle(); .maybeSingle();
@@ -84,7 +84,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
// 2. Try finding restaurant by custom domain hostname // 2. Try finding restaurant by custom domain hostname
const { data: domainRow } = await supabase const { data: domainRow } = await supabase
.from("domains") .from("domains")
.select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address)") .select("restaurant_id, restaurants(id, name, slug, logo_url, cover_url, phone, address)")
.eq("hostname", slug) .eq("hostname", slug)
.maybeSingle(); .maybeSingle();
@@ -144,6 +144,7 @@ export default async function PublicMenuPage({ params, searchParams }: PageProps
name: restaurant.name, name: restaurant.name,
slug: restaurant.slug, slug: restaurant.slug,
logo_url: restaurant.logo_url, logo_url: restaurant.logo_url,
cover_url: restaurant.cover_url,
phone: restaurant.phone, phone: restaurant.phone,
address: restaurant.address, address: restaurant.address,
}; };
+103 -108
View File
@@ -26,6 +26,7 @@ export interface RestaurantData {
name: string; name: string;
slug: string; slug: string;
logo_url: string | null; logo_url: string | null;
cover_url?: string | null;
phone?: string | null; phone?: string | null;
address?: string | null; address?: string | null;
} }
@@ -43,7 +44,7 @@ export function PublicMenuClient({
categories, categories,
initialThemeKey = "elegant", initialThemeKey = "elegant",
customThemeConfig, customThemeConfig,
allowThemeSwitching = true, allowThemeSwitching = false,
}: PublicMenuClientProps) { }: PublicMenuClientProps) {
const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey); const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
@@ -176,26 +177,38 @@ export function PublicMenuClient({
{/* Header Hero Banner */} {/* Header Hero Banner */}
<header <header
style={{ background: theme.headerGradient }} style={{ background: restaurant.cover_url ? "none" : theme.headerGradient }}
className="relative text-white px-4 sm:px-6 pt-8 sm:pt-10 pb-7 sm:pb-8 rounded-b-3xl shadow-lg overflow-hidden" className="relative text-white px-4 sm:px-6 pt-8 sm:pt-10 pb-7 sm:pb-8 rounded-b-3xl shadow-lg overflow-hidden min-h-[200px]"
> >
{/* Background Cover Image if available */}
{restaurant.cover_url && (
<>
<img
src={restaurant.cover_url}
alt={restaurant.name}
className="absolute inset-0 w-full h-full object-cover z-0"
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/65 via-black/45 to-black/85 z-0 backdrop-blur-[1px]" />
</>
)}
{/* Subtle Ambient Glow */} {/* Subtle Ambient Glow */}
<div {!restaurant.cover_url && (
className="absolute top-0 right-0 w-64 h-64 rounded-full blur-3xl opacity-20 pointer-events-none" <div
style={{ backgroundColor: theme.config.primaryColor }} className="absolute top-0 right-0 w-64 h-64 rounded-full blur-3xl opacity-20 pointer-events-none"
/> style={{ backgroundColor: theme.config.primaryColor }}
/>
)}
{/* Top Bar: Table & Action Buttons */} {/* Top Bar: Table & Action Buttons */}
<div className="flex items-center justify-between mb-4 sm:mb-5 relative z-10"> <div className="flex items-center justify-between mb-2 relative z-10">
{tableNumber ? ( {tableNumber ? (
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-md border border-white/15 text-xs font-semibold tracking-wide"> <div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-black/40 backdrop-blur-md border border-white/20 text-xs font-semibold tracking-wide text-white">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" /> <span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
Masa {tableNumber} Masa {tableNumber}
</div> </div>
) : ( ) : (
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-md border border-white/15 text-xs font-medium tracking-wide"> <div />
<span></span> Dijital QR Menü
</div>
)} )}
<div className="flex items-center gap-1.5 sm:gap-2"> <div className="flex items-center gap-1.5 sm:gap-2">
@@ -230,54 +243,84 @@ export function PublicMenuClient({
</svg> </svg>
)} )}
</button> </button>
{/* Live Theme Switcher Trigger */}
{allowThemeSwitching && (
<button
type="button"
onClick={() => setShowThemePicker(!showThemePicker)}
className="px-2.5 py-1 sm:py-1.5 rounded-full bg-white/10 hover:bg-white/20 active:scale-95 transition-all flex items-center gap-1.5 border border-white/15 backdrop-blur-md text-[11px] sm:text-xs font-semibold"
aria-label="Tema Değiştir"
>
<span className="w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full" style={{ backgroundColor: theme.config.primaryColor }} />
<span>Tema</span>
</button>
)}
</div> </div>
</div> </div>
{/* Restaurant Identity */} {/* Restaurant Identity - Dynamic Header Layout (Centered or Left) */}
<div className="flex items-center gap-3 sm:gap-4 relative z-10"> {theme.config.headerLayout === "left" ? (
{restaurant.logo_url ? ( /* Left-Aligned Header Layout */
<img <div className="flex items-center gap-3.5 sm:gap-4 relative z-10 pt-1 pb-1">
src={restaurant.logo_url} {restaurant.logo_url ? (
alt={restaurant.name} <img
className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl object-cover border-2 shadow-md flex-shrink-0" src={restaurant.logo_url}
style={{ borderColor: theme.config.primaryColor }} alt={restaurant.name}
/> className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl object-cover border-2 shadow-md flex-shrink-0"
) : ( style={{ borderColor: theme.config.primaryColor }}
<div />
className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl flex items-center justify-center font-bold text-xl sm:text-2xl flex-shrink-0 shadow-inner border border-white/20" ) : (
style={{ <div
background: "rgba(255, 255, 255, 0.12)", className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl flex items-center justify-center font-bold text-xl sm:text-2xl flex-shrink-0 shadow-inner border border-white/20"
color: theme.config.primaryColor, style={{
}} background: "rgba(255, 255, 255, 0.12)",
> color: theme.config.primaryColor,
{restaurant.name.charAt(0).toUpperCase()} }}
</div> >
)} {restaurant.name.charAt(0).toUpperCase()}
</div>
)}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="text-xl sm:text-2xl font-bold tracking-tight leading-snug break-words"> <h1 className="text-xl sm:text-2xl font-bold tracking-tight leading-snug break-words text-white">
{restaurant.name}
</h1>
<p className="text-[11px] sm:text-xs text-white/80 mt-0.5 flex items-center gap-1.5 font-medium">
<span>{totalItemsCount} Özel Lezzet</span>
</p>
</div>
</div>
) : (
/* Centered Header Layout */
<div className="flex flex-col items-center text-center relative z-10 pt-1 pb-2">
{/* Centered Logo Avatar */}
<div className="relative group">
{restaurant.logo_url ? (
<img
src={restaurant.logo_url}
alt={restaurant.name}
className="w-18 h-18 sm:w-20 sm:h-20 rounded-full object-cover border-2 shadow-2xl transition-transform duration-300 group-hover:scale-105"
style={{ borderColor: theme.config.primaryColor }}
/>
) : (
<div
className="w-18 h-18 sm:w-20 sm:h-20 rounded-full flex items-center justify-center font-black text-2xl sm:text-3xl shadow-2xl border-2 backdrop-blur-md"
style={{
background: "rgba(255, 255, 255, 0.15)",
borderColor: theme.config.primaryColor,
color: "#FFFFFF",
}}
>
{restaurant.name.charAt(0).toUpperCase()}
</div>
)}
{/* Decorative Ambient Glow Ring */}
<div
className="absolute -inset-1 rounded-full blur-md opacity-30 pointer-events-none"
style={{ backgroundColor: theme.config.primaryColor }}
/>
</div>
{/* Centered Restaurant Title */}
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight leading-snug mt-3 text-white drop-shadow-md max-w-md">
{restaurant.name} {restaurant.name}
</h1> </h1>
<p className="text-[11px] sm:text-xs text-white/70 mt-0.5 flex flex-wrap items-center gap-1.5">
{/* Centered Subtitle Badge Pill */}
<div className="mt-2 inline-flex items-center gap-1.5 px-3.5 py-1 rounded-full bg-black/35 backdrop-blur-md border border-white/15 text-xs font-medium text-white/90 shadow-sm">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
<span>{totalItemsCount} Özel Lezzet</span> <span>{totalItemsCount} Özel Lezzet</span>
<span></span> </div>
<span className="capitalize">{theme.name} Şablonu</span>
</p>
</div> </div>
</div> )}
{/* Live Search Bar */} {/* Live Search Bar */}
<div className="mt-6 relative z-10"> <div className="mt-6 relative z-10">
@@ -810,12 +853,6 @@ export function PublicMenuClient({
> >
{item.price.toFixed(0)} {item.price.toFixed(0)}
</span> </span>
<span
className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-stone-950 shadow-sm transition-transform group-hover:scale-110"
style={{ backgroundColor: theme.config.primaryColor }}
>
+
</span>
</div> </div>
</div> </div>
</article> </article>
@@ -905,48 +942,19 @@ export function PublicMenuClient({
</p> </p>
</footer> </footer>
{/* Floating Quick Action Bar (Garson Çağır / Hesap İste / Başa Dön) */} {/* Floating Back to Top Button */}
<aside aria-label="Masa Servis İşlemleri" className="fixed bottom-5 left-1/2 -translate-x-1/2 z-40 max-w-sm w-full px-4 flex items-center justify-between gap-2 pointer-events-none"> {showBackToTop && (
<div className="flex items-center gap-2 pointer-events-auto shadow-2xl rounded-full p-1 bg-stone-900/90 backdrop-blur-lg border border-stone-700 text-white"> <aside aria-label="Başa Dön" className="fixed bottom-5 right-5 z-40">
<button
type="button"
onClick={() =>
triggerServiceAction(
tableNumber
? `Masa ${tableNumber} için Garson Çağrıldı! Garsonunuz en kısa sürede masanızda olacaktır.`
: "Garson çağrıldı! Garsonunuz hemen masanızda olacaktır.",
)
}
className="px-3.5 py-2 rounded-full hover:bg-white/15 active:scale-95 transition-all text-xs font-bold flex items-center gap-1.5"
>
<span>👋</span> Garson Çağır
</button>
<button
type="button"
onClick={() =>
triggerServiceAction(
tableNumber
? `Masa ${tableNumber} için Hesap İsteği iletildi!`
: "Hesap isteği iletildi!",
)
}
className="px-3.5 py-2 rounded-full hover:bg-white/15 active:scale-95 transition-all text-xs font-bold flex items-center gap-1.5"
>
<span>💳</span> Hesap İste
</button>
</div>
{showBackToTop && (
<button <button
type="button" type="button"
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })} onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
className="w-11 h-11 rounded-full bg-stone-900 text-white shadow-2xl flex items-center justify-center pointer-events-auto active:scale-90 transition-all border border-stone-700" className="w-11 h-11 rounded-full bg-stone-900 text-white shadow-2xl flex items-center justify-center active:scale-90 transition-all border border-stone-700 font-bold"
aria-label="Başa Dön" aria-label="Başa Dön"
> >
</button> </button>
)} </aside>
</aside> )}
</div> </div>
{/* Item Detail Modal */} {/* Item Detail Modal */}
@@ -1003,30 +1011,17 @@ export function PublicMenuClient({
{selectedItem.description} {selectedItem.description}
</p> </p>
)} )}
{/* Quality Badges */}
<div className="pt-2 flex flex-wrap gap-2 text-xs">
<span className="px-3 py-1 rounded-full bg-emerald-50 text-emerald-700 font-semibold border border-emerald-200">
🌱 Taze & Günlük
</span>
<span className="px-3 py-1 rounded-full bg-amber-50 text-amber-700 font-semibold border border-amber-200">
Şefin İmzası
</span>
</div>
</div> </div>
{/* Modal Footer */} {/* Modal Footer */}
<div className="p-4 border-t" style={{ borderColor: theme.cardBorder }}> <div className="p-4 border-t" style={{ borderColor: theme.cardBorder }}>
<button <button
type="button" type="button"
onClick={() => { onClick={() => setSelectedItem(null)}
setSelectedItem(null);
triggerServiceAction(`"${selectedItem.name}" sipariş tercihleriniz garsona iletildi!`);
}}
className="w-full py-3.5 rounded-2xl font-bold text-white shadow-lg active:scale-98 transition-all flex items-center justify-center gap-2 text-sm" className="w-full py-3.5 rounded-2xl font-bold text-white shadow-lg active:scale-98 transition-all flex items-center justify-center gap-2 text-sm"
style={{ backgroundColor: theme.config.primaryColor }} style={{ backgroundColor: theme.config.primaryColor }}
> >
<span></span> Garsona Sipariş Olarak Bildir Kapat
</button> </button>
</div> </div>
</div> </div>
+64 -178
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { PublicMenuClient, type MenuCategory, type RestaurantData } from "@/components/PublicMenuClient"; import { PublicMenuClient, type MenuCategory, type RestaurantData } from "@/components/PublicMenuClient";
import { THEME_PRESETS, type ThemePreset } from "@/lib/theme"; import { THEME_PRESETS } from "@/lib/theme";
interface TemplateGalleryClientProps { interface TemplateGalleryClientProps {
restaurant: RestaurantData; restaurant: RestaurantData;
@@ -14,199 +13,86 @@ interface TemplateGalleryClientProps {
export function TemplateGalleryClient({ export function TemplateGalleryClient({
restaurant, restaurant,
categories, categories,
initialThemeKey = "elegant",
}: TemplateGalleryClientProps) { }: TemplateGalleryClientProps) {
const [selectedThemeKey, setSelectedThemeKey] = useState<string>(initialThemeKey); const presetsList = Object.values(THEME_PRESETS);
const [viewMode, setViewMode] = useState<"phone" | "fullscreen">("phone");
const currentPreset: ThemePreset = THEME_PRESETS[selectedThemeKey] || THEME_PRESETS.elegant!;
return ( return (
<div className="min-h-screen bg-stone-950 text-stone-100 flex flex-col font-sans"> <div className="min-h-screen bg-stone-950 text-stone-100 flex flex-col font-sans">
{/* Top Navbar */} {/* Top Navbar */}
<header className="border-b border-stone-800/80 bg-stone-900/90 backdrop-blur-xl sticky top-0 z-50 px-4 sm:px-6 py-3.5"> <header className="border-b border-stone-800/80 bg-stone-900/90 backdrop-blur-xl sticky top-0 z-50 px-4 sm:px-6 py-3.5">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-3"> <div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center justify-between w-full md:w-auto"> <div className="flex items-center gap-3">
<div className="flex items-center gap-3"> <Link href="/" className="font-black text-xl tracking-tight text-amber-400">
<Link href="/" className="font-black text-xl tracking-tight text-amber-400"> MENULIO
MENULIO
</Link>
<span className="text-stone-600 hidden sm:inline">/</span>
<span className="text-xs font-semibold text-stone-400 hidden sm:inline">Şablon Galerisi</span>
</div>
{/* View Mode Toggle (Visible on desktop/tablet) */}
<div className="flex items-center bg-stone-800/80 p-1 rounded-xl border border-stone-700 md:hidden">
<button
type="button"
onClick={() => setViewMode("phone")}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "phone" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400"
}`}
>
📱 Mobil
</button>
<button
type="button"
onClick={() => setViewMode("fullscreen")}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "fullscreen" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400"
}`}
>
🖥 Tam
</button>
</div>
</div>
{/* 5 Theme Pills - Smooth Horizontal Scroll */}
<div className="flex items-center gap-2 overflow-x-auto w-full md:w-auto pb-1 md:pb-0 no-scrollbar">
{Object.values(THEME_PRESETS).map((preset) => {
const isActive = selectedThemeKey === preset.key;
return (
<button
key={preset.key}
type="button"
onClick={() => setSelectedThemeKey(preset.key)}
className={`px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 whitespace-nowrap border flex-shrink-0 ${
isActive
? "bg-white text-stone-950 border-white shadow-lg scale-105"
: "bg-stone-800/90 text-stone-300 border-stone-700 hover:border-stone-500 hover:text-white"
}`}
>
<span
className="w-2.5 h-2.5 rounded-full"
style={{ backgroundColor: preset.config.primaryColor }}
/>
<span>{preset.name}</span>
</button>
);
})}
</div>
<div className="hidden md:flex items-center gap-3">
{/* View Mode Toggle (Desktop) */}
<div className="flex items-center bg-stone-800/80 p-1 rounded-xl border border-stone-700">
<button
type="button"
onClick={() => setViewMode("phone")}
className={`px-3 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "phone" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400 hover:text-white"
}`}
>
📱 Telefon
</button>
<button
type="button"
onClick={() => setViewMode("fullscreen")}
className={`px-3 py-1 rounded-lg text-xs font-bold transition-all ${
viewMode === "fullscreen" ? "bg-amber-400 text-stone-950 shadow" : "text-stone-400 hover:text-white"
}`}
>
🖥 Tam Ekran
</button>
</div>
<Link
href="/"
className="text-xs font-bold px-4 py-2 rounded-xl bg-amber-500 hover:bg-amber-400 text-stone-950 transition-all shadow-md shadow-amber-500/20"
>
Uygulamayı İndir
</Link> </Link>
<span className="text-stone-600 hidden sm:inline">/</span>
<span className="text-xs font-semibold text-stone-400 hidden sm:inline">
Şablon Galerisi
</span>
</div> </div>
<Link
href="/"
className="text-xs font-bold px-4 py-2 rounded-xl bg-amber-500 hover:bg-amber-400 text-stone-950 transition-all shadow-md shadow-amber-500/20"
>
Ana Sayfa
</Link>
</div> </div>
</header> </header>
{/* Main Content Area */} {/* Main Content Area - Stacked Vertically */}
<main className="flex-1 w-full flex flex-col items-center justify-center p-0 sm:p-6 lg:p-8"> <main className="flex-1 w-full max-w-4xl mx-auto py-10 px-4 space-y-16">
{viewMode === "fullscreen" ? ( {presetsList.map((preset) => (
/* Fullscreen Fluid View */ <section
<div className="w-full flex-1 min-h-[85vh]"> key={preset.key}
<PublicMenuClient className="flex flex-col items-center gap-8 border-b border-stone-800/80 pb-16 last:border-0"
restaurant={restaurant} >
categories={categories} {/* Theme Info */}
initialThemeKey={selectedThemeKey} <div className="text-center max-w-xl space-y-3">
allowThemeSwitching={true} <div className="inline-flex items-center gap-2.5 px-3.5 py-1.5 rounded-full bg-stone-900 border border-stone-800 text-xs font-bold text-stone-300">
/> <span
</div> className="w-2.5 h-2.5 rounded-full"
) : ( style={{ backgroundColor: preset.config.primaryColor }}
/* Responsive Device Simulator / Side-by-Side on Desktop */ />
<div className="w-full max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-8 items-center justify-center py-4"> <span className="uppercase tracking-wider text-[11px]">{preset.key} Şablonu</span>
{/* Left Info Panel (Visible on Desktop) */}
<div className="hidden lg:flex lg:col-span-5 flex-col space-y-6 pr-4">
<div className="space-y-2">
<span className="text-amber-400 text-xs font-bold uppercase tracking-wider">
ŞABLON DETAYI
</span>
<h2 className="text-3xl font-extrabold text-white">
{currentPreset.name}
</h2>
<p className="text-sm text-stone-400 leading-relaxed">
{currentPreset.key === "elegant" && "Fine dining restoranlar, şarap evleri ve lüks steakhouse mekanlar için altın & krem tonlarında yüksek prestijli tasarım."}
{currentPreset.key === "modern" && "Kafeler, burgerciler ve dinamik mekanlar için canlı mavi safir tonları ve hızlı arama odaklı ızgara düzeni."}
{currentPreset.key === "dark" && "Gece kulüpleri, kokteyl barlar ve lounge mekanlar için obsidian siyahı ve kehribar parıltılı lüks mod."}
{currentPreset.key === "minimal" && "Butik kahveciler, fırınlar ve üçüncü nesil mekanlar için ferah ve monokromatik Nordic mizanpaj."}
{currentPreset.key === "classic" && "Geleneksel brasserie'ler, meyhaneler ve trattoria'lar için Toskana bordo ve sıcak rustik doku."}
</p>
</div> </div>
{/* Theme Specs */} <h2 className="text-2xl sm:text-3xl font-extrabold text-white">
<div className="p-5 rounded-2xl bg-stone-900 border border-stone-800 space-y-3.5 text-xs"> {preset.name}
<div className="flex items-center justify-between"> </h2>
<span className="text-stone-400">Vurgu Rengi</span>
<div className="flex items-center gap-2">
<span
className="w-4 h-4 rounded-full border border-white/20"
style={{ backgroundColor: currentPreset.config.primaryColor }}
/>
<span className="font-mono text-stone-200">{currentPreset.config.primaryColor}</span>
</div>
</div>
<div className="flex items-center justify-between"> <p className="text-xs sm:text-sm text-stone-400 leading-relaxed">
<span className="text-stone-400">Tipografi Ailesi</span> Tipografi: <span className="uppercase text-stone-200 font-semibold">{preset.fontFamily}</span> Mizanpaj: <span className="uppercase text-stone-200 font-semibold">{preset.config.productLayout}</span>
<span className="font-semibold text-stone-200 uppercase">{currentPreset.fontFamily}</span> </p>
</div>
<div className="flex items-center justify-between"> <div className="pt-2">
<span className="text-stone-400">Ürün Mizanpajı</span> <Link
<span className="font-semibold text-stone-200 uppercase">{currentPreset.config.productLayout}</span> href={`/demo?theme=${preset.key}`}
</div> target="_blank"
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-stone-900 hover:bg-stone-800 text-stone-200 border border-stone-700/80 text-xs font-bold transition-all shadow-md"
<div className="flex items-center justify-between"> >
<span className="text-stone-400">Kategori Gezintisi</span> <span></span> Canlı Demoyu Sekmede
<span className="font-semibold text-stone-200 uppercase">{currentPreset.config.categoryLayout}</span> </Link>
</div>
</div>
{/* Direct Demo Link */}
<Link
href={`/demo?theme=${selectedThemeKey}`}
target="_blank"
className="w-full py-3.5 rounded-xl font-bold text-center bg-stone-800 hover:bg-stone-700 text-stone-200 border border-stone-700 transition-all text-xs flex items-center justify-center gap-2"
>
<span></span> Yeni Sekmede Canlı Menüyü
</Link>
</div>
{/* Right Phone Mockup Container */}
<div className="lg:col-span-7 flex justify-center w-full">
<div className="w-full max-w-[420px] rounded-[36px] sm:rounded-[44px] overflow-hidden shadow-2xl border-0 sm:border-[8px] border-stone-800 bg-stone-900 relative">
{/* Dynamic Island (Desktop only) */}
<div className="hidden sm:block w-24 h-4 bg-stone-800 rounded-full mx-auto mt-2 mb-1" />
{/* Simulated Screen Body */}
<div className="overflow-y-auto max-h-[85vh] sm:max-h-[780px] rounded-none sm:rounded-[32px] no-scrollbar">
<PublicMenuClient
restaurant={restaurant}
categories={categories}
initialThemeKey={selectedThemeKey}
allowThemeSwitching={true}
/>
</div>
</div> </div>
</div> </div>
</div>
)} {/* Phone Mockup Preview */}
<div className="w-full max-w-[420px] rounded-[36px] sm:rounded-[44px] overflow-hidden shadow-2xl border-0 sm:border-[8px] border-stone-800 bg-stone-900 relative">
{/* Dynamic Island */}
<div className="hidden sm:block w-24 h-4 bg-stone-800 rounded-full mx-auto mt-2.5 mb-1" />
{/* Screen Body */}
<div className="overflow-y-auto max-h-[720px] rounded-none sm:rounded-[32px] no-scrollbar">
<PublicMenuClient
restaurant={restaurant}
categories={categories}
initialThemeKey={preset.key}
allowThemeSwitching={false}
/>
</div>
</div>
</section>
))}
</main> </main>
</div> </div>
); );
+1
View File
@@ -5,6 +5,7 @@ export const DEMO_RESTAURANT: RestaurantData = {
name: "Gusto & Co. Brasserie", name: "Gusto & Co. Brasserie",
slug: "gusto-brasserie", slug: "gusto-brasserie",
logo_url: null, logo_url: null,
cover_url: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=1200&auto=format&fit=crop&q=80",
phone: "+90 (212) 555 0192", phone: "+90 (212) 555 0192",
address: "Nişantaşı, Abdi İpekçi Cad. No: 42, İstanbul", address: "Nişantaşı, Abdi İpekçi Cad. No: 42, İstanbul",
}; };
+10
View File
@@ -25,6 +25,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#F7F3EC", background: "#F7F3EC",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "centered",
}, },
cardBg: "#FCFAF6", cardBg: "#FCFAF6",
cardBorder: "#E4DBCF", cardBorder: "#E4DBCF",
@@ -44,6 +45,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#0D1117", background: "#0D1117",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "left",
}, },
cardBg: "#FCF7EC", cardBg: "#FCF7EC",
cardBorder: "rgba(255, 42, 133, 0.3)", cardBorder: "rgba(255, 42, 133, 0.3)",
@@ -63,6 +65,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#EEEAE4", background: "#EEEAE4",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "centered",
}, },
cardBg: "#F7F4EF", cardBg: "#F7F4EF",
cardBorder: "rgba(36, 70, 58, 0.2)", cardBorder: "rgba(36, 70, 58, 0.2)",
@@ -82,6 +85,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#FFFFFF", background: "#FFFFFF",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "left",
}, },
cardBg: "#FFFFFF", cardBg: "#FFFFFF",
cardBorder: "#000000", cardBorder: "#000000",
@@ -101,6 +105,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#121316", background: "#121316",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "centered",
}, },
cardBg: "#1C1E24", cardBg: "#1C1E24",
cardBorder: "rgba(255, 255, 255, 0.08)", cardBorder: "rgba(255, 255, 255, 0.08)",
@@ -120,6 +125,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#FAF8F5", background: "#FAF8F5",
productLayout: "card", productLayout: "card",
categoryLayout: "accordion", categoryLayout: "accordion",
headerLayout: "centered",
}, },
cardBg: "#FFFFFF", cardBg: "#FFFFFF",
cardBorder: "rgba(200, 169, 107, 0.18)", cardBorder: "rgba(200, 169, 107, 0.18)",
@@ -139,6 +145,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#F8FAFC", background: "#F8FAFC",
productLayout: "card", productLayout: "card",
categoryLayout: "tabs", categoryLayout: "tabs",
headerLayout: "left",
}, },
cardBg: "#FFFFFF", cardBg: "#FFFFFF",
cardBorder: "rgba(226, 232, 240, 0.9)", cardBorder: "rgba(226, 232, 240, 0.9)",
@@ -158,6 +165,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#0F0F12", background: "#0F0F12",
productLayout: "card", productLayout: "card",
categoryLayout: "accordion", categoryLayout: "accordion",
headerLayout: "centered",
}, },
cardBg: "#18181D", cardBg: "#18181D",
cardBorder: "rgba(255, 255, 255, 0.07)", cardBorder: "rgba(255, 255, 255, 0.07)",
@@ -177,6 +185,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#FAFAFA", background: "#FAFAFA",
productLayout: "list", productLayout: "list",
categoryLayout: "flat", categoryLayout: "flat",
headerLayout: "left",
}, },
cardBg: "#FFFFFF", cardBg: "#FFFFFF",
cardBorder: "#E4E4E7", cardBorder: "#E4E4E7",
@@ -196,6 +205,7 @@ export const THEME_PRESETS: Record<string, ThemePreset> = {
background: "#FFF9F2", background: "#FFF9F2",
productLayout: "list", productLayout: "list",
categoryLayout: "accordion", categoryLayout: "accordion",
headerLayout: "centered",
}, },
cardBg: "#FFFFFF", cardBg: "#FFFFFF",
cardBorder: "rgba(139, 30, 30, 0.15)", cardBorder: "rgba(139, 30, 30, 0.15)",
+1
View File
@@ -7,6 +7,7 @@ export const themeConfigSchema = z.object({
background: z.string(), background: z.string(),
productLayout: z.enum(["card", "list"]), productLayout: z.enum(["card", "list"]),
categoryLayout: z.enum(["accordion", "tabs", "flat"]), categoryLayout: z.enum(["accordion", "tabs", "flat"]),
headerLayout: z.enum(["centered", "left"]).optional().default("centered"),
}); });
export type ThemeConfig = z.infer<typeof themeConfigSchema>; export type ThemeConfig = z.infer<typeof themeConfigSchema>;
@@ -27,6 +27,7 @@ create table restaurants (
name text not null, name text not null,
slug text not null unique, slug text not null unique,
logo_url text, logo_url text,
cover_url text,
phone text, phone text,
address text, address text,
created_by uuid not null references users (id) on delete restrict, created_by uuid not null references users (id) on delete restrict,