Add commenter avatars & category colors to comments (v0.0.89)
- Fetch profile avatars for commenters - Apply post category gradient to comment avatars - Add events, friends, market to CATEGORY_CONFIG - Fix first comment race condition with setTimeout Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0119174969
commit
d051a9337c
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "sociowire-frontend",
|
"name": "sociowire-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.88",
|
"version": "0.0.89",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,10 @@ const CATEGORY_CONFIG = {
|
||||||
traffic: { icon: "fa-car", gradient: "from-yellow-500 to-amber-500", color: "#fbbf24" },
|
traffic: { icon: "fa-car", gradient: "from-yellow-500 to-amber-500", color: "#fbbf24" },
|
||||||
live: { icon: "fa-broadcast-tower", gradient: "from-red-500 to-rose-500", color: "#ef4444" },
|
live: { icon: "fa-broadcast-tower", gradient: "from-red-500 to-rose-500", color: "#ef4444" },
|
||||||
camera: { icon: "fa-video", gradient: "from-red-500 to-rose-500", color: "#ef4444" },
|
camera: { icon: "fa-video", gradient: "from-red-500 to-rose-500", color: "#ef4444" },
|
||||||
|
events: { icon: "fa-calendar", gradient: "from-yellow-500 to-orange-500", color: "#eab308" },
|
||||||
|
event: { icon: "fa-calendar", gradient: "from-yellow-500 to-orange-500", color: "#eab308" },
|
||||||
|
friends: { icon: "fa-user-group", gradient: "from-green-500 to-emerald-500", color: "#22c55e" },
|
||||||
|
market: { icon: "fa-store", gradient: "from-amber-500 to-orange-500", color: "#f59e0b" },
|
||||||
default: { icon: "fa-newspaper", gradient: "from-blue-500 to-cyan-500", color: "#60a5fa" },
|
default: { icon: "fa-newspaper", gradient: "from-blue-500 to-cyan-500", color: "#60a5fa" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -486,6 +490,7 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
const [liked, setLiked] = useState(false);
|
const [liked, setLiked] = useState(false);
|
||||||
const [truth, setTruth] = useState({ score: 50, count: 0 });
|
const [truth, setTruth] = useState({ score: 50, count: 0 });
|
||||||
const [authorAvatar, setAuthorAvatar] = useState("");
|
const [authorAvatar, setAuthorAvatar] = useState("");
|
||||||
|
const [commenterAvatars, setCommenterAvatars] = useState({});
|
||||||
const [comments, setComments] = useState([]);
|
const [comments, setComments] = useState([]);
|
||||||
const [commentInput, setCommentInput] = useState("");
|
const [commentInput, setCommentInput] = useState("");
|
||||||
const [commentSending, setCommentSending] = useState(false);
|
const [commentSending, setCommentSending] = useState(false);
|
||||||
|
|
@ -535,6 +540,22 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
});
|
});
|
||||||
}, [post?.author, post?.username]);
|
}, [post?.author, post?.username]);
|
||||||
|
|
||||||
|
// Fetch commenter avatars
|
||||||
|
useEffect(() => {
|
||||||
|
if (!comments.length) return;
|
||||||
|
const usernames = [...new Set(comments.map(c => c.username).filter(Boolean))];
|
||||||
|
// Only fetch for usernames we don't already have
|
||||||
|
const toFetch = usernames.filter(u => !commenterAvatars[u]);
|
||||||
|
if (!toFetch.length) return;
|
||||||
|
toFetch.forEach(username => {
|
||||||
|
fetchProfile(username).then(res => {
|
||||||
|
if (res?.avatar_url) {
|
||||||
|
setCommenterAvatars(prev => ({ ...prev, [username]: res.avatar_url }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [comments]);
|
||||||
|
|
||||||
// Fetch comments
|
// Fetch comments
|
||||||
const refreshComments = useCallback(async (autoOpen = false) => {
|
const refreshComments = useCallback(async (autoOpen = false) => {
|
||||||
if (!postId) return;
|
if (!postId) return;
|
||||||
|
|
@ -592,8 +613,8 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
} else {
|
} else {
|
||||||
setComments(prev => prev.map(c => c.id === tempId ? { ...c, _pending: false } : c));
|
setComments(prev => prev.map(c => c.id === tempId ? { ...c, _pending: false } : c));
|
||||||
}
|
}
|
||||||
// Refresh comments to get the real list
|
// Delay refresh to let backend commit
|
||||||
refreshComments();
|
setTimeout(() => refreshComments(), 500);
|
||||||
} else {
|
} else {
|
||||||
setComments(prev => prev.map(c => c.id === tempId ? { ...c, _failed: true, _pending: false } : c));
|
setComments(prev => prev.map(c => c.id === tempId ? { ...c, _failed: true, _pending: false } : c));
|
||||||
}
|
}
|
||||||
|
|
@ -874,8 +895,12 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
<motion.div key={c.id} className={`p-3 rounded-xl bg-themed-primary border border-themed ${c._pending ? "opacity-50" : ""} ${c._failed ? "border-red-500/30" : ""}`}
|
<motion.div key={c.id} className={`p-3 rounded-xl bg-themed-primary border border-themed ${c._pending ? "opacity-50" : ""} ${c._failed ? "border-red-500/30" : ""}`}
|
||||||
initial={{ opacity: 0, y: 10 }} animate={{ opacity: c._pending ? 0.5 : 1, y: 0 }}>
|
initial={{ opacity: 0, y: 10 }} animate={{ opacity: c._pending ? 0.5 : 1, y: 0 }}>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-accent to-blue-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">
|
<div className={`w-8 h-8 rounded-full bg-gradient-to-br ${config.gradient} flex items-center justify-center text-white text-xs font-bold flex-shrink-0 overflow-hidden`}>
|
||||||
{(c.username || "?")[0]?.toUpperCase()}
|
{commenterAvatars[c.username] ? (
|
||||||
|
<img src={commenterAvatars[c.username]} alt={c.username} className="w-full h-full object-cover" />
|
||||||
|
) : (
|
||||||
|
(c.username || "?")[0]?.toUpperCase()
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-0.5">
|
<div className="flex items-center gap-2 mb-0.5">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue