699 lines
23 KiB
JavaScript
699 lines
23 KiB
JavaScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { loadAuth } from "../../auth/authStorage";
|
|
import {
|
|
fetchPostLikeState,
|
|
recordPostShare,
|
|
togglePostLike,
|
|
updatePostVisibility,
|
|
fetchProfile,
|
|
editPost,
|
|
deletePost,
|
|
} from "../../api/client";
|
|
import { useAuth } from "../../auth/AuthContext";
|
|
import { trackEvent } from "../../utils/analytics";
|
|
|
|
function isAuthed() {
|
|
try {
|
|
const auth = loadAuth();
|
|
return !!(auth && auth.access_token);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function requestAuth(message) {
|
|
try {
|
|
window.dispatchEvent(
|
|
new CustomEvent("sociowire:auth-required", {
|
|
detail: { message, mode: "login" },
|
|
})
|
|
);
|
|
} catch {}
|
|
trackEvent("auth_required", { context: "wall", mode: "login" });
|
|
}
|
|
|
|
// Time formatting is now handled in the component with translations
|
|
|
|
function formatRelativeTime(createdAt) {
|
|
try {
|
|
if (!createdAt) return "now";
|
|
const d = createdAt instanceof Date ? createdAt : new Date(String(createdAt));
|
|
if (Number.isNaN(d.getTime())) return "now";
|
|
|
|
const s = Math.floor((Date.now() - d.getTime()) / 1000);
|
|
if (s < 60) return "now";
|
|
const m = Math.floor(s / 60);
|
|
if (m < 60) return `${m}m`;
|
|
const h = Math.floor(m / 60);
|
|
if (h < 48) return `${h}h`;
|
|
const days = Math.floor(h / 24);
|
|
return `${days}d`;
|
|
} catch {
|
|
return "now";
|
|
}
|
|
}
|
|
|
|
function formatCount(value) {
|
|
const n = Number(value);
|
|
if (!Number.isFinite(n)) return "0";
|
|
if (n < 1000) return String(Math.max(0, Math.floor(n)));
|
|
if (n < 1000000) return `${(n / 1000).toFixed(1)}k`;
|
|
return `${(n / 1000000).toFixed(1)}m`;
|
|
}
|
|
|
|
function categoryLabel(post) {
|
|
const c = (post?.category || "NEWS").toString().toUpperCase();
|
|
if (c === "EVENT") return "EVENTS";
|
|
return c;
|
|
}
|
|
|
|
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";
|
|
default:
|
|
return "fa-regular fa-newspaper";
|
|
}
|
|
}
|
|
|
|
function normalizeImageUrl(raw) {
|
|
const v = (raw || "").toString().trim();
|
|
if (!v) return "";
|
|
const lower = v.toLowerCase();
|
|
if (
|
|
lower === "null" ||
|
|
lower === "undefined" ||
|
|
lower === "none" ||
|
|
lower === "false" ||
|
|
lower === "0" ||
|
|
lower === "self" ||
|
|
lower === "default"
|
|
) {
|
|
return "";
|
|
}
|
|
if (lower.startsWith("data:")) return "";
|
|
if (
|
|
lower.startsWith("http://") ||
|
|
lower.startsWith("https://") ||
|
|
lower.startsWith("//") ||
|
|
lower.startsWith("/") ||
|
|
lower.startsWith("./")
|
|
) {
|
|
return v;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
const wallAvatarCache = new Map();
|
|
const wallAvatarInflight = new Map();
|
|
|
|
async function getAvatarForUser(name) {
|
|
const key = (name || "").toLowerCase().trim();
|
|
if (!key) return "";
|
|
if (wallAvatarCache.has(key)) return wallAvatarCache.get(key);
|
|
if (wallAvatarInflight.has(key)) return wallAvatarInflight.get(key);
|
|
const p = (async () => {
|
|
const profile = await fetchProfile(name);
|
|
const url = (profile && profile.avatar_url) ? String(profile.avatar_url) : "";
|
|
wallAvatarCache.set(key, url);
|
|
wallAvatarInflight.delete(key);
|
|
return url;
|
|
})().catch(() => {
|
|
wallAvatarCache.set(key, "");
|
|
wallAvatarInflight.delete(key);
|
|
return "";
|
|
});
|
|
wallAvatarInflight.set(key, p);
|
|
return p;
|
|
}
|
|
|
|
export default function PostCard({ post, selected, onSelect, onPostDeleted, onPostUpdated }) {
|
|
const { t } = useTranslation();
|
|
const [liked, setLiked] = useState(false);
|
|
const [authed, setAuthed] = useState(isAuthed());
|
|
const [activeImageIndex, setActiveImageIndex] = useState(0);
|
|
const [failedImageIndices, setFailedImageIndices] = useState(() => new Set());
|
|
const [imgFailed, setImgFailed] = useState(false);
|
|
const [showPrivacy, setShowPrivacy] = useState(false);
|
|
const [privacySaving, setPrivacySaving] = useState(false);
|
|
const [privacyError, setPrivacyError] = useState("");
|
|
const [postVisibility, setPostVisibility] = useState(
|
|
(post?.visibility || "public").toString()
|
|
);
|
|
const [postGroupId, setPostGroupId] = useState(
|
|
(post?.group_id || "").toString()
|
|
);
|
|
const [postUsers, setPostUsers] = useState("");
|
|
const [applyToImages, setApplyToImages] = useState(true);
|
|
const [counts, setCounts] = useState(() => ({
|
|
like: post?.like_count ?? 0,
|
|
comment: post?.comment_count ?? 0,
|
|
share: post?.share_count ?? 0,
|
|
}));
|
|
const { username, token } = useAuth();
|
|
|
|
// Edit/Delete state
|
|
const [showEditDelete, setShowEditDelete] = useState(false);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editTitle, setEditTitle] = useState("");
|
|
const [editSnippet, setEditSnippet] = useState("");
|
|
const [editSaving, setEditSaving] = useState(false);
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
const [deleting, setDeleting] = useState(false);
|
|
|
|
const postId = post?.id ?? post?._id ?? 0;
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
if (!authed || !postId) return () => {};
|
|
fetchPostLikeState(postId).then((res) => {
|
|
if (!active) return;
|
|
if (res?.ok && res.liked) setLiked(true);
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [authed, postId]);
|
|
|
|
useEffect(() => {
|
|
const handler = () => {
|
|
const next = isAuthed();
|
|
setAuthed(next);
|
|
if (!next) setLiked(false);
|
|
};
|
|
window.addEventListener("sociowire:auth-changed", handler);
|
|
return () => window.removeEventListener("sociowire:auth-changed", handler);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
setCounts({
|
|
like: post?.like_count ?? 0,
|
|
comment: post?.comment_count ?? 0,
|
|
share: post?.share_count ?? 0,
|
|
});
|
|
}, [post?.like_count, post?.comment_count, post?.share_count]);
|
|
useEffect(() => {
|
|
setPostVisibility((post?.visibility || "public").toString());
|
|
setPostGroupId((post?.group_id || "").toString());
|
|
}, [post?.visibility, post?.group_id]);
|
|
|
|
const mediaUrls = useMemo(() => {
|
|
const list = [];
|
|
const seen = new Set();
|
|
const addUrl = (value) => {
|
|
const normalized = normalizeImageUrl(value);
|
|
if (!normalized || seen.has(normalized)) {
|
|
return;
|
|
}
|
|
seen.add(normalized);
|
|
list.push(normalized);
|
|
};
|
|
if (Array.isArray(post?.media_urls)) {
|
|
post.media_urls.forEach(addUrl);
|
|
}
|
|
[post?.image_large, post?.image_medium, post?.image_small, post?.image, post?.media_url].forEach(addUrl);
|
|
return list;
|
|
}, [post?.media_urls, post?.image_large, post?.image_medium, post?.image_small, post?.image, post?.media_url]);
|
|
const mediaKey = mediaUrls.join("|");
|
|
|
|
useEffect(() => {
|
|
setActiveImageIndex(0);
|
|
setFailedImageIndices(new Set());
|
|
setImgFailed(false);
|
|
}, [mediaKey]);
|
|
|
|
const title = post?.title || "Untitled";
|
|
const snippet = post?.snippet || "";
|
|
const author = (post?.author || post?.Author || "anon").toString();
|
|
const isAuthor =
|
|
username && author && author.toLowerCase() === username.toLowerCase();
|
|
const [authorAvatar, setAuthorAvatar] = useState("");
|
|
const sub = (post?.sub_category || post?.subCategory || "").toString();
|
|
const timeAgo = formatRelativeTime(post?.created_at || post?.CreatedAt || post?.createdAt);
|
|
const cat = categoryLabel(post);
|
|
const metaLine = useMemo(() => {
|
|
const parts = [];
|
|
if (sub) parts.push(sub);
|
|
if (post?.region) parts.push(post.region);
|
|
return parts.join(" • ");
|
|
}, [sub, post?.region]);
|
|
|
|
const safeImageIndex = mediaUrls.length
|
|
? Math.min(Math.max(activeImageIndex, 0), mediaUrls.length - 1)
|
|
: 0;
|
|
const activeImage = mediaUrls[safeImageIndex] || "";
|
|
const hasGalleryImage = Boolean(activeImage && !imgFailed);
|
|
|
|
const findNextAvailableIndex = (current, failed) => {
|
|
if (mediaUrls.length === 0) return -1;
|
|
for (let offset = 1; offset <= mediaUrls.length; offset += 1) {
|
|
const idx = (current + offset) % mediaUrls.length;
|
|
if (!failed.has(idx)) {
|
|
return idx;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
const findPrevAvailableIndex = (current, failed) => {
|
|
if (mediaUrls.length === 0) return -1;
|
|
for (let offset = 1; offset <= mediaUrls.length; offset += 1) {
|
|
const idx = (current - offset + mediaUrls.length) % mediaUrls.length;
|
|
if (!failed.has(idx)) {
|
|
return idx;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
const handleImageError = () => {
|
|
if (!activeImage) {
|
|
setImgFailed(true);
|
|
return;
|
|
}
|
|
const nextFailed = new Set(failedImageIndices);
|
|
nextFailed.add(safeImageIndex);
|
|
setFailedImageIndices(nextFailed);
|
|
if (nextFailed.size >= mediaUrls.length) {
|
|
setImgFailed(true);
|
|
return;
|
|
}
|
|
const nextIndex = findNextAvailableIndex(safeImageIndex, nextFailed);
|
|
if (nextIndex >= 0) {
|
|
setActiveImageIndex(nextIndex);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
const who = (author || "").trim();
|
|
if (!who) {
|
|
setAuthorAvatar("");
|
|
return;
|
|
}
|
|
getAvatarForUser(who).then((url) => {
|
|
if (cancelled) return;
|
|
setAuthorAvatar(url || "");
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [author]);
|
|
|
|
return (
|
|
<article
|
|
className={"post-card sw-wall-card" + (selected ? " selected" : "")}
|
|
onClick={() => {
|
|
trackEvent("post_view", {
|
|
post_id: postId,
|
|
post_title: title?.slice(0, 100),
|
|
post_category: cat,
|
|
post_author: author,
|
|
content_type: "post",
|
|
});
|
|
onSelect && onSelect(post);
|
|
}}
|
|
>
|
|
<div className="sw-wall-head">
|
|
<div className="sw-wall-avatar">
|
|
{authorAvatar ? <img src={authorAvatar} alt={author} /> : (author ? author[0]?.toUpperCase() : "U")}
|
|
</div>
|
|
<div className="sw-wall-meta">
|
|
<div className="sw-wall-author">{author}</div>
|
|
<div className="sw-wall-time">{timeAgo}</div>
|
|
</div>
|
|
<div className="sw-wall-badge">{cat}</div>
|
|
</div>
|
|
|
|
{hasGalleryImage ? (
|
|
<div className="sw-wall-gallery">
|
|
<div className="sw-wall-image">
|
|
<img
|
|
src={activeImage}
|
|
alt=""
|
|
loading="lazy"
|
|
onError={handleImageError}
|
|
/>
|
|
{mediaUrls.length > 1 && (
|
|
<div className="sw-wall-gallery-count">
|
|
{safeImageIndex + 1}/{mediaUrls.length}
|
|
</div>
|
|
)}
|
|
{mediaUrls.length > 1 && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-gallery-arrow left"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
const prev = findPrevAvailableIndex(safeImageIndex, failedImageIndices);
|
|
if (prev >= 0) setActiveImageIndex(prev);
|
|
}}
|
|
aria-label="Previous image"
|
|
>
|
|
<i className="fa-solid fa-chevron-left" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-gallery-arrow right"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
const next = findNextAvailableIndex(safeImageIndex, failedImageIndices);
|
|
if (next >= 0) setActiveImageIndex(next);
|
|
}}
|
|
aria-label="Next image"
|
|
>
|
|
<i className="fa-solid fa-chevron-right" />
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
{mediaUrls.length > 1 && (
|
|
<div className="sw-wall-gallery-thumbs">
|
|
{mediaUrls.map((value, idx) => (
|
|
<button
|
|
key={`thumb-${value}-${idx}`}
|
|
type="button"
|
|
className={`sw-wall-gallery-thumb${idx === safeImageIndex ? " active" : ""}`}
|
|
onClick={() => setActiveImageIndex(idx)}
|
|
>
|
|
<img src={value} alt="" loading="lazy" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="sw-wall-image sw-wall-image-fallback" aria-hidden="true">
|
|
<div className="sw-wall-image-fallback-inner">
|
|
<i className={categoryIconClass(post)} />
|
|
<span>{categoryLabel(post)}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="sw-wall-body">
|
|
<div className="sw-wall-title">{title}</div>
|
|
{snippet ? <div className="sw-wall-snippet">{snippet}</div> : null}
|
|
{metaLine ? <div className="sw-wall-meta-line">{metaLine}</div> : null}
|
|
</div>
|
|
|
|
<div className="sw-wall-footer">
|
|
<div className="sw-wall-stats">
|
|
<span><i className="fa-regular fa-heart" /> {formatCount(counts.like)}</span>
|
|
<span><i className="fa-regular fa-comment" /> {formatCount(counts.comment)}</span>
|
|
<span><i className="fa-solid fa-share-nodes" /> {formatCount(counts.share)}</span>
|
|
</div>
|
|
|
|
<div className="sw-wall-actions">
|
|
<button
|
|
type="button"
|
|
className={"sw-wall-btn" + (liked ? " is-active" : "")}
|
|
onClick={async (e) => {
|
|
e.stopPropagation();
|
|
if (!authed) {
|
|
requestAuth(t('post.signInToLike'));
|
|
return;
|
|
}
|
|
trackEvent("post_like", { post_id: postId, post_title: title?.slice(0, 100), post_category: cat });
|
|
const res = await togglePostLike(postId, !liked);
|
|
if (res?.ok) {
|
|
setLiked(!!res.liked);
|
|
if (res?.counts) {
|
|
setCounts({
|
|
like: res.counts.like_count,
|
|
comment: res.counts.comment_count,
|
|
share: res.counts.share_count,
|
|
});
|
|
}
|
|
}
|
|
}}
|
|
>
|
|
<i className="fa-regular fa-heart" /> {t('post.like')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (!authed) {
|
|
requestAuth(t('post.signInToComment'));
|
|
return;
|
|
}
|
|
trackEvent("post_comment", { post_id: postId, post_title: title?.slice(0, 100), post_category: cat });
|
|
if (onSelect) onSelect(post);
|
|
}}
|
|
>
|
|
<i className="fa-regular fa-comment" /> {t('post.comment')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
onClick={async (e) => {
|
|
e.stopPropagation();
|
|
const shareUrl = `${window.location.origin}/p/${postId}`;
|
|
trackEvent("post_share", { post_id: postId, post_title: title?.slice(0, 100), post_category: cat });
|
|
try {
|
|
if (navigator.share) {
|
|
await navigator.share({ title: title || "SocioWire", url: shareUrl });
|
|
} else if (navigator.clipboard) {
|
|
await navigator.clipboard.writeText(shareUrl);
|
|
}
|
|
} catch {}
|
|
const res = await recordPostShare(postId);
|
|
if (res?.counts) {
|
|
setCounts({
|
|
like: res.counts.like_count,
|
|
comment: res.counts.comment_count,
|
|
share: res.counts.share_count,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
<i className="fa-solid fa-share-nodes" /> {t('post.share')}
|
|
</button>
|
|
|
|
{isAuthor ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className={"sw-wall-btn" + (showPrivacy ? " is-active" : "")}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setShowPrivacy((prev) => !prev);
|
|
setShowEditDelete(false);
|
|
}}
|
|
>
|
|
<i className="fa-solid fa-lock" /> {t('post.visibility')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={"sw-wall-btn" + (showEditDelete ? " is-active" : "")}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setShowEditDelete((prev) => !prev);
|
|
setShowPrivacy(false);
|
|
if (!showEditDelete) {
|
|
setEditTitle(post?.title || "");
|
|
setEditSnippet(post?.snippet || "");
|
|
}
|
|
}}
|
|
>
|
|
<i className="fa-solid fa-ellipsis" />
|
|
</button>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
{isAuthor && showPrivacy ? (
|
|
<div className="sw-wall-privacy" onClick={(e) => e.stopPropagation()}>
|
|
<div className="sw-wall-privacy-row">
|
|
<label>{t('common.post')}</label>
|
|
<select
|
|
value={postVisibility}
|
|
onChange={(e) => {
|
|
const next = e.target.value;
|
|
setPostVisibility(next);
|
|
if (next !== "group") setPostGroupId("");
|
|
if (next !== "custom") setPostUsers("");
|
|
}}
|
|
>
|
|
<option value="public">{t('post.public')}</option>
|
|
<option value="friends">{t('post.friendsOnly')}</option>
|
|
<option value="private">{t('post.private')}</option>
|
|
<option value="group">{t('post.group')}</option>
|
|
<option value="custom">{t('post.custom')}</option>
|
|
</select>
|
|
</div>
|
|
{postVisibility === "group" ? (
|
|
<input
|
|
className="sw-wall-privacy-input"
|
|
placeholder={t('post.groupId')}
|
|
value={postGroupId}
|
|
onChange={(e) => setPostGroupId(e.target.value)}
|
|
/>
|
|
) : null}
|
|
{postVisibility === "custom" ? (
|
|
<input
|
|
className="sw-wall-privacy-input"
|
|
placeholder={t('post.usernamesComma')}
|
|
value={postUsers}
|
|
onChange={(e) => setPostUsers(e.target.value)}
|
|
/>
|
|
) : null}
|
|
<label className="sw-wall-privacy-check">
|
|
<input
|
|
type="checkbox"
|
|
checked={applyToImages}
|
|
onChange={(e) => setApplyToImages(e.target.checked)}
|
|
/>
|
|
{t('post.applyToImages')}
|
|
</label>
|
|
{privacyError ? <div className="sw-wall-privacy-error">{privacyError}</div> : null}
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
disabled={privacySaving}
|
|
onClick={async () => {
|
|
setPrivacyError("");
|
|
setPrivacySaving(true);
|
|
try {
|
|
const payload = {
|
|
visibility: postVisibility,
|
|
group_id: postGroupId.trim(),
|
|
users: postUsers
|
|
.split(",")
|
|
.map((u) => u.trim())
|
|
.filter(Boolean),
|
|
};
|
|
if (applyToImages) {
|
|
payload.image_visibility = postVisibility;
|
|
payload.image_group_id = postGroupId.trim();
|
|
payload.image_users = payload.users;
|
|
}
|
|
await updatePostVisibility(postId, payload, token);
|
|
setPrivacySaving(false);
|
|
} catch (err) {
|
|
setPrivacyError(t('post.visibilityError'));
|
|
setPrivacySaving(false);
|
|
}
|
|
}}
|
|
>
|
|
{privacySaving ? t('post.saving') : t('common.save')}
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
|
|
{isAuthor && showEditDelete ? (
|
|
<div className="sw-wall-edit-menu" onClick={(e) => e.stopPropagation()}>
|
|
{!isEditing && !showDeleteConfirm ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
onClick={() => setIsEditing(true)}
|
|
>
|
|
<i className="fa-solid fa-pen" /> {t('common.edit')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn sw-wall-btn-danger"
|
|
onClick={() => setShowDeleteConfirm(true)}
|
|
>
|
|
<i className="fa-solid fa-trash" /> {t('common.delete')}
|
|
</button>
|
|
</>
|
|
) : null}
|
|
|
|
{isEditing ? (
|
|
<div className="sw-wall-edit-form">
|
|
<input
|
|
type="text"
|
|
className="sw-wall-edit-input"
|
|
placeholder={t('post.title')}
|
|
value={editTitle}
|
|
onChange={(e) => setEditTitle(e.target.value)}
|
|
/>
|
|
<textarea
|
|
className="sw-wall-edit-textarea"
|
|
placeholder={t('post.description')}
|
|
value={editSnippet}
|
|
onChange={(e) => setEditSnippet(e.target.value)}
|
|
rows={3}
|
|
/>
|
|
<div className="sw-wall-edit-actions">
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
disabled={editSaving}
|
|
onClick={async () => {
|
|
setEditSaving(true);
|
|
const res = await editPost(postId, {
|
|
title: editTitle.trim(),
|
|
snippet: editSnippet.trim(),
|
|
}, token);
|
|
setEditSaving(false);
|
|
if (res?.ok) {
|
|
setIsEditing(false);
|
|
setShowEditDelete(false);
|
|
if (onPostUpdated) onPostUpdated(res);
|
|
}
|
|
}}
|
|
>
|
|
{editSaving ? t('post.saving') : t('common.save')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
onClick={() => setIsEditing(false)}
|
|
>
|
|
{t('common.cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{showDeleteConfirm ? (
|
|
<div className="sw-wall-delete-confirm">
|
|
<p>{t('post.deletePost')}</p>
|
|
<div className="sw-wall-edit-actions">
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn sw-wall-btn-danger"
|
|
disabled={deleting}
|
|
onClick={async () => {
|
|
setDeleting(true);
|
|
const res = await deletePost(postId, token);
|
|
setDeleting(false);
|
|
if (res?.ok) {
|
|
setShowDeleteConfirm(false);
|
|
setShowEditDelete(false);
|
|
if (onPostDeleted) onPostDeleted(postId);
|
|
}
|
|
}}
|
|
>
|
|
{deleting ? t('post.deleting') : t('common.delete')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="sw-wall-btn"
|
|
onClick={() => setShowDeleteConfirm(false)}
|
|
>
|
|
{t('common.cancel')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
);
|
|
}
|