feat(mobile): add multiline text with Enter, per-text independent font selection and font size controls

This commit is contained in:
AyrisAI
2026-08-21 00:55:44 +03:00
parent b72846bd91
commit 2530ab4005
8 changed files with 1522 additions and 1608 deletions
+219 -266
View File
@@ -23,7 +23,8 @@ import {
COMPONENT_TEMPLATES, COMPONENT_TEMPLATES,
TEMPLATE_ASPECT_RATIOS, TEMPLATE_ASPECT_RATIOS,
FONT_OPTIONS, FONT_OPTIONS,
type QrCustomTextConfig, type QrCustomFields,
type TextItemConfig,
} from "@/components/qr-templates"; } from "@/components/qr-templates";
@@ -45,24 +46,49 @@ export default function QrScreen() {
(p) => !!COMPONENT_TEMPLATES[p.key] (p) => !!COMPONENT_TEMPLATES[p.key]
); );
const [selectedKey, setSelectedKey] = useState<string>( const [selectedKey, setSelectedKey] = useState<string>(
availablePresets[0]?.key || "qr-lumiere" availablePresets[0]?.key || "qr-1"
); );
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 [showCustomizePanel, setShowCustomizePanel] = useState(false);
const [selectedFontId, setSelectedFontId] = useState<string>("didot");
const [customText, setCustomText] = useState<QrCustomTextConfig>({ const [fields, setFields] = useState<QrCustomFields>({
restaurantName: "Gourmet Bistro", restaurantName: {
subtitle: "RESTAURANT & BAR | MODERN CUISINE", text: "Gourmet Bistro",
tableNo: "MASA NO: 01", fontSize: 26,
ctaText: "MENÜYÜ GÖRMEK İÇİN OKUTUN", fontId: "serif",
wifiText: "WiFi: Gourmet_Guest | Şifre: lezzetli01", visible: true,
socialText: "@gourmetbistro", },
showTableNo: true, subtitle: {
showSubtitle: true, text: "RESTAURANT & BAR\nMODERN CUISINE",
showCta: true, fontSize: 11,
showWifi: true, fontId: "sans",
showSocial: true, visible: true,
},
tableNo: {
text: "MASA NO: 01",
fontSize: 11,
fontId: "sans",
visible: true,
},
ctaText: {
text: "MENÜYÜ GÖRMEK İÇİN OKUTUN",
fontSize: 13,
fontId: "serif",
visible: true,
},
wifiText: {
text: "WiFi: Gourmet_Guest | Şifre: lezzetli01",
fontSize: 10,
fontId: "sans",
visible: true,
},
socialText: {
text: "@gourmetbistro",
fontSize: 10,
fontId: "sans",
visible: true,
},
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -71,10 +97,6 @@ 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];
@@ -84,6 +106,7 @@ export default function QrScreen() {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const activeRest = await getActiveRestaurant(); const activeRest = await getActiveRestaurant();
@@ -99,9 +122,12 @@ 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) => ({ setFields((prev) => ({
...prev, ...prev,
restaurantName: restDetail.name, restaurantName: {
...prev.restaurantName,
text: restDetail.name || "Gourmet Bistro",
},
})); }));
} }
} catch { } catch {
@@ -116,6 +142,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);
@@ -355,25 +382,14 @@ export default function QrScreen() {
qrBase64={qr.pngBase64} qrBase64={qr.pngBase64}
width={displayWidth} width={displayWidth}
height={displayHeight} height={displayHeight}
restaurantName={customText.restaurantName} fields={fields}
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) */} {/* Metin & Font & Boyut Özelleştirme Kartı (Açılır/Kapanır) */}
<View <View
style={{ style={{
backgroundColor: "#18181B", backgroundColor: "#18181B",
@@ -395,22 +411,22 @@ export default function QrScreen() {
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}> <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<View <View
style={{ style={{
width: 32, width: 34,
height: 32, height: 34,
borderRadius: 8, borderRadius: 10,
backgroundColor: "rgba(212, 175, 55, 0.15)", backgroundColor: "rgba(212, 175, 55, 0.15)",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}} }}
> >
<Ionicons name="text-outline" size={18} color="#D4AF37" /> <Ionicons name="text-outline" size={19} color="#D4AF37" />
</View> </View>
<View> <View>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}> <Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
Metin ve Yazı Tipi Özelleştir Metin, Font & Boyut Özelleştir
</Text> </Text>
<Text style={{ color: "#A1A1AA", fontSize: 12 }}> <Text style={{ color: "#A1A1AA", fontSize: 12 }}>
İstediğiniz alanları düzenleyin veya kaldırın Çok satırlı metin (Enter), özel font ve boyut ayarı
</Text> </Text>
</View> </View>
</View> </View>
@@ -423,31 +439,127 @@ export default function QrScreen() {
{showCustomizePanel && ( {showCustomizePanel && (
<View style={{ marginTop: 16, gap: 16, borderTopWidth: 1, borderTopColor: "rgba(255,255,255,0.06)", paddingTop: 16 }}> <View style={{ marginTop: 16, gap: 16, borderTopWidth: 1, borderTopColor: "rgba(255,255,255,0.06)", paddingTop: 16 }}>
{/* Yazı Tipi (Font) Seçici */} {/* Yardımcı Açıklama */}
<View> <View
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700", marginBottom: 8, letterSpacing: 0.5 }}> style={{
YAZI TİPİ (FONT) backgroundColor: "rgba(212, 175, 55, 0.08)",
borderRadius: 10,
padding: 10,
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
<Ionicons name="information-circle-outline" size={16} color="#D4AF37" />
<Text style={{ color: "#E5C378", fontSize: 11.5, flex: 1 }}>
İpucu: Uzun metinlerde alt satıra geçmek için klavyeden Enter&apos;a basabilirsiniz.
</Text> </Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}> </View>
{/* Alan Düzenleyicileri */}
{(
[
{ key: "restaurantName", label: "RESTORAN BAŞLIĞI", placeholder: "Restoran Adı", min: 14, max: 48 },
{ key: "subtitle", label: "SLOGAN / ALT BAŞLIK", placeholder: "Slogan (Enter ile alt satıra geçebilir)", min: 8, max: 24 },
{ key: "ctaText", label: "YÖNLENDİRME (CTA) METNİ", placeholder: "Yönlendirme Metni", min: 9, max: 28 },
{ key: "tableNo", label: "MASA NUMARASI", placeholder: "Örn: MASA NO: 01", min: 8, max: 22 },
{ key: "wifiText", label: "WIFI BİLGİSİ", placeholder: "Örn: WiFi: Bistro | Şifre: 12345", min: 7, max: 20 },
{ key: "socialText", label: "SOSYAL MEDYA / İLETİŞİM", placeholder: "Örn: @menulio", min: 7, max: 20 },
] as const
).map(({ key, label, placeholder, min, max }) => {
const item = fields[key];
return (
<View
key={key}
style={{
gap: 8,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: "rgba(255,255,255,0.06)",
}}
>
{/* Başlık ve Görünürlük Anahtarı */}
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>{label}</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<Text style={{ color: item.visible ? "#10B981" : "#71717A", fontSize: 11, fontWeight: "600" }}>
{item.visible ? "Açık" : "Gizli"}
</Text>
<Switch
value={item.visible}
onValueChange={(v) =>
setFields((prev) => ({
...prev,
[key]: { ...prev[key], visible: v },
}))
}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{item.visible && (
<>
{/* Çok Satırlı TextInput */}
<TextInput
value={item.text}
onChangeText={(t) =>
setFields((prev) => ({
...prev,
[key]: { ...prev[key], text: t },
}))
}
placeholder={placeholder}
placeholderTextColor="#71717A"
multiline
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 12,
paddingVertical: 8,
color: "#FFFFFF",
fontSize: 13,
minHeight: 42,
}}
/>
{/* Font ve Boyut Kontrol Çubuğu */}
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
{/* Font Hapları */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ gap: 6 }}
style={{ flex: 1 }}
>
{FONT_OPTIONS.map((f) => { {FONT_OPTIONS.map((f) => {
const isSelected = f.id === selectedFontId; const isSelected = item.fontId === f.id;
return ( return (
<Pressable <Pressable
key={f.id} key={f.id}
onPress={() => setSelectedFontId(f.id)} onPress={() =>
setFields((prev) => ({
...prev,
[key]: { ...prev[key], fontId: f.id },
}))
}
style={{ style={{
backgroundColor: isSelected ? "#D4AF37" : "#242429", backgroundColor: isSelected ? "#D4AF37" : "#242429",
paddingHorizontal: 14, paddingHorizontal: 9,
paddingVertical: 8, paddingVertical: 5,
borderRadius: 10, borderRadius: 8,
borderWidth: 1, borderWidth: 1,
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.08)", borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.06)",
}} }}
> >
<Text <Text
style={{ style={{
color: isSelected ? "#0C0C0E" : "#FFFFFF", color: isSelected ? "#0C0C0E" : "#D4D4D8",
fontSize: 12, fontSize: 10.5,
fontWeight: isSelected ? "700" : "500", fontWeight: isSelected ? "700" : "500",
}} }}
> >
@@ -457,228 +569,69 @@ export default function QrScreen() {
); );
})} })}
</ScrollView> </ScrollView>
</View>
{/* Alan 1: Restoran Başlığı */} {/* Boyut Artır / Azalt */}
<View style={{ gap: 6 }}> <View
<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={{ style={{
backgroundColor: "#121215", flexDirection: "row",
alignItems: "center",
backgroundColor: "#242429",
borderRadius: 8,
paddingHorizontal: 4,
borderWidth: 1, borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)", borderColor: "rgba(255,255,255,0.06)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}} }}
/> >
</View> <Pressable
onPress={() =>
{/* Alan 2: Slogan / Alt Başlık */} setFields((prev) => ({
<View style={{ gap: 6 }}> ...prev,
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}> [key]: {
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}> ...prev[key],
SLOGAN / ALT BAŞLIK fontSize: Math.max(min, (prev[key].fontSize || 12) - 1),
</Text> },
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}> }))
<Text style={{ color: "#71717A", fontSize: 11 }}> }
{customText.showSubtitle ? "Göster" : "Gizle"} style={{ padding: 6 }}
</Text> >
<Switch <Ionicons name="remove" size={13} color="#FFFFFF" />
value={customText.showSubtitle} </Pressable>
onValueChange={(v) => setCustomText((p) => ({ ...p, showSubtitle: v }))} <Text
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={{ style={{
backgroundColor: "#121215", color: "#D4AF37",
borderWidth: 1, fontSize: 11,
borderColor: "rgba(255,255,255,0.1)", fontWeight: "700",
borderRadius: 12, minWidth: 26,
paddingHorizontal: 14, textAlign: "center",
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}} }}
/> >
{item.fontSize || 12}
</Text>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[key]: {
...prev[key],
fontSize: Math.min(max, (prev[key].fontSize || 12) + 1),
},
}))
}
style={{ padding: 6 }}
>
<Ionicons name="add" size={13} color="#FFFFFF" />
</Pressable>
</View>
</View>
</>
)}
</View>
);
})}
</View>
)} )}
</View> </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 */}
@@ -8,30 +8,27 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index"; import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate2 = ({ const QrTemplate2 = ({
qrBase64, qrBase64,
restaurantName = "The Daily Grind", fields,
subtitle = "COFFEE & KITCHEN",
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 ...props
}: QrTemplateProps) => ( }: QrTemplateProps) => {
<Svg const rName = fields?.restaurantName;
viewBox="0 0 400 650" const sub = fields?.subtitle;
width={300} const table = fields?.tableNo;
height={487.5} const cta = fields?.ctaText;
{...props} const wifi = fields?.wifiText;
> const social = fields?.socialText;
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Times New Roman', serif");
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Times New Roman', serif");
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
return (
<Svg viewBox="0 0 400 650" width={300} height={487.5} {...props}>
{/* Arka Plan */} {/* Arka Plan */}
<Path fill="#252c36" d="M0 0h400v650H0z" /> <Path fill="#252c36" d="M0 0h400v650H0z" />
@@ -193,7 +190,7 @@ const QrTemplate2 = ({
x={110} x={110}
y={132} y={132}
fill="#8c6239" fill="#8c6239"
fontFamily="Inter, Arial, sans-serif" fontFamily="System, sans-serif"
fontSize={11} fontSize={11}
fontWeight={700} fontWeight={700}
letterSpacing={1} letterSpacing={1}
@@ -205,14 +202,13 @@ const QrTemplate2 = ({
)} )}
</G> </G>
{/* Metinler */} {/* Metinler (Çok satırlı destekli) */}
<G textAnchor="middle"> <G textAnchor="middle">
{/* Üst Tag / Takı */}
<Text <Text
x={200} x={200}
y={70} y={68}
fill="#3a2312" fill="#3a2312"
fontFamily={fontFamily} fontFamily={rNameFont}
fontSize={14} fontSize={14}
fontStyle="italic" fontStyle="italic"
fontWeight={700} fontWeight={700}
@@ -222,90 +218,80 @@ const QrTemplate2 = ({
</Text> </Text>
{/* Restoran Adı */} {/* Restoran Adı */}
{restaurantName ? ( {rName?.visible !== false &&
<Text renderSvgLines({
x={200} text: rName?.text || "The Daily Grind",
y={114} x: 200,
fill="#2c1a0e" y: 110,
fontFamily={fontFamily} fill: "#2c1a0e",
fontSize={30} fontFamily: rNameFont,
fontWeight={800} fontSize: rName?.fontSize || 28,
letterSpacing={0.5} fontWeight: "bold",
> letterSpacing: 0.5,
{restaurantName} })}
</Text>
) : null}
{/* Slogan */} {/* Slogan */}
{showSubtitle && subtitle ? ( {sub?.visible !== false &&
<Text renderSvgLines({
x={200} text: sub?.text || "COFFEE & KITCHEN",
y={146} x: 200,
fill="#3a2312" y: 144,
fontFamily="Inter, Arial, sans-serif" fill: "#3a2312",
fontSize={12} fontFamily: subFont,
fontWeight={700} fontSize: sub?.fontSize || 12,
letterSpacing={2.5} fontWeight: 700,
> letterSpacing: 2.5,
{subtitle} })}
</Text>
) : null}
{/* CTA */} {/* CTA */}
{showCta && ctaText ? ( {cta?.visible !== false &&
<Text renderSvgLines({
x={200} text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
y={488} x: 200,
fill="#2c1a0e" y: 486,
fontFamily={fontFamily} fill: "#2c1a0e",
fontSize={16} fontFamily: ctaFont,
fontWeight={800} fontSize: cta?.fontSize || 15,
letterSpacing={1.2} fontWeight: "bold",
> letterSpacing: 1.2,
{ctaText} })}
</Text>
) : null}
{/* WiFi */} {/* WiFi */}
{showWifi && wifiText ? ( {wifi?.visible !== false &&
<Text renderSvgLines({
x={200} text: wifi?.text || "WiFi: DailyGrind | Şifre: coffee2026",
y={520} x: 200,
fill="#3a2312" y: 520,
fontFamily="Inter, Arial, sans-serif" fill: "#3a2312",
fontSize={10.5} fontFamily: infoFont,
fontWeight={700} fontSize: wifi?.fontSize || 10.5,
letterSpacing={0.5} fontWeight: 700,
> letterSpacing: 0.5,
{wifiText} })}
</Text>
) : null}
{/* Masa No & Sosyal */} {/* Masa No & Sosyal */}
{showTableNo || showSocial ? ( {(table?.visible !== false || social?.visible !== false) &&
<Text renderSvgLines({
x={200} text: [
y={544} table?.visible !== false && table?.text ? table.text : "",
fill="#8c6239" social?.visible !== false && social?.text ? social.text : "",
fontFamily="Inter, Arial, sans-serif"
fontSize={10}
fontWeight={600}
letterSpacing={1}
>
{[
showTableNo && tableNo ? tableNo : "",
showSocial && socialText ? socialText : "",
] ]
.filter(Boolean) .filter(Boolean)
.join(" • ")} .join(" • "),
</Text> x: 200,
) : null} y: 544,
fill: "#8c6239",
fontFamily: infoFont,
fontSize: table?.fontSize || social?.fontSize || 10,
fontWeight: 600,
letterSpacing: 1,
})}
<Text <Text
x={200} x={200}
y={611} y={611}
fill="#3a2312" fill="#3a2312"
fontFamily={fontFamily} fontFamily={rNameFont}
fontSize={11} fontSize={11}
fontStyle="italic" fontStyle="italic"
fontWeight={700} fontWeight={700}
@@ -315,6 +301,7 @@ const QrTemplate2 = ({
</Text> </Text>
</G> </G>
</Svg> </Svg>
); );
};
export default QrTemplate2; export default QrTemplate2;
@@ -12,30 +12,27 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index"; import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate3 = ({ const QrTemplate3 = ({
qrBase64, qrBase64,
restaurantName = "BELLA NAPOLI", fields,
subtitle = "AUTENTICA CUCINA ITALIANA",
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 ...props
}: QrTemplateProps) => ( }: QrTemplateProps) => {
<Svg const rName = fields?.restaurantName;
viewBox="0 0 400 650" const sub = fields?.subtitle;
width={300} const table = fields?.tableNo;
height={487.5} const cta = fields?.ctaText;
{...props} const wifi = fields?.wifiText;
> const social = fields?.socialText;
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Playfair Display', serif");
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Playfair Display', serif");
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
return (
<Svg viewBox="0 0 400 650" width={300} height={487.5} {...props}>
<Defs> <Defs>
<LinearGradient id="card-grad-3" x1="0%" x2="0%" y1="0%" y2="100%"> <LinearGradient id="card-grad-3" x1="0%" x2="0%" y1="0%" y2="100%">
<Stop offset="0%" stopColor="#fdfbf5" /> <Stop offset="0%" stopColor="#fdfbf5" />
@@ -88,7 +85,7 @@ const QrTemplate3 = ({
strokeWidth={0.8} strokeWidth={0.8}
rx={15} rx={15}
/> />
{showTableNo && tableNo ? ( {table?.visible !== false && table?.text ? (
<Rect <Rect
width={140} width={140}
height={24} height={24}
@@ -242,7 +239,7 @@ const QrTemplate3 = ({
x={110} x={110}
y={115} y={115}
fill="#71717a" fill="#71717a"
fontFamily="Inter, Arial, sans-serif" fontFamily="System, sans-serif"
fontSize={12} fontSize={12}
fontWeight={700} fontWeight={700}
textAnchor="middle" textAnchor="middle"
@@ -253,29 +250,26 @@ const QrTemplate3 = ({
)} )}
</G> </G>
{/* Metinler */} {/* Metinler (Çok satırlı destekli) */}
<G id="layer-texts"> <G id="layer-texts">
{/* Masa No */} {/* Masa No */}
{showTableNo && tableNo ? ( {table?.visible !== false &&
<Text renderSvgLines({
x={200} text: table?.text || "MASA NO: 01",
y={50} x: 200,
fill="#18181b" y: 50,
fontFamily="Inter, Arial, sans-serif" fill: "#18181b",
fontSize={11} fontFamily: infoFont,
fontWeight={800} fontSize: table?.fontSize || 11,
letterSpacing={1.2} fontWeight: "bold",
textAnchor="middle" letterSpacing: 1.2,
> })}
{tableNo}
</Text>
) : null}
<Text <Text
x={200} x={200}
y={96} y={96}
fill="#3e2723" fill="#3e2723"
fontFamily={fontFamily} fontFamily={rNameFont}
fontSize={24} fontSize={24}
textAnchor="middle" textAnchor="middle"
> >
@@ -283,85 +277,71 @@ const QrTemplate3 = ({
</Text> </Text>
{/* Restoran Adı */} {/* Restoran Adı */}
{restaurantName ? ( {rName?.visible !== false &&
<Text renderSvgLines({
x={200} text: rName?.text || "BELLA NAPOLI",
y={132} x: 200,
fill="#18181b" y: 130,
fontFamily={fontFamily} fill: "#18181b",
fontSize={28} fontFamily: rNameFont,
fontWeight={700} fontSize: rName?.fontSize || 26,
letterSpacing={1} fontWeight: "bold",
textAnchor="middle" letterSpacing: 1,
> })}
{restaurantName}
</Text>
) : null}
{/* Slogan */} {/* Slogan (Enter ile çok satır olabilir) */}
{showSubtitle && subtitle ? ( {sub?.visible !== false &&
<Text renderSvgLines({
x={200} text: sub?.text || "AUTENTICA CUCINA ITALIANA",
y={222} x: 200,
fill="#2e7d32" y: 222,
fontFamily="Inter, Arial, sans-serif" fill: "#2e7d32",
fontSize={11.5} fontFamily: subFont,
fontWeight={800} fontSize: sub?.fontSize || 11.5,
letterSpacing={1.8} fontWeight: "bold",
textAnchor="middle" letterSpacing: 1.8,
> })}
{subtitle}
</Text>
) : null}
{/* CTA */} {/* CTA */}
{showCta && ctaText ? ( {cta?.visible !== false &&
<Text renderSvgLines({
x={200} text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
y={474} x: 200,
fill="#b92b27" y: 474,
fontFamily={fontFamily} fill: "#b92b27",
fontSize={14} fontFamily: ctaFont,
fontWeight={700} fontSize: cta?.fontSize || 13,
letterSpacing={1.2} fontWeight: "bold",
textAnchor="middle" letterSpacing: 1.2,
> })}
{ctaText}
</Text>
) : null}
{/* WiFi */} {/* WiFi */}
{showWifi && wifiText ? ( {wifi?.visible !== false &&
<Text renderSvgLines({
x={200} text: wifi?.text || "WiFi: GourmetBistro | Şifre: lezzetli01",
y={546} x: 200,
fill="#3e2723" y: 546,
fontFamily="Inter, Arial, sans-serif" fill: "#3e2723",
fontSize={10.5} fontFamily: infoFont,
fontWeight={700} fontSize: wifi?.fontSize || 10.5,
textAnchor="middle" fontWeight: "bold",
> })}
{wifiText}
</Text>
) : null}
{/* Sosyal Medya */} {/* Sosyal Medya */}
{showSocial && socialText ? ( {social?.visible !== false &&
<Text renderSvgLines({
x={200} text: social?.text || "BİZİ TAKİP EDİN | @gourmetbistro",
y={570} x: 200,
fill="#b92b27" y: 570,
fontFamily="Inter, Arial, sans-serif" fill: "#b92b27",
fontSize={10.5} fontFamily: infoFont,
fontWeight={700} fontSize: social?.fontSize || 10.5,
letterSpacing={0.5} fontWeight: "bold",
textAnchor="middle" letterSpacing: 0.5,
> })}
{socialText}
</Text>
) : null}
</G> </G>
</Svg> </Svg>
); );
};
export default QrTemplate3; export default QrTemplate3;
@@ -9,24 +9,26 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index"; import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate4 = ({ const QrTemplate4 = ({
qrBase64, qrBase64,
restaurantName = "THE CRIMSON PETAL", fields,
subtitle = "FINE DINING EXPERIENCE",
tableNo = "MASA NO: 01",
ctaText = "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText = "WiFi: CrimsonGuest | Şifre: petal2026",
socialText = "@thecrimsonpetal",
fontFamily = "Georgia, 'Times New Roman', serif",
showTableNo = true,
showSubtitle = true,
showCta = true,
showWifi = true,
showSocial = true,
...props ...props
}: QrTemplateProps) => ( }: QrTemplateProps) => {
const rName = fields?.restaurantName;
const sub = fields?.subtitle;
const table = fields?.tableNo;
const cta = fields?.ctaText;
const wifi = fields?.wifiText;
const social = fields?.socialText;
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Times New Roman', serif");
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Times New Roman', serif");
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
return (
<Svg width={300} height={435} viewBox="0 0 400 580" {...props}> <Svg width={300} height={435} viewBox="0 0 400 580" {...props}>
<Defs> <Defs>
<G id="b4"> <G id="b4">
@@ -169,7 +171,7 @@ const QrTemplate4 = ({
x={75} x={75}
y={79} y={79}
fill="#c59b4b" fill="#c59b4b"
fontFamily="Inter, Arial, sans-serif" fontFamily="System, sans-serif"
fontSize={10} fontSize={10}
fontWeight={700} fontWeight={700}
letterSpacing={1} letterSpacing={1}
@@ -184,96 +186,85 @@ const QrTemplate4 = ({
{/* Metinler */} {/* Metinler */}
<G fill="#111" textAnchor="middle"> <G fill="#111" textAnchor="middle">
{/* Masa No */} {/* Masa No */}
{showTableNo && tableNo ? ( {table?.visible !== false &&
<Text renderSvgLines({
x={200} text: table?.text || "MASA NO: 01",
y={56} x: 200,
fill="#c59b4b" y: 56,
fontFamily={fontFamily} fill: "#c59b4b",
fontSize={11} fontFamily: infoFont,
fontWeight={700} fontSize: table?.fontSize || 11,
letterSpacing={1.5} fontWeight: "bold",
> letterSpacing: 1.5,
{tableNo} })}
</Text>
) : null}
{/* Restoran Başlığı */} {/* Restoran Başlığı (Enter ile alt satıra geçebilir) */}
{restaurantName ? ( {rName?.visible !== false &&
<Text renderSvgLines({
x={200} text: rName?.text || "THE CRIMSON PETAL",
y={116} x: 200,
fill="#111111" y: 114,
fontFamily={fontFamily} fill: "#111111",
fontSize={26} fontFamily: rNameFont,
fontWeight={700} fontSize: rName?.fontSize || 24,
letterSpacing={2} fontWeight: "bold",
> letterSpacing: 1.5,
{restaurantName} })}
</Text>
) : null}
{/* Slogan */} {/* Slogan */}
{showSubtitle && subtitle ? ( {sub?.visible !== false &&
<Text renderSvgLines({
x={200} text: sub?.text || "FINE DINING EXPERIENCE",
y={154} x: 200,
fill="#71717a" y: 154,
fontFamily={fontFamily} fill: "#71717a",
fontSize={11} fontFamily: subFont,
fontWeight={600} fontSize: sub?.fontSize || 11,
letterSpacing={1.8} fontWeight: 600,
> letterSpacing: 1.8,
{subtitle} })}
</Text>
) : null}
{/* CTA Metni */} {/* CTA Metni */}
{showCta && ctaText ? ( {cta?.visible !== false &&
<Text renderSvgLines({
x={200} text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
y={440} x: 200,
fill="#111111" y: 440,
fontFamily={fontFamily} fill: "#111111",
fontSize={15} fontFamily: ctaFont,
fontWeight={700} fontSize: cta?.fontSize || 15,
letterSpacing={1} fontWeight: "bold",
> letterSpacing: 1,
{ctaText} })}
</Text>
) : null}
{/* WiFi Bilgisi */} {/* WiFi Bilgisi */}
{showWifi && wifiText ? ( {wifi?.visible !== false &&
<Text renderSvgLines({
x={200} text: wifi?.text || "WiFi: CrimsonGuest | Şifre: petal2026",
y={488} x: 200,
fill="#71717a" y: 488,
fontFamily="System, -apple-system, Roboto, sans-serif" fill: "#71717a",
fontSize={10.5} fontFamily: infoFont,
fontWeight={600} fontSize: wifi?.fontSize || 10.5,
letterSpacing={0.5} fontWeight: 600,
> letterSpacing: 0.5,
{wifiText} })}
</Text>
) : null}
{/* Sosyal Medya */} {/* Sosyal Medya */}
{showSocial && socialText ? ( {social?.visible !== false &&
<Text renderSvgLines({
x={200} text: social?.text || "@thecrimsonpetal",
y={514} x: 200,
fill="#c59b4b" y: 514,
fontFamily="System, -apple-system, Roboto, sans-serif" fill: "#c59b4b",
fontSize={10.5} fontFamily: infoFont,
fontWeight={600} fontSize: social?.fontSize || 10.5,
letterSpacing={0.5} fontWeight: 600,
> letterSpacing: 0.5,
{socialText} })}
</Text>
) : null}
</G> </G>
</Svg> </Svg>
); );
};
export default QrTemplate4; export default QrTemplate4;
@@ -6,27 +6,29 @@ import Svg, {
Path, Path,
Text, Text,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index"; import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
// eslint-disable-next-line @typescript-eslint/no-require-imports // eslint-disable-next-line @typescript-eslint/no-require-imports
const bgImage = require("../../../assets/templates/5.png"); const bgImage = require("../../../assets/templates/5.png");
const QrTemplate5 = ({ const QrTemplate5 = ({
qrBase64, qrBase64,
restaurantName = "THE URBAN FORK", fields,
subtitle = "FRESH FLAVORS DAILY",
tableNo = "MASA NO: 01",
ctaText = "DİJİTAL MENÜYÜ GÖRÜNTÜLE",
wifiText = "WiFi: UrbanFork | Şifre: urban2026",
socialText = "@theurbanfork",
fontFamily = "System, -apple-system, Roboto, Arial, sans-serif",
showTableNo = true,
showSubtitle = true,
showCta = true,
showWifi = true,
showSocial = true,
...props ...props
}: QrTemplateProps) => ( }: QrTemplateProps) => {
const rName = fields?.restaurantName;
const sub = fields?.subtitle;
const table = fields?.tableNo;
const cta = fields?.ctaText;
const wifi = fields?.wifiText;
const social = fields?.socialText;
const rNameFont = getFontFamily(rName?.fontId, "System, -apple-system, Roboto, sans-serif");
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
const ctaFont = getFontFamily(cta?.fontId, "System, -apple-system, Roboto, sans-serif");
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
return (
<Svg width={300} height={394.7} viewBox="0 0 380 500" {...props}> <Svg width={300} height={394.7} viewBox="0 0 380 500" {...props}>
{/* PNG Arka Plan */} {/* PNG Arka Plan */}
<Image <Image
@@ -78,7 +80,7 @@ const QrTemplate5 = ({
x={82} x={82}
y={87} y={87}
fill="#003554" fill="#003554"
fontFamily="Inter, Arial, sans-serif" fontFamily="System, sans-serif"
fontSize={11} fontSize={11}
fontWeight={700} fontWeight={700}
letterSpacing={1} letterSpacing={1}
@@ -93,96 +95,85 @@ const QrTemplate5 = ({
{/* Metinler */} {/* Metinler */}
<G fill="#003554" textAnchor="middle"> <G fill="#003554" textAnchor="middle">
{/* Masa Numarası */} {/* Masa Numarası */}
{showTableNo && tableNo ? ( {table?.visible !== false &&
<Text renderSvgLines({
x={190} text: table?.text || "MASA NO: 01",
y={40} x: 190,
fill="#003554" y: 40,
fontFamily={fontFamily} fill: "#003554",
fontSize={11} fontFamily: infoFont,
fontWeight={800} fontSize: table?.fontSize || 11,
letterSpacing={1.5} fontWeight: "bold",
> letterSpacing: 1.5,
{tableNo} })}
</Text>
) : null}
{/* Restoran Başlığı */} {/* Restoran Başlığı */}
{restaurantName ? ( {rName?.visible !== false &&
<Text renderSvgLines({
x={190} text: rName?.text || "THE URBAN FORK",
y={86} x: 190,
fill="#003554" y: 86,
fontFamily={fontFamily} fill: "#003554",
fontSize={24} fontFamily: rNameFont,
fontWeight={900} fontSize: rName?.fontSize || 24,
letterSpacing={1} fontWeight: "bold",
> letterSpacing: 1,
{restaurantName} })}
</Text>
) : null}
{/* Yönlendirme (CTA) */} {/* Yönlendirme (CTA) */}
{showCta && ctaText ? ( {cta?.visible !== false &&
<Text renderSvgLines({
x={190} text: cta?.text || "DİJİTAL MENÜYÜ GÖRÜNTÜLE",
y={160} x: 190,
fill="#003554" y: 160,
fontFamily={fontFamily} fill: "#003554",
fontSize={13} fontFamily: ctaFont,
fontWeight={800} fontSize: cta?.fontSize || 13,
letterSpacing={0.5} fontWeight: "bold",
> letterSpacing: 0.5,
{ctaText} })}
</Text>
) : null}
{/* Slogan */} {/* Slogan (Enter ile çok satır olabilir) */}
{showSubtitle && subtitle ? ( {sub?.visible !== false &&
<Text renderSvgLines({
x={190} text: sub?.text || "FRESH FLAVORS DAILY",
y={402} x: 190,
fill="#003554" y: 402,
fontFamily={fontFamily} fill: "#003554",
fontSize={14} fontFamily: subFont,
fontWeight={900} fontSize: sub?.fontSize || 13,
letterSpacing={0.8} fontWeight: "bold",
> letterSpacing: 0.8,
{subtitle} })}
</Text>
) : null}
{/* WiFi */} {/* WiFi */}
{showWifi && wifiText ? ( {wifi?.visible !== false &&
<Text renderSvgLines({
x={190} text: wifi?.text || "WiFi: UrbanFork | Şifre: urban2026",
y={430} x: 190,
fill="#003554" y: 430,
fontFamily="System, -apple-system, Roboto, sans-serif" fill: "#003554",
fontSize={10} fontFamily: infoFont,
fontWeight={700} fontSize: wifi?.fontSize || 10,
letterSpacing={0.2} fontWeight: 700,
> letterSpacing: 0.2,
{wifiText} })}
</Text>
) : null}
{/* Sosyal Medya */} {/* Sosyal Medya */}
{showSocial && socialText ? ( {social?.visible !== false &&
<Text renderSvgLines({
x={190} text: social?.text || "@theurbanfork",
y={454} x: 190,
fill="#003554" y: 454,
fontFamily="System, -apple-system, Roboto, sans-serif" fill: "#003554",
fontSize={10} fontFamily: infoFont,
fontWeight={700} fontSize: social?.fontSize || 10,
letterSpacing={0.2} fontWeight: 700,
> letterSpacing: 0.2,
{socialText} })}
</Text>
) : null}
</G> </G>
</Svg> </Svg>
); );
};
export default QrTemplate5; export default QrTemplate5;
@@ -7,30 +7,27 @@ import Svg, {
Text, Text,
Image, Image,
} from "react-native-svg"; } from "react-native-svg";
import type { QrTemplateProps } from "./index"; import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplateLumiere = ({ const QrTemplateLumiere = ({
qrBase64, qrBase64,
restaurantName = "LUMIÈRE", fields,
subtitle = "RESTAURANT & BAR | MODERN CUISINE",
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 ...props
}: QrTemplateProps) => ( }: QrTemplateProps) => {
<Svg const rName = fields?.restaurantName;
viewBox="0 0 600 600" const sub = fields?.subtitle;
width={300} const table = fields?.tableNo;
height={300} const cta = fields?.ctaText;
{...props} const wifi = fields?.wifiText;
> const social = fields?.socialText;
const rNameFont = getFontFamily(rName?.fontId, "Georgia, 'Playfair Display', serif");
const subFont = getFontFamily(sub?.fontId, "System, -apple-system, Roboto, sans-serif");
const ctaFont = getFontFamily(cta?.fontId, "Georgia, 'Playfair Display', serif");
const infoFont = getFontFamily(wifi?.fontId, "System, -apple-system, Roboto, sans-serif");
return (
<Svg viewBox="0 0 600 600" width={300} height={300} {...props}>
<Path fill="#0b0d13" d="M0 0h600v600H0z" /> <Path fill="#0b0d13" d="M0 0h600v600H0z" />
<Rect <Rect
width={560} width={560}
@@ -61,33 +58,13 @@ const QrTemplateLumiere = ({
rx={8} rx={8}
/> />
{/* Köşe süslemeleri */} {/* Köşe süslemeleri */}
<Path <Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M40 68a18 18 0 0 0 28-28" />
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M40 68a18 18 0 0 0 28-28"
/>
<Circle cx={52} cy={52} r={2} fill="#d4af37" /> <Circle cx={52} cy={52} r={2} fill="#d4af37" />
<Path <Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M560 68a18 18 0 0 1-28-28" />
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M560 68a18 18 0 0 1-28-28"
/>
<Circle cx={548} cy={52} r={2} fill="#d4af37" /> <Circle cx={548} cy={52} r={2} fill="#d4af37" />
<Path <Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M40 532a18 18 0 0 1 28 28" />
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M40 532a18 18 0 0 1 28 28"
/>
<Circle cx={52} cy={548} r={2} fill="#d4af37" /> <Circle cx={52} cy={548} r={2} fill="#d4af37" />
<Path <Path fill="none" stroke="#d4af37" strokeWidth={1.5} d="M560 532a18 18 0 0 0-28 28" />
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
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 */}
@@ -111,23 +88,11 @@ const QrTemplateLumiere = ({
</G> </G>
{/* Yatay çizgi */} {/* Yatay çizgi */}
<Path <Path stroke="#d4af37" strokeOpacity={0.4} strokeWidth={0.8} d="M120 198h360" />
stroke="#d4af37"
strokeOpacity={0.4}
strokeWidth={0.8}
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 width={220} height={220} fill="#fff" stroke="#d4af37" strokeWidth={2} rx={20} />
width={220}
height={220}
fill="#fff"
stroke="#d4af37"
strokeWidth={2}
rx={20}
/>
{qrBase64 ? ( {qrBase64 ? (
<Image <Image
href={`data:image/png;base64,${qrBase64}`} href={`data:image/png;base64,${qrBase64}`}
@@ -153,7 +118,7 @@ const QrTemplateLumiere = ({
x={110} x={110}
y={115} y={115}
fill="#71717a" fill="#71717a"
fontFamily="Inter, Arial, sans-serif" fontFamily="System, sans-serif"
fontSize={12} fontSize={12}
fontWeight={600} fontWeight={600}
textAnchor="middle" textAnchor="middle"
@@ -164,89 +129,80 @@ const QrTemplateLumiere = ({
)} )}
</G> </G>
{/* Metinler */} {/* Metinler (Çok satırlı ve bağımsız font destekli) */}
<G textAnchor="middle"> <G textAnchor="middle">
{/* Restoran Adı */} {/* Restoran Adı */}
{restaurantName ? ( {rName?.visible !== false &&
<Text renderSvgLines({
x={300} text: rName?.text || "LUMIÈRE",
y={150} x: 300,
fill="#d4af37" y: 148,
fontFamily={fontFamily} fill: "#d4af37",
fontSize={32} fontFamily: rNameFont,
fontWeight={700} fontSize: rName?.fontSize || 30,
letterSpacing={4} fontWeight: "bold",
> letterSpacing: 4,
{restaurantName} })}
</Text>
) : null}
{/* Slogan / Alt Başlık */} {/* Slogan / Alt Başlık (Enter ile alt satıra geçebilir) */}
{showSubtitle && subtitle ? ( {sub?.visible !== false &&
<Text renderSvgLines({
x={300} text: sub?.text || "RESTAURANT & BAR | MODERN CUISINE",
y={180} x: 300,
fill="#e5c378" y: 178,
fontFamily="Inter, Arial, sans-serif" fill: "#e5c378",
fontSize={11} fontFamily: subFont,
fontWeight={600} fontSize: sub?.fontSize || 11,
letterSpacing={3} fontWeight: 600,
> letterSpacing: 2.5,
{subtitle} })}
</Text>
) : null}
{/* CTA / Yönlendirme */} {/* CTA */}
{showCta && ctaText ? ( {cta?.visible !== false &&
<Text renderSvgLines({
x={300} text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
y={472} x: 300,
fill="#d4af37" y: 472,
fontFamily={fontFamily} fill: "#d4af37",
fontSize={13} fontFamily: ctaFont,
fontWeight={700} fontSize: cta?.fontSize || 13,
letterSpacing={2} fontWeight: "bold",
> letterSpacing: 2,
{ctaText} })}
</Text>
) : null}
{/* Sosyal / İletişim */} {/* Sosyal Medya / İletişim */}
{showSocial && socialText ? ( {social?.visible !== false &&
<Text renderSvgLines({
x={300} text: social?.text || "Rezervasyon: 0212 555 0199",
y={508} x: 300,
fill="#e2ddd2" y: 504,
fontFamily="Inter, Arial, sans-serif" fill: "#e2ddd2",
fontSize={10.5} fontFamily: infoFont,
fontWeight={500} fontSize: social?.fontSize || 10.5,
letterSpacing={1} fontWeight: 500,
> letterSpacing: 1,
{socialText} })}
</Text>
) : null}
{/* Masa No & WiFi */} {/* Masa No & WiFi */}
{showTableNo || showWifi ? ( {(table?.visible !== false || wifi?.visible !== false) &&
<Text renderSvgLines({
x={300} text: [
y={528} table?.visible !== false && table?.text ? table.text : "",
fill="#c5a059" wifi?.visible !== false && wifi?.text ? wifi.text : "",
fontFamily="Inter, Arial, sans-serif"
fontSize={10}
fontWeight={600}
letterSpacing={1}
>
{[
showTableNo && tableNo ? tableNo : "",
showWifi && wifiText ? wifiText : "",
] ]
.filter(Boolean) .filter(Boolean)
.join(" • ")} .join(" • "),
</Text> x: 300,
) : null} y: 526,
fill: "#c5a059",
fontFamily: infoFont,
fontSize: table?.fontSize || wifi?.fontSize || 10,
fontWeight: 600,
letterSpacing: 1,
})}
</G> </G>
</Svg> </Svg>
); );
};
export default QrTemplateLumiere; export default QrTemplateLumiere;
@@ -1,77 +0,0 @@
import type React from "react";
import type { SvgProps } from "react-native-svg";
import QrTemplate1 from "./QrTemplateLumiere";
import QrTemplate2 from "./QrTemplate2";
import QrTemplate3 from "./QrTemplate3";
import QrTemplate4 from "./QrTemplate4";
import QrTemplate5 from "./QrTemplate5";
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;
}
// Türkçe karakterleri (İ, ı, Ş, ş, Ğ, ğ, Ü, ü, Ö, ö, Ç, ç) eksiksiz destekleyen sistem fontları
export const FONT_OPTIONS = [
{
id: "sans",
label: "Modern Sans (Sistem)",
fontFamily: "System, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif",
},
{
id: "serif",
label: "Zarif Serif (Georgia)",
fontFamily: "Georgia, 'Times New Roman', serif",
},
{
id: "times",
label: "Klasik (Times New Roman)",
fontFamily: "'Times New Roman', Times, Georgia, serif",
},
{
id: "palatino",
label: "Lüks Palatino",
fontFamily: "Palatino, 'Palatino Linotype', Georgia, serif",
},
{
id: "trebuchet",
label: "Dinamik (Trebuchet)",
fontFamily: "'Trebuchet MS', 'Segoe UI', Arial, sans-serif",
},
];
// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet
export const COMPONENT_TEMPLATES: Record<
string,
React.FC<QrTemplateProps>
> = {
"qr-1": QrTemplate1,
"qr-2": QrTemplate2,
"qr-3": QrTemplate3,
"qr-4": QrTemplate4,
"qr-5": QrTemplate5,
};
// Her template'in aspect ratio'su (width/height)
export const TEMPLATE_ASPECT_RATIOS: Record<string, number> = {
"qr-1": 1, // 600x600 = 1:1
"qr-2": 400 / 650, // 400x650 = standart kart
"qr-3": 400 / 650, // 400x650 = standart kart
"qr-4": 400 / 580, // 400x580 = zarif altın çerçeve
"qr-5": 380 / 500, // 380x500 = PNG arka planlı modern kart
};
@@ -0,0 +1,133 @@
import React from "react";
import { Text as SvgText, type SvgProps } from "react-native-svg";
import QrTemplate1 from "./QrTemplateLumiere";
import QrTemplate2 from "./QrTemplate2";
import QrTemplate3 from "./QrTemplate3";
import QrTemplate4 from "./QrTemplate4";
import QrTemplate5 from "./QrTemplate5";
export interface TextItemConfig {
text: string;
fontSize?: number;
fontId?: string;
visible?: boolean;
}
export interface QrCustomFields {
restaurantName: TextItemConfig;
subtitle: TextItemConfig;
tableNo: TextItemConfig;
ctaText: TextItemConfig;
wifiText: TextItemConfig;
socialText: TextItemConfig;
}
export interface QrTemplateProps extends SvgProps {
qrBase64?: string;
fields?: Partial<QrCustomFields>;
}
// Türkçe karakterleri (İ, ı, Ş, ş, Ğ, ğ, Ü, ü, Ö, ö, Ç, ç) eksiksiz destekleyen sistem fontları
export const FONT_OPTIONS = [
{
id: "sans",
label: "Modern Sans",
fontFamily: "System, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif",
},
{
id: "serif",
label: "Zarif Serif",
fontFamily: "Georgia, 'Times New Roman', serif",
},
{
id: "times",
label: "Klasik (Times)",
fontFamily: "'Times New Roman', Times, Georgia, serif",
},
{
id: "palatino",
label: "Lüks Palatino",
fontFamily: "Palatino, 'Palatino Linotype', Georgia, serif",
},
{
id: "trebuchet",
label: "Dinamik",
fontFamily: "'Trebuchet MS', 'Segoe UI', Arial, sans-serif",
},
];
export function getFontFamily(fontId?: string, defaultFamily = "Georgia, serif"): string {
if (!fontId) return defaultFamily;
const found = FONT_OPTIONS.find((f) => f.id === fontId);
return found ? found.fontFamily : defaultFamily;
}
// Çok satırlı (Enter / \n) metinleri SVG içinde alt alta kusursuz render eden yardımcı fonksiyon
export function renderSvgLines({
text,
x,
y,
lineHeight,
fill,
fontFamily,
fontSize,
fontWeight = "normal",
letterSpacing,
textAnchor = "middle",
}: {
text?: string;
x: number;
y: number;
lineHeight?: number;
fill?: string;
fontFamily?: string;
fontSize?: number;
fontWeight?: string | number;
letterSpacing?: number;
textAnchor?: "middle" | "start" | "end";
}) {
if (!text) return null;
const lines = text.split("\n");
const computedLineHeight = lineHeight || (fontSize ? fontSize * 1.25 : 18);
return (
<>
{lines.map((line, idx) => (
<SvgText
key={idx}
x={x}
y={y + idx * computedLineHeight}
fill={fill}
fontFamily={fontFamily}
fontSize={fontSize}
fontWeight={fontWeight}
letterSpacing={letterSpacing}
textAnchor={textAnchor}
>
{line}
</SvgText>
))}
</>
);
}
// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet
export const COMPONENT_TEMPLATES: Record<
string,
React.FC<QrTemplateProps>
> = {
"qr-1": QrTemplate1,
"qr-2": QrTemplate2,
"qr-3": QrTemplate3,
"qr-4": QrTemplate4,
"qr-5": QrTemplate5,
};
// Her template'in aspect ratio'su (width/height)
export const TEMPLATE_ASPECT_RATIOS: Record<string, number> = {
"qr-1": 1, // 600x600 = 1:1
"qr-2": 400 / 650, // 400x650 = standart kart
"qr-3": 400 / 650, // 400x650 = standart kart
"qr-4": 400 / 580, // 400x580 = zarif altın çerçeve
"qr-5": 380 / 500, // 380x500 = PNG arka planlı modern kart
};