feat(mobile): add full text editing, show/hide switches and font family selector for QR stands

This commit is contained in:
AyrisAI
2026-08-21 00:36:14 +03:00
parent 932c174b5f
commit 4dbb68a73a
5 changed files with 712 additions and 234 deletions
+354 -1
View File
@@ -6,7 +6,9 @@ import {
Pressable, Pressable,
ScrollView, ScrollView,
Share, Share,
Switch,
Text, Text,
TextInput,
View, View,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -17,7 +19,13 @@ import * as Print from "expo-print";
import { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared"; import { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant";
import { COMPONENT_TEMPLATES, TEMPLATE_ASPECT_RATIOS } from "@/components/qr-templates"; import {
COMPONENT_TEMPLATES,
TEMPLATE_ASPECT_RATIOS,
FONT_OPTIONS,
type QrCustomTextConfig,
} from "@/components/qr-templates";
interface QrResponse { interface QrResponse {
id: string; id: string;
@@ -41,6 +49,21 @@ export default function QrScreen() {
); );
const [exportingPng, setExportingPng] = useState(false); const [exportingPng, setExportingPng] = useState(false);
const [exportingPdf, setExportingPdf] = useState(false); const [exportingPdf, setExportingPdf] = useState(false);
const [showCustomizePanel, setShowCustomizePanel] = useState(false);
const [selectedFontId, setSelectedFontId] = useState<string>("didot");
const [customText, setCustomText] = useState<QrCustomTextConfig>({
restaurantName: "Gourmet Bistro",
subtitle: "RESTAURANT & BAR | MODERN CUISINE",
tableNo: "MASA NO: 01",
ctaText: "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText: "WiFi: Gourmet_Guest | Şifre: lezzetli01",
socialText: "@gourmetbistro",
showTableNo: true,
showSubtitle: true,
showCta: true,
showWifi: true,
showSocial: true,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const viewShotRef = useRef<any>(null); const viewShotRef = useRef<any>(null);
@@ -48,6 +71,10 @@ export default function QrScreen() {
const activePreset: QrStandPreset = const activePreset: QrStandPreset =
QR_STAND_PRESETS[selectedKey] || availablePresets[0] || Object.values(QR_STAND_PRESETS)[0]!; QR_STAND_PRESETS[selectedKey] || availablePresets[0] || Object.values(QR_STAND_PRESETS)[0]!;
const activeFont =
FONT_OPTIONS.find((f) => f.id === selectedFontId)?.fontFamily ||
"'Playfair Display', Georgia, serif";
// Component-based template sistemi // Component-based template sistemi
const TemplateComponent = const TemplateComponent =
COMPONENT_TEMPLATES[selectedKey] ?? Object.values(COMPONENT_TEMPLATES)[0]; COMPONENT_TEMPLATES[selectedKey] ?? Object.values(COMPONENT_TEMPLATES)[0];
@@ -56,6 +83,7 @@ export default function QrScreen() {
const displayHeight = Math.round(displayWidth / aspectRatio); const displayHeight = Math.round(displayWidth / aspectRatio);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const activeRest = await getActiveRestaurant(); const activeRest = await getActiveRestaurant();
@@ -71,6 +99,10 @@ export default function QrScreen() {
const restDetail = await api.get<{ name?: string }>(`/restaurants/${activeRest.restaurantId}`); const restDetail = await api.get<{ name?: string }>(`/restaurants/${activeRest.restaurantId}`);
if (restDetail?.name) { if (restDetail?.name) {
setRestaurantName(restDetail.name); setRestaurantName(restDetail.name);
setCustomText((prev) => ({
...prev,
restaurantName: restDetail.name,
}));
} }
} catch { } catch {
// fallback // fallback
@@ -83,6 +115,7 @@ export default function QrScreen() {
})(); })();
}, []); }, []);
async function handleCopy() { async function handleCopy() {
if (!qr?.targetUrl) return; if (!qr?.targetUrl) return;
await Clipboard.setStringAsync(qr.targetUrl); await Clipboard.setStringAsync(qr.targetUrl);
@@ -322,12 +355,332 @@ export default function QrScreen() {
qrBase64={qr.pngBase64} qrBase64={qr.pngBase64}
width={displayWidth} width={displayWidth}
height={displayHeight} height={displayHeight}
restaurantName={customText.restaurantName}
subtitle={customText.subtitle}
tableNo={customText.tableNo}
ctaText={customText.ctaText}
wifiText={customText.wifiText}
socialText={customText.socialText}
fontFamily={activeFont}
showTableNo={customText.showTableNo}
showSubtitle={customText.showSubtitle}
showCta={customText.showCta}
showWifi={customText.showWifi}
showSocial={customText.showSocial}
/> />
) : null} ) : null}
</View> </View>
</ViewShot> </ViewShot>
</View> </View>
{/* Metin & Font Özelleştirme Kartı (Açılır/Kapanır) */}
<View
style={{
backgroundColor: "#18181B",
borderRadius: 20,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.08)",
padding: 16,
marginBottom: 20,
}}
>
<Pressable
onPress={() => setShowCustomizePanel(!showCustomizePanel)}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<View
style={{
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: "rgba(212, 175, 55, 0.15)",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="text-outline" size={18} color="#D4AF37" />
</View>
<View>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
Metin ve Yazı Tipi Özelleştir
</Text>
<Text style={{ color: "#A1A1AA", fontSize: 12 }}>
İstediğiniz alanları düzenleyin veya kaldırın
</Text>
</View>
</View>
<Ionicons
name={showCustomizePanel ? "chevron-up" : "chevron-down"}
size={20}
color="#A1A1AA"
/>
</Pressable>
{showCustomizePanel && (
<View style={{ marginTop: 16, gap: 16, borderTopWidth: 1, borderTopColor: "rgba(255,255,255,0.06)", paddingTop: 16 }}>
{/* Yazı Tipi (Font) Seçici */}
<View>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700", marginBottom: 8, letterSpacing: 0.5 }}>
YAZI TİPİ (FONT)
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
{FONT_OPTIONS.map((f) => {
const isSelected = f.id === selectedFontId;
return (
<Pressable
key={f.id}
onPress={() => setSelectedFontId(f.id)}
style={{
backgroundColor: isSelected ? "#D4AF37" : "#242429",
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 10,
borderWidth: 1,
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.08)",
}}
>
<Text
style={{
color: isSelected ? "#0C0C0E" : "#FFFFFF",
fontSize: 12,
fontWeight: isSelected ? "700" : "500",
}}
>
{f.label}
</Text>
</Pressable>
);
})}
</ScrollView>
</View>
{/* Alan 1: Restoran Başlığı */}
<View style={{ gap: 6 }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
RESTORAN BAŞLIĞI
</Text>
<TextInput
value={customText.restaurantName}
onChangeText={(t) => setCustomText((p) => ({ ...p, restaurantName: t }))}
placeholder="Restoran Adı"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
</View>
{/* Alan 2: Slogan / Alt Başlık */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
SLOGAN / ALT BAŞLIK
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showSubtitle ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showSubtitle}
onValueChange={(v) => setCustomText((p) => ({ ...p, showSubtitle: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showSubtitle && (
<TextInput
value={customText.subtitle}
onChangeText={(t) => setCustomText((p) => ({ ...p, subtitle: t }))}
placeholder="Slogan / Açıklama"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 3: Masa Numarası */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
MASA NUMARASI
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showTableNo ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showTableNo}
onValueChange={(v) => setCustomText((p) => ({ ...p, showTableNo: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showTableNo && (
<TextInput
value={customText.tableNo}
onChangeText={(t) => setCustomText((p) => ({ ...p, tableNo: t }))}
placeholder="Örn: MASA NO: 01"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 4: Yönlendirme (CTA) */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
YÖNLENDİRME (CTA) METNİ
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showCta ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showCta}
onValueChange={(v) => setCustomText((p) => ({ ...p, showCta: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showCta && (
<TextInput
value={customText.ctaText}
onChangeText={(t) => setCustomText((p) => ({ ...p, ctaText: t }))}
placeholder="Örn: MENÜYÜ GÖRMEK İÇİN OKUTUN"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 5: WiFi Bilgileri */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
WIFI BİLGİSİ
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showWifi ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showWifi}
onValueChange={(v) => setCustomText((p) => ({ ...p, showWifi: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showWifi && (
<TextInput
value={customText.wifiText}
onChangeText={(t) => setCustomText((p) => ({ ...p, wifiText: t }))}
placeholder="Örn: WiFi: Bistro | Şifre: 12345"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 6: Sosyal Medya / İletişim */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
SOSYAL MEDYA / İLETİŞİM
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showSocial ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showSocial}
onValueChange={(v) => setCustomText((p) => ({ ...p, showSocial: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showSocial && (
<TextInput
value={customText.socialText}
onChangeText={(t) => setCustomText((p) => ({ ...p, socialText: t }))}
placeholder="Örn: @menulio"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
</View>
)}
</View>
{/* Output Action Buttons */} {/* Output Action Buttons */}
<View style={{ width: "100%", gap: 10 }}> <View style={{ width: "100%", gap: 10 }}>
{/* PDF Download Button */} {/* PDF Download Button */}
@@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import Svg, { import Svg, {
SvgProps,
Path, Path,
Rect, Rect,
Circle, Circle,
@@ -9,12 +8,24 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index";
interface QrTemplate2Props extends SvgProps { const QrTemplate2 = ({
qrBase64?: string; qrBase64,
} restaurantName = "The Daily Grind",
subtitle = "COFFEE & KITCHEN",
const QrTemplate2 = ({ qrBase64, ...props }: QrTemplate2Props) => ( tableNo = "MASA NO: 04",
ctaText = "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText = "WiFi: DailyGrind | Şifre: coffee2026",
socialText = "@dailygrind",
fontFamily = "'Playfair Display', Georgia, serif",
showTableNo = true,
showSubtitle = true,
showCta = true,
showWifi = true,
showSocial = true,
...props
}: QrTemplateProps) => (
<Svg <Svg
viewBox="0 0 400 650" viewBox="0 0 400 650"
width={300} width={300}
@@ -117,7 +128,7 @@ const QrTemplate2 = ({ qrBase64, ...props }: QrTemplate2Props) => (
/> />
</G> </G>
{/* Kahve Çekirdekleri (arka plan süsü) */} {/* Kahve Çekirdekleri */}
<G stroke="#3a2312" strokeWidth={1.2} transform="rotate(-20 1741.206 -8.75) scale(.8)"> <G stroke="#3a2312" strokeWidth={1.2} transform="rotate(-20 1741.206 -8.75) scale(.8)">
<Ellipse fill="#cbb296" rx={9} ry={6.5} /> <Ellipse fill="#cbb296" rx={9} ry={6.5} />
<Path fill="none" d="M-7 0q7 3 7 0t7 0" /> <Path fill="none" d="M-7 0q7 3 7 0t7 0" />
@@ -196,11 +207,12 @@ const QrTemplate2 = ({ qrBase64, ...props }: QrTemplate2Props) => (
{/* Metinler */} {/* Metinler */}
<G textAnchor="middle"> <G textAnchor="middle">
{/* Üst Tag / Takı */}
<Text <Text
x={200} x={200}
y={70} y={70}
fill="#3a2312" fill="#3a2312"
fontFamily="'Playfair Display', Georgia, serif" fontFamily={fontFamily}
fontSize={14} fontSize={14}
fontStyle="italic" fontStyle="italic"
fontWeight={700} fontWeight={700}
@@ -208,72 +220,98 @@ const QrTemplate2 = ({ qrBase64, ...props }: QrTemplate2Props) => (
> >
{"THE"} {"THE"}
</Text> </Text>
<Text
x={200} {/* Restoran Adı */}
y={114} {restaurantName ? (
fill="#2c1a0e" <Text
fontFamily="'Playfair Display', Georgia, serif" x={200}
fontSize={34} y={114}
fontWeight={800} fill="#2c1a0e"
letterSpacing={0.5} fontFamily={fontFamily}
> fontSize={30}
{"The Daily Grind"} fontWeight={800}
</Text> letterSpacing={0.5}
<Text >
x={200} {restaurantName}
y={146} </Text>
fill="#3a2312" ) : null}
fontFamily="Inter, Arial, sans-serif"
fontSize={13} {/* Slogan */}
fontWeight={800} {showSubtitle && subtitle ? (
letterSpacing={3} <Text
> x={200}
{"COFFEE & KITCHEN"} y={146}
</Text> fill="#3a2312"
<Text fontFamily="Inter, Arial, sans-serif"
x={200} fontSize={12}
y={488} fontWeight={700}
fill="#2c1a0e" letterSpacing={2.5}
fontFamily="Inter, Arial, sans-serif" >
fontSize={19} {subtitle}
fontWeight={900} </Text>
letterSpacing={1.5} ) : null}
>
{"SCAN TO VIEW MENU"} {/* CTA */}
</Text> {showCta && ctaText ? (
<Text <Text
x={200} x={200}
y={520} y={488}
fill="#3a2312" fill="#2c1a0e"
fontFamily="Inter, Arial, sans-serif" fontFamily={fontFamily}
fontSize={10.5} fontSize={16}
fontWeight={700} fontWeight={800}
letterSpacing={0.5} letterSpacing={1.2}
> >
{"DAILY GRIND CAFE | WIFI: DAILY_GRIND_GUEST"} {ctaText}
</Text> </Text>
<Text ) : null}
x={200}
y={542} {/* WiFi */}
fill="#8c6239" {showWifi && wifiText ? (
fontFamily="Inter, Arial, sans-serif" <Text
fontSize={9} x={200}
fontWeight={600} y={520}
letterSpacing={1} fill="#3a2312"
> fontFamily="Inter, Arial, sans-serif"
{"\u015E\u0130FRE: coffee2026 \u2022 MASA NO: 04"} fontSize={10.5}
</Text> fontWeight={700}
letterSpacing={0.5}
>
{wifiText}
</Text>
) : null}
{/* Masa No & Sosyal */}
{showTableNo || showSocial ? (
<Text
x={200}
y={544}
fill="#8c6239"
fontFamily="Inter, Arial, sans-serif"
fontSize={10}
fontWeight={600}
letterSpacing={1}
>
{[
showTableNo && tableNo ? tableNo : "",
showSocial && socialText ? socialText : "",
]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
<Text <Text
x={200} x={200}
y={611} y={611}
fill="#3a2312" fill="#3a2312"
fontFamily="'Playfair Display', Georgia, serif" fontFamily={fontFamily}
fontSize={11} fontSize={11}
fontStyle="italic" fontStyle="italic"
fontWeight={700} fontWeight={700}
letterSpacing={2} letterSpacing={2}
> >
{"EST. 2018"} {"EST. 2026"}
</Text> </Text>
</G> </G>
</Svg> </Svg>
@@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import Svg, { import Svg, {
SvgProps,
Defs, Defs,
LinearGradient, LinearGradient,
Stop, Stop,
@@ -11,15 +10,26 @@ import Svg, {
Ellipse, Ellipse,
Circle, Circle,
Text, Text,
TSpan,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index";
interface QrTemplate3Props extends SvgProps { const QrTemplate3 = ({
qrBase64?: string; qrBase64,
} restaurantName = "BELLA NAPOLI",
subtitle = "AUTENTICA CUCINA ITALIANA",
const QrTemplate3 = ({ qrBase64, ...props }: QrTemplate3Props) => ( tableNo = "MASA NO: 01",
ctaText = "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText = "WiFi: GourmetBistro | Şifre: lezzetli01",
socialText = "@gourmetbistro",
fontFamily = "'Playfair Display', Georgia, serif",
showTableNo = true,
showSubtitle = true,
showCta = true,
showWifi = true,
showSocial = true,
...props
}: QrTemplateProps) => (
<Svg <Svg
viewBox="0 0 400 650" viewBox="0 0 400 650"
width={300} width={300}
@@ -78,16 +88,18 @@ const QrTemplate3 = ({ qrBase64, ...props }: QrTemplate3Props) => (
strokeWidth={0.8} strokeWidth={0.8}
rx={15} rx={15}
/> />
<Rect {showTableNo && tableNo ? (
width={140} <Rect
height={24} width={140}
x={130} height={24}
y={34} x={130}
fill="#faf4e6" y={34}
stroke="#8c6239" fill="#faf4e6"
strokeWidth={1.2} stroke="#8c6239"
rx={12} strokeWidth={1.2}
/> rx={12}
/>
) : null}
</G> </G>
{/* İkonlar */} {/* İkonlar */}
@@ -243,102 +255,113 @@ const QrTemplate3 = ({ qrBase64, ...props }: QrTemplate3Props) => (
{/* Metinler */} {/* Metinler */}
<G id="layer-texts"> <G id="layer-texts">
<Text {/* Masa No */}
x={200} {showTableNo && tableNo ? (
y={50} <Text
fill="#18181b" x={200}
fontFamily="Inter, Arial, sans-serif" y={50}
fontSize={11} fill="#18181b"
fontWeight={800} fontFamily="Inter, Arial, sans-serif"
letterSpacing={1.2} fontSize={11}
textAnchor="middle" fontWeight={800}
> letterSpacing={1.2}
{"MASA NO: 01"} textAnchor="middle"
</Text> >
{tableNo}
</Text>
) : null}
<Text <Text
x={200} x={200}
y={96} y={96}
fill="#3e2723" fill="#3e2723"
fontFamily="'Playfair Display', Georgia, serif" fontFamily={fontFamily}
fontSize={24} fontSize={24}
textAnchor="middle" textAnchor="middle"
> >
{"PIZZERIA"} {"PIZZERIA"}
</Text> </Text>
<Text
x={200} {/* Restoran Adı */}
y={132} {restaurantName ? (
fill="#18181b" <Text
fontFamily="'Playfair Display', Georgia, serif" x={200}
fontSize={31} y={132}
fontWeight={700} fill="#18181b"
letterSpacing={1} fontFamily={fontFamily}
textAnchor="middle" fontSize={28}
> fontWeight={700}
{"BELLA NAPOLI"} letterSpacing={1}
</Text> textAnchor="middle"
<Text >
x={200} {restaurantName}
y={222} </Text>
fill="#2e7d32" ) : null}
fontFamily="Inter, Arial, sans-serif"
fontSize={11.5} {/* Slogan */}
fontWeight={800} {showSubtitle && subtitle ? (
letterSpacing={1.8} <Text
textAnchor="middle" x={200}
> y={222}
{"AUTENTICA CUCINA ITALIANA"} fill="#2e7d32"
</Text> fontFamily="Inter, Arial, sans-serif"
<Text fontSize={11.5}
x={200} fontWeight={800}
y={474} letterSpacing={1.8}
fill="#b92b27" textAnchor="middle"
fontFamily="Inter, Arial, sans-serif" >
fontSize={13} {subtitle}
fontWeight={700} </Text>
letterSpacing={1.5} ) : null}
textAnchor="middle"
> {/* CTA */}
{"MENÜYÜ GÖRMEK"} {showCta && ctaText ? (
</Text> <Text
<Text x={200}
x={200} y={474}
y={494} fill="#b92b27"
fill="#b92b27" fontFamily={fontFamily}
fontFamily="Inter, Arial, sans-serif" fontSize={14}
fontSize={13} fontWeight={700}
fontWeight={700} letterSpacing={1.2}
letterSpacing={1.5} textAnchor="middle"
textAnchor="middle" >
> {ctaText}
{"İÇİN OKUTUN"} </Text>
</Text> ) : null}
<Text
x={200} {/* WiFi */}
y={546} {showWifi && wifiText ? (
fill="#3e2723" <Text
fontFamily="Inter, Arial, sans-serif" x={200}
fontSize={10.5} y={546}
fontWeight={700} fill="#3e2723"
textAnchor="middle" fontFamily="Inter, Arial, sans-serif"
> fontSize={10.5}
{"WiFi: GourmetBistro | Şifre: lezzetli01"} fontWeight={700}
</Text> textAnchor="middle"
<Text >
x={200} {wifiText}
y={570} </Text>
fill="#b92b27" ) : null}
fontFamily="Inter, Arial, sans-serif"
fontSize={10.5} {/* Sosyal Medya */}
fontWeight={700} {showSocial && socialText ? (
letterSpacing={0.5} <Text
textAnchor="middle" x={200}
> y={570}
{"BİZİ TAKİP EDİN | @gourmetbistro"} fill="#b92b27"
</Text> fontFamily="Inter, Arial, sans-serif"
fontSize={10.5}
fontWeight={700}
letterSpacing={0.5}
textAnchor="middle"
>
{socialText}
</Text>
) : null}
</G> </G>
</Svg> </Svg>
); );
export default QrTemplate3; export default QrTemplate3;
@@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import Svg, { import Svg, {
SvgProps,
Path, Path,
Rect, Rect,
Circle, Circle,
@@ -8,12 +7,24 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index";
interface QrTemplateLumiereProps extends SvgProps { const QrTemplateLumiere = ({
qrBase64?: string; qrBase64,
} restaurantName = "LUMIÈRE",
subtitle = "RESTAURANT & BAR | MODERN CUISINE",
const QrTemplate1 = ({ qrBase64, ...props }: QrTemplateLumiereProps) => ( tableNo = "MASA NO: 01",
ctaText = "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText = "WiFi: Lumiere_Guest | Şifre: lumiere2026",
socialText = "Rezervasyon: 0212 555 0199",
fontFamily = "'Playfair Display', Georgia, serif",
showTableNo = true,
showSubtitle = true,
showCta = true,
showWifi = true,
showSocial = true,
...props
}: QrTemplateProps) => (
<Svg <Svg
viewBox="0 0 600 600" viewBox="0 0 600 600"
width={300} width={300}
@@ -78,6 +89,7 @@ const QrTemplate1 = ({ qrBase64, ...props }: QrTemplateLumiereProps) => (
d="M560 532a18 18 0 0 0-28 28" d="M560 532a18 18 0 0 0-28 28"
/> />
<Circle cx={548} cy={548} r={2} fill="#d4af37" /> <Circle cx={548} cy={548} r={2} fill="#d4af37" />
{/* Merkez amblem */} {/* Merkez amblem */}
<G transform="translate(300 85)"> <G transform="translate(300 85)">
<Path <Path
@@ -97,6 +109,7 @@ const QrTemplate1 = ({ qrBase64, ...props }: QrTemplateLumiereProps) => (
/> />
<Circle cy={10} r={1.8} fill="#d4af37" /> <Circle cy={10} r={1.8} fill="#d4af37" />
</G> </G>
{/* Yatay çizgi */} {/* Yatay çizgi */}
<Path <Path
stroke="#d4af37" stroke="#d4af37"
@@ -104,6 +117,7 @@ const QrTemplate1 = ({ qrBase64, ...props }: QrTemplateLumiereProps) => (
strokeWidth={0.8} strokeWidth={0.8}
d="M120 198h360" d="M120 198h360"
/> />
{/* QR Placeholder / Gerçek QR */} {/* QR Placeholder / Gerçek QR */}
<G transform="translate(190 215)"> <G transform="translate(190 215)">
<Rect <Rect
@@ -149,65 +163,90 @@ const QrTemplate1 = ({ qrBase64, ...props }: QrTemplateLumiereProps) => (
</> </>
)} )}
</G> </G>
{/* Metinler */} {/* Metinler */}
<G textAnchor="middle"> <G textAnchor="middle">
<Text {/* Restoran Adı */}
x={300} {restaurantName ? (
y={152} <Text
fill="#d4af37" x={300}
fontFamily="'Playfair Display', 'Cinzel', 'Didot', Georgia, serif" y={150}
fontSize={38} fill="#d4af37"
fontWeight={500} fontFamily={fontFamily}
letterSpacing={6} fontSize={32}
> fontWeight={700}
{"LUMI\xC8RE"} letterSpacing={4}
</Text> >
<Text {restaurantName}
x={300} </Text>
y={180} ) : null}
fill="#e5c378"
fontFamily="Inter, Arial, sans-serif" {/* Slogan / Alt Başlık */}
fontSize={11} {showSubtitle && subtitle ? (
fontWeight={600} <Text
letterSpacing={4} x={300}
> y={180}
{"RESTAURANT & BAR | MODERN CUISINE"} fill="#e5c378"
</Text> fontFamily="Inter, Arial, sans-serif"
<Text fontSize={11}
x={300} fontWeight={600}
y={472} letterSpacing={3}
fill="#d4af37" >
fontFamily="Inter, Arial, sans-serif" {subtitle}
fontSize={13} </Text>
fontWeight={600} ) : null}
letterSpacing={3}
> {/* CTA / Yönlendirme */}
{"SCAN WITH CAMERA"} {showCta && ctaText ? (
</Text> <Text
<Text x={300}
x={300} y={472}
y={508} fill="#d4af37"
fill="#e2ddd2" fontFamily={fontFamily}
fontFamily="Inter, Arial, sans-serif" fontSize={13}
fontSize={10.5} fontWeight={700}
fontWeight={400} letterSpacing={2}
letterSpacing={1.2} >
> {ctaText}
{"Reservations: LumiereDining.com | 240-555-0199"} </Text>
</Text> ) : null}
<Text
x={300} {/* Sosyal / İletişim */}
y={528} {showSocial && socialText ? (
fill="#c5a059" <Text
fontFamily="Inter, Arial, sans-serif" x={300}
fontSize={9.5} y={508}
fontWeight={500} fill="#e2ddd2"
letterSpacing={1.8} fontFamily="Inter, Arial, sans-serif"
> fontSize={10.5}
{"TABLE 01 \u2022 WIFI: Lumiere_Guest"} fontWeight={500}
</Text> letterSpacing={1}
>
{socialText}
</Text>
) : null}
{/* Masa No & WiFi */}
{showTableNo || showWifi ? (
<Text
x={300}
y={528}
fill="#c5a059"
fontFamily="Inter, Arial, sans-serif"
fontSize={10}
fontWeight={600}
letterSpacing={1}
>
{[
showTableNo && tableNo ? tableNo : "",
showWifi && wifiText ? wifiText : "",
]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
</G> </G>
</Svg> </Svg>
); );
export default QrTemplate1; export default QrTemplateLumiere;
@@ -1,27 +1,52 @@
import type React from "react"; import type React from "react";
import type { SvgProps } from "react-native-svg"; import type { SvgProps } from "react-native-svg";
import QrTemplateLumiere from "./QrTemplateLumiere"; import QrTemplate1 from "./QrTemplateLumiere";
import QrTemplate2 from "./QrTemplate2"; import QrTemplate2 from "./QrTemplate2";
import QrTemplate3 from "./QrTemplate3"; import QrTemplate3 from "./QrTemplate3";
export interface QrTemplateProps extends SvgProps { export interface QrCustomTextConfig {
restaurantName?: string;
subtitle?: string;
tableNo?: string;
ctaText?: string;
wifiText?: string;
socialText?: string;
fontFamily?: string;
// Görünürlük anahtarları
showTableNo?: boolean;
showSubtitle?: boolean;
showCta?: boolean;
showWifi?: boolean;
showSocial?: boolean;
}
export interface QrTemplateProps extends SvgProps, QrCustomTextConfig {
qrBase64?: string; qrBase64?: string;
} }
export const FONT_OPTIONS = [
{ id: "sans", label: "Modern Sans", fontFamily: "System, -apple-system, Arial, sans-serif" },
{ id: "serif", label: "Zarif Serif", fontFamily: "Georgia, 'Times New Roman', serif" },
{ id: "didot", label: "Lüks Didot", fontFamily: "Didot, 'Playfair Display', Georgia, serif" },
{ id: "palatino", label: "Klasik Kitap", fontFamily: "Palatino, 'Palatino Linotype', serif" },
{ id: "trebuchet", label: "Dinamik", fontFamily: "'Trebuchet MS', Helvetica, sans-serif" },
];
// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet // Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet
export const COMPONENT_TEMPLATES: Record< export const COMPONENT_TEMPLATES: Record<
string, string,
React.FC<QrTemplateProps> React.FC<QrTemplateProps>
> = { > = {
"qr-lumiere": QrTemplateLumiere, "qr-1": QrTemplate1,
"qr-2": QrTemplate2, "qr-2": QrTemplate2,
"qr-3": QrTemplate3, "qr-3": QrTemplate3,
}; };
// Her template'in aspect ratio'su (width/height) // Her template'in aspect ratio'su (width/height)
export const TEMPLATE_ASPECT_RATIOS: Record<string, number> = { export const TEMPLATE_ASPECT_RATIOS: Record<string, number> = {
"qr-lumiere": 1, // 600x600 = 1:1 "qr-1": 1, // 600x600 = 1:1
"qr-2": 400 / 650, // 400x650 = standart kart "qr-2": 400 / 650, // 400x650 = standart kart
"qr-3": 400 / 650, // 400x650 = standart kart "qr-3": 400 / 650, // 400x650 = standart kart
}; };