From 2530ab400543795775dad7a35b7c6bb1ced5b625 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Fri, 21 Aug 2026 00:55:44 +0300 Subject: [PATCH] feat(mobile): add multiline text with Enter, per-text independent font selection and font size controls --- apps/mobile/src/app/menu/qr.tsx | 529 +++++++-------- .../components/qr-templates/QrTemplate2.tsx | 543 ++++++++------- .../components/qr-templates/QrTemplate3.tsx | 642 +++++++++--------- .../components/qr-templates/QrTemplate4.tsx | 491 +++++++------- .../components/qr-templates/QrTemplate5.tsx | 309 ++++----- .../qr-templates/QrTemplateLumiere.tsx | 406 +++++------ .../src/components/qr-templates/index.ts | 77 --- .../src/components/qr-templates/index.tsx | 133 ++++ 8 files changed, 1522 insertions(+), 1608 deletions(-) delete mode 100644 apps/mobile/src/components/qr-templates/index.ts create mode 100644 apps/mobile/src/components/qr-templates/index.tsx diff --git a/apps/mobile/src/app/menu/qr.tsx b/apps/mobile/src/app/menu/qr.tsx index cf6f67f..08183d4 100644 --- a/apps/mobile/src/app/menu/qr.tsx +++ b/apps/mobile/src/app/menu/qr.tsx @@ -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( - 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("didot"); - const [customText, setCustomText] = useState({ - 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({ + 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} - {/* Metin & Font Özelleştirme Kartı (Açılır/Kapanır) */} + {/* Metin & Font & Boyut Özelleştirme Kartı (Açılır/Kapanır) */} - + - Metin ve Yazı Tipi Özelleştir + Metin, Font & Boyut Özelleştir - İstediğiniz alanları düzenleyin veya kaldırın + Çok satırlı metin (Enter), özel font ve boyut ayarı @@ -423,264 +439,201 @@ export default function QrScreen() { {showCustomizePanel && ( - {/* Yazı Tipi (Font) Seçici */} - - - YAZI TİPİ (FONT) + {/* Yardımcı Açıklama */} + + + + İpucu: Uzun metinlerde alt satıra geçmek için klavyeden Enter'a basabilirsiniz. - - {FONT_OPTIONS.map((f) => { - const isSelected = f.id === selectedFontId; - return ( - setSelectedFontId(f.id)} - style={{ - backgroundColor: isSelected ? "#D4AF37" : "#242429", - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: 10, - borderWidth: 1, - borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.08)", - }} - > - - {f.label} + + + {/* 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 ( + + {/* Başlık ve Görünürlük Anahtarı */} + + {label} + + + {item.visible ? "Açık" : "Gizli"} - - ); - })} - - + + setFields((prev) => ({ + ...prev, + [key]: { ...prev[key], visible: v }, + })) + } + trackColor={{ false: "#27272A", true: "#D4AF37" }} + thumbColor="#FFFFFF" + ios_backgroundColor="#27272A" + /> + + - {/* Alan 1: Restoran Başlığı */} - - - RESTORAN BAŞLIĞI - - setCustomText((p) => ({ ...p, restaurantName: t }))} - placeholder="Restoran Adı" - placeholderTextColor="#71717A" - style={{ - backgroundColor: "#121215", - borderWidth: 1, - borderColor: "rgba(255,255,255,0.1)", - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 10, - color: "#FFFFFF", - fontSize: 14, - }} - /> - + {item.visible && ( + <> + {/* Çok Satırlı TextInput */} + + 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, + }} + /> - {/* Alan 2: Slogan / Alt Başlık */} - - - - SLOGAN / ALT BAŞLIK - - - - {customText.showSubtitle ? "Göster" : "Gizle"} - - setCustomText((p) => ({ ...p, showSubtitle: v }))} - trackColor={{ false: "#27272A", true: "#D4AF37" }} - thumbColor="#FFFFFF" - ios_backgroundColor="#27272A" - /> + {/* Font ve Boyut Kontrol Çubuğu */} + + {/* Font Hapları */} + + {FONT_OPTIONS.map((f) => { + const isSelected = item.fontId === f.id; + return ( + + setFields((prev) => ({ + ...prev, + [key]: { ...prev[key], fontId: f.id }, + })) + } + style={{ + backgroundColor: isSelected ? "#D4AF37" : "#242429", + paddingHorizontal: 9, + paddingVertical: 5, + borderRadius: 8, + borderWidth: 1, + borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.06)", + }} + > + + {f.label} + + + ); + })} + + + {/* Boyut Artır / Azalt */} + + + setFields((prev) => ({ + ...prev, + [key]: { + ...prev[key], + fontSize: Math.max(min, (prev[key].fontSize || 12) - 1), + }, + })) + } + style={{ padding: 6 }} + > + + + + {item.fontSize || 12} + + + setFields((prev) => ({ + ...prev, + [key]: { + ...prev[key], + fontSize: Math.min(max, (prev[key].fontSize || 12) + 1), + }, + })) + } + style={{ padding: 6 }} + > + + + + + + )} - - {customText.showSubtitle && ( - setCustomText((p) => ({ ...p, subtitle: t }))} - placeholder="Slogan / Açıklama" - placeholderTextColor="#71717A" - style={{ - backgroundColor: "#121215", - borderWidth: 1, - borderColor: "rgba(255,255,255,0.1)", - borderRadius: 12, - paddingHorizontal: 14, - paddingVertical: 10, - color: "#FFFFFF", - fontSize: 14, - }} - /> - )} - - - {/* Alan 3: Masa Numarası */} - - - - MASA NUMARASI - - - - {customText.showTableNo ? "Göster" : "Gizle"} - - setCustomText((p) => ({ ...p, showTableNo: v }))} - trackColor={{ false: "#27272A", true: "#D4AF37" }} - thumbColor="#FFFFFF" - ios_backgroundColor="#27272A" - /> - - - {customText.showTableNo && ( - 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, - }} - /> - )} - - - {/* Alan 4: Yönlendirme (CTA) */} - - - - YÖNLENDİRME (CTA) METNİ - - - - {customText.showCta ? "Göster" : "Gizle"} - - setCustomText((p) => ({ ...p, showCta: v }))} - trackColor={{ false: "#27272A", true: "#D4AF37" }} - thumbColor="#FFFFFF" - ios_backgroundColor="#27272A" - /> - - - {customText.showCta && ( - 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, - }} - /> - )} - - - {/* Alan 5: WiFi Bilgileri */} - - - - WIFI BİLGİSİ - - - - {customText.showWifi ? "Göster" : "Gizle"} - - setCustomText((p) => ({ ...p, showWifi: v }))} - trackColor={{ false: "#27272A", true: "#D4AF37" }} - thumbColor="#FFFFFF" - ios_backgroundColor="#27272A" - /> - - - {customText.showWifi && ( - 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, - }} - /> - )} - - - {/* Alan 6: Sosyal Medya / İletişim */} - - - - SOSYAL MEDYA / İLETİŞİM - - - - {customText.showSocial ? "Göster" : "Gizle"} - - setCustomText((p) => ({ ...p, showSocial: v }))} - trackColor={{ false: "#27272A", true: "#D4AF37" }} - thumbColor="#FFFFFF" - ios_backgroundColor="#27272A" - /> - - - {customText.showSocial && ( - 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, - }} - /> - )} - + ); + })} )} + {/* Output Action Buttons */} {/* PDF Download Button */} diff --git a/apps/mobile/src/components/qr-templates/QrTemplate2.tsx b/apps/mobile/src/components/qr-templates/QrTemplate2.tsx index 59d8ca9..1ec6152 100644 --- a/apps/mobile/src/components/qr-templates/QrTemplate2.tsx +++ b/apps/mobile/src/components/qr-templates/QrTemplate2.tsx @@ -8,313 +8,300 @@ 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) => ( - - {/* Arka Plan */} - +}: 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; - {/* Ana Kart */} - - - + 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"); - {/* Sol Üst Botanik Dal */} - - - - + return ( + + {/* Arka Plan */} + - {/* Sağ Üst Kahve İkonu */} - - - + - - - - - - - {/* Sol Orta Kahve Bardağı */} - - - - - - - - {/* Sağ Dal */} - - - - - {/* Dekoratif Çizgi */} - - - {/* Sol Alt Dal */} - - - - - - {/* Kahve Çekirdekleri */} - - - - - - - - - - - - - - {/* Sağ Alt Fincan */} - - - - - - - - - {/* QR Köşe Çerçeveleri */} - - - - {/* QR Placeholder / Gerçek QR */} - - - {qrBase64 ? ( - + + - ) : ( - <> - + + {/* Sağ Üst Kahve İkonu */} + + + + + + + + + + {/* Sol Orta Kahve Bardağı */} + + + + + + + + {/* Sağ Dal */} + + + + + + {/* Dekoratif Çizgi */} + + + {/* Sol Alt Dal */} + + + + + + {/* Kahve Çekirdekleri */} + + + + + + + + + + + + + + {/* Sağ Alt Fincan */} + + + + + + + + + {/* QR Köşe Çerçeveleri */} + + + + {/* QR Placeholder / Gerçek QR */} + + + {qrBase64 ? ( + - - - - - - - {"QR KOD ALANI"} - - - )} - + ) : ( + <> + + + + + + + + {"QR KOD ALANI"} + + + )} + - {/* Metinler */} - - {/* Üst Tag / Takı */} - - {"THE"} - - - {/* Restoran Adı */} - {restaurantName ? ( + {/* Metinler (Çok satırlı destekli) */} + - {restaurantName} - - ) : null} - - {/* Slogan */} - {showSubtitle && subtitle ? ( - - {subtitle} + {"THE"} - ) : null} - {/* CTA */} - {showCta && ctaText ? ( + {/* Restoran Adı */} + {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 */} + {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 */} + {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 */} + {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 */} + {(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(" • "), + x: 200, + y: 544, + fill: "#8c6239", + fontFamily: infoFont, + fontSize: table?.fontSize || social?.fontSize || 10, + fontWeight: 600, + letterSpacing: 1, + })} + - {ctaText} - - ) : null} - - {/* WiFi */} - {showWifi && wifiText ? ( - - {wifiText} + {"EST. 2026"} - ) : null} - - {/* Masa No & Sosyal */} - {showTableNo || showSocial ? ( - - {[ - showTableNo && tableNo ? tableNo : "", - showSocial && socialText ? socialText : "", - ] - .filter(Boolean) - .join(" • ")} - - ) : null} - - - {"EST. 2026"} - - - -); + + + ); +}; export default QrTemplate2; diff --git a/apps/mobile/src/components/qr-templates/QrTemplate3.tsx b/apps/mobile/src/components/qr-templates/QrTemplate3.tsx index 70cfdd8..aa2516c 100644 --- a/apps/mobile/src/components/qr-templates/QrTemplate3.tsx +++ b/apps/mobile/src/components/qr-templates/QrTemplate3.tsx @@ -12,356 +12,336 @@ 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) => ( - - - - - - - - - - - - - - - - - - - - - +}: 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; - {/* Arka Plan */} - + 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"); - {/* Kart */} - - - - {showTableNo && tableNo ? ( + return ( + + + + + + + + + + + + + + + + + + + + + + + {/* Arka Plan */} + + + {/* Kart */} + + - ) : null} - - - {/* İkonlar */} - - - {/* Pizza Fırını */} - - - - - - - - - {/* Sol Malzemeler */} - - - - - - - - - - - {/* Sağ Malzemeler */} - - - - - - - - - - {/* Alt Pizza Dilimi */} - - - - - - - - - - {/* Şarap Kadehi */} - - - - - - - - - {/* QR Köşe Aksanları */} - - - - - - - - {/* QR Placeholder / Gerçek QR */} - - - {qrBase64 ? ( - - ) : ( - <> + {table?.visible !== false && table?.text ? ( - - {"QR KOD ALANI"} - - - )} - + ) : null} + + + {/* İkonlar */} + + + {/* Pizza Fırını */} + + + + + + + + + {/* Sol Malzemeler */} + + + + + + + + + + + {/* Sağ Malzemeler */} + + + + + + + + + + {/* Alt Pizza Dilimi */} + + + + + + + + + + {/* Şarap Kadehi */} + + + + + + + + + {/* QR Köşe Aksanları */} + + + + + + + + {/* QR Placeholder / Gerçek QR */} + + + {qrBase64 ? ( + + ) : ( + <> + + + {"QR KOD ALANI"} + + + )} + + + {/* Metinler (Çok satırlı destekli) */} + + {/* Masa No */} + {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, + })} - {/* Metinler */} - - {/* Masa No */} - {showTableNo && tableNo ? ( - {tableNo} - - ) : null} - - - {"PIZZERIA"} - - - {/* Restoran Adı */} - {restaurantName ? ( - - {restaurantName} - - ) : null} - - {/* Slogan */} - {showSubtitle && subtitle ? ( - - {subtitle} - - ) : null} - - {/* CTA */} - {showCta && ctaText ? ( - - {ctaText} - - ) : null} - - {/* WiFi */} - {showWifi && wifiText ? ( - - {wifiText} + {"PIZZERIA"} - ) : null} - {/* Sosyal Medya */} - {showSocial && socialText ? ( - - {socialText} - - ) : null} - - -); + {/* Restoran Adı */} + {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 (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 */} + {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 */} + {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 */} + {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, + })} + + + ); +}; export default QrTemplate3; diff --git a/apps/mobile/src/components/qr-templates/QrTemplate4.tsx b/apps/mobile/src/components/qr-templates/QrTemplate4.tsx index 4876a2e..67e7337 100644 --- a/apps/mobile/src/components/qr-templates/QrTemplate4.tsx +++ b/apps/mobile/src/components/qr-templates/QrTemplate4.tsx @@ -9,271 +9,262 @@ 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; - {/* QR Placeholder / Live QR */} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - {qrBase64 ? ( - + + + + + + + + + {/* QR Placeholder / Live QR */} + + - ) : ( - <> - - - {"QR KOD ALANI"} - - - )} - + ) : ( + <> + + + {"QR KOD ALANI"} + + + )} + - {/* Metinler */} - - {/* Masa No */} - {showTableNo && tableNo ? ( - - {tableNo} - - ) : null} + {/* Metinler */} + + {/* Masa No */} + {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 ? ( - - {restaurantName} - - ) : 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 ? ( - - {subtitle} - - ) : null} + {/* Slogan */} + {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 ? ( - - {ctaText} - - ) : null} + {/* CTA Metni */} + {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 ? ( - - {wifiText} - - ) : null} + {/* WiFi Bilgisi */} + {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 ? ( - - {socialText} - - ) : null} - - -); + {/* Sosyal Medya */} + {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, + })} + + + ); +}; export default QrTemplate4; diff --git a/apps/mobile/src/components/qr-templates/QrTemplate5.tsx b/apps/mobile/src/components/qr-templates/QrTemplate5.tsx index 426b9c7..eb181c9 100644 --- a/apps/mobile/src/components/qr-templates/QrTemplate5.tsx +++ b/apps/mobile/src/components/qr-templates/QrTemplate5.tsx @@ -6,183 +6,174 @@ 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) => ( - - {/* PNG Arka Plan */} - +}: 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; - {/* QR Placeholder / Live QR */} - - + {/* PNG Arka Plan */} + - - {qrBase64 ? ( - + - ) : ( - <> - + {qrBase64 ? ( + - - {"QR KOD ALANI"} - - - )} - + ) : ( + <> + + + {"QR KOD ALANI"} + + + )} + - {/* Metinler */} - - {/* Masa Numarası */} - {showTableNo && tableNo ? ( - - {tableNo} - - ) : null} + {/* Metinler */} + + {/* Masa Numarası */} + {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 ? ( - - {restaurantName} - - ) : null} + {/* Restoran Başlığı */} + {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 ? ( - - {ctaText} - - ) : null} + {/* Yönlendirme (CTA) */} + {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 ? ( - - {subtitle} - - ) : 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 ? ( - - {wifiText} - - ) : null} + {/* WiFi */} + {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 ? ( - - {socialText} - - ) : null} - - -); + {/* Sosyal Medya */} + {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, + })} + + + ); +}; export default QrTemplate5; diff --git a/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx b/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx index 3930dbf..095317f 100644 --- a/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx +++ b/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx @@ -7,246 +7,202 @@ 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) => ( - - - - - - {/* Köşe süslemeleri */} - - - - - - - - +}: 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; - {/* Merkez amblem */} - - - - - - + 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"); - {/* Yatay çizgi */} - - - {/* QR Placeholder / Gerçek QR */} - + return ( + + - {qrBase64 ? ( - + + {/* Köşe süslemeleri */} + + + + + + + + + + {/* Merkez amblem */} + + - ) : ( - <> - + + + + + {/* Yatay çizgi */} + + + {/* QR Placeholder / Gerçek QR */} + + + {qrBase64 ? ( + - - {"QR KOD ALANI"} - - - )} - + ) : ( + <> + + + {"QR KOD ALANI"} + + + )} + - {/* Metinler */} - - {/* Restoran Adı */} - {restaurantName ? ( - - {restaurantName} - - ) : null} + {/* Metinler (Çok satırlı ve bağımsız font destekli) */} + + {/* Restoran Adı */} + {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 ? ( - - {subtitle} - - ) : 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 ? ( - - {ctaText} - - ) : 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 ? ( - - {socialText} - - ) : 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 ? ( - - {[ - showTableNo && tableNo ? tableNo : "", - showWifi && wifiText ? wifiText : "", - ] - .filter(Boolean) - .join(" • ")} - - ) : null} - - -); + {/* Masa No & WiFi */} + {(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(" • "), + x: 300, + y: 526, + fill: "#c5a059", + fontFamily: infoFont, + fontSize: table?.fontSize || wifi?.fontSize || 10, + fontWeight: 600, + letterSpacing: 1, + })} + + + ); +}; export default QrTemplateLumiere; diff --git a/apps/mobile/src/components/qr-templates/index.ts b/apps/mobile/src/components/qr-templates/index.ts deleted file mode 100644 index 8bc22f3..0000000 --- a/apps/mobile/src/components/qr-templates/index.ts +++ /dev/null @@ -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 -> = { - "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 = { - "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 -}; diff --git a/apps/mobile/src/components/qr-templates/index.tsx b/apps/mobile/src/components/qr-templates/index.tsx new file mode 100644 index 0000000..72e6713 --- /dev/null +++ b/apps/mobile/src/components/qr-templates/index.tsx @@ -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; +} + +// 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) => ( + + {line} + + ))} + + ); +} + +// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet +export const COMPONENT_TEMPLATES: Record< + string, + React.FC +> = { + "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 = { + "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 +};