diff --git a/src/App.css b/src/App.css index 46613f4..8f42b67 100644 --- a/src/App.css +++ b/src/App.css @@ -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; } diff --git a/src/App.jsx b/src/App.jsx index 6675fa0..f8db6be 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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} /> } /> diff --git a/src/api/client.js b/src/api/client.js index 1e8e9b6..8af846d 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -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", }); diff --git a/src/components/PostCard.jsx b/src/components/PostCard.jsx index c341da7..d9f9bee 100644 --- a/src/components/PostCard.jsx +++ b/src/components/PostCard.jsx @@ -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

{error}

; } - return source ? {alt} : null; + + return source ? ( + {alt} { + 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); diff --git a/src/components/Shell.jsx b/src/components/Shell.jsx index 37bbbd3..90a2de4 100644 --- a/src/components/Shell.jsx +++ b/src/components/Shell.jsx @@ -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 (
diff --git a/src/pages/FeedPage.jsx b/src/pages/FeedPage.jsx index 4f49ff6..d9cd561 100644 --- a/src/pages/FeedPage.jsx +++ b/src/pages/FeedPage.jsx @@ -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

Your daily archive

Community Feed

- Explore posts from others + Explore community posts {error ?

{error}

: null} {loading ? (

Loading community feed...

) : posts.length === 0 ? (
-

No posts from other users yet

-

Posts created by you will appear on your profile. Check back later for community updates!

+

No posts yet

+

When others share posts, they will show up here.

) : ( diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx index 75a052b..227438b 100644 --- a/src/pages/ProfilePage.jsx +++ b/src/pages/ProfilePage.jsx @@ -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 (
@@ -140,7 +140,6 @@ export function ProfilePage({

{displayName}

{displayUsername ?

@{displayUsername}

: null} - ID: {displayId}