fix spacing and me-profile getter and post image

This commit is contained in:
Sven laptop
2026-07-29 22:36:17 +02:00
parent a1b6226fbe
commit b712c6ec07
7 changed files with 165 additions and 50 deletions
+8 -2
View File
@@ -353,6 +353,7 @@ button:disabled {
margin-top: 1.15rem; margin-top: 1.15rem;
display: grid; display: grid;
gap: 0.9rem; gap: 0.9rem;
padding-bottom: 1rem;
} }
.post-loading { .post-loading {
@@ -376,7 +377,7 @@ button:disabled {
/* Post Card & Headers */ /* Post Card & Headers */
.post-card { .post-card {
padding: 0; padding: 0 0 1rem 0;
overflow: hidden; overflow: hidden;
} }
@@ -384,11 +385,16 @@ button:disabled {
.post-card > .post-copy, .post-card > .post-copy,
.post-card > .inline-actions, .post-card > .inline-actions,
.post-card > .post-form, .post-card > .post-form,
.post-card > .error-text { .post-card > .error-text,
.post-card > .post-image {
margin-left: 1rem; margin-left: 1rem;
margin-right: 1rem; margin-right: 1rem;
} }
.post-card > .post-image {
width: calc(100% - 2rem);
}
.post-card > .post-top { .post-card > .post-top {
margin-top: 1rem; margin-top: 1rem;
} }
-1
View File
@@ -187,7 +187,6 @@ function App() {
deletePost={(id) => apiClient.deletePost(session.token, id)} deletePost={(id) => apiClient.deletePost(session.token, id)}
loadImage={(filename) => apiClient.postImage(session.token, filename)} loadImage={(filename) => apiClient.postImage(session.token, filename)}
currentUserId={session.user.id} currentUserId={session.user.id}
currentUsername={userProfile?.username}
/> />
} }
/> />
+36 -4
View File
@@ -1,3 +1,5 @@
import { getAuthSession } from "../utils/cookie";
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
async function request(path, { method = "GET", token, body, formData } = {}) { async function request(path, { method = "GET", token, body, formData } = {}) {
@@ -7,7 +9,7 @@ async function request(path, { method = "GET", token, body, formData } = {}) {
const headers = {}; const headers = {};
if (token) { if (token && token !== "active-session") {
headers.Authorization = `Bearer ${token}`; headers.Authorization = `Bearer ${token}`;
} }
@@ -87,10 +89,40 @@ export const apiClient = {
}), }),
postImage: async (token, filename) => { postImage: async (token, filename) => {
const headers = {}; const headers = {};
if (token) { const effectiveToken = token && token !== "active-session" ? token : getAuthSession().token || null;
headers.Authorization = `Bearer ${token}`;
if (effectiveToken && effectiveToken !== "active-session") {
headers.Authorization = `Bearer ${effectiveToken}`;
} }
const response = await fetch(`${API_BASE_URL}/posts/image/${encodeURIComponent(filename)}`, {
let url;
if (typeof filename === "string") {
let target = filename.trim();
if (API_BASE_URL && target.startsWith("API_BASE")) {
target = target.replace(/^API_BASE/, API_BASE_URL);
}
if (target.startsWith("http://") || target.startsWith("https://")) {
if (target.includes("/posts/image/")) {
const imagePart = target.substring(target.indexOf("/posts/image/") + "/posts/image/".length);
const cleanName = encodeURIComponent(decodeURIComponent(imagePart));
url = `${API_BASE_URL}/posts/image/${cleanName}`;
} else {
url = target;
}
} else if (target.includes("/posts/image/")) {
const imagePart = target.substring(target.indexOf("/posts/image/") + "/posts/image/".length);
const cleanName = encodeURIComponent(decodeURIComponent(imagePart));
url = `${API_BASE_URL}/posts/image/${cleanName}`;
} else {
const cleanName = encodeURIComponent(decodeURIComponent(target.split("/").pop()));
url = `${API_BASE_URL}/posts/image/${cleanName}`;
}
} else {
url = `${API_BASE_URL}/posts/image/${encodeURIComponent(filename)}`;
}
const response = await fetch(url, {
headers, headers,
credentials: "include", credentials: "include",
}); });
+88 -9
View File
@@ -2,11 +2,55 @@ import { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
function getImageFilename(post) { function getImageFilename(post) {
return post.image || post.image_filename || post.filename || null; if (!post) return null;
let img =
post.image_link ||
post.imageLink ||
post.image ||
post.image_filename ||
post.imageFilename ||
post.image_url ||
post.imageUrl ||
post.image_path ||
post.imagePath ||
post.img ||
post.photo ||
post.picture ||
post.filename ||
post.file_name ||
post.fileName ||
post.file ||
post.filepath ||
post.file_path ||
post.media ||
post.media_url ||
post.media_filename ||
post.media_link ||
post.attachment ||
post.url ||
post.link ||
null;
if (typeof img === "object" && img !== null) {
img = img.url || img.filename || img.src || img.path || img.name || img.link || null;
} }
function getAuthorName(post, authorId) { if (!img) {
return post.author?.name || post.author_name || (post.author_username ? `@${post.author_username}` : `User ${authorId || ""}`); const text = post.text || post.content || post.body || post.description || "";
const match =
text.match(/(?:(?:https?:\/\/[^\s]+|API_BASE)?\/posts\/image\/[^\s"'<>)]+)/i) ||
text.match(/https?:\/\/[^\s]+\.(?:png|jpe?g|gif|webp|svg|avif)/i);
if (match) {
img = match[0];
}
}
return img;
}
function getAuthorName(post) {
return post.author?.name || post.author_name || (post.author_username ? `@${post.author_username}` : "User");
} }
function getAuthorUsername(post) { function getAuthorUsername(post) {
@@ -50,23 +94,46 @@ function AuthenticatedImage({ filename, alt, loadImage }) {
let active = true; let active = true;
let objectUrl = ""; let objectUrl = "";
setError("");
if (!filename) { if (!filename) {
setSource(""); setSource("");
return () => {}; return () => {};
} }
if (typeof loadImage === "function") {
loadImage(filename) loadImage(filename)
.then((blob) => { .then((result) => {
if (active) { if (!active) return;
objectUrl = URL.createObjectURL(blob); if (result instanceof Blob) {
objectUrl = URL.createObjectURL(result);
setSource(objectUrl); setSource(objectUrl);
} else if (typeof result === "string") {
setSource(result);
} }
setError("");
}) })
.catch(() => { .catch(() => {
if (active) { if (!active) return;
if (typeof filename === "string") {
const apiBase = import.meta.env.VITE_API_BASE_URL || "";
let directUrl = filename;
if (directUrl.startsWith("API_BASE")) {
directUrl = directUrl.replace(/^API_BASE/, apiBase);
} else if (directUrl.includes("/posts/image/")) {
const imagePart = directUrl.substring(directUrl.indexOf("/posts/image/") + "/posts/image/".length);
directUrl = `${apiBase}/posts/image/${encodeURIComponent(decodeURIComponent(imagePart))}`;
} else if (!directUrl.startsWith("http://") && !directUrl.startsWith("https://")) {
directUrl = `${apiBase}/posts/image/${encodeURIComponent(decodeURIComponent(directUrl.split("/").pop()))}`;
}
setSource(directUrl);
setError("");
} else {
setError("Image unavailable."); setError("Image unavailable.");
} }
}); });
} else if (typeof filename === "string") {
setSource(filename);
}
return () => { return () => {
active = false; active = false;
@@ -79,7 +146,19 @@ function AuthenticatedImage({ filename, alt, loadImage }) {
if (error) { if (error) {
return <p className="muted">{error}</p>; return <p className="muted">{error}</p>;
} }
return source ? <img className="post-image" src={source} alt={alt} loading="lazy" decoding="async" /> : null;
return source ? (
<img
className="post-image"
src={source}
alt={alt}
loading="lazy"
decoding="async"
onError={() => {
setError("Image unavailable.");
}}
/>
) : null;
} }
export function PostCard({ post, currentUserId, loadImage, onEdit, onDelete }) { export function PostCard({ post, currentUserId, loadImage, onEdit, onDelete }) {
@@ -90,7 +169,7 @@ export function PostCard({ post, currentUserId, loadImage, onEdit, onDelete }) {
const authorId = post.author_id ?? post.authorId ?? post.author?.id; const authorId = post.author_id ?? post.authorId ?? post.author?.id;
const isOwner = authorId !== undefined && String(authorId) === String(currentUserId); const isOwner = authorId !== undefined && String(authorId) === String(currentUserId);
const imageFilename = getImageFilename(post); const imageFilename = getImageFilename(post);
const authorName = getAuthorName(post, authorId); const authorName = getAuthorName(post);
const authorUsername = getAuthorUsername(post); const authorUsername = getAuthorUsername(post);
const formattedDate = formatDate(post.created_at || post.createdAt); const formattedDate = formatDate(post.created_at || post.createdAt);
+2 -2
View File
@@ -15,8 +15,8 @@ function getInitials(name) {
} }
export function Shell({ user, onLogout, children }) { export function Shell({ user, onLogout, children }) {
const displayName = user?.name || (user?.username ? `@${user.username}` : `User ${user?.id || ""}`); const displayName = user?.name || (user?.username ? `@${user.username}` : "Your Account");
const displaySub = user?.username && user?.name ? `@${user.username}` : `ID: ${user?.id}`; const displaySub = user?.username && user?.name ? `@${user.username}` : "";
return ( return (
<div className="shell"> <div className="shell">
+17 -17
View File
@@ -5,7 +5,18 @@ function getPosts(data) {
return Array.isArray(data) ? data : data.posts || data.items || []; return Array.isArray(data) ? data : data.posts || data.items || [];
} }
export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUserId, currentUsername }) { function getAuthorId(post) {
return post.author_id ?? post.authorId ?? post.author?.id;
}
function postsFromOthers(posts, currentUserId) {
if (!currentUserId) {
return posts;
}
return posts.filter((post) => String(getAuthorId(post)) !== String(currentUserId));
}
export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUserId }) {
const [posts, setPosts] = useState([]); const [posts, setPosts] = useState([]);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -16,22 +27,11 @@ export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUs
return loadPosts() return loadPosts()
.then((data) => { .then((data) => {
const rawPosts = getPosts(data); const rawPosts = getPosts(data);
// Filter out logged-in user's own posts on feed setPosts(postsFromOthers(rawPosts, currentUserId));
const otherUserPosts = rawPosts.filter((post) => {
const authorId = post.author_id ?? post.authorId ?? post.author?.id;
if (authorId !== undefined && authorId !== null) {
return String(authorId) !== String(currentUserId);
}
if (post.author_username && currentUsername) {
return post.author_username !== currentUsername;
}
return true;
});
setPosts(otherUserPosts);
}) })
.catch((loadError) => setError(loadError.message)) .catch((loadError) => setError(loadError.message))
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [loadPosts, currentUserId, currentUsername]); }, [loadPosts, currentUserId]);
useEffect(() => { useEffect(() => {
refresh(); refresh();
@@ -54,15 +54,15 @@ export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUs
<p className="eyebrow">Your daily archive</p> <p className="eyebrow">Your daily archive</p>
<h2>Community Feed</h2> <h2>Community Feed</h2>
</div> </div>
<span className="feed-status">Explore posts from others</span> <span className="feed-status">Explore community posts</span>
</header> </header>
{error ? <p className="error-text">{error}</p> : null} {error ? <p className="error-text">{error}</p> : null}
{loading ? ( {loading ? (
<p className="post-loading">Loading community feed...</p> <p className="post-loading">Loading community feed...</p>
) : posts.length === 0 ? ( ) : posts.length === 0 ? (
<div className="panel empty-feed-panel"> <div className="panel empty-feed-panel">
<h3>No posts from other users yet</h3> <h3>No posts yet</h3>
<p className="muted">Posts created by you will appear on your profile. Check back later for community updates!</p> <p className="muted">When others share posts, they will show up here.</p>
</div> </div>
) : ( ) : (
<PostList posts={posts} currentUserId={currentUserId} loadImage={loadImage} onEdit={handleEdit} onDelete={handleDelete} /> <PostList posts={posts} currentUserId={currentUserId} loadImage={loadImage} onEdit={handleEdit} onDelete={handleDelete} />
+6 -7
View File
@@ -68,10 +68,10 @@ export function ProfilePage({
const targetProfile = await getProfile(userId); const targetProfile = await getProfile(userId);
setProfileData(targetProfile); setProfileData(targetProfile);
} catch { } catch {
setProfileData({ id: userId, name: `User ${userId}`, username: null }); setProfileData({ name: "User Profile", username: null });
} }
} else { } else {
setProfileData({ id: userId, name: `User ${userId}`, username: null }); setProfileData({ name: "User Profile", username: null });
} }
const allPostsData = await loadAllPosts(); const allPostsData = await loadAllPosts();
@@ -100,12 +100,13 @@ export function ProfilePage({
await createRelationship(userId); await createRelationship(userId);
} }
setFollowing(true); setFollowing(true);
const nameOrHandle = profileData?.name || (profileData?.username ? `@${profileData.username}` : `User ${userId}`); const nameOrHandle = profileData?.name || (profileData?.username ? `@${profileData.username}` : "this user");
setFollowMessage(`You are now following ${nameOrHandle}!`); setFollowMessage(`You are now following ${nameOrHandle}!`);
} catch { } catch {
// Unbinded preview fallback // Unbinded preview fallback
setFollowing(true); setFollowing(true);
setFollowMessage(`Follow relationship recorded for User ${userId}`); const nameOrHandle = profileData?.name || (profileData?.username ? `@${profileData.username}` : "this user");
setFollowMessage(`Follow relationship recorded for ${nameOrHandle}`);
} }
} }
@@ -123,9 +124,8 @@ export function ProfilePage({
await loadData(); await loadData();
} }
const displayName = profileData?.name || (isOwnProfile ? (user?.name || "Your Profile") : `User ${userId}`); const displayName = profileData?.name || (isOwnProfile ? (user?.name || "Your Profile") : (profileData?.username ? `@${profileData.username}` : "User Profile"));
const displayUsername = profileData?.username || (isOwnProfile ? user?.username : null); const displayUsername = profileData?.username || (isOwnProfile ? user?.username : null);
const displayId = profileData?.id || userId || currentUserId;
return ( return (
<section className="profile-page"> <section className="profile-page">
@@ -140,7 +140,6 @@ export function ProfilePage({
<div className="profile-details"> <div className="profile-details">
<h3 className="profile-name">{displayName}</h3> <h3 className="profile-name">{displayName}</h3>
{displayUsername ? <p className="profile-username">@{displayUsername}</p> : null} {displayUsername ? <p className="profile-username">@{displayUsername}</p> : null}
<span className="profile-id-badge">ID: {displayId}</span>
</div> </div>
<div className="profile-actions-bar"> <div className="profile-actions-bar">