fix: guest and admin logic
This commit is contained in:
@@ -45,6 +45,21 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
onExploreTours,
|
||||
onViewTour
|
||||
}) => {
|
||||
// GUARD: Prevent guest users from accessing dashboard
|
||||
const guestToken = localStorage.getItem('guest_token');
|
||||
const token = localStorage.getItem('token');
|
||||
if (guestToken && !token) {
|
||||
// This is a guest user - redirect them by triggering onLogout
|
||||
// which will clear everything and redirect to landing
|
||||
console.warn('Guest user attempted to access MemberDashboard - redirecting to home');
|
||||
// Clear guest tokens
|
||||
localStorage.removeItem('guest_token');
|
||||
localStorage.removeItem('guest_user');
|
||||
// Redirect to home
|
||||
window.location.href = '/';
|
||||
return null;
|
||||
}
|
||||
|
||||
const notify = useNotification();
|
||||
const confirm = useConfirm();
|
||||
const { t, lang, changeLanguage } = useTranslation();
|
||||
@@ -130,7 +145,7 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
setShareStatus(data);
|
||||
notify({
|
||||
title: 'Thành công',
|
||||
message: isEnabled ? 'Đã bật chia sẻ hành trình cứu hộ.' : 'Đã tắt chia sẻ.',
|
||||
message: isEnabled ? t('emergencySharingEnabled') : t('emergencySharingDisabled'),
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
@@ -337,9 +352,15 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
|
||||
// Fetch connections, tours, and photos on mount
|
||||
useEffect(() => {
|
||||
fetchPublicTours();
|
||||
fetchConnections();
|
||||
fetchPhotos();
|
||||
Promise.all([
|
||||
fetchPublicTours().catch(err => {
|
||||
console.error('[MemberDashboard] fetchPublicTours failed:', err.message);
|
||||
// If fetch fails due to anonymous user rejection, don't crash
|
||||
// The page is already loaded
|
||||
}),
|
||||
fetchConnections(),
|
||||
fetchPhotos()
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const fetchConnectionsRef = useRef<() => Promise<void>>(null as any);
|
||||
@@ -560,7 +581,7 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
if (res.ok) {
|
||||
notify({
|
||||
title: 'Đã cập nhật',
|
||||
message: `Mối quan hệ đã được chuyển sang nhóm: ${type === 'FAMILY' ? 'Gia đình' : 'Bạn bè'}.`,
|
||||
message: `${t('changeConnectionType')}${type === 'FAMILY' ? t('familyGroup') : t('friends')}.`,
|
||||
type: 'success'
|
||||
});
|
||||
fetchConnections();
|
||||
@@ -573,8 +594,8 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
// Remove connection
|
||||
const handleRemoveConnection = async (connId: string, targetName: string) => {
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Hủy kết nối',
|
||||
message: `Bạn có chắc chắn muốn hủy kết nối với ${targetName}?`
|
||||
title: t('disconnectTitle'),
|
||||
message: `${t('disconnectConfirm')} ${targetName}?`
|
||||
});
|
||||
|
||||
if (isConfirmed) {
|
||||
@@ -586,7 +607,7 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
if (res.ok) {
|
||||
notify({
|
||||
title: 'Thành công',
|
||||
message: 'Đã hủy kết nối thành công.',
|
||||
message: t('disconnectSuccess'),
|
||||
type: 'success'
|
||||
});
|
||||
fetchConnections();
|
||||
@@ -595,7 +616,7 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Lỗi hủy kết nối:', err);
|
||||
console.error(t('disconnectError'), err);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -684,7 +705,7 @@ export const MemberDashboard: React.FC<MemberDashboardProps> = ({
|
||||
const getConnectionStatusText = (targetId: string) => {
|
||||
const isConnected = connections.find(c => c.targetUser?.id === targetId);
|
||||
if (isConnected) {
|
||||
return isConnected.type === 'FAMILY' ? 'Gia đình' : 'Bạn bè';
|
||||
return isConnected.type === 'FAMILY' ? t('familyGroup') : t('friends');
|
||||
}
|
||||
const isPendingReceived = receivedRequests.find(r => r.requester?.id === targetId);
|
||||
if (isPendingReceived) return 'Chờ bạn duyệt';
|
||||
@@ -805,7 +826,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
className="w-full py-4 px-4 bg-indigo-600 hover:bg-indigo-700 text-white rounded-2xl font-bold flex items-center justify-center gap-2 shadow-lg active:scale-98 transition-all"
|
||||
>
|
||||
<Compass className="w-5 h-5 animate-spin-slow" />
|
||||
Khám phá Bản đồ Tour
|
||||
{t('exploreTourMap')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -823,12 +844,12 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<Compass className="w-5 h-5 text-indigo-400" />
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="text-sm flex items-center gap-1.5">
|
||||
Hành trình của tôi
|
||||
{t('myItineraries')}
|
||||
{(unreadTourSenders.length > 0 || unreadTourChats.length > 0) && (
|
||||
<span className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse" title="Có tin nhắn mới"></span>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">Xem và quản lý các chuyến đi</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">{t('toursManagementDesc')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -850,8 +871,8 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div className="flex items-center gap-3">
|
||||
<ImageIcon className="w-5 h-5 text-indigo-400" />
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="text-sm">Thư viện ảnh</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">Kho ảnh gốc của bạn từ các tour</span>
|
||||
<span className="text-sm">{t('photoGallery')}</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">{t('photoGalleryDesc')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -876,8 +897,8 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="w-5 h-5 text-indigo-400" />
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="text-sm">Danh sách bạn bè</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">Bạn bè, gia đình, yêu cầu chờ duyệt</span>
|
||||
<span className="text-sm">{t('friendsList')}</span>
|
||||
<span className="text-[10px] font-medium text-slate-400">{t('manageFriendsDesc')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -962,8 +983,8 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
</button>
|
||||
<div>
|
||||
<h2 className="text-sm font-black uppercase text-white tracking-wider">
|
||||
{activeTab === 'tours' && 'Hành trình của tôi'}
|
||||
{activeTab === 'connections' && 'Danh sách bạn bè'}
|
||||
{activeTab === 'tours' && t('myItineraries')}
|
||||
{activeTab === 'connections' && t('friendsList')}
|
||||
{activeTab === 'chats' && 'Trò chuyện trực tiếp'}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -1096,7 +1117,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<ImageIcon className="w-4 h-4" />
|
||||
<span>Thư viện ảnh</span>
|
||||
<span>{t('photoGallery')}</span>
|
||||
</div>
|
||||
<span className="bg-slate-800/80 px-2 py-0.5 text-xs rounded-full text-slate-400 font-semibold border border-slate-700">
|
||||
{photos.length}
|
||||
@@ -1135,7 +1156,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="w-4 h-4" />
|
||||
<span>Danh sách bạn bè</span>
|
||||
<span>{t('friendsList')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{receivedRequests.length > 0 && (
|
||||
@@ -1188,7 +1209,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div>
|
||||
<h1 className="text-2xl font-black uppercase tracking-tight flex items-center gap-2 text-white">
|
||||
<Compass className="w-6 h-6 text-indigo-500" />
|
||||
Hành trình của tôi
|
||||
{t('myItineraries')}
|
||||
</h1>
|
||||
<p className="text-xs text-slate-400 mt-1">Danh sách các chuyến đi bạn tham gia (với vai trò chủ sở hữu, quản lý hoặc thành viên).</p>
|
||||
</div>
|
||||
@@ -1331,9 +1352,9 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-black uppercase tracking-tight flex items-center gap-2 text-white">
|
||||
<Users className="w-6 h-6 text-indigo-500" />
|
||||
Danh sách bạn bè
|
||||
{t('friendsList')}
|
||||
</h1>
|
||||
<p className="text-xs text-slate-400 mt-1">Quản lý các mối quan hệ bạn bè, gia đình, duyệt các yêu cầu kết nối từ thành viên khác.</p>
|
||||
<p className="text-xs text-slate-400 mt-1">{t('manageRelations')}</p>
|
||||
</div>
|
||||
|
||||
{/* Sub-tabs for connections */}
|
||||
@@ -1382,7 +1403,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div className="bg-slate-900/40 border border-slate-800/60 rounded-3xl p-12 text-center max-w-md mx-auto my-6">
|
||||
<Users className="w-12 h-12 text-indigo-500 mx-auto mb-4" />
|
||||
<h3 className="text-base font-bold mb-1">Chưa có kết nối nào</h3>
|
||||
<p className="text-xs text-slate-400 mb-6">Bạn chưa kết nối với ai. Hãy chuyển sang tìm kiếm để gửi lời mời.</p>
|
||||
<p className="text-xs text-slate-400 mb-6">{t('noConnections')}</p>
|
||||
<button
|
||||
onClick={() => setConnectionSubTab('search')}
|
||||
className="py-2 px-4 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-xs font-bold transition-all"
|
||||
@@ -1418,7 +1439,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
? 'bg-rose-950/40 text-rose-300 border-rose-900/50'
|
||||
: 'bg-indigo-950/40 text-indigo-300 border-indigo-900/50'
|
||||
}`}>
|
||||
{conn.type === 'FAMILY' ? 'Gia đình' : 'Bạn bè'}
|
||||
{conn.type === 'FAMILY' ? t('familyGroup') : t('friends')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1431,7 +1452,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
onChange={(e) => handleChangeConnectionType(conn.id, e.target.value as any)}
|
||||
className="bg-slate-800 text-slate-200 border border-slate-700/80 rounded-md text-[10px] px-2 py-1 outline-none font-bold cursor-pointer hover:bg-slate-750 transition-colors"
|
||||
>
|
||||
<option value="FRIEND">Bạn bè</option>
|
||||
<option value="FRIEND">{t('friends')}</option>
|
||||
<option value="FAMILY">Gia đình</option>
|
||||
</select>
|
||||
|
||||
@@ -1452,7 +1473,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<button
|
||||
onClick={() => handleRemoveConnection(conn.id, connUser.name)}
|
||||
className="p-1.5 bg-rose-950/50 hover:bg-rose-600 text-rose-300 hover:text-white border border-rose-800/40 hover:border-rose-500 rounded-lg text-xs font-bold transition-all"
|
||||
title="Hủy kết nối"
|
||||
title={t('disconnectTitle')}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -1474,7 +1495,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<Search className="absolute left-4 top-3.5 w-4 h-4 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tìm kiếm theo Tên hoặc Email (nhập tối thiểu 2 ký tự)..."
|
||||
placeholder={t('searchMembers')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-slate-950 border border-slate-800/80 rounded-2xl py-3 pl-11 pr-4 text-sm text-white placeholder-slate-500 outline-none focus:border-indigo-500/80 transition-all duration-150"
|
||||
@@ -1483,11 +1504,11 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
|
||||
{searchingUsers ? (
|
||||
<div className="text-center py-8 text-slate-400 text-xs font-bold flex items-center justify-center gap-1.5">
|
||||
<Clock className="w-4 h-4 animate-spin text-indigo-500" /> Đang tìm kiếm...
|
||||
<Clock className="w-4 h-4 animate-spin text-indigo-500" /> {t('searching')}...
|
||||
</div>
|
||||
) : searchQuery.trim().length < 2 ? (
|
||||
<div className="text-center py-12 text-slate-500 text-xs italic">
|
||||
Vui lòng nhập tối thiểu 2 ký tự để tìm kiếm thành viên.
|
||||
{t('minCharsRequired')}
|
||||
</div>
|
||||
) : searchResults.length === 0 ? (
|
||||
<div className="text-center py-12 text-slate-400 text-xs">
|
||||
@@ -1522,7 +1543,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
<div>
|
||||
{statusText ? (
|
||||
<span className={`px-3 py-1 rounded-lg text-xs font-bold border ${
|
||||
statusText === 'Bạn bè' || statusText === 'Gia đình'
|
||||
statusText === t('friends') || statusText === t('familyGroup')
|
||||
? 'bg-emerald-950/30 border-emerald-900/50 text-emerald-300'
|
||||
: 'bg-slate-800/80 border-slate-700 text-slate-400'
|
||||
}`}>
|
||||
|
||||
Reference in New Issue
Block a user