fix spacing and me-profile getter and post image
This commit is contained in:
+8
-2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
});
|
});
|
||||||
|
|||||||
+96
-17
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
function getAuthorName(post) {
|
||||||
return post.author?.name || post.author_name || (post.author_username ? `@${post.author_username}` : `User ${authorId || ""}`);
|
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 () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
loadImage(filename)
|
if (typeof loadImage === "function") {
|
||||||
.then((blob) => {
|
loadImage(filename)
|
||||||
if (active) {
|
.then((result) => {
|
||||||
objectUrl = URL.createObjectURL(blob);
|
if (!active) return;
|
||||||
setSource(objectUrl);
|
if (result instanceof Blob) {
|
||||||
}
|
objectUrl = URL.createObjectURL(result);
|
||||||
})
|
setSource(objectUrl);
|
||||||
.catch(() => {
|
} else if (typeof result === "string") {
|
||||||
if (active) {
|
setSource(result);
|
||||||
setError("Image unavailable.");
|
}
|
||||||
}
|
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 () => {
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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} />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user