bugs: lỗi khi người dùng link để tham gia tour nhưng không xuất hiện trong danh sách thành viên

This commit is contained in:
2026-06-20 20:59:01 +07:00
parent 52706cab7d
commit 36157bd53b
18 changed files with 1419 additions and 188 deletions
+62 -12
View File
@@ -5,7 +5,7 @@ const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerCluste
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useTourStore } from '@/store/useTourStore';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Share2, Filter, MapPin, Loader2 } from 'lucide-react';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Share2, Filter, MapPin, Loader2, UserPlus, Clock } from 'lucide-react';
import { UserManagementModal } from '@/components/UserManagementModal';
import { useNotification } from '@/hooks/useNotification';
import { CreateTourModal } from '../components/CreateTourModal';
@@ -174,7 +174,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
}, [publicTours]);
// State cho menu chuột phải chia sẻ
const [shareMenu, setShareMenu] = useState<{ x: number, y: number, id: string, title: string, canShare: boolean } | null>(null);
const [shareMenu, setShareMenu] = useState<{ x: number, y: number, id: string, title: string, canShare: boolean, isParticipant: boolean, hasPendingRequest: boolean } | null>(null);
const handleShare = (id: string, title: string) => {
const shareUrl = `${window.location.origin}?viewTour=${id}`;
@@ -211,6 +211,35 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
setShareMenu(null);
};
const handleRequestJoin = async (tourId: string) => {
setShareMenu(null);
try {
const res = await fetch(`/api/v1/tours/${tourId}/join-requests`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.message || 'Gửi yêu cầu tham gia thất bại.');
}
notify({
title: 'Thành công',
message: 'Đã gửi yêu cầu tham gia tour. Vui lòng chờ chủ tour duyệt.',
type: 'success'
});
fetchPublicTours();
} catch (err: any) {
notify({
title: 'Lỗi',
message: err.message || 'Không thể gửi yêu cầu tham gia.',
type: 'error'
});
}
};
const handleSelectSuggestion = (s: any) => {
if (s.type === 'tour') {
onViewTour(s.id);
@@ -471,9 +500,12 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
eventHandlers={{
click: () => onViewTour(tour.id),
contextmenu: (e) => {
// Kiểm tra quyền chia sẻ (OWNER, MANAGER, MEMBER)
const role = tour.participants?.[0]?.role;
const canShare = ['OWNER', 'MANAGER', 'MEMBER'].includes(role);
const currentUserId = user?.id;
const myParticipant = tour.participants?.find((p: any) => p.userId === currentUserId);
const isParticipant = !!myParticipant;
const myRole = myParticipant?.role;
const canShare = isParticipant && ['OWNER', 'MANAGER', 'MEMBER'].includes(myRole);
const hasPendingRequest = tour.joinRequests && tour.joinRequests.length > 0;
// Hiển thị menu tại vị trí chuột
setShareMenu({
@@ -481,7 +513,9 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
y: e.containerPoint.y,
id: tour.id,
title: tour.title,
canShare
canShare,
isParticipant,
hasPendingRequest
});
}
}}
@@ -575,15 +609,31 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
style={{ top: shareMenu.y, left: shareMenu.x }}
onClick={(e) => e.stopPropagation()}
>
{shareMenu.canShare ? (
<button
onClick={() => handleShare(shareMenu.id, shareMenu.title)}
className="w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-bold text-gray-700 flex items-center gap-2 transition-colors"
{shareMenu.isParticipant ? (
shareMenu.canShare ? (
<button
onClick={() => handleShare(shareMenu.id, shareMenu.title)}
className="w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-bold text-gray-700 flex items-center gap-2 transition-colors"
>
<Share2 className="w-4 h-4 text-blue-600" /> Sao chép liên kết
</button>
) : (
<div className="px-4 py-2 text-xs text-gray-400 italic font-bold">Bạn đã gia nhập tour này</div>
)
) : shareMenu.hasPendingRequest ? (
<button
disabled
className="w-full text-left px-4 py-2 text-sm font-bold text-gray-400 flex items-center gap-2 cursor-not-allowed bg-gray-50/50"
>
<Share2 className="w-4 h-4 text-blue-600" /> Sao chép liên kết
<Clock className="w-4 h-4 text-gray-400" /> Đang chờ duyệt...
</button>
) : (
<div className="px-4 py-2 text-xs text-gray-400 italic">Bạn không quyền chia sẻ tour này</div>
<button
onClick={() => handleRequestJoin(shareMenu.id)}
className="w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-bold text-blue-700 flex items-center gap-2 transition-colors"
>
<UserPlus className="w-4 h-4 text-blue-600" /> Yêu cầu tham gia Tour
</button>
)}
</div>
)}