import { useEffect, useRef, useState } from "react"; import { router } from "expo-router"; import { ActivityIndicator, Alert, Pressable, ScrollView, Share, Switch, Text, TextInput, View, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; 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 { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared"; import { api } from "@/lib/api"; import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; import { COMPONENT_TEMPLATES, TEMPLATE_ASPECT_RATIOS, FONT_OPTIONS, type QrCustomTextConfig, } from "@/components/qr-templates"; interface QrResponse { id: string; redirectUrl: string; targetUrl: string; pngBase64: string; } export default function QrScreen() { const [active, setActive] = useState(null); const [restaurantName, setRestaurantName] = useState("Gourmet Bistro"); const [qr, setQr] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const availablePresets = Object.values(QR_STAND_PRESETS).filter( (p) => !!COMPONENT_TEMPLATES[p.key] ); const [selectedKey, setSelectedKey] = useState( availablePresets[0]?.key || "qr-lumiere" ); 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, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const viewShotRef = useRef(null); 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]; const aspectRatio = TEMPLATE_ASPECT_RATIOS[selectedKey] ?? 1; const displayWidth = 300; const displayHeight = Math.round(displayWidth / aspectRatio); useEffect(() => { (async () => { const activeRest = await getActiveRestaurant(); if (!activeRest) return; setActive(activeRest); try { const data = await api.post(`/restaurants/${activeRest.restaurantId}/qr`); setQr(data); // Fetch detailed restaurant name try { const restDetail = await api.get<{ name?: string }>(`/restaurants/${activeRest.restaurantId}`); if (restDetail?.name) { setRestaurantName(restDetail.name); setCustomText((prev) => ({ ...prev, restaurantName: restDetail.name, })); } } catch { // fallback } } catch (err) { setError(err instanceof Error ? err.message : "QR oluşturulamadı"); } finally { setLoading(false); } })(); }, []); async function handleCopy() { if (!qr?.targetUrl) return; await Clipboard.setStringAsync(qr.targetUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); } async function handleShareQuick() { if (!qr?.targetUrl) return; await Share.share({ title: "Dijital QR Menü", message: `${qr.targetUrl}\nMenümüzü incelemek için QR kodu taratın veya linke tıklayın.`, url: qr.targetUrl, }); } async function handleExportPng() { if (!viewShotRef.current) return; setExportingPng(true); try { const uri = await viewShotRef.current.capture?.(); if (uri) { await Sharing.shareAsync(uri, { mimeType: "image/png", dialogTitle: "Masa Stant QR Görselini Paylaş / Kaydet", }); } } catch { Alert.alert("Hata", "Görsel oluşturulamadı."); } finally { setExportingPng(false); } } async function handleExportPdf() { if (!qr || !active) return; setExportingPdf(true); try { // 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 = `
`; const { uri } = await Print.printToFileAsync({ html: htmlContent, width: 420, height: 595, }); await Sharing.shareAsync(uri, { mimeType: "application/pdf", dialogTitle: "A5 Baskıya Hazır PDF İndir / Paylaş", }); } catch { Alert.alert("Hata", "PDF oluşturulamadı."); } finally { setExportingPdf(false); } } if (loading) { return ( QR Kod ve Şablonlar Hazırlanıyor... ); } if (error || !qr) { return ( {error ?? "QR kod yüklenemedi"} router.back()} style={{ marginTop: 20, backgroundColor: "#1C1917", paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 }} > Geri Dön ); } return ( {/* Header Badge */} MASA QR STANT JENERATÖRÜ Masa Stant Tasarımları Şablon seçin; QR kodunuz otomatik yerleşsin. Baskıya hazır A5 PDF veya PNG olarak indirin. {/* Horizontal Theme Selector */} {availablePresets.length > 1 && ( {availablePresets.map((preset) => { const isSelected = preset.key === selectedKey; return ( setSelectedKey(preset.key)} style={{ backgroundColor: isSelected ? "#D4AF37" : "#1A1A1E", borderWidth: 1.5, borderColor: isSelected ? "#D4AF37" : "#2B2B32", borderRadius: 14, paddingHorizontal: 16, paddingVertical: 10, flexDirection: "row", alignItems: "center", gap: 8, }} > {preset.name} ); })} )} {/* Live Stand Preview Wrapped in ViewShot */} {/* Component-based template — no XML parsing, pixel-perfect */} {TemplateComponent ? ( ) : null} {/* Metin & Font Özelleştirme Kartı (Açılır/Kapanır) */} setShowCustomizePanel(!showCustomizePanel)} style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", }} > Metin ve Yazı Tipi Özelleştir İstediğiniz alanları düzenleyin veya kaldırın {showCustomizePanel && ( {/* Yazı Tipi (Font) Seçici */} YAZI TİPİ (FONT) {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 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, }} /> {/* 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" /> {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 */} ({ backgroundColor: "#D4AF37", borderRadius: 14, padding: 16, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, opacity: pressed || exportingPdf ? 0.85 : 1, shadowColor: "#D4AF37", shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8, elevation: 3, })} > {exportingPdf ? ( ) : ( <> PDF Olarak İndir (A5 Matbaa Baskı) )} {/* PNG Download Button */} ({ backgroundColor: "#1A1A1E", borderWidth: 1.5, borderColor: "#2B2B32", borderRadius: 14, padding: 16, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, opacity: pressed || exportingPng ? 0.85 : 1, })} > {exportingPng ? ( ) : ( <> PNG Olarak Kaydet / Paylaş )} {/* Direct Link Share Button */} ({ backgroundColor: "#1A1A1E", borderWidth: 1, borderColor: "#2B2B32", borderRadius: 14, padding: 14, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, opacity: pressed ? 0.85 : 1, })} > Menü Linkini Paylaş {/* Quick Copy Link Box */} {qr.targetUrl} ); }