fix: guest and admin logic

This commit is contained in:
2026-06-22 11:41:34 +07:00
parent b1a539235b
commit 860395cb14
61 changed files with 1291 additions and 244 deletions
+53 -32
View File
@@ -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 quản 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 </span>
<span className="text-[10px] font-medium text-slate-400">Bạn , 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 </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 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
{t('friendsList')}
</h1>
<p className="text-xs text-slate-400 mt-1">Quản các mối quan hệ bạn , 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 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 </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 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'
}`}>