Add category icons & author avatars to PostDetailModal (v0.0.88)
- Category icon shown when post has no image (with gradient background) - Author avatar fetched from profile API and displayed - Logo position adjusted (6px down, 1px left) - Comments auto-open when existing comments present Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
68c1fce064
commit
0119174969
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "sociowire-frontend",
|
"name": "sociowire-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.87",
|
"version": "0.0.88",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
|
|
|
||||||
|
|
@ -512,7 +512,7 @@ export default function TopBarNew({ theme = "dark", onChangeTheme, onIslandsClic
|
||||||
transition={{ duration: 2, repeat: Infinity }}
|
transition={{ duration: 2, repeat: Infinity }}
|
||||||
/>
|
/>
|
||||||
<div className="relative w-8 h-8 sm:w-10 sm:h-10 md:w-11 md:h-11 lg:w-12 lg:h-12 rounded-lg sm:rounded-xl bg-gradient-to-br from-blue-500 via-cyan-500 to-emerald-500 shadow-lg shadow-cyan-500/25 flex items-center justify-center flex-shrink-0">
|
<div className="relative w-8 h-8 sm:w-10 sm:h-10 md:w-11 md:h-11 lg:w-12 lg:h-12 rounded-lg sm:rounded-xl bg-gradient-to-br from-blue-500 via-cyan-500 to-emerald-500 shadow-lg shadow-cyan-500/25 flex items-center justify-center flex-shrink-0">
|
||||||
<img src="/logo.png" alt="SocioWire" className="w-[130%] h-[130%] max-w-none object-cover" style={{ marginTop: '5px', marginLeft: '2px' }} />
|
<img src="/logo.png" alt="SocioWire" className="w-[130%] h-[130%] max-w-none object-cover" style={{ marginTop: '6px', marginLeft: '1px' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col min-w-0">
|
<div className="flex flex-col min-w-0">
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
editPost,
|
editPost,
|
||||||
deletePost,
|
deletePost,
|
||||||
updatePostVisibility,
|
updatePostVisibility,
|
||||||
|
fetchProfile,
|
||||||
} from "../../api/client";
|
} from "../../api/client";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -44,6 +45,25 @@ function formatCount(n) {
|
||||||
return num.toString();
|
return num.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function categoryIconClass(post) {
|
||||||
|
const c = (post?.category || "NEWS").toString().toUpperCase();
|
||||||
|
switch (c) {
|
||||||
|
case "EVENT":
|
||||||
|
case "EVENTS":
|
||||||
|
return "fa-regular fa-calendar";
|
||||||
|
case "FRIENDS":
|
||||||
|
return "fa-solid fa-user-group";
|
||||||
|
case "MARKET":
|
||||||
|
return "fa-solid fa-store";
|
||||||
|
case "CAMERA":
|
||||||
|
return "fa-solid fa-video";
|
||||||
|
case "LIVE":
|
||||||
|
return "fa-solid fa-tower-broadcast";
|
||||||
|
default:
|
||||||
|
return "fa-regular fa-newspaper";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function relativeTime(dateStr, t) {
|
function relativeTime(dateStr, t) {
|
||||||
if (!dateStr) return "";
|
if (!dateStr) return "";
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
|
|
@ -362,13 +382,14 @@ function HeroStory({ post, gallery, imageIndex, setImageIndex }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No image - show gradient with author
|
// No image - show gradient with category icon
|
||||||
return (
|
return (
|
||||||
<div className={`absolute inset-0 bg-gradient-to-br ${config.gradient}`}>
|
<div className={`absolute inset-0 bg-gradient-to-br ${config.gradient}`}>
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3">
|
||||||
<div className="w-24 h-24 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center text-white font-bold text-4xl shadow-lg">
|
<div className="w-20 h-20 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center text-white shadow-lg">
|
||||||
{author.charAt(0).toUpperCase()}
|
<i className={`${categoryIconClass(post)} text-4xl`} />
|
||||||
</div>
|
</div>
|
||||||
|
<span className="text-white/80 text-sm font-medium uppercase tracking-wider">{post?.category || "News"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -464,6 +485,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 [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);
|
||||||
|
|
@ -504,6 +526,15 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
fetchPostTruth(postId).then(res => { if (res) setTruth({ score: res.truth_score ?? res.user_score ?? 50, count: res.vote_count ?? 0 }); });
|
fetchPostTruth(postId).then(res => { if (res) setTruth({ score: res.truth_score ?? res.user_score ?? 50, count: res.vote_count ?? 0 }); });
|
||||||
}, [postId]);
|
}, [postId]);
|
||||||
|
|
||||||
|
// Fetch author avatar
|
||||||
|
useEffect(() => {
|
||||||
|
const authorName = post?.author || post?.username;
|
||||||
|
if (!authorName) return;
|
||||||
|
fetchProfile(authorName).then(res => {
|
||||||
|
if (res?.avatar_url) setAuthorAvatar(res.avatar_url);
|
||||||
|
});
|
||||||
|
}, [post?.author, post?.username]);
|
||||||
|
|
||||||
// Fetch comments
|
// Fetch comments
|
||||||
const refreshComments = useCallback(async (autoOpen = false) => {
|
const refreshComments = useCallback(async (autoOpen = false) => {
|
||||||
if (!postId) return;
|
if (!postId) return;
|
||||||
|
|
@ -659,8 +690,12 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe
|
||||||
{/* Author Row */}
|
{/* Author Row */}
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<div className={`w-11 h-11 sm:w-12 sm:h-12 rounded-full bg-gradient-to-br ${config.gradient} flex items-center justify-center
|
<div className={`w-11 h-11 sm:w-12 sm:h-12 rounded-full bg-gradient-to-br ${config.gradient} flex items-center justify-center
|
||||||
text-white font-bold text-base sm:text-lg shadow-lg`}>
|
text-white font-bold text-base sm:text-lg shadow-lg overflow-hidden`}>
|
||||||
{author.charAt(0).toUpperCase()}
|
{authorAvatar ? (
|
||||||
|
<img src={authorAvatar} alt={author} className="w-full h-full object-cover" />
|
||||||
|
) : (
|
||||||
|
author.charAt(0).toUpperCase()
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="font-semibold text-themed-primary truncate">@{author}</p>
|
<p className="font-semibold text-themed-primary truncate">@{author}</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue