2026-02-21 19:04:54 +00:00

660 lines
23 KiB
TypeScript

"use client";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { getSocialAuthStatus } from "@/utils/commonFunction.utils";
import SocialCommentItem from "@/components/SocialCommentItem";
import {
Image as ImageIcon,
Video,
MessageSquare,
Heart,
Calendar,
ExternalLink,
Send,
RefreshCw,
ChevronLeft,
MoreVertical,
BarChart3,
Users,
Clock,
Shield,
AlertCircle,
CheckCircle
} from "lucide-react";
type Reply = {
id: string;
text: string;
timestamp: string;
username: string;
hidden?: boolean;
like_count?: number;
};
type Comment = {
id: string;
text: string;
username: string;
timestamp: string;
like_count?: number;
hidden?: boolean;
replies?: { data: Reply[] };
};
type MediaDetails = {
id: string;
media_url: string;
caption?: string;
media_type: string;
timestamp: string;
like_count?: number;
comments_count?: number;
permalink?: string;
};
const MediaDetailsPage = ({ params }: { params: { id: string } }) => {
const { id } = params;
const router = useRouter();
const [media, setMedia] = useState<MediaDetails | null>(null);
const [comments, setComments] = useState<Comment[]>([]);
const [loading, setLoading] = useState(true);
const [commentsLoading, setCommentsLoading] = useState(false);
const [newCommentText, setNewCommentText] = useState("");
const [error, setError] = useState("");
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
async function validate() {
const userEmail = localStorage.getItem("user_email");
if (!userEmail) {
router.push("/social-media-connect");
return;
}
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) {
router.push("/social-media-connect");
return;
}
const user = JSON.parse(storedUser);
const role = user?.role;
if (role === "customer") {
const session = localStorage.getItem("payment_session");
if (!session) {
router.push("/pricing");
return;
}
}
const res = await getSocialAuthStatus(userEmail);
if (!res?.connected) {
router.push("/social-media-connect");
return;
}
}
validate();
}, []);
// Fetch Media Details
useEffect(() => {
const fetchMedia = async () => {
try {
const res = await axios.get(`${ApiServerBaseUrl}/social/media/${id}?userId=${localStorage.getItem(
"user_email"
)}`);
setMedia(res.data);
} catch (err: any) {
setError(err.response?.data?.message || "Unable to load media");
} finally {
setLoading(false);
}
};
fetchMedia();
}, [id]);
// Fetch Comments
const loadComments = async () => {
setCommentsLoading(true);
try {
const res = await axios.get(
`${ApiServerBaseUrl}/social/media/${id}/comments?userId=${localStorage.getItem(
"user_email"
)}`
);
setComments(res.data?.data || []);
} catch {
console.error("Failed to load comments");
} finally {
setCommentsLoading(false);
}
};
useEffect(() => {
if (media) loadComments();
}, [media]);
// Post new top-level comment
const postNewComment = async () => {
if (!newCommentText.trim()) {
alert("Comment cannot be empty.");
return;
}
try {
const res = await axios.post(
`${ApiServerBaseUrl}/social/media/${id}/comments?userId=${localStorage.getItem(
"user_email"
)}`,
{ message: newCommentText }
);
const newComment: Comment = {
id: res.data?.comment_id || res.data?.id || `temp-${Date.now()}`,
text: newCommentText,
username: "Me",
timestamp: new Date().toISOString(),
replies: { data: [] }
};
setComments((prev) => [newComment, ...prev]);
setNewCommentText("");
} catch (err: any) {
alert(err.response?.data?.error || "Failed to post comment.");
}
};
// Reply handler
const handleReply = async (commentId: string, text: string) => {
try {
const res = await axios.post(
`${ApiServerBaseUrl}/social/comments/${commentId}/reply?userId=${localStorage.getItem(
"user_email"
)}`,
{ message: text }
);
const newReply: Reply = {
id: res.data?.reply_id || res.data?.id || `temp-${Date.now()}`,
text: text,
username: "Me",
timestamp: new Date().toISOString(),
};
setComments((prev) =>
prev.map((cm) =>
cm.id === commentId
? {
...cm,
replies: {
data: [...(cm.replies?.data || []), newReply],
},
}
: cm
)
);
} catch (err: any) {
alert(err.response?.data?.error || "Reply failed.");
}
};
// Delete handler
const handleDelete = async (id: string, isReply: boolean = false, parentId?: string) => {
const confirmed = window.confirm("Are you sure you want to delete this?");
if (!confirmed) return;
try {
await axios.delete(`${ApiServerBaseUrl}/social/comments/${id}?userId=${localStorage.getItem(
"user_email"
)}`);
if (isReply && parentId) {
setComments((prev) =>
prev.map((cm) => {
if (cm.id === parentId) {
return {
...cm,
replies: {
data: cm.replies?.data.filter((r) => r.id !== id) || [],
},
};
}
return cm;
})
);
} else {
setComments((prev) => prev.filter((c) => c.id !== id));
}
} catch (err: any) {
alert("Delete failed: " + (err.response?.data?.error || err.message));
}
};
// Hide/Unhide handler
const handleHide = async (id: string, currentStatus: boolean, isReply: boolean = false) => {
try {
const newHideStatus = !currentStatus;
await axios.post(`${ApiServerBaseUrl}/social/comments/${id}/hide?userId=${localStorage.getItem("user_email")}`, {
hide: newHideStatus,
});
if (isReply) {
setComments((prev) =>
prev.map((cm) => {
const replyIndex = cm.replies?.data.findIndex(r => r.id === id);
if (replyIndex !== undefined && replyIndex !== -1 && cm.replies?.data) {
const updatedReplies = [...cm.replies.data];
updatedReplies[replyIndex] = { ...updatedReplies[replyIndex], hidden: newHideStatus };
return { ...cm, replies: { data: updatedReplies } };
}
return cm;
})
);
} else {
setComments((prev) =>
prev.map((c) => (c.id === id ? { ...c, hidden: newHideStatus } : c))
);
}
} catch (err: any) {
alert(err.response?.data?.error || "Hide/Unhide failed.");
}
};
// Edit handler
const handleEdit = async (id: string, newText: string, isReply: boolean = false, parentId?: string) => {
if (id.toString().startsWith("temp-") || id.toString().startsWith("new-")) {
alert("Please refresh the page to get the real ID from Instagram before editing this comment again.");
return;
}
const confirmed = window.confirm(
"Compatible Edit Mode:\n\nInstagram does not allow editing comments directly.\n\nProceeding will DELETE the original comment (losing likes/replies) and POST a new one with the updated text.\n\nDo you want to continue?"
);
if (!confirmed) return;
try {
await axios.delete(`${ApiServerBaseUrl}/social/comments/${id}?userId=${localStorage.getItem("user_email")}`);
let res;
if (isReply && parentId) {
res = await axios.post(
`${ApiServerBaseUrl}/social/comments/${parentId}/reply?userId=${localStorage.getItem("user_email")}`,
{ message: newText }
);
} else {
res = await axios.post(
`${ApiServerBaseUrl}/social/media/${media?.id}/comments?userId=${localStorage.getItem("user_email")}`,
{ message: newText }
);
}
const newId = res.data?.id || res.data?.comment_id || res.data?.reply_id;
const safeId = newId || `new-${Date.now()}`;
if (isReply && parentId) {
setComments((prev) =>
prev.map((cm) => {
if (cm.id === parentId) {
const oldReplies = cm.replies?.data || [];
const filteredReplies = oldReplies.filter((r) => r.id !== id);
const newReplyObj: Reply = {
id: safeId,
text: newText,
username: "Me",
timestamp: new Date().toISOString(),
hidden: false,
like_count: 0
};
return {
...cm,
replies: { data: [...filteredReplies, newReplyObj] }
};
}
return cm;
})
);
} else {
setComments((prev) => {
const filtered = prev.filter((c) => c.id !== id);
const newCommentObj: Comment = {
id: safeId,
text: newText,
username: "Me",
timestamp: new Date().toISOString(),
like_count: 0,
hidden: false,
replies: { data: [] }
};
return [newCommentObj, ...filtered];
});
}
} catch (err: any) {
console.error(err);
alert("Edit failed: " + (err.response?.data?.error || err.message));
}
};
// Refresh comments
const handleRefreshComments = async () => {
setRefreshing(true);
await loadComments();
setTimeout(() => setRefreshing(false), 1000);
};
// Format date
const formatDate = (timestamp: string) => {
const date = new Date(timestamp);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
if (loading) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-pink-500 mx-auto mb-4"></div>
<p className="text-gray-300">Loading media details...</p>
</div>
</div>
);
if (error) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-6">
<div className="max-w-4xl mx-auto">
<div className="bg-red-900/20 backdrop-blur-xl rounded-2xl p-8 border border-red-500/30">
<div className="flex items-center gap-4">
<AlertCircle className="w-12 h-12 text-red-400" />
<div>
<h2 className="text-2xl font-bold text-white mb-2">Error Loading Media</h2>
<p className="text-gray-300">{error}</p>
</div>
</div>
</div>
</div>
</div>
);
if (!media) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] flex items-center justify-center">
<p className="text-gray-300">No media found.</p>
</div>
);
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="max-w-6xl mx-auto relative z-10">
{/* Header */}
<div className="mb-8">
<button
onClick={() => router.push("/social-media-posts")}
className="inline-flex items-center gap-2 text-gray-400 hover:text-white mb-6 transition-colors"
>
<ChevronLeft className="w-4 h-4" />
Back to Media Library
</button>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white mb-2">Post Details</h1>
<p className="text-gray-300">Manage comments and view post details</p>
</div>
<div className="flex items-center gap-4">
<button
onClick={handleRefreshComments}
disabled={refreshing}
className="px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-all duration-300 flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh Comments
</button>
</div>
</div>
</div>
{/* Main Content */}
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Media */}
<div className="lg:col-span-2">
{/* Media Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl overflow-hidden mb-8">
{/* Media Display */}
<div className="relative">
{media.media_type === "VIDEO" ? (
<video
src={media.media_url}
controls
className="w-full h-auto max-h-[600px] object-contain bg-black"
poster={media.media_url}
/>
) : (
<img
src={media.media_url}
alt="Instagram Media"
className="w-full h-auto max-h-[600px] object-contain bg-black"
/>
)}
<div className="absolute top-4 right-4">
<div className="px-3 py-1 rounded-full bg-black/80 backdrop-blur-sm text-sm font-medium text-white flex items-center gap-2">
{media.media_type === "VIDEO" ? (
<Video className="w-4 h-4" />
) : (
<ImageIcon className="w-4 h-4" />
)}
<span className="capitalize">{media.media_type.toLowerCase()}</span>
</div>
</div>
</div>
{/* Media Details */}
<div className="p-6">
{media.caption && (
<div className="mb-6">
<h3 className="text-white font-semibold mb-2">Caption</h3>
<p className="text-gray-300 leading-relaxed">{media.caption}</p>
</div>
)}
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Heart className="w-4 h-4 text-pink-400" />
<span className="text-gray-400 text-sm">Likes</span>
</div>
<div className="text-2xl font-bold text-white">{media.like_count || 0}</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<MessageSquare className="w-4 h-4 text-blue-400" />
<span className="text-gray-400 text-sm">Comments</span>
</div>
<div className="text-2xl font-bold text-white">{media.comments_count || 0}</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Calendar className="w-4 h-4 text-green-400" />
<span className="text-gray-400 text-sm">Posted</span>
</div>
<div className="text-lg font-bold text-white">
{new Date(media.timestamp).toLocaleDateString()}
</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Clock className="w-4 h-4 text-purple-400" />
<span className="text-gray-400 text-sm">Time</span>
</div>
<div className="text-lg font-bold text-white">
{new Date(media.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</div>
</div>
</div>
{/* View on Instagram Button */}
{media.permalink && (
<a
href={media.permalink}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 w-full py-3 rounded-xl bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105"
>
<ExternalLink className="w-5 h-5" />
View on Instagram
</a>
)}
</div>
</div>
{/* New Comment Input */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl p-6 mb-8">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-3">
<MessageSquare className="w-5 h-5 text-blue-400" />
Add a Comment
</h3>
<div className="space-y-4">
<textarea
placeholder="Write your comment here..."
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500/30 focus:ring-1 focus:ring-blue-500/30 min-h-[120px] resize-none"
value={newCommentText}
onChange={(e) => setNewCommentText(e.target.value)}
/>
<div className="flex justify-between items-center">
<p className="text-gray-400 text-sm">
Comments are posted directly to Instagram
</p>
<button
onClick={postNewComment}
disabled={!newCommentText.trim()}
className="px-6 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
<Send className="w-4 h-4" />
Post Comment
</button>
</div>
</div>
</div>
</div>
{/* Right Column - Comments & Info */}
<div className="space-y-8">
{/* Comments Header */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white flex items-center gap-3">
<Users className="w-5 h-5 text-blue-400" />
Comments
</h3>
<div className="px-3 py-1 rounded-full bg-blue-500/20 text-blue-400 text-sm font-medium border border-blue-500/30">
{comments.length} total
</div>
</div>
{/* Comments List */}
<div className="space-y-4 max-h-[600px] overflow-y-auto pr-2">
{commentsLoading ? (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
) : comments.length === 0 ? (
<div className="text-center py-8">
<MessageSquare className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-400">No comments yet</p>
<p className="text-gray-500 text-sm mt-1">Be the first to comment!</p>
</div>
) : (
comments.map((comment) => (
<SocialCommentItem
key={comment.id}
comment={comment}
onReply={handleReply}
onDelete={handleDelete}
onHide={handleHide}
onEdit={handleEdit}
/>
))
)}
</div>
</div>
{/* Info Card */}
<div className="bg-gradient-to-br from-blue-900/20 to-indigo-900/20 rounded-2xl p-6 border border-blue-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Shield className="w-5 h-5 text-blue-400" />
Comment Management
</h4>
<ul className="space-y-3 text-sm">
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>All actions are manual and user-controlled</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>No automated responses or AI-generated comments</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>Direct moderation tools for community management</span>
</li>
</ul>
</div>
</div>
</div>
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default MediaDetailsPage;