diff --git a/app/root.jsx b/app/root.jsx index 805f121..52ce9b7 100644 --- a/app/root.jsx +++ b/app/root.jsx @@ -4,9 +4,178 @@ import { Outlet, Scripts, ScrollRestoration, + useLoaderData, } from "@remix-run/react"; +import { useEffect, useRef, useState } from "react"; +import { json } from "@remix-run/node"; + +export const loader = async ({ request }) => { + try { + const { authenticate } = await import("./shopify.server"); + const { session } = await authenticate.admin(request); + return json({ shop: session?.shop || null }); + } catch { + return json({ shop: null }); + } +}; + +// ── Chat Widget ────────────────────────────────────────────────────────────── +function ChatWidget({ shop }) { + const [open, setOpen] = useState(false); + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [unread, setUnread] = useState(0); + const [sending, setSending] = useState(false); + const msgsRef = useRef(null); + const pollRef = useRef(null); + const lastTsRef = useRef(""); + const BASE = "https://backend.data4autos.com"; + + const visitorId = (() => { + try { + let v = localStorage.getItem("d4a_vid"); + if (!v) { v = "v" + Math.random().toString(36).slice(2); localStorage.setItem("d4a_vid", v); } + return v; + } catch { return "anon"; } + })(); + + const loadMessages = async (scrollToBottom = false) => { + if (!shop) return; + try { + const r = await fetch(`${BASE}/chat/${encodeURIComponent(shop)}`); + const d = await r.json(); + const msgs = d.messages || []; + const latest = msgs[msgs.length - 1]?.timestamp || ""; + const isNew = latest && latest !== lastTsRef.current; + lastTsRef.current = latest; + setMessages(msgs); + if (!open && isNew && msgs[msgs.length - 1]?.from === "admin") { + setUnread(u => u + 1); + } + if (scrollToBottom || (msgsRef.current && msgsRef.current.scrollHeight - msgsRef.current.scrollTop - msgsRef.current.clientHeight < 80)) { + setTimeout(() => { if (msgsRef.current) msgsRef.current.scrollTop = msgsRef.current.scrollHeight; }, 50); + } + } catch {} + }; + + useEffect(() => { + if (!shop) return; + loadMessages(false); + pollRef.current = setInterval(() => loadMessages(false), 4000); + return () => clearInterval(pollRef.current); + }, [shop]); + + useEffect(() => { + if (open) { + setUnread(0); + loadMessages(true); + } + }, [open]); + + const sendMessage = async () => { + if (!input.trim() || !shop || sending) return; + const text = input.trim(); + setInput(""); + setSending(true); + try { + await fetch(`${BASE}/chat/${encodeURIComponent(shop)}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text, visitorId }), + }); + await loadMessages(true); + } catch {} + setSending(false); + }; + + if (!shop) return null; + + return ( + <> + + + {/* Floating button */} + + + {/* Chat window */} + {open && ( +