diff --git a/frontend/src/components/AddLocationModal.tsx b/frontend/src/components/AddLocationModal.tsx index 00d68b4..ae7d47d 100644 --- a/frontend/src/components/AddLocationModal.tsx +++ b/frontend/src/components/AddLocationModal.tsx @@ -45,13 +45,13 @@ const MapPicker = ({ onPick, center }: { onPick: (latlng: L.LatLng) => void, cen {menuPos && (
Chi phí nhanh tại điểm này
💡 Dán "lat, lng" để tự động điền cả vĩ độ và kinh độ
💡 Dán "lat, lng" để tự Động điền cả vĩ độ và kinh độ
Nhấn để chọn ảnh
Hỗ trợ JPG, PNG, WEBP
Đã chọn {previews.length} tệp
{locationName}
{c.userName}
{c.content}
+
{new Date(c.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{message || 'Bạn có chắc chắn muốn thực hiện hành động này không?'}
Đăng nhập để tham gia chuyến du lịch này
Chào mừng bạn quay trở lại!
Chưa có tài khoản?{' '} { onClose(); onSwitchToSignup?.(); }} diff --git a/frontend/src/components/NotificationModal.tsx b/frontend/src/components/NotificationModal.tsx index f60faca..ff09e5a 100644 --- a/frontend/src/components/NotificationModal.tsx +++ b/frontend/src/components/NotificationModal.tsx @@ -39,13 +39,13 @@ export const NotificationModal: React.FC = ({ {/* Modal Content */} - + {icons[type]} - {title} - + {title} + {message || "Bạn không được phép gỡ bỏ thành viên này!"} diff --git a/frontend/src/index.css b/frontend/src/index.css index 46328ae..b741ef0 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,10 +1,96 @@ @import "tailwindcss"; @layer base { + /* ============ LIGHT THEME (Default) ============ */ :root { --main-header-height: 56px; --sub-nav-height: 48px; --combined-top-height: 104px; + + /* Light Theme Variables - Soft Parchment & Yale Blue */ + --background: #f6f0ed; /* Parchment */ + --background-alt: #f9fafb; /* Light Gray Alt */ + --surface: #ffffff; /* Pure White for crisp card layers */ + --surface-muted: #f3f4f6; /* Muted Surface */ + --surface-hover: #f9fafb; /* Surface Hover */ + + --border: #e5e7eb; /* Light Border */ + --border-light: #f3f4f6; /* Light Border Alt */ + + --text-primary: #28536b; /* Yale Blue (High contrast text) */ + --text-secondary: #7ea8be; /* Steel Blue */ + --text-accent: #c2948a; /* Rosy Taupe */ + --text-muted: #9ca3af; /* Muted Text */ + --text-disabled: #d1d5db; /* Disabled Text */ + + --primary: #28536b; /* Yale Blue for main buttons */ + --primary-hover: #1f4154; /* Darker Yale Blue */ + --primary-light: #eff6ff; /* Light Blue Background */ + + --secondary: #7ea8be; /* Steel Blue */ + --secondary-light: #f0f9ff; /* Light Secondary */ + + --success: #10b981; /* Green */ + --success-light: #ecfdf5; + + --danger: #ef4444; /* Red */ + --danger-light: #fef2f2; + + --warning: #f59e0b; /* Amber */ + --warning-light: #fffbeb; + + --info: #3b82f6; /* Blue */ + --info-light: #eff6ff; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + + /* Smooth theme transition */ + transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; + } + + /* ============ DARK THEME ============ */ + :root.dark { + /* Dark Theme Variables - Vintage Grape & Bondi Blue */ + --background: #513b56; /* Vintage Grape */ + --background-alt: #434354; /* Dark Alt */ + --surface: #525174; /* Dusty Grape */ + --surface-muted: #626186; /* Surface Muted */ + --surface-hover: #5f6b7e; /* Surface Hover */ + + --border: #626186; /* Border */ + --border-light: #525174; /* Border Light */ + + --text-primary: #f8fafc; /* Soft White */ + --text-secondary: #cbd5e1; /* Secondary Text */ + --text-accent: #bce784; /* Lime Cream */ + --text-muted: #94a3b8; /* Muted Slate */ + --text-disabled: #64748b; /* Disabled Text */ + + --primary: #348aa7; /* Bondi Blue */ + --primary-hover: #296f86; /* Darker Bondi Blue */ + --primary-light: #0c2340; /* Dark Blue Background */ + + --secondary: #5dd39e; /* Emerald */ + --secondary-light: #064e3b; /* Dark Secondary */ + + --success: #10b981; /* Green */ + --success-light: #064e3b; + + --danger: #ef4444; /* Red */ + --danger-light: #3f0f0f; + + --warning: #f59e0b; /* Amber */ + --warning-light: #3f2009; + + --info: #3b82f6; /* Blue */ + --info-light: #0c2340; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); + + /* Smooth theme transition */ + transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; } html, body, #root, .app-container { @@ -33,7 +119,8 @@ top: 0; height: var(--main-header-height); z-index: 50; - background-color: #ffffff; + background-color: var(--surface); + color: var(--text-primary); } .sub-nav-menu { @@ -45,8 +132,9 @@ margin: 0 !important; border-radius: 0 !important; box-shadow: none !important; - border-bottom: 1px solid #e5e7eb; - background-color: #ffffff; + border-bottom: 1px solid var(--border); + background-color: var(--surface); + color: var(--text-primary); } /* Master viewport wrapper - contains entire itinerary */ @@ -71,7 +159,8 @@ position: sticky !important; top: 0; z-index: 100 !important; - background-color: #ffffff; + background-color: var(--surface); + color: var(--text-primary); flex-shrink: 0 !important; } @@ -79,8 +168,9 @@ .fixed-top-header-block { flex-shrink: 0 !important; width: 100% !important; - background-color: #ffffff; - border-bottom: 1px solid #e5e7eb; + background-color: var(--surface); + color: var(--text-primary); + border-bottom: 1px solid var(--border); } /* Timeline scroll container - main scrollable region */ @@ -92,7 +182,8 @@ width: 100% !important; padding: 0 !important; margin: 0 !important; - background-color: #f8fafc; + background-color: var(--background); + color: var(--text-primary); } .timeline-scroll-container { @@ -111,7 +202,8 @@ width: 100% !important; padding: 0 !important; margin-top: 0px !important; - background-color: #f8fafc; + background-color: var(--background); + color: var(--text-primary); } .timeline-scroll-viewport { @@ -134,17 +226,19 @@ /* Individual folder node wrapper - base stage container */ .folder-node-wrapper { width: 100% !important; - background-color: #ffffff; + background-color: var(--surface); + color: var(--text-primary); margin-bottom: 0px !important; display: flex; flex-direction: column; - border-bottom: 1px solid #f3f4f6; + border-bottom: 1px solid var(--border); } /* Individual stage card block - backwards compatibility */ .stage-card-block { width: 100% !important; - background-color: #ffffff; + background-color: var(--surface); + color: var(--text-primary); margin-bottom: 1px !important; display: flex; flex-direction: column; @@ -168,14 +262,15 @@ padding: 12px 16px !important; display: flex; align-items: center; - background-color: #ffffff !important; + background-color: var(--surface) !important; + color: var(--text-primary); cursor: pointer; - border-bottom: 1px solid #f3f4f6; + border-bottom: 1px solid var(--border); transition: background-color 0.2s; } .folder-header-row:hover { - background-color: #f9fafb !important; + background-color: var(--surface-muted) !important; } /* Stage header row - sticky positioning for pinned effect */ @@ -187,14 +282,15 @@ padding: 12px 16px !important; display: flex; align-items: center; - background-color: #ffffff !important; + background-color: var(--surface) !important; + color: var(--text-primary); cursor: pointer; - border-bottom: 1px solid #f3f4f6; + border-bottom: 1px solid var(--border); transition: background-color 0.2s; } .stage-header-row:hover { - background-color: #f9fafb !important; + background-color: var(--surface-muted) !important; } /* Alternate sticky header styling (kept for backwards compatibility) */ diff --git a/frontend/src/pages/ExploreMap.tsx b/frontend/src/pages/ExploreMap.tsx index ddd6e70..59f373a 100644 --- a/frontend/src/pages/ExploreMap.tsx +++ b/frontend/src/pages/ExploreMap.tsx @@ -655,35 +655,35 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, - + {/* Nút lọc Tag và Dropdown */} 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 Dropdown Content */} {isFilterDropdownOpen && ( - + {/* Tour Filter Section */} - + - 🧳 Chuyến đi + 🧳 Chuyến đi { 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ả @@ -691,7 +691,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, { 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} @@ -701,15 +701,15 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {/* Photo Filter Section */} {availablePhotoTags.length > 0 && ( - - + + - 📸 Ảnh công khai + 📸 Ảnh công khai { 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ả @@ -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, {/* Search Box - Thay thế div "Khám phá khu vực" */} - + setSearchQuery(e.target.value)} /> {isSearchingSuggestions && } {searchQuery && ( - { setSearchQuery(''); setSuggestions([]); }} className="p-1 text-gray-400 hover:text-gray-600 rounded-full"> + { setSearchQuery(''); setSuggestions([]); }} className="p-1 text-[var(--text-muted)] hover:text-[var(--text-secondary)] rounded-full"> )} {/* Dropdown danh sách gợi ý */} {suggestions.length > 0 && ( - + {suggestions.map((s, idx) => ( 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" > {s.type === 'tour' ? : } - {s.name} - + {s.name} + {s.type === 'tour' ? 'Chuyến đi của bạn' : 'Địa điểm trên bản đồ'} @@ -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" > @@ -822,31 +822,31 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {/* Lựa chọn Ngôn ngữ */} - + - - 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 - 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 - 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'}`}>中文 + + 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 + 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 + 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'}`}>中文 {/* Lựa chọn Giao diện */} - + {theme === 'light' && } {theme === 'dark' && } {theme === 'system' && } - - 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'}`}> + + 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'}`}> {t('themeLight')} - 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'}`}> + 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'}`}> {t('themeDark')} - 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'}`}> + 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'}`}> {t('themeSystem')} @@ -880,7 +880,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {onLogout && ( @@ -996,7 +996,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, className: 'custom-bubble', html: ` - + @@ -1019,16 +1019,16 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, )} {tour.description && ( - + {tour.description} )} - + - {status.label} + {status.label} @@ -1146,13 +1146,13 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {item.type === 'RESTAURANT' ? t('businessRestaurant') : item.type === 'HOTEL' ? t('businessHotel') : t('businessHomestay')} {item.address && ( - {item.address} + {item.address} )} {item.phone && ( - SĐT: {item.phone} + SĐT: {item.phone} )} {item.email && ( - Email: {item.email} + Email: {item.email} )} {item.description} @@ -1167,7 +1167,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {/* Context Menu Chia sẻ */} {shareMenu && ( e.stopPropagation()} > @@ -1175,19 +1175,19 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, shareMenu.canShare ? ( 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" > Sao chép liên kết ) : ( - Bạn đã gia nhập tour này + Bạn đã gia nhập tour này ) ) : shareMenu.hasPendingRequest ? ( - Đang chờ duyệt... + Đang chờ duyệt... ) : ( 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" > {t('trustedMembers')} ({trustedUsers.length}) {isLeaderboardOpen && ( - + 🏆 {t('trustedMembers')} {trustedUsers.length === 0 ? ( - Chưa có thành viên nào được đánh giá. + Chưa có thành viên nào được đánh giá. ) : ( {trustedUsers.map((u, idx) => ( - + - + {idx + 1} - {u.name} + {u.name} ★ {u.averageScore} - ({u.ratingCount}) + ({u.ratingCount}) ))} @@ -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" > {t('recommendedTitle') || 'Đề xuất dịch vụ'} ({recommendedLocations.length}) @@ -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" > {t('blacklistTitle') || 'Danh sách đen'} ({blacklist.length}) @@ -1332,8 +1332,8 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {/* Recommendations Panel */} {isRecommendedOpen && ( - - + + 🌟 {t('recommendedTitle') || 'Đề xuất chất lượng'} @@ -1363,7 +1363,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {processedRecommendations.length === 0 ? ( - Chưa có địa điểm đề xuất nào. + Chưa có địa điểm đề xuất nào. ) : ( {processedRecommendations.map((item) => { @@ -1411,7 +1411,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, )} - + {item.description} @@ -1424,8 +1424,8 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {/* Blacklist Panel */} {isBlacklistOpen && ( - - + + ⚠️ {t('blacklistTitle')} @@ -1448,7 +1448,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, {processedBlacklist.length === 0 ? ( - {t('emptyBlacklist')} + {t('emptyBlacklist')} ) : ( {processedBlacklist.map((item) => { @@ -1488,7 +1488,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos, 📍 Định vị trên bản đồ )} - + Lý do: {item.reason} diff --git a/frontend/src/pages/JoinTourPage.tsx b/frontend/src/pages/JoinTourPage.tsx index 3fa6615..301873d 100644 --- a/frontend/src/pages/JoinTourPage.tsx +++ b/frontend/src/pages/JoinTourPage.tsx @@ -101,8 +101,8 @@ export const JoinTourPage: React.FC = ({ onLoginSuccess, onGo const isLoggedIn = !!localStorage.getItem('token') && !localStorage.getItem('guest_token'); return ( - - + + {/* Logo/Icon */} @@ -114,16 +114,16 @@ export const JoinTourPage: React.FC = ({ onLoginSuccess, onGo {loading ? ( - Đang xử lý tham gia hành trình... - Vui lòng đợi trong giây lát. + Đang xử lý tham gia hành trình... + Vui lòng đợi trong giây lát. ) : error ? ( - Gia nhập thất bại - {error} + Gia nhập thất bại + {error} {isLoggedIn ? ( = ({ onLoginSuccess, onGo Về trang chủ @@ -155,7 +155,7 @@ export const JoinTourPage: React.FC = ({ onLoginSuccess, onGo - Thành công! + Thành công! {successMsg} = ({ onLoginSuccess, onGo ) : ( - Chào mừng bạn! - + Chào mừng bạn! + 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. @@ -185,7 +185,7 @@ export const JoinTourPage: React.FC = ({ onLoginSuccess, onGo Đăng ký tài khoản mới diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index dab3e71..97b9404 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -315,7 +315,7 @@ export const LandingPage: React.FC = ({ onGoToSignup, onGoToMa return ( - + {/* Background Image with Horizontal Panning */} {/* Active image for panning */} diff --git a/frontend/src/pages/MyNotePage.tsx b/frontend/src/pages/MyNotePage.tsx index b28dc8c..83f8887 100644 --- a/frontend/src/pages/MyNotePage.tsx +++ b/frontend/src/pages/MyNotePage.tsx @@ -339,15 +339,15 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v }); return ( - + {/* Header */} - - - + + + - Ghi chú của tôi - Sổ tay hành trình cá nhân + Ghi chú của tôi + Sổ tay hành trình cá nhân @@ -358,11 +358,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v setSearchQuery(e.target.value)} /> - + setIsCreating(true)} @@ -374,7 +374,7 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v )} {isLoading ? ( - + Đang tải ghi chú... @@ -383,11 +383,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v setNoteForm({ ...noteForm, title: e.target.value })} /> - + 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" /> @@ -416,11 +416,11 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v ) : filteredNotes.length > 0 ? ( {filteredNotes.map((note) => ( - + - {note.title} - + {note.title} + {new Date(note.createdAt).toLocaleDateString('vi-VN')} @@ -428,14 +428,14 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v 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ú" > 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ú" > @@ -443,14 +443,14 @@ export const MyNotePage = ({ tourId, onBack }: { tourId: string; onBack: () => v ))} ) : ( - + diff --git a/frontend/src/pages/MyPhotosPage.tsx b/frontend/src/pages/MyPhotosPage.tsx index 7602bb0..9df1034 100644 --- a/frontend/src/pages/MyPhotosPage.tsx +++ b/frontend/src/pages/MyPhotosPage.tsx @@ -358,7 +358,7 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { title={isLiked ? "Bỏ thích" : "Thích"} > {likeCount} diff --git a/frontend/src/pages/ShareJourneyPage.tsx b/frontend/src/pages/ShareJourneyPage.tsx index 26fc787..45b8396 100644 --- a/frontend/src/pages/ShareJourneyPage.tsx +++ b/frontend/src/pages/ShareJourneyPage.tsx @@ -239,7 +239,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT if (error || !tour) { return ( - + @@ -261,11 +261,11 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT return ( {/* Header */} - + @@ -274,7 +274,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {t('emergencyJourney')} - {tour.title} + {tour.title} @@ -283,7 +283,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT 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" > VI EN @@ -294,7 +294,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT 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" > {t('themeLight')} {t('themeDark')} @@ -304,7 +304,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {/* Mobile Tab Switcher */} - + 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 = ({ token, onGoT {/* Main Layout Area */} {/* Left Side Pane (Itinerary and Contacts) */} - + {/* Tour Meta Info */} - {tour.title} + {tour.title} {tour.startDate && ( - + {new Date(tour.startDate).toLocaleDateString()} @@ -339,7 +339,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT )} {tour.description && ( - + {tour.description} )} @@ -358,8 +358,8 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {t('tourOwner')} - {ownerContact.name || 'Anonymous'} - {ownerContact.phone || t('noPhone')} + {ownerContact.name || 'Anonymous'} + {ownerContact.phone || t('noPhone')} {ownerContact.phone && ( = ({ token, onGoT {/* Manager contacts */} {managerContacts.map((mgr, idx) => ( - + - {t('tourManager')} - {mgr.name} - {mgr.phone} + {t('tourManager')} + {mgr.name} + {mgr.phone} = ({ token, onGoT ))} {!ownerContact?.phone && managerContacts.length === 0 && ( - {t('noPhone')} + {t('noPhone')} )} {/* Stops Hierarchy List */} - + {t('viewTimeline')} - + {allLocations.length === 0 ? ( - {t('noStops')} + {t('noStops')} ) : ( allLocations.map((loc, idx) => { const isStart = idx === 0; @@ -430,15 +430,15 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {/* Details */} - - + + {loc.name} {loc.address && ( - {loc.address} + {loc.address} )} {loc.plannedStart && ( - + {new Date(loc.plannedStart).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {loc.plannedEnd && ` - ${new Date(loc.plannedEnd).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`} @@ -454,7 +454,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {/* Right Side Map Pane */} - + {allLocations.length > 0 ? ( = ({ token, onGoT {loc.name} - {loc.address && {loc.address}} + {loc.address && {loc.address}} @@ -527,7 +527,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {/* Locate Me button */} {isLocating ? ( @@ -541,7 +541,7 @@ export const ShareJourneyPage: React.FC = ({ token, onGoT {allLocations.length > 0 && ( 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ộ" > diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx index 5e2b554..f1d2e08 100644 --- a/frontend/src/pages/SignupPage.tsx +++ b/frontend/src/pages/SignupPage.tsx @@ -157,20 +157,20 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { }; return ( - - + + - + {step === 'form' ? 'Tạo tài khoản mới' : 'Xác thực tài khoản'} - + {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 = ({ onBack, onSuccess }) => { {step === 'form' ? ( <> - Họ và tên + Họ và tên - + 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" /> - Email + Email - + 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" /> - Mật khẩu + Mật khẩu - + 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" /> - Xác nhận mật khẩu + Xác nhận mật khẩu - + 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" /> @@ -249,28 +249,28 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { - Số điện thoại + Số điện thoại - + 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" /> - Địa chỉ + Địa chỉ - + 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" /> @@ -278,9 +278,9 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { > ) : ( - Mã xác thực OTP + Mã xác thực OTP - + = ({ 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" /> @@ -308,9 +308,9 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { <> - + - Hoặc + Hoặc diff --git a/frontend/src/pages/TourDetailPage.tsx b/frontend/src/pages/TourDetailPage.tsx index 5229c1c..840e235 100644 --- a/frontend/src/pages/TourDetailPage.tsx +++ b/frontend/src/pages/TourDetailPage.tsx @@ -310,23 +310,23 @@ const MapContextMenu = ({ onAction, onOpen }: { onAction: (action: string, latln return ( e.stopPropagation()} onContextMenu={(e) => e.preventDefault()} > - { 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"> + { 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"> Bắt đầu từ đây - { 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"> + { 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)]"> Kết thúc ở đây - Thêm vào chặng + Thêm vào chặng {legs.map(leg => ( { 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ú'} @@ -1706,13 +1706,13 @@ export const TourDetailPage = ({ }; return ( - + {/* Top Navigation Bar */} - - - + + + - + {tourInfo.title} diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index b2d293f..838d868 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -7,7 +7,58 @@ const config: Config = { './src/**/*.{js,ts,jsx,tsx}', ], theme: { - extend: {}, + extend: { + colors: { + // Light Theme Colors ("Classic Heritage & Soft Sand") + yotripLight: { + bg: '#f6f0ed', // Parchment - Global Page Backgrounds + text: '#28536b', // Yale Blue - Primary Text & Main Titles + steel: '#7ea8be', // Steel Blue - Active Navigation & Secondary Actions + khaki: '#bbb193', // Khaki Beige - Borders, Dividers, Muted States + rosy: '#c2948a', // Rosy Taupe - Special Highlight Badges, Notifications + }, + // Dark Theme Colors ("Vintage Velvet & Aquatic Zest") + yotripDark: { + bg: '#513b56', // Vintage Grape - Global Page Backgrounds + surface: '#525174', // Dusty Grape - Component Surfaces (Cards, Bubbles) + lime: '#bce784', // Lime Cream - Brand Text Highlights, Active Icons + bondi: '#348aa7', // Bondi Blue - Primary Action Buttons, Links + emerald: '#5dd39e', // Emerald - Success Utilities & "Tối ưu" badges + }, + // CSS Variable based colors for smooth theme support + theme: { + bg: 'var(--background)', + 'bg-alt': 'var(--background-alt)', + surface: 'var(--surface)', + 'surface-muted': 'var(--surface-muted)', + 'surface-hover': 'var(--surface-hover)', + border: 'var(--border)', + 'border-light': 'var(--border-light)', + 'text': 'var(--text-primary)', + 'text-secondary': 'var(--text-secondary)', + 'text-accent': 'var(--text-accent)', + 'text-muted': 'var(--text-muted)', + 'text-disabled': 'var(--text-disabled)', + primary: 'var(--primary)', + 'primary-hover': 'var(--primary-hover)', + 'primary-light': 'var(--primary-light)', + secondary: 'var(--secondary)', + 'secondary-light': 'var(--secondary-light)', + success: 'var(--success)', + 'success-light': 'var(--success-light)', + danger: 'var(--danger)', + 'danger-light': 'var(--danger-light)', + warning: 'var(--warning)', + 'warning-light': 'var(--warning-light)', + info: 'var(--info)', + 'info-light': 'var(--info-light)', + } + }, + boxShadow: { + 'theme-sm': 'var(--shadow-sm)', + 'theme-md': 'var(--shadow-md)', + } + }, }, plugins: [], };
{message || "Bạn không được phép gỡ bỏ thành viên này!"}
Chưa có thành viên nào được đánh giá.
Chưa có địa điểm đề xuất nào.
{t('emptyBlacklist')}
Vui lòng đợi trong giây lát.
{error}
{successMsg}
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.
Sổ tay hành trình cá nhân
Đang tải ghi chú...
{tour.title}
{tour.description}
{t('noPhone')}
{t('noStops')}
{loc.address}
{new Date(loc.plannedStart).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {loc.plannedEnd && ` - ${new Date(loc.plannedEnd).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`}
{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 = ({ onBack, onSuccess }) => { {step === 'form' ? ( <> - Họ và tên + Họ và tên - + 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" /> - Email + Email - + 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" /> - Mật khẩu + Mật khẩu - + 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" /> - Xác nhận mật khẩu + Xác nhận mật khẩu - + 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" /> @@ -249,28 +249,28 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { - Số điện thoại + Số điện thoại - + 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" /> - Địa chỉ + Địa chỉ - + 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" /> @@ -278,9 +278,9 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { > ) : ( - Mã xác thực OTP + Mã xác thực OTP - + = ({ 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" /> @@ -308,9 +308,9 @@ const SignupPage: React.FC = ({ onBack, onSuccess }) => { <> - + - Hoặc + Hoặc diff --git a/frontend/src/pages/TourDetailPage.tsx b/frontend/src/pages/TourDetailPage.tsx index 5229c1c..840e235 100644 --- a/frontend/src/pages/TourDetailPage.tsx +++ b/frontend/src/pages/TourDetailPage.tsx @@ -310,23 +310,23 @@ const MapContextMenu = ({ onAction, onOpen }: { onAction: (action: string, latln return ( e.stopPropagation()} onContextMenu={(e) => e.preventDefault()} > - { 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"> + { 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"> Bắt đầu từ đây - { 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"> + { 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)]"> Kết thúc ở đây - Thêm vào chặng + Thêm vào chặng {legs.map(leg => ( { 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ú'} @@ -1706,13 +1706,13 @@ export const TourDetailPage = ({ }; return ( - + {/* Top Navigation Bar */} - - - + + + - + {tourInfo.title} diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index b2d293f..838d868 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -7,7 +7,58 @@ const config: Config = { './src/**/*.{js,ts,jsx,tsx}', ], theme: { - extend: {}, + extend: { + colors: { + // Light Theme Colors ("Classic Heritage & Soft Sand") + yotripLight: { + bg: '#f6f0ed', // Parchment - Global Page Backgrounds + text: '#28536b', // Yale Blue - Primary Text & Main Titles + steel: '#7ea8be', // Steel Blue - Active Navigation & Secondary Actions + khaki: '#bbb193', // Khaki Beige - Borders, Dividers, Muted States + rosy: '#c2948a', // Rosy Taupe - Special Highlight Badges, Notifications + }, + // Dark Theme Colors ("Vintage Velvet & Aquatic Zest") + yotripDark: { + bg: '#513b56', // Vintage Grape - Global Page Backgrounds + surface: '#525174', // Dusty Grape - Component Surfaces (Cards, Bubbles) + lime: '#bce784', // Lime Cream - Brand Text Highlights, Active Icons + bondi: '#348aa7', // Bondi Blue - Primary Action Buttons, Links + emerald: '#5dd39e', // Emerald - Success Utilities & "Tối ưu" badges + }, + // CSS Variable based colors for smooth theme support + theme: { + bg: 'var(--background)', + 'bg-alt': 'var(--background-alt)', + surface: 'var(--surface)', + 'surface-muted': 'var(--surface-muted)', + 'surface-hover': 'var(--surface-hover)', + border: 'var(--border)', + 'border-light': 'var(--border-light)', + 'text': 'var(--text-primary)', + 'text-secondary': 'var(--text-secondary)', + 'text-accent': 'var(--text-accent)', + 'text-muted': 'var(--text-muted)', + 'text-disabled': 'var(--text-disabled)', + primary: 'var(--primary)', + 'primary-hover': 'var(--primary-hover)', + 'primary-light': 'var(--primary-light)', + secondary: 'var(--secondary)', + 'secondary-light': 'var(--secondary-light)', + success: 'var(--success)', + 'success-light': 'var(--success-light)', + danger: 'var(--danger)', + 'danger-light': 'var(--danger-light)', + warning: 'var(--warning)', + 'warning-light': 'var(--warning-light)', + info: 'var(--info)', + 'info-light': 'var(--info-light)', + } + }, + boxShadow: { + 'theme-sm': 'var(--shadow-sm)', + 'theme-md': 'var(--shadow-md)', + } + }, }, plugins: [], };