import { useCallback, useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { ExternalLink, MessageSquareText, Send, Star } from "lucide-react";
import { api } from "../api.js";
import { Badge, Button, cx, Empty, ErrorNote, fmt, Panel, Spinner, timeAgo } from "../ui.jsx";
function Stars({ n }) {
if (n == null) return null;
return (
{[1, 2, 3, 4, 5].map((i) => )}
);
}
function NotConnected({ platform, client, error }) {
return (
Open Connections}>
{error?.message || `Connect ${platform} on the Connections page first.`}
);
}
function ReviewRow({ r, client, onReplied }) {
const [text, setText] = useState("");
const [open, setOpen] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState(null);
const send = async () => {
setBusy(true); setError(null);
try { const { reply } = await api("/api/inbox/reviews/reply", { method: "POST", body: { client, reviewId: r.id, text } }); onReplied(reply); setOpen(false); setText(""); }
catch (e) { setError(e); }
setBusy(false);
};
return (
{r.author}{timeAgo(r.at)}
{!r.reply && No reply yet}
{r.text ? {r.text}
: Rating only, no text.
}
{r.reply && Your reply · {timeAgo(r.reply.at)}{r.reply.text}
}
{!open ? (
) : (
)}
);
}
export function GoogleReviews({ client }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [view, setView] = useState("unreplied");
const load = useCallback(() => { setData(null); setError(null); api(`/api/inbox/reviews?client=${client}`).then(setData).catch((e) => { setError(e); setData(false); }); }, [client]);
useEffect(() => { if (client) load(); }, [load, client]);
const list = useMemo(() => (data?.reviews || []).filter((r) => view === "all" || !r.reply), [data, view]);
const patch = (id, reply) => setData((d) => ({ ...d, reviews: d.reviews.map((r) => (r.id === id ? { ...r, reply } : r)) }));
if (data === null) return ;
if (data === false) return ;
const unreplied = data.reviews.filter((r) => !r.reply).length;
return (
{data.location}
{data.average != null && {data.average.toFixed(1)}from {fmt(data.total)} reviews}
{[["unreplied", `Needs reply (${unreplied})`], ["all", "All"]].map(([k, l]) => (
))}
{!list.length ? Replying to every review, good and bad, helps the business rank in Google Maps.
: {list.map((r) => patch(r.id, reply)} />)}
}
);
}
export function YouTubeComments({ client }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => { if (!client) return; setData(null); setError(null); api(`/api/inbox/youtube?client=${client}`).then(setData).catch((e) => { setError(e); setData(false); }); }, [client]);
if (data === null) return ;
if (data === false) return ;
return (
Latest comments on {data.channel}'s videos. Reply on YouTube for now: the button opens the comment.
{!data.comments.length ? : (
{data.comments.map((c) => (
-
{c.avatar ?
: }
{c.author} {timeAgo(c.at)}{c.replies ? ` · ${c.replies} repl${c.replies === 1 ? "y" : "ies"}` : ""}{c.likes ? ` · ${c.likes} likes` : ""}
{c.text}
Reply on YouTube
))}
)}
);
}