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,
TEMPLATE_ASPECT_RATIOS,
FONT_OPTIONS,
type QrCustomTextConfig,
type QrCustomFields,
type TextItemConfig,
} from "@/components/qr-templates";
@@ -45,24 +46,49 @@ export default function QrScreen() {
(p) => !!COMPONENT_TEMPLATES[p.key]
);
const [selectedKey, setSelectedKey] = useState<string>(
availablePresets[0]?.key || "qr-lumiere"
availablePresets[0]?.key || "qr-1"
);
const [exportingPng, setExportingPng] = 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,
const [fields, setFields] = useState<QrCustomFields>({
restaurantName: {
text: "Gourmet Bistro",
fontSize: 26,
fontId: "serif",
visible: true,
},
subtitle: {
text: "RESTAURANT & BAR\nMODERN CUISINE",
fontSize: 11,
fontId: "sans",
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
@@ -71,10 +97,6 @@ export default function QrScreen() {
const activePreset: QrStandPreset =
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
const TemplateComponent =
COMPONENT_TEMPLATES[selectedKey] ?? Object.values(COMPONENT_TEMPLATES)[0];
@@ -84,6 +106,7 @@ export default function QrScreen() {
useEffect(() => {
(async () => {
const activeRest = await getActiveRestaurant();
@@ -99,9 +122,12 @@ export default function QrScreen() {
const restDetail = await api.get<{ name?: string }>(`/restaurants/${activeRest.restaurantId}`);
if (restDetail?.name) {
setRestaurantName(restDetail.name);
setCustomText((prev) => ({
setFields((prev) => ({
...prev,
restaurantName: restDetail.name,
restaurantName: {
...prev.restaurantName,
text: restDetail.name || "Gourmet Bistro",
},
}));
}
} catch {
@@ -116,6 +142,7 @@ export default function QrScreen() {
}, []);
async function handleCopy() {
if (!qr?.targetUrl) return;
await Clipboard.setStringAsync(qr.targetUrl);
@@ -355,25 +382,14 @@ export default function QrScreen() {
qrBase64={qr.pngBase64}
width={displayWidth}
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}
fields={fields}
/>
) : null}
</View>
</ViewShot>
</View>
{/* Metin & Font Özelleştirme Kartı (Açılır/Kapanır) */}
{/* Metin & Font & Boyut Özelleştirme Kartı (Açılır/Kapanır) */}
<View
style={{
backgroundColor: "#18181B",
@@ -395,22 +411,22 @@ export default function QrScreen() {
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<View
style={{
width: 32,
height: 32,
borderRadius: 8,
width: 34,
height: 34,
borderRadius: 10,
backgroundColor: "rgba(212, 175, 55, 0.15)",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="text-outline" size={18} color="#D4AF37" />
<Ionicons name="text-outline" size={19} color="#D4AF37" />
</View>
<View>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
Metin ve Yazı Tipi Özelleştir
Metin, Font & Boyut Özelleştir
</Text>
<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>
</View>
</View>
@@ -423,31 +439,127 @@ export default function QrScreen() {
{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)
{/* Yardımcı Açıklama */}
<View
style={{
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>
<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) => {
const isSelected = f.id === selectedFontId;
const isSelected = item.fontId === f.id;
return (
<Pressable
key={f.id}
onPress={() => setSelectedFontId(f.id)}
onPress={() =>
setFields((prev) => ({
...prev,
[key]: { ...prev[key], fontId: f.id },
}))
}
style={{
backgroundColor: isSelected ? "#D4AF37" : "#242429",
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 10,
paddingHorizontal: 9,
paddingVertical: 5,
borderRadius: 8,
borderWidth: 1,
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.08)",
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.06)",
}}
>
<Text
style={{
color: isSelected ? "#0C0C0E" : "#FFFFFF",
fontSize: 12,
color: isSelected ? "#0C0C0E" : "#D4D4D8",
fontSize: 10.5,
fontWeight: isSelected ? "700" : "500",
}}
>
@@ -457,228 +569,69 @@ export default function QrScreen() {
);
})}
</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"
{/* Boyut Artır / Azalt */}
<View
style={{
backgroundColor: "#121215",
flexDirection: "row",
alignItems: "center",
backgroundColor: "#242429",
borderRadius: 8,
paddingHorizontal: 4,
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
borderColor: "rgba(255,255,255,0.06)",
}}
/>
</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"
>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[key]: {
...prev[key],
fontSize: Math.max(min, (prev[key].fontSize || 12) - 1),
},
}))
}
style={{ padding: 6 }}
>
<Ionicons name="remove" size={13} color="#FFFFFF" />
</Pressable>
<Text
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
color: "#D4AF37",
fontSize: 11,
fontWeight: "700",
minWidth: 26,
textAlign: "center",
}}
/>
>
{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>
{/* 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 */}
@@ -8,30 +8,27 @@ import Svg, {
Text,
Image,
} from "react-native-svg";
import type { QrTemplateProps } from "./index";
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate2 = ({
qrBase64,
restaurantName = "The Daily Grind",
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,
fields,
...props
}: QrTemplateProps) => (
<Svg
viewBox="0 0 400 650"
width={300}
height={487.5}
{...props}
>
}: 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 viewBox="0 0 400 650" width={300} height={487.5} {...props}>
{/* Arka Plan */}
<Path fill="#252c36" d="M0 0h400v650H0z" />
@@ -193,7 +190,7 @@ const QrTemplate2 = ({
x={110}
y={132}
fill="#8c6239"
fontFamily="Inter, Arial, sans-serif"
fontFamily="System, sans-serif"
fontSize={11}
fontWeight={700}
letterSpacing={1}
@@ -205,14 +202,13 @@ const QrTemplate2 = ({
)}
</G>
{/* Metinler */}
{/* Metinler (Çok satırlı destekli) */}
<G textAnchor="middle">
{/* Üst Tag / Takı */}
<Text
x={200}
y={70}
y={68}
fill="#3a2312"
fontFamily={fontFamily}
fontFamily={rNameFont}
fontSize={14}
fontStyle="italic"
fontWeight={700}
@@ -222,90 +218,80 @@ const QrTemplate2 = ({
</Text>
{/* Restoran Adı */}
{restaurantName ? (
<Text
x={200}
y={114}
fill="#2c1a0e"
fontFamily={fontFamily}
fontSize={30}
fontWeight={800}
letterSpacing={0.5}
>
{restaurantName}
</Text>
) : null}
{rName?.visible !== false &&
renderSvgLines({
text: rName?.text || "The Daily Grind",
x: 200,
y: 110,
fill: "#2c1a0e",
fontFamily: rNameFont,
fontSize: rName?.fontSize || 28,
fontWeight: "bold",
letterSpacing: 0.5,
})}
{/* Slogan */}
{showSubtitle && subtitle ? (
<Text
x={200}
y={146}
fill="#3a2312"
fontFamily="Inter, Arial, sans-serif"
fontSize={12}
fontWeight={700}
letterSpacing={2.5}
>
{subtitle}
</Text>
) : null}
{sub?.visible !== false &&
renderSvgLines({
text: sub?.text || "COFFEE & KITCHEN",
x: 200,
y: 144,
fill: "#3a2312",
fontFamily: subFont,
fontSize: sub?.fontSize || 12,
fontWeight: 700,
letterSpacing: 2.5,
})}
{/* CTA */}
{showCta && ctaText ? (
<Text
x={200}
y={488}
fill="#2c1a0e"
fontFamily={fontFamily}
fontSize={16}
fontWeight={800}
letterSpacing={1.2}
>
{ctaText}
</Text>
) : null}
{cta?.visible !== false &&
renderSvgLines({
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
x: 200,
y: 486,
fill: "#2c1a0e",
fontFamily: ctaFont,
fontSize: cta?.fontSize || 15,
fontWeight: "bold",
letterSpacing: 1.2,
})}
{/* WiFi */}
{showWifi && wifiText ? (
<Text
x={200}
y={520}
fill="#3a2312"
fontFamily="Inter, Arial, sans-serif"
fontSize={10.5}
fontWeight={700}
letterSpacing={0.5}
>
{wifiText}
</Text>
) : null}
{wifi?.visible !== false &&
renderSvgLines({
text: wifi?.text || "WiFi: DailyGrind | Şifre: coffee2026",
x: 200,
y: 520,
fill: "#3a2312",
fontFamily: infoFont,
fontSize: wifi?.fontSize || 10.5,
fontWeight: 700,
letterSpacing: 0.5,
})}
{/* 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 : "",
{(table?.visible !== false || social?.visible !== false) &&
renderSvgLines({
text: [
table?.visible !== false && table?.text ? table.text : "",
social?.visible !== false && social?.text ? social.text : "",
]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
.join(" • "),
x: 200,
y: 544,
fill: "#8c6239",
fontFamily: infoFont,
fontSize: table?.fontSize || social?.fontSize || 10,
fontWeight: 600,
letterSpacing: 1,
})}
<Text
x={200}
y={611}
fill="#3a2312"
fontFamily={fontFamily}
fontFamily={rNameFont}
fontSize={11}
fontStyle="italic"
fontWeight={700}
@@ -316,5 +302,6 @@ const QrTemplate2 = ({
</G>
</Svg>
);
};
export default QrTemplate2;
@@ -12,30 +12,27 @@ import Svg, {
Text,
Image,
} from "react-native-svg";
import type { QrTemplateProps } from "./index";
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate3 = ({
qrBase64,
restaurantName = "BELLA NAPOLI",
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,
fields,
...props
}: QrTemplateProps) => (
<Svg
viewBox="0 0 400 650"
width={300}
height={487.5}
{...props}
>
}: 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, '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>
<LinearGradient id="card-grad-3" x1="0%" x2="0%" y1="0%" y2="100%">
<Stop offset="0%" stopColor="#fdfbf5" />
@@ -88,7 +85,7 @@ const QrTemplate3 = ({
strokeWidth={0.8}
rx={15}
/>
{showTableNo && tableNo ? (
{table?.visible !== false && table?.text ? (
<Rect
width={140}
height={24}
@@ -242,7 +239,7 @@ const QrTemplate3 = ({
x={110}
y={115}
fill="#71717a"
fontFamily="Inter, Arial, sans-serif"
fontFamily="System, sans-serif"
fontSize={12}
fontWeight={700}
textAnchor="middle"
@@ -253,29 +250,26 @@ const QrTemplate3 = ({
)}
</G>
{/* Metinler */}
{/* Metinler (Çok satırlı destekli) */}
<G id="layer-texts">
{/* Masa No */}
{showTableNo && tableNo ? (
<Text
x={200}
y={50}
fill="#18181b"
fontFamily="Inter, Arial, sans-serif"
fontSize={11}
fontWeight={800}
letterSpacing={1.2}
textAnchor="middle"
>
{tableNo}
</Text>
) : null}
{table?.visible !== false &&
renderSvgLines({
text: table?.text || "MASA NO: 01",
x: 200,
y: 50,
fill: "#18181b",
fontFamily: infoFont,
fontSize: table?.fontSize || 11,
fontWeight: "bold",
letterSpacing: 1.2,
})}
<Text
x={200}
y={96}
fill="#3e2723"
fontFamily={fontFamily}
fontFamily={rNameFont}
fontSize={24}
textAnchor="middle"
>
@@ -283,85 +277,71 @@ const QrTemplate3 = ({
</Text>
{/* Restoran Adı */}
{restaurantName ? (
<Text
x={200}
y={132}
fill="#18181b"
fontFamily={fontFamily}
fontSize={28}
fontWeight={700}
letterSpacing={1}
textAnchor="middle"
>
{restaurantName}
</Text>
) : null}
{rName?.visible !== false &&
renderSvgLines({
text: rName?.text || "BELLA NAPOLI",
x: 200,
y: 130,
fill: "#18181b",
fontFamily: rNameFont,
fontSize: rName?.fontSize || 26,
fontWeight: "bold",
letterSpacing: 1,
})}
{/* Slogan */}
{showSubtitle && subtitle ? (
<Text
x={200}
y={222}
fill="#2e7d32"
fontFamily="Inter, Arial, sans-serif"
fontSize={11.5}
fontWeight={800}
letterSpacing={1.8}
textAnchor="middle"
>
{subtitle}
</Text>
) : null}
{/* Slogan (Enter ile çok satır olabilir) */}
{sub?.visible !== false &&
renderSvgLines({
text: sub?.text || "AUTENTICA CUCINA ITALIANA",
x: 200,
y: 222,
fill: "#2e7d32",
fontFamily: subFont,
fontSize: sub?.fontSize || 11.5,
fontWeight: "bold",
letterSpacing: 1.8,
})}
{/* CTA */}
{showCta && ctaText ? (
<Text
x={200}
y={474}
fill="#b92b27"
fontFamily={fontFamily}
fontSize={14}
fontWeight={700}
letterSpacing={1.2}
textAnchor="middle"
>
{ctaText}
</Text>
) : null}
{cta?.visible !== false &&
renderSvgLines({
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
x: 200,
y: 474,
fill: "#b92b27",
fontFamily: ctaFont,
fontSize: cta?.fontSize || 13,
fontWeight: "bold",
letterSpacing: 1.2,
})}
{/* WiFi */}
{showWifi && wifiText ? (
<Text
x={200}
y={546}
fill="#3e2723"
fontFamily="Inter, Arial, sans-serif"
fontSize={10.5}
fontWeight={700}
textAnchor="middle"
>
{wifiText}
</Text>
) : null}
{wifi?.visible !== false &&
renderSvgLines({
text: wifi?.text || "WiFi: GourmetBistro | Şifre: lezzetli01",
x: 200,
y: 546,
fill: "#3e2723",
fontFamily: infoFont,
fontSize: wifi?.fontSize || 10.5,
fontWeight: "bold",
})}
{/* Sosyal Medya */}
{showSocial && socialText ? (
<Text
x={200}
y={570}
fill="#b92b27"
fontFamily="Inter, Arial, sans-serif"
fontSize={10.5}
fontWeight={700}
letterSpacing={0.5}
textAnchor="middle"
>
{socialText}
</Text>
) : null}
{social?.visible !== false &&
renderSvgLines({
text: social?.text || "BİZİ TAKİP EDİN | @gourmetbistro",
x: 200,
y: 570,
fill: "#b92b27",
fontFamily: infoFont,
fontSize: social?.fontSize || 10.5,
fontWeight: "bold",
letterSpacing: 0.5,
})}
</G>
</Svg>
);
};
export default QrTemplate3;
@@ -9,24 +9,26 @@ import Svg, {
Text,
Image,
} from "react-native-svg";
import type { QrTemplateProps } from "./index";
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplate4 = ({
qrBase64,
restaurantName = "THE CRIMSON PETAL",
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,
fields,
...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}>
<Defs>
<G id="b4">
@@ -169,7 +171,7 @@ const QrTemplate4 = ({
x={75}
y={79}
fill="#c59b4b"
fontFamily="Inter, Arial, sans-serif"
fontFamily="System, sans-serif"
fontSize={10}
fontWeight={700}
letterSpacing={1}
@@ -184,96 +186,85 @@ const QrTemplate4 = ({
{/* Metinler */}
<G fill="#111" textAnchor="middle">
{/* Masa No */}
{showTableNo && tableNo ? (
<Text
x={200}
y={56}
fill="#c59b4b"
fontFamily={fontFamily}
fontSize={11}
fontWeight={700}
letterSpacing={1.5}
>
{tableNo}
</Text>
) : null}
{table?.visible !== false &&
renderSvgLines({
text: table?.text || "MASA NO: 01",
x: 200,
y: 56,
fill: "#c59b4b",
fontFamily: infoFont,
fontSize: table?.fontSize || 11,
fontWeight: "bold",
letterSpacing: 1.5,
})}
{/* Restoran Başlığı */}
{restaurantName ? (
<Text
x={200}
y={116}
fill="#111111"
fontFamily={fontFamily}
fontSize={26}
fontWeight={700}
letterSpacing={2}
>
{restaurantName}
</Text>
) : null}
{/* Restoran Başlığı (Enter ile alt satıra geçebilir) */}
{rName?.visible !== false &&
renderSvgLines({
text: rName?.text || "THE CRIMSON PETAL",
x: 200,
y: 114,
fill: "#111111",
fontFamily: rNameFont,
fontSize: rName?.fontSize || 24,
fontWeight: "bold",
letterSpacing: 1.5,
})}
{/* Slogan */}
{showSubtitle && subtitle ? (
<Text
x={200}
y={154}
fill="#71717a"
fontFamily={fontFamily}
fontSize={11}
fontWeight={600}
letterSpacing={1.8}
>
{subtitle}
</Text>
) : null}
{sub?.visible !== false &&
renderSvgLines({
text: sub?.text || "FINE DINING EXPERIENCE",
x: 200,
y: 154,
fill: "#71717a",
fontFamily: subFont,
fontSize: sub?.fontSize || 11,
fontWeight: 600,
letterSpacing: 1.8,
})}
{/* CTA Metni */}
{showCta && ctaText ? (
<Text
x={200}
y={440}
fill="#111111"
fontFamily={fontFamily}
fontSize={15}
fontWeight={700}
letterSpacing={1}
>
{ctaText}
</Text>
) : null}
{cta?.visible !== false &&
renderSvgLines({
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
x: 200,
y: 440,
fill: "#111111",
fontFamily: ctaFont,
fontSize: cta?.fontSize || 15,
fontWeight: "bold",
letterSpacing: 1,
})}
{/* WiFi Bilgisi */}
{showWifi && wifiText ? (
<Text
x={200}
y={488}
fill="#71717a"
fontFamily="System, -apple-system, Roboto, sans-serif"
fontSize={10.5}
fontWeight={600}
letterSpacing={0.5}
>
{wifiText}
</Text>
) : null}
{wifi?.visible !== false &&
renderSvgLines({
text: wifi?.text || "WiFi: CrimsonGuest | Şifre: petal2026",
x: 200,
y: 488,
fill: "#71717a",
fontFamily: infoFont,
fontSize: wifi?.fontSize || 10.5,
fontWeight: 600,
letterSpacing: 0.5,
})}
{/* Sosyal Medya */}
{showSocial && socialText ? (
<Text
x={200}
y={514}
fill="#c59b4b"
fontFamily="System, -apple-system, Roboto, sans-serif"
fontSize={10.5}
fontWeight={600}
letterSpacing={0.5}
>
{socialText}
</Text>
) : null}
{social?.visible !== false &&
renderSvgLines({
text: social?.text || "@thecrimsonpetal",
x: 200,
y: 514,
fill: "#c59b4b",
fontFamily: infoFont,
fontSize: social?.fontSize || 10.5,
fontWeight: 600,
letterSpacing: 0.5,
})}
</G>
</Svg>
);
};
export default QrTemplate4;
@@ -6,27 +6,29 @@ import Svg, {
Path,
Text,
} 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
const bgImage = require("../../../assets/templates/5.png");
const QrTemplate5 = ({
qrBase64,
restaurantName = "THE URBAN FORK",
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,
fields,
...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}>
{/* PNG Arka Plan */}
<Image
@@ -78,7 +80,7 @@ const QrTemplate5 = ({
x={82}
y={87}
fill="#003554"
fontFamily="Inter, Arial, sans-serif"
fontFamily="System, sans-serif"
fontSize={11}
fontWeight={700}
letterSpacing={1}
@@ -93,96 +95,85 @@ const QrTemplate5 = ({
{/* Metinler */}
<G fill="#003554" textAnchor="middle">
{/* Masa Numarası */}
{showTableNo && tableNo ? (
<Text
x={190}
y={40}
fill="#003554"
fontFamily={fontFamily}
fontSize={11}
fontWeight={800}
letterSpacing={1.5}
>
{tableNo}
</Text>
) : null}
{table?.visible !== false &&
renderSvgLines({
text: table?.text || "MASA NO: 01",
x: 190,
y: 40,
fill: "#003554",
fontFamily: infoFont,
fontSize: table?.fontSize || 11,
fontWeight: "bold",
letterSpacing: 1.5,
})}
{/* Restoran Başlığı */}
{restaurantName ? (
<Text
x={190}
y={86}
fill="#003554"
fontFamily={fontFamily}
fontSize={24}
fontWeight={900}
letterSpacing={1}
>
{restaurantName}
</Text>
) : null}
{rName?.visible !== false &&
renderSvgLines({
text: rName?.text || "THE URBAN FORK",
x: 190,
y: 86,
fill: "#003554",
fontFamily: rNameFont,
fontSize: rName?.fontSize || 24,
fontWeight: "bold",
letterSpacing: 1,
})}
{/* Yönlendirme (CTA) */}
{showCta && ctaText ? (
<Text
x={190}
y={160}
fill="#003554"
fontFamily={fontFamily}
fontSize={13}
fontWeight={800}
letterSpacing={0.5}
>
{ctaText}
</Text>
) : null}
{cta?.visible !== false &&
renderSvgLines({
text: cta?.text || "DİJİTAL MENÜYÜ GÖRÜNTÜLE",
x: 190,
y: 160,
fill: "#003554",
fontFamily: ctaFont,
fontSize: cta?.fontSize || 13,
fontWeight: "bold",
letterSpacing: 0.5,
})}
{/* Slogan */}
{showSubtitle && subtitle ? (
<Text
x={190}
y={402}
fill="#003554"
fontFamily={fontFamily}
fontSize={14}
fontWeight={900}
letterSpacing={0.8}
>
{subtitle}
</Text>
) : null}
{/* Slogan (Enter ile çok satır olabilir) */}
{sub?.visible !== false &&
renderSvgLines({
text: sub?.text || "FRESH FLAVORS DAILY",
x: 190,
y: 402,
fill: "#003554",
fontFamily: subFont,
fontSize: sub?.fontSize || 13,
fontWeight: "bold",
letterSpacing: 0.8,
})}
{/* WiFi */}
{showWifi && wifiText ? (
<Text
x={190}
y={430}
fill="#003554"
fontFamily="System, -apple-system, Roboto, sans-serif"
fontSize={10}
fontWeight={700}
letterSpacing={0.2}
>
{wifiText}
</Text>
) : null}
{wifi?.visible !== false &&
renderSvgLines({
text: wifi?.text || "WiFi: UrbanFork | Şifre: urban2026",
x: 190,
y: 430,
fill: "#003554",
fontFamily: infoFont,
fontSize: wifi?.fontSize || 10,
fontWeight: 700,
letterSpacing: 0.2,
})}
{/* Sosyal Medya */}
{showSocial && socialText ? (
<Text
x={190}
y={454}
fill="#003554"
fontFamily="System, -apple-system, Roboto, sans-serif"
fontSize={10}
fontWeight={700}
letterSpacing={0.2}
>
{socialText}
</Text>
) : null}
{social?.visible !== false &&
renderSvgLines({
text: social?.text || "@theurbanfork",
x: 190,
y: 454,
fill: "#003554",
fontFamily: infoFont,
fontSize: social?.fontSize || 10,
fontWeight: 700,
letterSpacing: 0.2,
})}
</G>
</Svg>
);
};
export default QrTemplate5;
@@ -7,30 +7,27 @@ import Svg, {
Text,
Image,
} from "react-native-svg";
import type { QrTemplateProps } from "./index";
import { type QrTemplateProps, getFontFamily, renderSvgLines } from "./index";
const QrTemplateLumiere = ({
qrBase64,
restaurantName = "LUMIÈRE",
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,
fields,
...props
}: QrTemplateProps) => (
<Svg
viewBox="0 0 600 600"
width={300}
height={300}
{...props}
>
}: 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, '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" />
<Rect
width={560}
@@ -61,33 +58,13 @@ const QrTemplateLumiere = ({
rx={8}
/>
{/* Köşe süslemeleri */}
<Path
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M40 68a18 18 0 0 0 28-28"
/>
<Path 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" />
<Path
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M560 68a18 18 0 0 1-28-28"
/>
<Path 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" />
<Path
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M40 532a18 18 0 0 1 28 28"
/>
<Path 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" />
<Path
fill="none"
stroke="#d4af37"
strokeWidth={1.5}
d="M560 532a18 18 0 0 0-28 28"
/>
<Path 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" />
{/* Merkez amblem */}
@@ -111,23 +88,11 @@ const QrTemplateLumiere = ({
</G>
{/* Yatay çizgi */}
<Path
stroke="#d4af37"
strokeOpacity={0.4}
strokeWidth={0.8}
d="M120 198h360"
/>
<Path stroke="#d4af37" strokeOpacity={0.4} strokeWidth={0.8} d="M120 198h360" />
{/* QR Placeholder / Gerçek QR */}
<G transform="translate(190 215)">
<Rect
width={220}
height={220}
fill="#fff"
stroke="#d4af37"
strokeWidth={2}
rx={20}
/>
<Rect width={220} height={220} fill="#fff" stroke="#d4af37" strokeWidth={2} rx={20} />
{qrBase64 ? (
<Image
href={`data:image/png;base64,${qrBase64}`}
@@ -153,7 +118,7 @@ const QrTemplateLumiere = ({
x={110}
y={115}
fill="#71717a"
fontFamily="Inter, Arial, sans-serif"
fontFamily="System, sans-serif"
fontSize={12}
fontWeight={600}
textAnchor="middle"
@@ -164,89 +129,80 @@ const QrTemplateLumiere = ({
)}
</G>
{/* Metinler */}
{/* Metinler (Çok satırlı ve bağımsız font destekli) */}
<G textAnchor="middle">
{/* Restoran Adı */}
{restaurantName ? (
<Text
x={300}
y={150}
fill="#d4af37"
fontFamily={fontFamily}
fontSize={32}
fontWeight={700}
letterSpacing={4}
>
{restaurantName}
</Text>
) : null}
{rName?.visible !== false &&
renderSvgLines({
text: rName?.text || "LUMIÈRE",
x: 300,
y: 148,
fill: "#d4af37",
fontFamily: rNameFont,
fontSize: rName?.fontSize || 30,
fontWeight: "bold",
letterSpacing: 4,
})}
{/* Slogan / Alt Başlık */}
{showSubtitle && subtitle ? (
<Text
x={300}
y={180}
fill="#e5c378"
fontFamily="Inter, Arial, sans-serif"
fontSize={11}
fontWeight={600}
letterSpacing={3}
>
{subtitle}
</Text>
) : null}
{/* Slogan / Alt Başlık (Enter ile alt satıra geçebilir) */}
{sub?.visible !== false &&
renderSvgLines({
text: sub?.text || "RESTAURANT & BAR | MODERN CUISINE",
x: 300,
y: 178,
fill: "#e5c378",
fontFamily: subFont,
fontSize: sub?.fontSize || 11,
fontWeight: 600,
letterSpacing: 2.5,
})}
{/* CTA / Yönlendirme */}
{showCta && ctaText ? (
<Text
x={300}
y={472}
fill="#d4af37"
fontFamily={fontFamily}
fontSize={13}
fontWeight={700}
letterSpacing={2}
>
{ctaText}
</Text>
) : null}
{/* CTA */}
{cta?.visible !== false &&
renderSvgLines({
text: cta?.text || "MENÜYÜ GÖRMEK İÇİN OKUTUN",
x: 300,
y: 472,
fill: "#d4af37",
fontFamily: ctaFont,
fontSize: cta?.fontSize || 13,
fontWeight: "bold",
letterSpacing: 2,
})}
{/* Sosyal / İletişim */}
{showSocial && socialText ? (
<Text
x={300}
y={508}
fill="#e2ddd2"
fontFamily="Inter, Arial, sans-serif"
fontSize={10.5}
fontWeight={500}
letterSpacing={1}
>
{socialText}
</Text>
) : null}
{/* Sosyal Medya / İletişim */}
{social?.visible !== false &&
renderSvgLines({
text: social?.text || "Rezervasyon: 0212 555 0199",
x: 300,
y: 504,
fill: "#e2ddd2",
fontFamily: infoFont,
fontSize: social?.fontSize || 10.5,
fontWeight: 500,
letterSpacing: 1,
})}
{/* 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 : "",
{(table?.visible !== false || wifi?.visible !== false) &&
renderSvgLines({
text: [
table?.visible !== false && table?.text ? table.text : "",
wifi?.visible !== false && wifi?.text ? wifi.text : "",
]
.filter(Boolean)
.join(" • ")}
</Text>
) : null}
.join(" • "),
x: 300,
y: 526,
fill: "#c5a059",
fontFamily: infoFont,
fontSize: table?.fontSize || wifi?.fontSize || 10,
fontWeight: 600,
letterSpacing: 1,
})}
</G>
</Svg>
);
};
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
};