import React, { useEffect, useMemo, useRef, useState } from "react"; import styles from "./IdentifyCafeModal.module.css"; import API_BASE_URL from "../config.js"; import { getTables, createTable } from "../helpers/tableHelper"; import { saveCafeDetails } from "../helpers/cafeHelpers"; import { getImageUrl } from "../helpers/itemHelper"; import { toPng } from "html-to-image"; export default function IdentifyCafeModal({ shop }) { const [cafeIdentifyName, setCafeIdentifyName] = useState(shop.cafeIdentifyName || ""); const [availability, setAvailability] = useState(null); // 200 ok, 409 taken const [checking, setChecking] = useState(false); const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(null); const [qrSize, setQrSize] = useState(Number(shop.scale) || 1); const [qrX, setQrX] = useState(Number(shop.xposition) || 50); const [qrY, setQrY] = useState(Number(shop.yposition) || 50); const [fontSize, setFontSize] = useState(Number(shop.fontsize) || 16); const [fontColor, setFontColor] = useState(shop.fontcolor || "#FFFFFF"); const [fontX, setFontX] = useState(Number(shop.fontxposition) || 50); const [fontY, setFontY] = useState(Number(shop.fontyposition) || 85); const [bgImageUrl, setBgImageUrl] = useState(getImageUrl(shop.qrBackground)); const bgFileRef = useRef(null); const [newTableNo, setNewTableNo] = useState(""); const previewRef = useRef(null); const [copied, setCopied] = useState(false); const [currentStep, setCurrentStep] = useState(1); // 1=Alamat, 2=Desain QR, 3=Meja const [saveStatus, setSaveStatus] = useState(null); // 'success' | 'error' const [saving, setSaving] = useState(false); const initialDesignRef = useRef({ qrSize: Number(shop.scale) || 1, qrX: Number(shop.xposition) || 50, qrY: Number(shop.yposition) || 50, fontSize: Number(shop.fontsize) || 16, fontColor: shop.fontcolor || "#FFFFFF", fontX: Number(shop.fontxposition) || 50, fontY: Number(shop.fontyposition) || 85, bgImageUrl: getImageUrl(shop.qrBackground), }); const shopHost = useMemo(() => window.location.hostname, []); const fullLink = useMemo(() => `${shopHost}/${cafeIdentifyName}`, [shopHost, cafeIdentifyName]); // Debounced availability check const debounceRef = useRef(null); const handleIdentifyChange = (e) => { const val = e.target.value .toLowerCase() .replace(/\s+/g, "_") .replace(/[^a-z0-9_]/g, ""); setCafeIdentifyName(val); setChecking(true); setAvailability(null); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(async () => { try { const res = await fetch(`${API_BASE_URL}/cafe/check-identifyName/${val}`); setAvailability(res.ok ? 200 : 409); } catch (_) { setAvailability(409); } finally { setChecking(false); } }, 600); }; // Load tables useEffect(() => { (async () => { try { const fetched = await getTables(shop.cafeId); setTables(fetched || []); } catch (e) { // ignore } })(); }, [shop.cafeId]); const handleUploadBg = (e) => { const file = e.target.files?.[0]; if (file) { const url = URL.createObjectURL(file); setBgImageUrl(url); } }; const handleCreateTable = async () => { if (!newTableNo) return; try { const created = await createTable(shop.cafeId, { tableNo: newTableNo }); setTables((t) => [...t, created]); setNewTableNo(""); } catch (e) { // noop } }; const handleSave = async () => { setSaving(true); setSaveStatus(null); const qrBackgroundFile = bgFileRef.current?.files?.[0]; const details = { qrSize, qrPosition: { left: qrX, top: qrY }, qrBackgroundFile, fontsize: fontSize, fontcolor: fontColor, fontPosition: { left: fontX, top: fontY }, cafeIdentifyName: shop.cafeIdentifyName !== cafeIdentifyName ? cafeIdentifyName : null, }; try { await saveCafeDetails(shop.cafeId, details); setSaveStatus('success'); } catch (e) { setSaveStatus('error'); } finally { setSaving(false); } }; const downloadPreview = async () => { if (!previewRef.current) return; const node = previewRef.current; const originalBg = node.style.backgroundColor; node.style.backgroundColor = "transparent"; try { const dataUrl = await toPng(node, { pixelRatio: 2 }); const link = document.createElement("a"); link.href = dataUrl; link.download = selectedTable ? `QR Meja (${selectedTable.tableNo}).png` : `QR ${shop.name}.png`; link.click(); } catch (e) { // noop } finally { node.style.backgroundColor = originalBg; } }; const copyLink = async () => { try { await navigator.clipboard.writeText(fullLink); setCopied(true); setTimeout(()=>setCopied(false), 1400); } catch (_) {} }; const applyPreset = (preset) => { if (preset === 'center') { setQrX(50); setQrY(50); setQrSize(1); setFontX(50); setFontY(85); } else if (preset === 'topLeft') { setQrX(25); setQrY(30); setQrSize(1); setFontX(50); setFontY(85); } else if (preset === 'bottomRight') { setQrX(75); setQrY(70); setQrSize(1); setFontX(50); setFontY(15); } }; const resetDesign = () => { const d = initialDesignRef.current; setQrSize(d.qrSize); setQrX(d.qrX); setQrY(d.qrY); setFontSize(d.fontSize); setFontColor(d.fontColor); setFontX(d.fontX); setFontY(d.fontY); setBgImageUrl(d.bgImageUrl); if (bgFileRef.current) bgFileRef.current.value = ''; }; // Positioning helpers const qrStyle = { left: `${qrX}%`, top: `${qrY}%`, transform: `translate(-50%, -50%) scale(${qrSize})`, }; const fontStyle = { left: `${fontX}%`, top: `${fontY}%`, transform: `translate(-50%, -50%)`, color: fontColor, fontSize: `${fontSize}px`, position: "absolute", fontWeight: 700, }; const qrData = selectedTable ? `${fullLink}/${selectedTable.tableNo}` : fullLink; const canProceedFromStep1 = () => { if (!cafeIdentifyName) return false; if (cafeIdentifyName === (shop.cafeIdentifyName || '')) return true; return availability === 200 && !checking; }; return (