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:
@@ -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 có 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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user