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;
display: grid;
gap: 0.9rem;
padding-bottom: 1rem;
}
.post-loading {
@@ -376,7 +377,7 @@ button:disabled {
/* Post Card & Headers */
.post-card {
padding: 0;
padding: 0 0 1rem 0;
overflow: hidden;
}
@@ -384,11 +385,16 @@ button:disabled {
.post-card > .post-copy,
.post-card > .inline-actions,
.post-card > .post-form,
.post-card > .error-text {
.post-card > .error-text,
.post-card > .post-image {
margin-left: 1rem;
margin-right: 1rem;
}
.post-card > .post-image {
width: calc(100% - 2rem);
}
.post-card > .post-top {
margin-top: 1rem;
}
-1
View File
@@ -187,7 +187,6 @@ function App() {
deletePost={(id) => apiClient.deletePost(session.token, id)}
loadImage={(filename) => apiClient.postImage(session.token, filename)}
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;
async function request(path, { method = "GET", token, body, formData } = {}) {
@@ -7,7 +9,7 @@ async function request(path, { method = "GET", token, body, formData } = {}) {
const headers = {};
if (token) {
if (token && token !== "active-session") {
headers.Authorization = `Bearer ${token}`;
}
@@ -87,10 +89,40 @@ export const apiClient = {
}),
postImage: async (token, filename) => {
const headers = {};
if (token) {
headers.Authorization = `Bearer ${token}`;
const effectiveToken = token && token !== "active-session" ? token : getAuthSession().token || null;
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,
credentials: "include",
});
+96 -17
View File
@@ -2,11 +2,55 @@ import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
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;
}
if (!img) {
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, authorId) {
return post.author?.name || post.author_name || (post.author_username ? `@${post.author_username}` : `User ${authorId || ""}`);
function getAuthorName(post) {
return post.author?.name || post.author_name || (post.author_username ? `@${post.author_username}` : "User");
}
function getAuthorUsername(post) {
@@ -50,23 +94,46 @@ function AuthenticatedImage({ filename, alt, loadImage }) {
let active = true;
let objectUrl = "";
setError("");
if (!filename) {
setSource("");
return () => {};
}
loadImage(filename)
.then((blob) => {
if (active) {
objectUrl = URL.createObjectURL(blob);
setSource(objectUrl);
}
})
.catch(() => {
if (active) {
setError("Image unavailable.");
}
});
if (typeof loadImage === "function") {
loadImage(filename)
.then((result) => {
if (!active) return;
if (result instanceof Blob) {
objectUrl = URL.createObjectURL(result);
setSource(objectUrl);
} else if (typeof result === "string") {
setSource(result);
}
setError("");
})
.catch(() => {
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.");
}
});
} else if (typeof filename === "string") {
setSource(filename);
}
return () => {
active = false;
@@ -79,7 +146,19 @@ function AuthenticatedImage({ filename, alt, loadImage }) {
if (error) {
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 }) {
@@ -90,7 +169,7 @@ export function PostCard({ post, currentUserId, loadImage, onEdit, onDelete }) {
const authorId = post.author_id ?? post.authorId ?? post.author?.id;
const isOwner = authorId !== undefined && String(authorId) === String(currentUserId);
const imageFilename = getImageFilename(post);
const authorName = getAuthorName(post, authorId);
const authorName = getAuthorName(post);
const authorUsername = getAuthorUsername(post);
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 }) {
const displayName = user?.name || (user?.username ? `@${user.username}` : `User ${user?.id || ""}`);
const displaySub = user?.username && user?.name ? `@${user.username}` : `ID: ${user?.id}`;
const displayName = user?.name || (user?.username ? `@${user.username}` : "Your Account");
const displaySub = user?.username && user?.name ? `@${user.username}` : "";
return (
<div className="shell">
+17 -17
View File
@@ -5,7 +5,18 @@ function getPosts(data) {
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 [error, setError] = useState("");
const [loading, setLoading] = useState(true);
@@ -16,22 +27,11 @@ export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUs
return loadPosts()
.then((data) => {
const rawPosts = getPosts(data);
// Filter out logged-in user's own posts on feed
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);
setPosts(postsFromOthers(rawPosts, currentUserId));
})
.catch((loadError) => setError(loadError.message))
.finally(() => setLoading(false));
}, [loadPosts, currentUserId, currentUsername]);
}, [loadPosts, currentUserId]);
useEffect(() => {
refresh();
@@ -54,15 +54,15 @@ export function FeedPage({ loadPosts, editPost, deletePost, loadImage, currentUs
<p className="eyebrow">Your daily archive</p>
<h2>Community Feed</h2>
</div>
<span className="feed-status">Explore posts from others</span>
<span className="feed-status">Explore community posts</span>
</header>
{error ? <p className="error-text">{error}</p> : null}
{loading ? (
<p className="post-loading">Loading community feed...</p>
) : posts.length === 0 ? (
<div className="panel empty-feed-panel">
<h3>No posts from other users yet</h3>
<p className="muted">Posts created by you will appear on your profile. Check back later for community updates!</p>
<h3>No posts yet</h3>
<p className="muted">When others share posts, they will show up here.</p>
</div>
) : (
<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);
setProfileData(targetProfile);
} catch {
setProfileData({ id: userId, name: `User ${userId}`, username: null });
setProfileData({ name: "User Profile", username: null });
}
} else {
setProfileData({ id: userId, name: `User ${userId}`, username: null });
setProfileData({ name: "User Profile", username: null });
}
const allPostsData = await loadAllPosts();
@@ -100,12 +100,13 @@ export function ProfilePage({
await createRelationship(userId);
}
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}!`);
} catch {
// Unbinded preview fallback
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();
}
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 displayId = profileData?.id || userId || currentUserId;
return (
<section className="profile-page">
@@ -140,7 +140,6 @@ export function ProfilePage({
<div className="profile-details">
<h3 className="profile-name">{displayName}</h3>
{displayUsername ? <p className="profile-username">@{displayUsername}</p> : null}
<span className="profile-id-badge">ID: {displayId}</span>
</div>
<div className="profile-actions-bar">