feat: apply theme colors to all pages and modals - implement light/dark theme CSS variables across UI components
This commit is contained in:
@@ -655,35 +655,35 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
<div className="flex items-center gap-3 pointer-events-auto">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="w-11 h-11 flex items-center justify-center bg-white rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100 shrink-0"
|
||||
className="w-11 h-11 flex items-center justify-center bg-[var(--surface)] rounded-full shadow-xl hover:bg-[var(--background)] transition-all border border-[var(--border)] shrink-0"
|
||||
title="Quay lại"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6 text-gray-800" />
|
||||
<ChevronLeft className="w-6 h-6 text-[var(--text-primary)]" />
|
||||
</button>
|
||||
|
||||
{/* Nút lọc Tag và Dropdown */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsFilterDropdownOpen(prev => !prev)}
|
||||
className="w-11 h-11 bg-white rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100 flex items-center justify-center shrink-0"
|
||||
className="w-11 h-11 bg-[var(--surface)] rounded-full shadow-xl hover:bg-[var(--background)] transition-all border border-[var(--border)] flex items-center justify-center shrink-0"
|
||||
title="Lọc theo loại"
|
||||
>
|
||||
<Filter className="w-6 h-6 text-gray-800" />
|
||||
<Filter className="w-6 h-6 text-[var(--text-primary)]" />
|
||||
</button>
|
||||
|
||||
{/* Filter Dropdown Content */}
|
||||
{isFilterDropdownOpen && (
|
||||
<div className="absolute top-full left-0 mt-3 bg-white/90 backdrop-blur-md p-3 rounded-2xl shadow-xl border border-white/20 flex flex-col gap-3 max-w-[220px] z-[1003] animate-in slide-in-from-left-2 duration-200 max-h-[400px] overflow-y-auto">
|
||||
<div className="absolute top-full left-0 mt-3 bg-[var(--surface)]/90 backdrop-blur-md p-3 rounded-2xl shadow-xl border border-[var(--surface)]/20 flex flex-col gap-3 max-w-[220px] z-[1003] animate-in slide-in-from-left-2 duration-200 max-h-[400px] overflow-y-auto">
|
||||
{/* Tour Filter Section */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 px-2 py-1 border-b border-gray-100 mb-1.5">
|
||||
<div className="flex items-center gap-2 px-2 py-1 border-b border-[var(--border)] mb-1.5">
|
||||
<Filter className="w-3.5 h-3.5 text-blue-600" />
|
||||
<span className="text-[10px] font-black uppercase text-gray-500 tracking-wider">🧳 Chuyến đi</span>
|
||||
<span className="text-[10px] font-black uppercase text-[var(--text-muted)] tracking-wider">🧳 Chuyến đi</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<button
|
||||
onClick={() => { setSelectedFilterTag(null); setIsFilterDropdownOpen(false); }}
|
||||
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${!selectedFilterTag ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
|
||||
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${!selectedFilterTag ? 'bg-blue-600 text-white' : 'bg-[var(--background)] text-[var(--text-muted)] hover:bg-[var(--border)]'}`}
|
||||
>
|
||||
Tất cả
|
||||
</button>
|
||||
@@ -691,7 +691,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
<button
|
||||
key={tag}
|
||||
onClick={() => { setSelectedFilterTag(tag === selectedFilterTag ? null : tag); setIsFilterDropdownOpen(false); }}
|
||||
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${selectedFilterTag === tag ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
|
||||
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${selectedFilterTag === tag ? 'bg-blue-600 text-white' : 'bg-[var(--background)] text-[var(--text-muted)] hover:bg-[var(--border)]'}`}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
@@ -701,15 +701,15 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
|
||||
{/* Photo Filter Section */}
|
||||
{availablePhotoTags.length > 0 && (
|
||||
<div className="border-t border-gray-200 pt-3">
|
||||
<div className="flex items-center gap-2 px-2 py-1 border-b border-gray-100 mb-1.5">
|
||||
<div className="border-t border-[var(--border)] pt-3">
|
||||
<div className="flex items-center gap-2 px-2 py-1 border-b border-[var(--border)] mb-1.5">
|
||||
<ImageIcon className="w-3.5 h-3.5 text-emerald-600" />
|
||||
<span className="text-[10px] font-black uppercase text-gray-500 tracking-wider">📸 Ảnh công khai</span>
|
||||
<span className="text-[10px] font-black uppercase text-[var(--text-muted)] tracking-wider">📸 Ảnh công khai</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
onClick={() => { setSelectedPhotoFilterTags([]); setIsFilterDropdownOpen(false); }}
|
||||
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.length === 0 ? 'bg-emerald-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
|
||||
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.length === 0 ? 'bg-emerald-600 text-white' : 'bg-[var(--background)] text-[var(--text-muted)] hover:bg-[var(--border)]'}`}
|
||||
>
|
||||
Tất cả
|
||||
</button>
|
||||
@@ -725,7 +725,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
: [...prev, tagId]
|
||||
);
|
||||
}}
|
||||
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.includes(tagId) ? 'bg-emerald-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
|
||||
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.includes(tagId) ? 'bg-emerald-600 text-white' : 'bg-[var(--background)] text-[var(--text-muted)] hover:bg-[var(--border)]'}`}
|
||||
title={tagLabel}
|
||||
>
|
||||
{tagLabel.length > 13 ? tagLabel.substring(0, 13) + '...' : tagLabel}
|
||||
@@ -740,37 +740,37 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
</div>
|
||||
|
||||
{/* Search Box - Thay thế div "Khám phá khu vực" */}
|
||||
<div className="relative flex items-center bg-white/90 backdrop-blur-md rounded-2xl shadow-xl border border-white/20 flex-1 max-w-xs sm:max-w-md pointer-events-auto hidden sm:flex px-4 py-3">
|
||||
<div className="relative flex items-center bg-[var(--surface)]/90 backdrop-blur-md rounded-2xl shadow-xl border border-[var(--surface)]/20 flex-1 max-w-xs sm:max-w-md pointer-events-auto hidden sm:flex px-4 py-3">
|
||||
<Navigation className="w-4 h-4 text-blue-600 mr-2 flex-shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tìm kiếm địa điểm, tour..."
|
||||
className="flex-1 bg-transparent outline-none text-gray-800 text-sm font-medium"
|
||||
className="flex-1 bg-transparent outline-none text-[var(--text-primary)] text-sm font-medium"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
{isSearchingSuggestions && <Loader2 className="w-4 h-4 animate-spin text-blue-500 mr-2" />}
|
||||
{searchQuery && (
|
||||
<button onClick={() => { setSearchQuery(''); setSuggestions([]); }} className="p-1 text-gray-400 hover:text-gray-600 rounded-full">
|
||||
<button onClick={() => { setSearchQuery(''); setSuggestions([]); }} className="p-1 text-[var(--text-muted)] hover:text-[var(--text-secondary)] rounded-full">
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Dropdown danh sách gợi ý */}
|
||||
{suggestions.length > 0 && (
|
||||
<div className="absolute top-full left-0 right-0 mt-3 bg-white/95 backdrop-blur-md rounded-2xl shadow-2xl border border-white/20 overflow-hidden z-[1003] animate-in slide-in-from-top-2 duration-200">
|
||||
<div className="absolute top-full left-0 right-0 mt-3 bg-[var(--surface)]/95 backdrop-blur-md rounded-2xl shadow-2xl border border-[var(--surface)]/20 overflow-hidden z-[1003] animate-in slide-in-from-top-2 duration-200">
|
||||
{suggestions.map((s, idx) => (
|
||||
<button
|
||||
key={`${s.type}-${s.id}-${idx}`}
|
||||
onClick={() => handleSelectSuggestion(s)}
|
||||
className="w-full text-left px-4 py-3 hover:bg-blue-50 flex items-center gap-3 transition-colors border-b border-gray-50 last:border-0"
|
||||
className="w-full text-left px-4 py-3 hover:bg-[var(--background)] flex items-center gap-3 transition-colors border-b border-[var(--border)] last:border-0"
|
||||
>
|
||||
<div className={`p-2 rounded-xl flex-shrink-0 ${s.type === 'tour' ? 'bg-blue-50 text-blue-600' : 'bg-green-50 text-green-600'}`}>
|
||||
{s.type === 'tour' ? <ImageIcon className="w-4 h-4" /> : <MapPin className="w-4 h-4" />}
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0">
|
||||
<span className="text-sm font-bold text-gray-800 truncate">{s.name}</span>
|
||||
<span className="text-[10px] font-black uppercase text-gray-400 tracking-wider">
|
||||
<span className="text-sm font-bold text-[var(--text-primary)] truncate">{s.name}</span>
|
||||
<span className="text-[10px] font-black uppercase text-[var(--text-muted)] tracking-wider">
|
||||
{s.type === 'tour' ? 'Chuyến đi của bạn' : 'Địa điểm trên bản đồ'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -790,7 +790,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
console.log("Đang mở Ảnh của tôi...");
|
||||
onOpenMyPhotos();
|
||||
}}
|
||||
className="w-11 h-11 md:w-auto bg-white p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-50 text-blue-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold border border-blue-100 shrink-0"
|
||||
className="w-11 h-11 md:w-auto bg-[var(--surface)] p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-100 text-blue-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold border border-blue-100 shrink-0"
|
||||
title="Ảnh của tôi"
|
||||
>
|
||||
<ImageIcon className="w-5 h-5" />
|
||||
@@ -822,31 +822,31 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
|
||||
{/* Lựa chọn Ngôn ngữ */}
|
||||
<div className="relative group shrink-0">
|
||||
<button className="w-11 h-11 bg-white dark:bg-slate-800 p-0 rounded-2xl shadow-xl hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 transition-all flex items-center justify-center border border-gray-100 dark:border-slate-700">
|
||||
<button className="w-11 h-11 bg-[var(--surface)] dark:bg-slate-800 p-0 rounded-2xl shadow-xl hover:bg-[var(--background)] dark:hover:bg-slate-700 text-[var(--text-secondary)] dark:text-slate-200 transition-all flex items-center justify-center border border-[var(--border)] dark:border-slate-700">
|
||||
<Globe className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="absolute right-0 top-12 mt-1 hidden group-hover:block bg-white dark:bg-slate-800 border border-gray-100 dark:border-slate-700 rounded-2xl shadow-2xl p-2 z-[9999] w-32 animate-in fade-in duration-200">
|
||||
<button onClick={() => changeLanguage('vi')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'vi' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>Tiếng Việt</button>
|
||||
<button onClick={() => changeLanguage('en')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'en' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>English</button>
|
||||
<button onClick={() => changeLanguage('zh')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'zh' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>中文</button>
|
||||
<div className="absolute right-0 top-12 mt-1 hidden group-hover:block bg-[var(--surface)] dark:bg-slate-800 border border-[var(--border)] dark:border-slate-700 rounded-2xl shadow-2xl p-2 z-[9999] w-32 animate-in fade-in duration-200">
|
||||
<button onClick={() => changeLanguage('vi')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'vi' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>Tiếng Việt</button>
|
||||
<button onClick={() => changeLanguage('en')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'en' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>English</button>
|
||||
<button onClick={() => changeLanguage('zh')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold ${lang === 'zh' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>中文</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lựa chọn Giao diện */}
|
||||
<div className="relative group shrink-0">
|
||||
<button className="w-11 h-11 bg-white dark:bg-slate-800 p-0 rounded-2xl shadow-xl hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 transition-all flex items-center justify-center border border-gray-100 dark:border-slate-700">
|
||||
<button className="w-11 h-11 bg-[var(--surface)] dark:bg-slate-800 p-0 rounded-2xl shadow-xl hover:bg-[var(--background)] dark:hover:bg-slate-700 text-[var(--text-secondary)] dark:text-slate-200 transition-all flex items-center justify-center border border-[var(--border)] dark:border-slate-700">
|
||||
{theme === 'light' && <Sun className="w-5 h-5 text-amber-500" />}
|
||||
{theme === 'dark' && <Moon className="w-5 h-5 text-indigo-400" />}
|
||||
{theme === 'system' && <Laptop className="w-5 h-5 animate-pulse" />}
|
||||
</button>
|
||||
<div className="absolute right-0 top-12 mt-1 hidden group-hover:block bg-white dark:bg-slate-800 border border-gray-100 dark:border-slate-700 rounded-2xl shadow-2xl p-2 z-[9999] w-32 animate-in fade-in duration-200">
|
||||
<button onClick={() => changeTheme('light')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'light' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>
|
||||
<div className="absolute right-0 top-12 mt-1 hidden group-hover:block bg-[var(--surface)] dark:bg-slate-800 border border-[var(--border)] dark:border-slate-700 rounded-2xl shadow-2xl p-2 z-[9999] w-32 animate-in fade-in duration-200">
|
||||
<button onClick={() => changeTheme('light')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'light' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>
|
||||
<Sun className="w-3.5 h-3.5 text-amber-500" /> {t('themeLight')}
|
||||
</button>
|
||||
<button onClick={() => changeTheme('dark')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'dark' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>
|
||||
<button onClick={() => changeTheme('dark')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'dark' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>
|
||||
<Moon className="w-3.5 h-3.5 text-indigo-400" /> {t('themeDark')}
|
||||
</button>
|
||||
<button onClick={() => changeTheme('system')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'system' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>
|
||||
<button onClick={() => changeTheme('system')} className={`w-full text-left px-3 py-2 rounded-xl text-xs font-bold flex items-center gap-2 ${theme === 'system' ? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400' : 'text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-700'}`}>
|
||||
<Laptop className="w-3.5 h-3.5" /> {t('themeSystem')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -880,7 +880,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
{onLogout && (
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className="w-11 h-11 md:w-auto bg-white p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold text-gray-700 border border-gray-100 shrink-0"
|
||||
className="w-11 h-11 md:w-auto bg-[var(--surface)] p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold text-[var(--text-secondary)] border border-[var(--border)] shrink-0"
|
||||
title="Đăng xuất"
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
@@ -996,7 +996,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
className: 'custom-bubble',
|
||||
html: `
|
||||
<div class="relative group w-14 h-14">
|
||||
<div class="w-14 h-14 rounded-full border-4 ${status.borderClass} shadow-lg overflow-hidden transition-transform group-hover:scale-110 flex items-center justify-center bg-gray-100">
|
||||
<div class="w-14 h-14 rounded-full border-4 ${status.borderClass} shadow-lg overflow-hidden transition-transform group-hover:scale-110 flex items-center justify-center bg-[var(--background)]">
|
||||
<img src="${tourImage}" class="w-full h-full object-cover" onerror="this.src='https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=100'"/>
|
||||
</div>
|
||||
<div class="absolute -bottom-1 -right-1 bg-blue-600 rounded-full w-5 h-5 border-2 border-white flex items-center justify-center text-[10px] font-black text-white">
|
||||
@@ -1019,16 +1019,16 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
</div>
|
||||
)}
|
||||
{tour.description && (
|
||||
<div className="text-[10px] text-gray-500 line-clamp-2 leading-tight italic mb-1.5">
|
||||
<div className="text-[10px] text-[var(--text-muted)] line-clamp-2 leading-tight italic mb-1.5">
|
||||
{tour.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5 pt-1 border-t border-gray-100">
|
||||
<div className="flex items-center gap-1.5 pt-1 border-t border-[var(--border)]">
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
status.color === 'green' ? 'bg-emerald-500' :
|
||||
status.color === 'red' ? 'bg-rose-500' : 'bg-gray-400'
|
||||
status.color === 'red' ? 'bg-rose-500' : 'bg-[var(--text-muted)]'
|
||||
}`} />
|
||||
<span className="text-[9px] font-bold text-gray-600">{status.label}</span>
|
||||
<span className="text-[9px] font-bold text-[var(--text-secondary)]">{status.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -1146,13 +1146,13 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
{item.type === 'RESTAURANT' ? t('businessRestaurant') : item.type === 'HOTEL' ? t('businessHotel') : t('businessHomestay')}
|
||||
</div>
|
||||
{item.address && (
|
||||
<div className="text-[10px] text-gray-500 mb-1 font-semibold">{item.address}</div>
|
||||
<div className="text-[10px] text-[var(--text-muted)] mb-1 font-semibold">{item.address}</div>
|
||||
)}
|
||||
{item.phone && (
|
||||
<div className="text-[9px] text-gray-400">SĐT: {item.phone}</div>
|
||||
<div className="text-[9px] text-[var(--text-muted)]">SĐT: {item.phone}</div>
|
||||
)}
|
||||
{item.email && (
|
||||
<div className="text-[9px] text-gray-400">Email: {item.email}</div>
|
||||
<div className="text-[9px] text-[var(--text-muted)]">Email: {item.email}</div>
|
||||
)}
|
||||
<div className="text-[10px] text-slate-600 font-medium italic mt-1 pt-1 border-t border-emerald-100 whitespace-pre-line">
|
||||
{item.description}
|
||||
@@ -1167,7 +1167,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
{/* Context Menu Chia sẻ */}
|
||||
{shareMenu && (
|
||||
<div
|
||||
className="absolute z-[2000] bg-white rounded-2xl shadow-2xl border border-gray-100 py-2 w-48 animate-in zoom-in-95 duration-200"
|
||||
className="absolute z-[2000] bg-[var(--surface)] rounded-2xl shadow-2xl border border-[var(--border)] py-2 w-48 animate-in zoom-in-95 duration-200"
|
||||
style={{ top: shareMenu.y, left: shareMenu.x }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
@@ -1175,19 +1175,19 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
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"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<div className="px-4 py-2 text-xs text-[var(--text-muted)] 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"
|
||||
className="w-full text-left px-4 py-2 text-sm font-bold text-[var(--text-muted)] flex items-center gap-2 cursor-not-allowed bg-[var(--background)]/50"
|
||||
>
|
||||
<Clock className="w-4 h-4 text-gray-400" /> Đang chờ duyệt...
|
||||
<Clock className="w-4 h-4 text-[var(--text-muted)]" /> Đang chờ duyệt...
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@@ -1268,33 +1268,33 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
<div className="absolute bottom-6 left-6 z-[1002] pointer-events-auto flex flex-col items-start gap-2">
|
||||
<button
|
||||
onClick={() => setIsLeaderboardOpen(prev => !prev)}
|
||||
className="bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 text-slate-800 dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95"
|
||||
className="bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 text-[var(--text-primary)] dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-[var(--background)] dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95"
|
||||
>
|
||||
<Users className="w-4 h-4 text-amber-500" />
|
||||
<span>{t('trustedMembers')} ({trustedUsers.length})</span>
|
||||
</button>
|
||||
|
||||
{isLeaderboardOpen && (
|
||||
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-md border border-gray-100 dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<div className="bg-[var(--surface)]/95 dark:bg-slate-900/95 backdrop-blur-md border border-[var(--border)] dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<h4 className="text-xs font-black uppercase text-amber-600 dark:text-amber-500 tracking-widest mb-3 flex items-center gap-2">
|
||||
🏆 {t('trustedMembers')}
|
||||
</h4>
|
||||
{trustedUsers.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic">Chưa có thành viên nào được đánh giá.</p>
|
||||
<p className="text-xs text-[var(--text-muted)] italic">Chưa có thành viên nào được đánh giá.</p>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{trustedUsers.map((u, idx) => (
|
||||
<div key={u.id} className="flex items-center justify-between gap-3 p-2 bg-gray-50/50 dark:bg-slate-850/50 rounded-xl border border-gray-100/50 dark:border-slate-800/50">
|
||||
<div key={u.id} className="flex items-center justify-between gap-3 p-2 bg-[var(--background)]/50 dark:bg-slate-850/50 rounded-xl border border-[var(--border)]/50 dark:border-slate-800/50">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="w-5 h-5 font-bold text-[10px] text-gray-500 flex items-center justify-center bg-gray-100 dark:bg-slate-800 rounded-lg">
|
||||
<div className="w-5 h-5 font-bold text-[10px] text-[var(--text-muted)] flex items-center justify-center bg-[var(--background)] dark:bg-slate-800 rounded-lg">
|
||||
{idx + 1}
|
||||
</div>
|
||||
<span className="text-xs font-bold text-gray-800 dark:text-slate-200 truncate">{u.name}</span>
|
||||
<span className="text-xs font-bold text-[var(--text-primary)] dark:text-slate-200 truncate">{u.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[11px] font-bold text-amber-500 shrink-0">
|
||||
<span>★</span>
|
||||
<span>{u.averageScore}</span>
|
||||
<span className="text-[9px] text-gray-400 font-medium">({u.ratingCount})</span>
|
||||
<span className="text-[9px] text-[var(--text-muted)] font-medium">({u.ratingCount})</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -1312,7 +1312,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
setIsRecommendedOpen(prev => !prev);
|
||||
setIsBlacklistOpen(false);
|
||||
}}
|
||||
className="bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 text-slate-800 dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-emerald-500/20"
|
||||
className="bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 text-[var(--text-primary)] dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-[var(--background)] dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-emerald-500/20"
|
||||
>
|
||||
<Star className="w-4 h-4 text-emerald-500 fill-emerald-500 animate-pulse" />
|
||||
<span>{t('recommendedTitle') || 'Đề xuất dịch vụ'} ({recommendedLocations.length})</span>
|
||||
@@ -1324,7 +1324,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
setIsBlacklistOpen(prev => !prev);
|
||||
setIsRecommendedOpen(false);
|
||||
}}
|
||||
className="bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 text-slate-800 dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-red-500/20"
|
||||
className="bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 text-[var(--text-primary)] dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-[var(--background)] dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-red-500/20"
|
||||
>
|
||||
<ShieldAlert className="w-4 h-4 text-red-500 animate-pulse" />
|
||||
<span>{t('blacklistTitle') || 'Danh sách đen'} ({blacklist.length})</span>
|
||||
@@ -1332,8 +1332,8 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
|
||||
{/* Recommendations Panel */}
|
||||
{isRecommendedOpen && (
|
||||
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-md border border-gray-100 dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<h4 className="text-xs font-black uppercase text-emerald-600 dark:text-emerald-500 tracking-widest mb-3 flex items-center gap-2 border-b border-gray-100 dark:border-slate-800 pb-2">
|
||||
<div className="bg-[var(--surface)]/95 dark:bg-slate-900/95 backdrop-blur-md border border-[var(--border)] dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<h4 className="text-xs font-black uppercase text-emerald-600 dark:text-emerald-500 tracking-widest mb-3 flex items-center gap-2 border-b border-[var(--border)] dark:border-slate-800 pb-2">
|
||||
🌟 {t('recommendedTitle') || 'Đề xuất chất lượng'}
|
||||
</h4>
|
||||
|
||||
@@ -1363,7 +1363,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
</div>
|
||||
|
||||
{processedRecommendations.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic text-center py-4">Chưa có địa điểm đề xuất nào.</p>
|
||||
<p className="text-xs text-[var(--text-muted)] italic text-center py-4">Chưa có địa điểm đề xuất nào.</p>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{processedRecommendations.map((item) => {
|
||||
@@ -1411,7 +1411,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="text-[10px] text-slate-600 dark:text-slate-400 font-medium italic mt-1.5 pt-1.5 border-t border-gray-100 dark:border-slate-800 whitespace-pre-line">
|
||||
<div className="text-[10px] text-[var(--text-secondary)] dark:text-slate-400 font-medium italic mt-1.5 pt-1.5 border-t border-[var(--border)] dark:border-slate-800 whitespace-pre-line">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1424,8 +1424,8 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
|
||||
{/* Blacklist Panel */}
|
||||
{isBlacklistOpen && (
|
||||
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-md border border-gray-100 dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<h4 className="text-xs font-black uppercase text-red-600 dark:text-red-500 tracking-widest mb-3 flex items-center gap-2 border-b border-gray-100 dark:border-slate-800 pb-2">
|
||||
<div className="bg-[var(--surface)]/95 dark:bg-slate-900/95 backdrop-blur-md border border-[var(--border)] dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
|
||||
<h4 className="text-xs font-black uppercase text-red-600 dark:text-red-500 tracking-widest mb-3 flex items-center gap-2 border-b border-[var(--border)] dark:border-slate-800 pb-2">
|
||||
⚠️ {t('blacklistTitle')}
|
||||
</h4>
|
||||
|
||||
@@ -1448,7 +1448,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
</div>
|
||||
|
||||
{processedBlacklist.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic text-center py-4">{t('emptyBlacklist')}</p>
|
||||
<p className="text-xs text-[var(--text-muted)] italic text-center py-4">{t('emptyBlacklist')}</p>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{processedBlacklist.map((item) => {
|
||||
@@ -1488,7 +1488,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
||||
📍 Định vị trên bản đồ
|
||||
</button>
|
||||
)}
|
||||
<div className="text-[10px] text-red-600 dark:text-red-400 font-medium italic mt-1.5 pt-1.5 border-t border-gray-100 dark:border-slate-850">
|
||||
<div className="text-[10px] text-red-600 dark:text-red-400 font-medium italic mt-1.5 pt-1.5 border-t border-[var(--border)] dark:border-slate-850">
|
||||
Lý do: {item.reason}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,8 +101,8 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
const isLoggedIn = !!localStorage.getItem('token') && !localStorage.getItem('guest_token');
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 font-sans">
|
||||
<div className="max-w-md w-full space-y-8 bg-white p-10 rounded-3xl shadow-2xl text-center border border-gray-100">
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-[var(--background)] py-12 px-4 sm:px-6 lg:px-8 font-sans">
|
||||
<div className="max-w-md w-full space-y-8 bg-[var(--surface)] p-10 rounded-3xl shadow-2xl text-center border border-[var(--border)]">
|
||||
|
||||
{/* Logo/Icon */}
|
||||
<div className="flex justify-center">
|
||||
@@ -114,16 +114,16 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
{loading ? (
|
||||
<div className="space-y-4 py-6">
|
||||
<Loader2 className="w-12 h-12 animate-spin text-blue-600 mx-auto" />
|
||||
<h2 className="text-xl font-bold text-gray-900">Đang xử lý tham gia hành trình...</h2>
|
||||
<p className="text-sm text-gray-500">Vui lòng đợi trong giây lát.</p>
|
||||
<h2 className="text-xl font-bold text-[var(--text-primary)]">Đang xử lý tham gia hành trình...</h2>
|
||||
<p className="text-sm text-[var(--text-muted)]">Vui lòng đợi trong giây lát.</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="w-12 h-12 bg-red-50 text-red-500 rounded-full flex items-center justify-center mx-auto">
|
||||
<AlertCircle className="w-6 h-6" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Gia nhập thất bại</h2>
|
||||
<p className="text-sm text-red-600 bg-red-50 p-4 rounded-2xl font-semibold border border-red-100">{error}</p>
|
||||
<h2 className="text-xl font-bold text-[var(--text-primary)]">Gia nhập thất bại</h2>
|
||||
<p className="text-sm text-[var(--text-muted)] bg-red-50 p-4 rounded-2xl font-semibold border border-red-100">{error}</p>
|
||||
<div className="pt-4 flex flex-col gap-2">
|
||||
{isLoggedIn ? (
|
||||
<button
|
||||
@@ -142,7 +142,7 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
</button>
|
||||
<button
|
||||
onClick={onGoToHome}
|
||||
className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold py-3.5 rounded-2xl transition-all"
|
||||
className="w-full bg-[var(--background)] hover:bg-[var(--background)]/80 text-[var(--text-primary)] font-bold py-3.5 rounded-2xl transition-all border border-[var(--border)]"
|
||||
>
|
||||
Về trang chủ
|
||||
</button>
|
||||
@@ -155,7 +155,7 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
<div className="w-12 h-12 bg-green-50 text-green-500 rounded-full flex items-center justify-center mx-auto">
|
||||
<CheckCircle className="w-6 h-6" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Thành công!</h2>
|
||||
<h2 className="text-xl font-bold text-[var(--text-primary)]">Thành công!</h2>
|
||||
<p className="text-sm text-green-700 bg-green-50 p-4 rounded-2xl font-semibold border border-green-100">{successMsg}</p>
|
||||
<div className="pt-4">
|
||||
<button
|
||||
@@ -171,8 +171,8 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-2xl font-black text-gray-900 tracking-tight">Chào mừng bạn!</h2>
|
||||
<p className="text-gray-600 text-sm leading-relaxed">
|
||||
<h2 className="text-2xl font-black text-[var(--text-primary)] tracking-tight">Chào mừng bạn!</h2>
|
||||
<p className="text-[var(--text-secondary)] text-sm leading-relaxed">
|
||||
Bạn nhận được một lời mời tham gia hành trình du lịch. Vui lòng đăng nhập hoặc tạo tài khoản để có thể join và xem các hoạt động, chi phí của tour.
|
||||
</p>
|
||||
|
||||
@@ -185,7 +185,7 @@ export const JoinTourPage: React.FC<JoinTourPageProps> = ({ onLoginSuccess, onGo
|
||||
</button>
|
||||
<button
|
||||
onClick={onGoToSignup}
|
||||
className="w-full flex items-center justify-center gap-2 bg-gray-50 hover:bg-gray-100 text-gray-700 font-bold py-4 rounded-2xl border border-gray-200 transition-all active:scale-[0.98]"
|
||||
className="w-full flex items-center justify-center gap-2 bg-[var(--background)] hover:bg-[var(--border)] text-[var(--text-primary)] font-bold py-4 rounded-2xl border border-[var(--border)] transition-all active:scale-[0.98]"
|
||||
>
|
||||
<UserPlus className="w-5 h-5" /> Đăng ký tài khoản mới
|
||||
</button>
|
||||
|
||||
@@ -315,7 +315,7 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onGoToSignup, onGoToMa
|
||||
|
||||
|
||||
return (
|
||||
<div className="h-dvh w-full overflow-hidden font-sans bg-gray-900 relative">
|
||||
<div className="h-dvh w-full overflow-hidden font-sans bg-[var(--background)] relative">
|
||||
{/* Background Image with Horizontal Panning */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
{/* Active image for panning */}
|
||||
|
||||
@@ -339,15 +339,15 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col">
|
||||
<div className="min-h-screen bg-[var(--background)] flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-gray-100 px-4 py-4 flex items-center gap-4">
|
||||
<button onClick={onBack} className="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
||||
<ChevronLeft className="w-6 h-6 text-gray-600" />
|
||||
<div className="sticky top-0 z-30 bg-[var(--surface)]/80 backdrop-blur-md border-b border-[var(--border)] px-4 py-4 flex items-center gap-4">
|
||||
<button onClick={onBack} className="p-2 hover:bg-[var(--background)] rounded-full transition-colors">
|
||||
<ChevronLeft className="w-6 h-6 text-[var(--text-secondary)]" />
|
||||
</button>
|
||||
<div>
|
||||
<h1 className="text-xl font-black text-gray-900">Ghi chú của tôi</h1>
|
||||
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Sổ tay hành trình cá nhân</p>
|
||||
<h1 className="text-xl font-black text-[var(--text-primary)]">Ghi chú của tôi</h1>
|
||||
<p className="text-[10px] font-bold text-[var(--text-muted)] uppercase tracking-widest">Sổ tay hành trình cá nhân</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -358,11 +358,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tìm kiếm nội dung ghi chú..."
|
||||
className="w-full pl-10 pr-4 py-3 bg-white rounded-2xl border border-gray-100 shadow-sm focus:ring-2 focus:ring-amber-500 outline-none transition-all text-sm"
|
||||
className="w-full pl-10 pr-4 py-3 bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm focus:ring-2 focus:ring-amber-500 outline-none transition-all text-sm"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-muted)]" />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsCreating(true)}
|
||||
@@ -374,7 +374,7 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-gray-400">
|
||||
<div className="flex flex-col items-center justify-center py-20 text-[var(--text-muted)]">
|
||||
<Loader2 className="w-10 h-10 animate-spin mb-4" />
|
||||
<p className="font-bold">Đang tải ghi chú...</p>
|
||||
</div>
|
||||
@@ -383,11 +383,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tiêu đề ghi chú..."
|
||||
className="w-full px-4 py-3 bg-white rounded-2xl border border-gray-100 shadow-sm focus:ring-2 focus:ring-amber-500 outline-none transition-all text-lg font-bold"
|
||||
className="w-full px-4 py-3 bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm focus:ring-2 focus:ring-amber-500 outline-none transition-all text-lg font-bold"
|
||||
value={noteForm.title}
|
||||
onChange={(e) => setNoteForm({ ...noteForm, title: e.target.value })}
|
||||
/>
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden min-h-[500px] flex flex-col">
|
||||
<div className="bg-[var(--surface)] rounded-2xl border border-[var(--border)] shadow-sm overflow-hidden min-h-[500px] flex flex-col">
|
||||
<ReactQuill
|
||||
ref={quillRef}
|
||||
theme="snow"
|
||||
@@ -395,7 +395,7 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
onChange={(content) => setNoteForm({ ...noteForm, content })}
|
||||
modules={quillModules}
|
||||
placeholder="Bắt đầu viết cảm nhận của bạn tại đây..."
|
||||
className="flex-1 flex flex-col [&_.ql-container]:flex-1 [&_.ql-container]:flex [&_.ql-container]:flex-col [&_.ql-editor]:flex-1 [&_.ql-editor]:text-base [&_.ql-toolbar]:flex [&_.ql-toolbar]:flex-wrap sm:[&_.ql-toolbar]:flex-nowrap [&_.ql-toolbar]:border-0 [&_.ql-toolbar]:border-b [&_.ql-toolbar]:border-gray-50 [&_.ql-editor_table]:border-collapse [&_.ql-editor_table]:w-full [&_.ql-editor_td]:border [&_.ql-editor_td]:border-gray-200 [&_.ql-editor_td]:p-2 [&_.ql-editor_.ql-align-center]:text-center [&_.ql-editor_.ql-align-right]:text-right [&_.ql-editor_.ql-align-justify]:text-justify [&_.ql-picker-options]:!z-[50] [&_.ql-picker-options]:!shadow-xl [&_.ql-picker-options]:!rounded-xl [&_.ql-picker-options]:!border-gray-100 [&_.ql-stroke]:!stroke-gray-500 [&_.ql-fill]:!fill-gray-500 [&_button:hover_.ql-stroke]:!stroke-amber-500 [&_button:hover_.ql-fill]:!stroke-amber-500 [&_button.ql-active_.ql-stroke]:!stroke-amber-500"
|
||||
className="flex-1 flex flex-col [&_.ql-container]:flex-1 [&_.ql-container]:flex [&_.ql-container]:flex-col [&_.ql-editor]:flex-1 [&_.ql-editor]:text-base [&_.ql-toolbar]:flex [&_.ql-toolbar]:flex-wrap sm:[&_.ql-toolbar]:flex-nowrap [&_.ql-toolbar]:border-0 [&_.ql-toolbar]:border-b [&_.ql-toolbar]:border-[var(--border)] [&_.ql-editor_table]:border-collapse [&_.ql-editor_table]:w-full [&_.ql-editor_td]:border [&_.ql-editor_td]:border-[var(--border)] [&_.ql-editor_td]:p-2 [&_.ql-editor_.ql-align-center]:text-center [&_.ql-editor_.ql-align-right]:text-right [&_.ql-editor_.ql-align-justify]:text-justify [&_.ql-picker-options]:!z-[50] [&_.ql-picker-options]:!shadow-xl [&_.ql-picker-options]:!rounded-xl [&_.ql-picker-options]:!border-[var(--border)] [&_.ql-stroke]:!stroke-[var(--text-secondary)] [&_.ql-fill]:!fill-[var(--text-secondary)] [&_button:hover_.ql-stroke]:!stroke-amber-500 [&_button:hover_.ql-fill]:!stroke-amber-500 [&_button.ql-active_.ql-stroke]:!stroke-amber-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
@@ -416,11 +416,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
) : filteredNotes.length > 0 ? (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{filteredNotes.map((note) => (
|
||||
<div key={note.id} className="bg-white p-5 rounded-3xl border border-gray-100 shadow-sm hover:shadow-md transition-all group">
|
||||
<div key={note.id} className="bg-[var(--surface)] p-5 rounded-3xl border border-[var(--border)] shadow-sm hover:shadow-md transition-all group">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<h4 className="font-bold text-gray-900">{note.title}</h4>
|
||||
<div className="flex items-center gap-2 text-[10px] text-gray-400 font-bold uppercase mt-1">
|
||||
<h4 className="font-bold text-[var(--text-primary)]">{note.title}</h4>
|
||||
<div className="flex items-center gap-2 text-[10px] text-[var(--text-muted)] font-bold uppercase mt-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
{new Date(note.createdAt).toLocaleDateString('vi-VN')}
|
||||
</div>
|
||||
@@ -428,14 +428,14 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => handleEditNote(note)}
|
||||
className="p-2 text-gray-300 hover:text-blue-500 hover:bg-blue-50 rounded-xl transition-all opacity-0 group-hover:opacity-100"
|
||||
className="p-2 text-[var(--text-muted)] hover:text-blue-500 hover:bg-[var(--background)] rounded-xl transition-all opacity-0 group-hover:opacity-100"
|
||||
title="Sửa ghi chú"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteNote(note.id)}
|
||||
className="p-2 text-gray-300 hover:text-red-500 hover:bg-red-50 rounded-xl transition-all opacity-0 group-hover:opacity-100"
|
||||
className="p-2 text-[var(--text-muted)] hover:text-red-500 hover:bg-[var(--background)] rounded-xl transition-all opacity-0 group-hover:opacity-100"
|
||||
title="Xóa ghi chú"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
@@ -443,14 +443,14 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-sm text-gray-600 line-clamp-3 prose prose-sm max-w-none ql-editor !p-0 [&_table]:border-collapse [&_table]:w-full [&_td]:border [&_td]:border-gray-200 [&_td]:p-2 [&_.ql-align-center]:text-center [&_.ql-align-right]:text-right [&_.ql-align-justify]:text-justify"
|
||||
className="text-sm text-[var(--text-secondary)] line-clamp-3 prose prose-sm max-w-none ql-editor !p-0 [&_table]:border-collapse [&_table]:w-full [&_td]:border [&_td]:border-[var(--border)] [&_td]:p-2 [&_.ql-align-center]:text-center [&_.ql-align-right]:text-right [&_.ql-align-justify]:text-justify"
|
||||
dangerouslySetInnerHTML={{ __html: note.content }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-24 bg-white rounded-[40px] border-2 border-dashed border-gray-100 shadow-inner">
|
||||
<div className="text-center py-24 bg-[var(--surface)] rounded-[40px] border-2 border-dashed border-[var(--border)] shadow-inner">
|
||||
<div className="w-20 h-20 bg-amber-50 rounded-3xl flex items-center justify-center mx-auto mb-6 text-amber-500">
|
||||
<FileText className="w-10 h-10" />
|
||||
</div>
|
||||
|
||||
@@ -358,7 +358,7 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
|
||||
title={isLiked ? "Bỏ thích" : "Thích"}
|
||||
>
|
||||
<Heart className={`w-4 h-4 transition-colors ${
|
||||
isLiked ? 'text-rose-500 fill-rose-500' : 'text-gray-300'
|
||||
isLiked ? 'text-rose-500 fill-rose-500' : 'text-[var(--text-muted)]'
|
||||
}`} />
|
||||
<span>{likeCount}</span>
|
||||
</button>
|
||||
|
||||
@@ -239,7 +239,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
if (error || !tour) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-slate-50 dark:bg-slate-950 text-slate-800 dark:text-slate-100 p-4 transition-colors">
|
||||
<div className="bg-white dark:bg-slate-900 border border-gray-100 dark:border-slate-800 shadow-2xl rounded-[32px] p-8 max-w-md w-full text-center flex flex-col items-center gap-5">
|
||||
<div className="bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 shadow-2xl rounded-[32px] p-8 max-w-md w-full text-center flex flex-col items-center gap-5">
|
||||
<div className="w-16 h-16 bg-rose-50 dark:bg-rose-950/20 border border-rose-100 dark:border-rose-900/30 rounded-full flex items-center justify-center text-rose-500">
|
||||
<ShieldAlert className="w-8 h-8" />
|
||||
</div>
|
||||
@@ -261,11 +261,11 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
return (
|
||||
<div className="h-screen w-screen flex flex-col bg-slate-50 dark:bg-slate-950 text-slate-800 dark:text-slate-100 transition-colors overflow-hidden">
|
||||
{/* Header */}
|
||||
<header className="h-16 shrink-0 bg-white/70 dark:bg-slate-900/70 backdrop-blur-md border-b border-gray-100 dark:border-slate-800 px-4 md:px-6 flex items-center justify-between z-20">
|
||||
<header className="h-16 shrink-0 bg-[var(--surface)]/70 dark:bg-slate-900/70 backdrop-blur-md border-b border-[var(--border)] dark:border-slate-800 px-4 md:px-6 flex items-center justify-between z-20">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onGoToHome}
|
||||
className="p-2 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-xl transition-all active:scale-95 text-slate-500 dark:text-slate-400"
|
||||
className="p-2 hover:bg-[var(--background)] dark:hover:bg-slate-800 rounded-xl transition-all active:scale-95 text-[var(--text-muted)] dark:text-slate-400"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -274,7 +274,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
<span className="w-2.5 h-2.5 bg-rose-500 rounded-full animate-pulse"></span>
|
||||
<h1 className="text-sm md:text-base font-black uppercase tracking-tight">{t('emergencyJourney')}</h1>
|
||||
</div>
|
||||
<p className="text-[10px] text-slate-500 dark:text-slate-400 truncate max-w-[200px] sm:max-w-xs">{tour.title}</p>
|
||||
<p className="text-[10px] text-[var(--text-muted)] dark:text-slate-400 truncate max-w-[200px] sm:max-w-xs">{tour.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -283,7 +283,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
<select
|
||||
value={lang}
|
||||
onChange={(e) => changeLanguage(e.target.value as any)}
|
||||
className="bg-gray-50 dark:bg-slate-800 border border-gray-100 dark:border-slate-700 text-slate-800 dark:text-slate-100 rounded-xl px-2 py-1.5 text-xs font-bold focus:outline-none cursor-pointer"
|
||||
className="bg-[var(--background)] dark:bg-slate-800 border border-[var(--border)] dark:border-slate-700 text-[var(--text-primary)] dark:text-slate-100 rounded-xl px-2 py-1.5 text-xs font-bold focus:outline-none cursor-pointer"
|
||||
>
|
||||
<option value="vi">VI</option>
|
||||
<option value="en">EN</option>
|
||||
@@ -294,7 +294,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
<select
|
||||
value={theme}
|
||||
onChange={(e) => changeTheme(e.target.value as any)}
|
||||
className="bg-gray-50 dark:bg-slate-800 border border-gray-100 dark:border-slate-700 text-slate-800 dark:text-slate-100 rounded-xl px-2 py-1.5 text-xs font-bold focus:outline-none cursor-pointer"
|
||||
className="bg-[var(--background)] dark:bg-slate-800 border border-[var(--border)] dark:border-slate-700 text-[var(--text-primary)] dark:text-slate-100 rounded-xl px-2 py-1.5 text-xs font-bold focus:outline-none cursor-pointer"
|
||||
>
|
||||
<option value="light">{t('themeLight')}</option>
|
||||
<option value="dark">{t('themeDark')}</option>
|
||||
@@ -304,7 +304,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
</header>
|
||||
|
||||
{/* Mobile Tab Switcher */}
|
||||
<div className="lg:hidden h-12 shrink-0 bg-white dark:bg-slate-900 border-b border-gray-100 dark:border-slate-800 flex items-center z-10 p-1">
|
||||
<div className="lg:hidden h-12 shrink-0 bg-[var(--surface)] dark:bg-slate-900 border-b border-[var(--border)] dark:border-slate-800 flex items-center z-10 p-1">
|
||||
<button
|
||||
onClick={() => setActiveTab('map')}
|
||||
className={`flex-1 h-full flex items-center justify-center gap-2 text-xs font-bold rounded-lg transition-all ${activeTab === 'map' ? 'bg-rose-500 text-white shadow-md' : 'text-slate-500 dark:text-slate-400'}`}
|
||||
@@ -324,13 +324,13 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
{/* Main Layout Area */}
|
||||
<div className="flex-1 flex flex-col lg:grid lg:grid-cols-12 overflow-hidden">
|
||||
{/* Left Side Pane (Itinerary and Contacts) */}
|
||||
<div className={`flex-col lg:col-span-4 bg-white dark:bg-slate-900 border-r border-gray-100 dark:border-slate-800 overflow-y-auto no-scrollbar ${activeTab === 'itinerary' ? 'flex h-full' : 'hidden lg:flex'}`}>
|
||||
<div className={`flex-col lg:col-span-4 bg-[var(--surface)] dark:bg-slate-900 border-r border-[var(--border)] dark:border-slate-800 overflow-y-auto no-scrollbar ${activeTab === 'itinerary' ? 'flex h-full' : 'hidden lg:flex'}`}>
|
||||
<div className="p-4 md:p-6 space-y-6">
|
||||
{/* Tour Meta Info */}
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg md:text-xl font-black text-slate-900 dark:text-white leading-tight">{tour.title}</h2>
|
||||
<h2 className="text-lg md:text-xl font-black text-[var(--text-primary)] dark:text-white leading-tight">{tour.title}</h2>
|
||||
{tour.startDate && (
|
||||
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<div className="flex items-center gap-2 text-xs text-[var(--text-muted)] dark:text-slate-400">
|
||||
<Calendar className="w-4 h-4 text-rose-500" />
|
||||
<span>
|
||||
{new Date(tour.startDate).toLocaleDateString()}
|
||||
@@ -339,7 +339,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
</div>
|
||||
)}
|
||||
{tour.description && (
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400 leading-relaxed bg-slate-50 dark:bg-slate-950 p-3 rounded-2xl border border-gray-100 dark:border-slate-800/40">
|
||||
<p className="text-xs text-[var(--text-secondary)] dark:text-slate-400 leading-relaxed bg-[var(--background)] dark:bg-slate-950 p-3 rounded-2xl border border-[var(--border)] dark:border-slate-800/40">
|
||||
{tour.description}
|
||||
</p>
|
||||
)}
|
||||
@@ -358,8 +358,8 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
<div className="bg-rose-50/50 dark:bg-rose-950/10 border border-rose-100/50 dark:border-rose-950/20 p-4 rounded-2xl flex items-center justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-rose-600 dark:text-rose-400 font-bold uppercase tracking-wider">{t('tourOwner')}</div>
|
||||
<div className="text-sm font-black text-slate-900 dark:text-white truncate mt-0.5">{ownerContact.name || 'Anonymous'}</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{ownerContact.phone || t('noPhone')}</div>
|
||||
<div className="text-sm font-black text-[var(--text-primary)] dark:text-white truncate mt-0.5">{ownerContact.name || 'Anonymous'}</div>
|
||||
<div className="text-xs text-[var(--text-muted)] dark:text-slate-400 mt-0.5">{ownerContact.phone || t('noPhone')}</div>
|
||||
</div>
|
||||
{ownerContact.phone && (
|
||||
<a
|
||||
@@ -374,11 +374,11 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
|
||||
{/* Manager contacts */}
|
||||
{managerContacts.map((mgr, idx) => (
|
||||
<div key={idx} className="bg-slate-50 dark:bg-slate-950 border border-gray-100 dark:border-slate-800 p-4 rounded-2xl flex items-center justify-between gap-4">
|
||||
<div key={idx} className="bg-[var(--background)] dark:bg-slate-950 border border-[var(--border)] dark:border-slate-800 p-4 rounded-2xl flex items-center justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 font-bold uppercase tracking-wider">{t('tourManager')}</div>
|
||||
<div className="text-sm font-black text-slate-900 dark:text-white truncate mt-0.5">{mgr.name}</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{mgr.phone}</div>
|
||||
<div className="text-xs text-[var(--text-muted)] dark:text-slate-400 font-bold uppercase tracking-wider">{t('tourManager')}</div>
|
||||
<div className="text-sm font-black text-[var(--text-primary)] dark:text-white truncate mt-0.5">{mgr.name}</div>
|
||||
<div className="text-xs text-[var(--text-muted)] dark:text-slate-400 mt-0.5">{mgr.phone}</div>
|
||||
</div>
|
||||
<a
|
||||
href={`tel:${mgr.phone}`}
|
||||
@@ -390,20 +390,20 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
))}
|
||||
|
||||
{!ownerContact?.phone && managerContacts.length === 0 && (
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 italic">{t('noPhone')}</p>
|
||||
<p className="text-xs text-[var(--text-muted)] dark:text-slate-500 italic">{t('noPhone')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stops Hierarchy List */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">
|
||||
<h3 className="text-xs font-black text-[var(--text-muted)] dark:text-slate-500 uppercase tracking-widest">
|
||||
{t('viewTimeline')}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 relative before:absolute before:left-3 before:top-2 before:bottom-2 before:w-[2px] before:bg-gray-100 dark:before:bg-slate-800">
|
||||
<div className="space-y-4 relative before:absolute before:left-3 before:top-2 before:bottom-2 before:w-[2px] before:bg-[var(--border)] dark:before:bg-slate-800">
|
||||
{allLocations.length === 0 ? (
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 italic pl-6">{t('noStops')}</p>
|
||||
<p className="text-xs text-[var(--text-muted)] dark:text-slate-500 italic pl-6">{t('noStops')}</p>
|
||||
) : (
|
||||
allLocations.map((loc, idx) => {
|
||||
const isStart = idx === 0;
|
||||
@@ -430,15 +430,15 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
</div>
|
||||
|
||||
{/* Details */}
|
||||
<div className="flex-1 bg-slate-50/50 hover:bg-slate-50 dark:bg-slate-950/40 dark:hover:bg-slate-950/80 p-3 rounded-2xl border border-gray-100/50 dark:border-slate-800/40 transition-all select-none">
|
||||
<h4 className="text-xs font-black text-slate-800 dark:text-slate-200 group-hover:text-rose-500 dark:group-hover:text-rose-400 transition-colors">
|
||||
<div className="flex-1 bg-[var(--background)]/50 hover:bg-[var(--background)] dark:bg-slate-950/40 dark:hover:bg-slate-950/80 p-3 rounded-2xl border border-[var(--border)]/50 dark:border-slate-800/40 transition-all select-none">
|
||||
<h4 className="text-xs font-black text-[var(--text-primary)] dark:text-slate-200 group-hover:text-rose-500 dark:group-hover:text-rose-400 transition-colors">
|
||||
{loc.name}
|
||||
</h4>
|
||||
{loc.address && (
|
||||
<p className="text-[10px] text-slate-500 dark:text-slate-400 truncate mt-0.5">{loc.address}</p>
|
||||
<p className="text-[10px] text-[var(--text-muted)] dark:text-slate-400 truncate mt-0.5">{loc.address}</p>
|
||||
)}
|
||||
{loc.plannedStart && (
|
||||
<p className="text-[9px] text-slate-400 dark:text-slate-500 mt-1">
|
||||
<p className="text-[9px] text-[var(--text-muted)] dark:text-slate-500 mt-1">
|
||||
{new Date(loc.plannedStart).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
{loc.plannedEnd && ` - ${new Date(loc.plannedEnd).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`}
|
||||
</p>
|
||||
@@ -454,7 +454,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
</div>
|
||||
|
||||
{/* Right Side Map Pane */}
|
||||
<div className={`relative flex-1 lg:col-span-8 h-full bg-slate-100 dark:bg-slate-950 ${activeTab === 'map' ? 'flex' : 'hidden lg:flex'}`}>
|
||||
<div className={`relative flex-1 lg:col-span-8 h-full bg-[var(--background)] dark:bg-slate-950 ${activeTab === 'map' ? 'flex' : 'hidden lg:flex'}`}>
|
||||
{allLocations.length > 0 ? (
|
||||
<MapContainer
|
||||
center={[allLocations[0].latitude, allLocations[0].longitude]}
|
||||
@@ -499,7 +499,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
<Popup>
|
||||
<div className="p-1">
|
||||
<div className="font-black text-xs text-slate-900">{loc.name}</div>
|
||||
{loc.address && <div className="text-[10px] text-gray-500 mt-0.5">{loc.address}</div>}
|
||||
{loc.address && <div className="text-[10px] text-[var(--text-muted)] mt-0.5">{loc.address}</div>}
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
@@ -527,7 +527,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
{/* Locate Me button */}
|
||||
<button
|
||||
onClick={handleLocateMe}
|
||||
className="w-12 h-12 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded-2xl shadow-xl flex items-center justify-center text-slate-700 dark:text-slate-200 hover:bg-gray-50 dark:hover:bg-slate-800 transition-all active:scale-95"
|
||||
className="w-12 h-12 bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 rounded-2xl shadow-xl flex items-center justify-center text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-800 transition-all active:scale-95"
|
||||
title="Định vị của tôi"
|
||||
>
|
||||
{isLocating ? (
|
||||
@@ -541,7 +541,7 @@ export const ShareJourneyPage: React.FC<ShareJourneyPageProps> = ({ token, onGoT
|
||||
{allLocations.length > 0 && (
|
||||
<button
|
||||
onClick={() => setFitBoundsTrigger(prev => prev + 1)}
|
||||
className="w-12 h-12 bg-white dark:bg-slate-900 border border-gray-200 dark:border-slate-800 rounded-2xl shadow-xl flex items-center justify-center text-slate-700 dark:text-slate-200 hover:bg-gray-50 dark:hover:bg-slate-800 transition-all active:scale-95"
|
||||
className="w-12 h-12 bg-[var(--surface)] dark:bg-slate-900 border border-[var(--border)] dark:border-slate-800 rounded-2xl shadow-xl flex items-center justify-center text-[var(--text-secondary)] dark:text-slate-200 hover:bg-[var(--background)] dark:hover:bg-slate-800 transition-all active:scale-95"
|
||||
title="Bao phủ toàn bộ"
|
||||
>
|
||||
<Compass className="w-5 h-5 text-violet-500" />
|
||||
|
||||
@@ -157,20 +157,20 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8 bg-white p-10 rounded-3xl shadow-2xl">
|
||||
<div className="min-h-screen flex items-center justify-center bg-[var(--background)] py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8 bg-[var(--surface)] p-10 rounded-3xl shadow-2xl">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="absolute top-6 left-6 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
className="absolute top-6 left-6 text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<div className="mb-10">
|
||||
<h1 className="text-3xl font-extrabold text-gray-900">
|
||||
<h1 className="text-3xl font-extrabold text-[var(--text-primary)]">
|
||||
{step === 'form' ? 'Tạo tài khoản mới' : 'Xác thực tài khoản'}
|
||||
</h1>
|
||||
<p className="text-gray-500 mt-2 font-medium">
|
||||
<p className="text-[var(--text-muted)] mt-2 font-medium">
|
||||
{step === 'form'
|
||||
? 'Khám phá các tính năng lập kế hoạch chuyên nghiệp.'
|
||||
: `Vui lòng nhập mã OTP đã được gửi tới ${formData.email}`}
|
||||
@@ -187,61 +187,61 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
{step === 'form' ? (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Họ và tên</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Họ và tên</label>
|
||||
<div className="relative group">
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
placeholder="Nhập họ và tên của bạn"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Email</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Email</label>
|
||||
<div className="relative group">
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
required
|
||||
type="email"
|
||||
placeholder="Nhập email của bạn"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Mật khẩu</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Mật khẩu</label>
|
||||
<div className="relative group">
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
required
|
||||
type="password"
|
||||
placeholder="Nhập mật khẩu"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleChange('password', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Xác nhận mật khẩu</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Xác nhận mật khẩu</label>
|
||||
<div className="relative group">
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
required
|
||||
type="password"
|
||||
placeholder="Xác nhận mật khẩu"
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => handleChange('confirmPassword', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,28 +249,28 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Số điện thoại</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Số điện thoại</label>
|
||||
<div className="relative group">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="Nhập số điện thoại"
|
||||
value={formData.phone}
|
||||
onChange={(e) => handleChange('phone', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Địa chỉ</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Địa chỉ</label>
|
||||
<div className="relative group">
|
||||
<MapPin className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<MapPin className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nhập địa chỉ"
|
||||
value={formData.address}
|
||||
onChange={(e) => handleChange('address', e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,9 +278,9 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
</>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-bold text-gray-700 ml-1">Mã xác thực OTP</label>
|
||||
<label className="text-sm font-bold text-[var(--text-primary)] ml-1">Mã xác thực OTP</label>
|
||||
<div className="relative group">
|
||||
<ShieldCheck className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
||||
<ShieldCheck className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[var(--text-muted)] group-focus-within:text-blue-500 transition-colors" />
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
@@ -288,7 +288,7 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
placeholder="Nhập 6 số mã OTP"
|
||||
value={otp}
|
||||
onChange={(e) => setOtp(e.target.value.replace(/\D/g, ''))}
|
||||
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl text-center font-bold tracking-[0.5em] text-lg focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
|
||||
className="w-full pl-12 pr-4 py-4 bg-[var(--background)] border border-[var(--border)] rounded-2xl text-center font-bold tracking-[0.5em] text-lg focus:ring-2 focus:ring-blue-500 focus:bg-[var(--surface)] outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -308,9 +308,9 @@ const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) => {
|
||||
<>
|
||||
<div className="relative my-6 flex items-center justify-center">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-200"></div>
|
||||
<div className="w-full border-t border-[var(--border)]"></div>
|
||||
</div>
|
||||
<span className="relative px-3 bg-white text-xs font-bold text-gray-400 uppercase">Hoặc</span>
|
||||
<span className="relative px-3 bg-[var(--surface)] text-xs font-bold text-[var(--text-muted)] uppercase">Hoặc</span>
|
||||
</div>
|
||||
|
||||
<div id="google-signin-btn-signup" className="w-full flex justify-center"></div>
|
||||
|
||||
@@ -310,23 +310,23 @@ const MapContextMenu = ({ onAction, onOpen }: { onAction: (action: string, latln
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="absolute z-[2000] bg-white rounded-2xl shadow-2xl border border-gray-100 py-2 w-48 animate-in zoom-in-95 duration-200"
|
||||
className="absolute z-[2000] bg-[var(--surface)] rounded-2xl shadow-2xl border border-[var(--border)] py-2 w-48 animate-in zoom-in-95 duration-200"
|
||||
style={{ top: menuPos.y, left: menuPos.x }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<button onClick={() => { onAction('START', menuPos.latlng); setMenuPos(null); }} 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">
|
||||
<button onClick={() => { onAction('START', menuPos.latlng); setMenuPos(null); }} className="w-full text-left px-4 py-2 hover:bg-[var(--background)] text-sm font-bold text-[var(--text-secondary)] flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-600" /> Bắt đầu từ đây
|
||||
</button>
|
||||
<button onClick={() => { onAction('END', menuPos.latlng); setMenuPos(null); }} className="w-full text-left px-4 py-2 hover:bg-green-50 text-sm font-bold text-gray-700 flex items-center gap-2 border-b border-gray-50">
|
||||
<button onClick={() => { onAction('END', menuPos.latlng); setMenuPos(null); }} className="w-full text-left px-4 py-2 hover:bg-[var(--background)] text-sm font-bold text-[var(--text-secondary)] flex items-center gap-2 border-b border-[var(--border)]">
|
||||
<div className="w-2 h-2 rounded-full bg-green-600" /> Kết thúc ở đây
|
||||
</button>
|
||||
<div className="px-4 py-2 text-[10px] font-black text-gray-400 uppercase tracking-widest">Thêm vào chặng</div>
|
||||
<div className="px-4 py-2 text-[10px] font-black text-[var(--text-muted)] uppercase tracking-widest">Thêm vào chặng</div>
|
||||
{legs.map(leg => (
|
||||
<button
|
||||
key={leg.id}
|
||||
onClick={() => { onAction(`ADD_TO_LEG_${leg.id}`, menuPos.latlng); setMenuPos(null); }}
|
||||
className="w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-medium text-gray-600 truncate"
|
||||
className="w-full text-left px-4 py-2 hover:bg-[var(--background)] text-sm font-medium text-[var(--text-secondary)] truncate"
|
||||
>
|
||||
Chặng {leg.sequence}: {leg.note || 'Không có ghi chú'}
|
||||
</button>
|
||||
@@ -1706,13 +1706,13 @@ export const TourDetailPage = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 pb-20">
|
||||
<div className="min-h-screen bg-[var(--background)] pb-20">
|
||||
{/* Top Navigation Bar */}
|
||||
<div className="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-gray-100 px-4 py-3 flex items-center justify-between">
|
||||
<button onClick={onBack} className="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
||||
<ChevronLeft className="w-6 h-6 text-gray-600" />
|
||||
<div className="sticky top-0 z-30 bg-[var(--surface)]/80 backdrop-blur-md border-b border-[var(--border)] px-4 py-3 flex items-center justify-between">
|
||||
<button onClick={onBack} className="p-2 hover:bg-[var(--background)] rounded-full transition-colors">
|
||||
<ChevronLeft className="w-6 h-6 text-[var(--text-secondary)]" />
|
||||
</button>
|
||||
<h1 className="text-lg font-bold text-gray-800 truncate px-4 flex-1 text-center">
|
||||
<h1 className="text-lg font-bold text-[var(--text-primary)] truncate px-4 flex-1 text-center">
|
||||
{tourInfo.title}
|
||||
</h1>
|
||||
<div className="flex items-center gap-1">
|
||||
|
||||
Reference in New Issue
Block a user