feat: tính năng chia sẻ khẩn cấp
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { X, Send, MessageSquare, User, Loader2, Calendar, MapPin, Download, Edit, Heart } from 'lucide-react';
|
||||
import { X, Send, MessageSquare, User, Loader2, Calendar, MapPin, Download, Edit, Heart, Trash2 } from 'lucide-react';
|
||||
import { io } from 'socket.io-client';
|
||||
import { CoordinateSelectModal } from './CoordinateSelectModal';
|
||||
import { useTranslation } from '../hooks/useTranslation';
|
||||
|
||||
interface Comment {
|
||||
id: string;
|
||||
@@ -46,6 +47,7 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
onLoginSuccess,
|
||||
onUpdatePhoto
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [newComment, setNewComment] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -260,6 +262,12 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('photoCommentDeleted', (deleted: any) => {
|
||||
if (deleted.photoId === photo.id) {
|
||||
setComments(prev => prev.filter(c => c.id !== deleted.id));
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
@@ -348,6 +356,28 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteComment = async (commentId: string) => {
|
||||
if (!confirm(t('confirmDeleteComment') || 'Bạn có chắc chắn muốn xóa bình luận này không?')) return;
|
||||
try {
|
||||
const token = localStorage.getItem('token') || localStorage.getItem('guest_token');
|
||||
const res = await fetch(`/api/v1/public-photos/comments/${commentId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
setComments(prev => prev.filter(c => c.id !== commentId));
|
||||
} else {
|
||||
const err = await res.json();
|
||||
alert(err.message || 'Lỗi khi xóa bình luận.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Lỗi khi xóa bình luận:', error);
|
||||
alert('Không thể kết nối đến máy chủ.');
|
||||
}
|
||||
};
|
||||
|
||||
const isAuthorized = currentUser?.isAdmin ||
|
||||
(currentUser && photo.uploader && currentUser.id === photo.uploader.id) ||
|
||||
(currentUser && photo.uploaderId && currentUser.id === photo.uploaderId);
|
||||
@@ -630,7 +660,7 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
<div>
|
||||
<h3 className="text-lg font-black tracking-tight text-white flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5 text-emerald-500" />
|
||||
Bình luận cộng đồng
|
||||
{t('commentSectionTitle')}
|
||||
</h3>
|
||||
<p className="text-xs text-slate-400 mt-1">Ảnh chia sẻ công khai trên bản đồ</p>
|
||||
</div>
|
||||
@@ -641,7 +671,7 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-slate-500 gap-2">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-emerald-500" />
|
||||
<span className="text-xs font-semibold">Đang tải bình luận...</span>
|
||||
<span className="text-xs font-semibold">{t('loading')}</span>
|
||||
</div>
|
||||
) : comments.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-slate-500 gap-3">
|
||||
@@ -661,9 +691,23 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
<div className="bg-slate-800/55 p-3.5 rounded-2xl rounded-tl-none border border-slate-800 shadow-lg">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<span className="text-xs font-black text-slate-200 truncate">{c.userName}</span>
|
||||
<span className="text-[9px] font-medium text-slate-500">
|
||||
{new Date(c.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[9px] font-medium text-slate-500">
|
||||
{new Date(c.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
{(currentUser?.isAdmin ||
|
||||
currentUser?.id === c.userId ||
|
||||
currentUser?.id === photo.uploaderId ||
|
||||
currentUser?.id === photo.uploader?.id) && (
|
||||
<button
|
||||
onClick={() => handleDeleteComment(c.id)}
|
||||
className="text-slate-500 hover:text-rose-500 transition-colors p-0.5"
|
||||
title={t('delete') || "Xóa"}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed break-words">{c.content}</p>
|
||||
</div>
|
||||
@@ -672,6 +716,7 @@ export const PublicPhotoModal: React.FC<PublicPhotoModalProps> = ({
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
<div ref={commentsEndRef} />
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user