diff --git a/apps/mobile/src/app/menu/qr.tsx b/apps/mobile/src/app/menu/qr.tsx index 7effaf4..464d04c 100644 --- a/apps/mobile/src/app/menu/qr.tsx +++ b/apps/mobile/src/app/menu/qr.tsx @@ -14,11 +14,10 @@ import * as Clipboard from "expo-clipboard"; import ViewShot from "react-native-view-shot"; import * as Sharing from "expo-sharing"; import * as Print from "expo-print"; -import { SvgXml } from "react-native-svg"; import { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared"; import { api } from "@/lib/api"; import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; -import { MOBILE_SVG_TEMPLATES } from "@/lib/svg-templates"; +import { COMPONENT_TEMPLATES, TEMPLATE_ASPECT_RATIOS } from "@/components/qr-templates"; interface QrResponse { id: string; @@ -42,54 +41,12 @@ export default function QrScreen() { const viewShotRef = useRef(null); const activePreset: QrStandPreset = (QR_STAND_PRESETS[selectedKey] || Object.values(QR_STAND_PRESETS)[0])!; - const currentSvgRaw = MOBILE_SVG_TEMPLATES[selectedKey] || MOBILE_SVG_TEMPLATES["qr-4"] || Object.values(MOBILE_SVG_TEMPLATES)[0] || ""; - // Inject the live QR code image directly into the SVG's #qr-placeholder group - // Yorum satırlarını temizle + iç içe taglarını doğru saymak için depth tracking kullan - function buildSvgWithQr(svgRaw: string, pngBase64: string): string { - // react-native-svg XML parser desteklemez - // Ayrıca text node'lardaki & < gibi entity'leri decode et - const clean = svgRaw - .replace(//g, "") - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/'/g, "'") - .replace(/"/g, '"'); - - - const startMarker = ' taglarını sayarak doğru kapanış 'yi bul - let depth = 0; - let i = startIdx; - let endIdx = -1; - while (i < clean.length - 3) { - if (clean[i] === "<") { - if (clean[i + 1] === "g" && (clean[i + 2] === " " || clean[i + 2] === ">")) { - depth++; - } else if (clean[i + 1] === "/" && clean[i + 2] === "g" && clean[i + 3] === ">") { - depth--; - if (depth === 0) { - endIdx = i + 4; // uzunluğu = 4 - break; - } - } - } - i++; - } - - if (endIdx === -1) return clean; - - const qrGroup = ` - - - `; - - return clean.substring(0, startIdx) + qrGroup + clean.substring(endIdx); - } + // Component-based template sistemi + const TemplateComponent = COMPONENT_TEMPLATES[selectedKey] ?? Object.values(COMPONENT_TEMPLATES)[0]; + const aspectRatio = TEMPLATE_ASPECT_RATIOS[selectedKey] ?? 1; + const displayWidth = 300; + const displayHeight = Math.round(displayWidth / aspectRatio); useEffect(() => { (async () => { @@ -153,19 +110,12 @@ export default function QrScreen() { } async function handleExportPdf() { - if (!qr || !active || !currentSvgRaw) return; + if (!qr || !active) return; setExportingPdf(true); try { - const qrImageUri = `data:image/png;base64,${qr.pngBase64}`; - - // Inject QR code directly into the SVG for print-ready A5 PDF - const svgWithQr = currentSvgRaw.replace( - /]*>[\s\S]*?<\/g>/, - ` - - - ` - ); + // ViewShot ile önce PNG al, sonra A5 HTML içine göm + const pngUri = await viewShotRef.current?.capture(); + if (!pngUri) throw new Error("Görsel alınamadı"); const htmlContent = ` @@ -181,14 +131,14 @@ export default function QrScreen() { background: #000000; -webkit-print-color-adjust: exact; } - .svg-wrap { + .img-wrap { width: 140mm; height: 200mm; display: flex; align-items: center; justify-content: center; } - svg { + img { width: 100%; height: 100%; object-fit: contain; @@ -196,8 +146,8 @@ export default function QrScreen() { -
- ${svgWithQr} +
+
@@ -220,6 +170,7 @@ export default function QrScreen() { } } + if (loading) { return ( @@ -347,24 +298,23 @@ export default function QrScreen() { }} > - {/* SVG with QR code injected directly — no overlay, pixel-perfect */} + {/* Component-based template — no XML parsing, pixel-perfect */} - {currentSvgRaw ? ( - ) : null} - diff --git a/apps/mobile/src/components/qr-templates/QrTemplate3.tsx b/apps/mobile/src/components/qr-templates/QrTemplate3.tsx new file mode 100644 index 0000000..4fddebb --- /dev/null +++ b/apps/mobile/src/components/qr-templates/QrTemplate3.tsx @@ -0,0 +1,348 @@ +import * as React from "react"; +import Svg, { + SvgProps, + Defs, + LinearGradient, + Stop, + Pattern, + Path, + G, + Rect, + Ellipse, + Circle, + Text, + TSpan, + Image, +} from "react-native-svg"; + +interface QrTemplate3Props extends SvgProps { + qrBase64?: string; +} + +const QrTemplate3 = ({ qrBase64, ...props }: QrTemplate3Props) => ( + + + + + + + + + + + + + + + + + + + + + + + {/* Arka Plan */} + + + {/* Kart */} + + + + + + + {/* İ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 */} + + + {"MASA NO: 01"} + + + {"PIZZERIA"} + + + {"BELLA NAPOLI"} + + + {"AUTENTICA CUCINA ITALIANA"} + + + {"MENÜYÜ GÖRMEK"} + + + {"İÇİN OKUTUN"} + + + + {"WiFi: "} + + + {"GourmetBistro"} + + + {" | "} + + + {"Şifre: "} + + + {"lezzetli01"} + + + + + {"BİZİ TAKİP EDİN"} + + + {" | "} + + + {"@gourmetbistro"} + + + + +); + +export default QrTemplate3; diff --git a/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx b/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx new file mode 100644 index 0000000..e1181ca --- /dev/null +++ b/apps/mobile/src/components/qr-templates/QrTemplateLumiere.tsx @@ -0,0 +1,213 @@ +import * as React from "react"; +import Svg, { + SvgProps, + Path, + Rect, + Circle, + G, + Text, + Image, +} from "react-native-svg"; + +interface QrTemplateLumiereProps extends SvgProps { + qrBase64?: string; +} + +const QrTemplateLumiere = ({ qrBase64, ...props }: QrTemplateLumiereProps) => ( + + + + + + {/* Köşe süslemeleri */} + + + + + + + + + {/* Merkez amblem */} + + + + + + + {/* Yatay çizgi */} + + {/* QR Placeholder / Gerçek QR */} + + + {qrBase64 ? ( + + ) : ( + <> + + + {"QR KOD ALANI"} + + + )} + + {/* Metinler */} + + + {"LUMI\xC8RE"} + + + {"RESTAURANT & BAR | MODERN CUISINE"} + + + {"SCAN WITH CAMERA"} + + + {"Reservations: LumiereDining.com | 240-555-0199"} + + + {"TABLE 01 \u2022 WIFI: Lumiere_Guest"} + + + +); + +export default QrTemplateLumiere; diff --git a/apps/mobile/src/components/qr-templates/index.ts b/apps/mobile/src/components/qr-templates/index.ts new file mode 100644 index 0000000..aba7447 --- /dev/null +++ b/apps/mobile/src/components/qr-templates/index.ts @@ -0,0 +1,23 @@ +import type React from "react"; +import type { SvgProps } from "react-native-svg"; +import QrTemplateLumiere from "./QrTemplateLumiere"; +import QrTemplate3 from "./QrTemplate3"; + +export interface QrTemplateProps extends SvgProps { + qrBase64?: string; +} + +// Yeni component eklemek için buraya import edip COMPONENT_TEMPLATES'e kaydet +export const COMPONENT_TEMPLATES: Record< + string, + React.FC +> = { + "qr-lumiere": QrTemplateLumiere, + "qr-3": QrTemplate3, +}; + +// Her template'in aspect ratio'su (width/height) +export const TEMPLATE_ASPECT_RATIOS: Record = { + "qr-lumiere": 1, // 600x600 = 1:1 + "qr-3": 400 / 650, // 400x650 = standart kart +}; diff --git a/apps/mobile/svgcomponentlist/1.tsx b/apps/mobile/svgcomponentlist/1.tsx new file mode 100644 index 0000000..e911e8e --- /dev/null +++ b/apps/mobile/svgcomponentlist/1.tsx @@ -0,0 +1,177 @@ +import * as React from "react" +import Svg, { SvgProps, Path, Rect, Circle, G, Text } from "react-native-svg" +const SvgComponent = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + {"QR KOD ALANI"} + + + + + {"LUMI\xC8RE"} + + + {"RESTAURANT & BAR | MODERN CUISINE"} + + + {"SCAN WITH CAMERA"} + + + {"Reservations: LumiereDining.com | 240-555-0199"} + + + {"TABLE 01 \u2022 WIFI: Lumiere_Guest"} + + + +) +export default SvgComponent diff --git a/apps/mobile/svgcomponentlist/2.tsx b/apps/mobile/svgcomponentlist/2.tsx new file mode 100644 index 0000000..a46d797 --- /dev/null +++ b/apps/mobile/svgcomponentlist/2.tsx @@ -0,0 +1,286 @@ +import * as React from "react" +import Svg, { + SvgProps, + Path, + Rect, + G, + Circle, + Ellipse, + Text, +} from "react-native-svg" +const SvgComponent = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"QR KOD ALANI"} + + + + + {"THE"} + + + {"The Daily Grind"} + + + {"COFFEE & KITCHEN"} + + + {"SCAN TO VIEW MENU"} + + + {"DAILY GRIND CAFE | WIFI: DAILY_GRIND_GUEST"} + + + {"\u015E\u0130FRE: coffee2026 \u2022 MASA NO: 04"} + + + {"EST. 2018"} + + + +) +export default SvgComponent diff --git a/apps/mobile/svgcomponentlist/3.tsx b/apps/mobile/svgcomponentlist/3.tsx new file mode 100644 index 0000000..f8bc092 --- /dev/null +++ b/apps/mobile/svgcomponentlist/3.tsx @@ -0,0 +1,416 @@ +import * as React from "react" +import Svg, { + SvgProps, + Defs, + LinearGradient, + Stop, + Pattern, + Path, + G, + Rect, + Ellipse, + Circle, + Text, + TSpan, +} from "react-native-svg" +/* SVGR has dropped some elements not supported by react-native-svg: style */ +const SvgComponent = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"QR KOD ALANI"} + + + + + {"MASA NO: 01"} + + + {"PIZZERIA"} + + + {"BELLA NAPOLI"} + + + {"AUTENTICA CUCINA ITALIANA"} + + + {"MEN\xDCY\xDC G\xD6RMEK"} + + + {"\u0130\xC7\u0130N OKUTUN"} + + + {"WiFi: "} + {"GourmetBistro"} + + {" | "} + + {"\u015Eifre: "} + {"lezzetli01"} + + + + {"B\u0130Z\u0130 TAK\u0130P ED\u0130N"} + + + {" | "} + + {"@gourmetbistro"} + + + +) +export default SvgComponent diff --git a/packages/shared/src/qr-stands.ts b/packages/shared/src/qr-stands.ts index b3aac83..2988572 100644 --- a/packages/shared/src/qr-stands.ts +++ b/packages/shared/src/qr-stands.ts @@ -77,6 +77,34 @@ export const QR_STAND_PRESETS: Record = { subText: "#6B5A3E", dividerColor: "rgba(212, 175, 55, 0.3)", }, + "qr-lumiere": { + key: "qr-lumiere", + name: "Lumière Restaurant (QR-L)", + categoryName: "Özel Masa Kartı", + svgTemplateUrl: "/qr-lumiere.svg", + aspectRatio: "1/1", + bgGradient: "radial-gradient(circle at 50% 50%, #132034 0%, #0b0d13 100%)", + plaqueColor: "#132034", + borderAccent: "#d4af37", + accentText: "#d4af37", + headerText: "#FFFFFF", + subText: "#e5c378", + dividerColor: "rgba(212, 175, 55, 0.3)", + }, + "qr-3": { + key: "qr-3", + name: "Pizzeria Bella Napoli (QR-3)", + categoryName: "Özel Masa Kartı", + svgTemplateUrl: "/qr-3.svg", + aspectRatio: "400/650", + bgGradient: "radial-gradient(circle at 50% 50%, #faf4e6 0%, #f7eed8 100%)", + plaqueColor: "#fdfbf5", + borderAccent: "#b92b27", + accentText: "#b92b27", + headerText: "#18181b", + subText: "#5d4037", + dividerColor: "rgba(185, 43, 39, 0.3)", + }, };