226 lines
14 KiB
Markdown
226 lines
14 KiB
Markdown
# To AI Agent: Consolidate Map Top-Bar into Unified Profile/Guest Dropdown Menu and Create Profile Modals
|
|
|
|
## 1. Context & Architectural Refactor Goal
|
|
We are optimizing the overcrowded top-bar control deck on the main Map Explorer page (`image_55397c.png`). Currently, a long row of utility buttons compromises mobile readability and viewport real estate.
|
|
|
|
**Objective:** Consolidate all secondary control buttons into a single **Avatar/Guest Profile Button**.
|
|
- When clicked, it will open an adaptive dropdown action menu (or a smooth mobile bottom-sheet) customized dynamically based on the session authentication state (**Authenticated Member** vs. **Anonymous Guest**).
|
|
- Migrate the global "Language" and "Theme/Interface" buttons from the old top-bar directly into this new profile configuration lifecycle.
|
|
|
|
---
|
|
|
|
## 2. Authentication State Menus Specification
|
|
|
|
### 2.1. State A: Authenticated Member Dropdown (User is logged in)
|
|
Display the user's fetched avatar image (`user.avatarUrl`). Clicking it reveals these options:
|
|
1. **Bảng điều khiển (Dashboard):** [Icon: LayoutDashboard] Redirects to user command center. *Note: Global language/theme quick-toggles are moved inside here/settings.*
|
|
2. **Cài đặt cá nhân (Settings):** [Icon: Settings] Opens the comprehensive **`ProfileSettingsModal.tsx`**.
|
|
3. **Tạo tour (Create Tour):** [Icon: Compass / MapPin] Launches the step-by-step route wizard.
|
|
4. **Báo cáo vi phạm (Report):** [Icon: ShieldAlert] Opens an infraction query form.
|
|
--- (Visual Divider Line) ---
|
|
5. **Đăng xuất (Logout):** [Icon: LogOut] Standard session termination trigger.
|
|
|
|
### 2.2. State B: Anonymous Guest Dropdown (User is not logged in)
|
|
Display a default vector Guest Icon. Clicking it reveals these options:
|
|
1. **Cài đặt (Settings):** [Icon: Sliders] Opens a lightweight configuration block allowing the Guest to dynamically choose **Ngôn ngữ** (Language) and **Giao diện** (Light/Dark Theme) stored in local storage cache.
|
|
--- (Visual Divider Line) ---
|
|
2. **Đăng ký / Đăng nhập (Auth):** [Icon: LogIn] Spawns the central authentication entry shield modal.
|
|
|
|
---
|
|
|
|
## 3. Component Blueprints & Implementation Steps
|
|
|
|
### Step 1: Create the Unified Top-Bar Controller (`MapProfileDropdown.tsx`)
|
|
Deploy this highly accessible responsive menu container. On screen dimensions mimicking mobile/Android viewports, it is highly recommended to render this as a slick **Bottom-Sheet Overlay** for better thumb-touch accuracy.
|
|
|
|
```typescript
|
|
import React, { useState } from 'react';
|
|
import { useAuth } from '../context/AuthContext'; // Leverage existing state system
|
|
import { LayoutDashboard, Settings, Compass, ShieldAlert, LogOut, LogIn, Sliders } from 'lucide-react';
|
|
|
|
export const MapProfileDropdown: React.FC<{ onOpenSettings: () => void }> = ({ onOpenSettings }) => {
|
|
const { isAuthenticated, user, logout, loginRedirect } = useAuth();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
return (
|
|
<div className="relative inline-block text-left select-none">
|
|
{/* TRIGGER BUTTON */}
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="w-11 h-11 rounded-full border-2 border-white bg-slate-800 flex items-center justify-center overflow-hidden shadow-lg active:scale-95 transition-transform"
|
|
>
|
|
{isAuthenticated && user?.avatarUrl ? (
|
|
<img src={user.avatarUrl} alt="Avatar" className="w-full h-full object-cover" />
|
|
) : (
|
|
/* Default Guest Avatar Icon placeholder */
|
|
<div className="w-full h-full flex items-center justify-center bg-slate-700 text-slate-300 font-bold text-sm">G</div>
|
|
)}
|
|
</button>
|
|
|
|
{/* DROPDOWN MENU / MOBILE BOTTOM SHEET LAYER */}
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop screen closer click-catcher */}
|
|
<div className="fixed inset-0 z-40 bg-black/20 sm:bg-transparent" onClick={() => setIsOpen(false)} />
|
|
|
|
<div className="fixed bottom-0 left-0 right-0 sm:absolute sm:bottom-auto sm:top-14 sm:right-0 sm:left-auto z-50 w-full sm:w-64 bg-slate-900 border-t sm:border border-slate-800 rounded-t-2xl sm:rounded-xl p-2 shadow-2xl animate-slide-up sm:animate-fade-in text-xs text-slate-200">
|
|
{isAuthenticated ? (
|
|
/* --- LOGGED IN MENU OPTION ROW STACK --- */
|
|
<div className="flex flex-col space-y-1">
|
|
<button className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left">
|
|
<LayoutDashboard className="w-4 h-4 text-blue-400" /> Bảng điều khiển
|
|
</button>
|
|
<button onClick={() => { setIsOpen(false); onOpenSettings(); }} className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left">
|
|
<Settings className="w-4 h-4 text-emerald-400" /> Cài đặt cá nhân
|
|
</button>
|
|
<button className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left">
|
|
<Compass className="w-4 h-4 text-amber-400" /> Tạo tour
|
|
</button>
|
|
<button className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left">
|
|
<ShieldAlert className="w-4 h-4 text-rose-400" /> Báo cáo vi phạm
|
|
</button>
|
|
<div className="h-[1px] bg-slate-800 my-1 mx-2" />
|
|
<button onClick={logout} className="flex items-center gap-3 w-full px-4 py-3 text-rose-400 hover:bg-slate-800 rounded-lg text-left font-bold">
|
|
<LogOut className="w-4 h-4" /> Đăng xuất
|
|
</button>
|
|
</div>
|
|
) : (
|
|
/* --- GUEST ANONYMOUS MENU OPTION ROW STACK --- */
|
|
<div className="flex flex-col space-y-1">
|
|
<div className="px-4 py-2 text-[11px] font-bold uppercase tracking-wider text-slate-500">Tùy chỉnh nhanh</div>
|
|
{/* Guest Quick Config Modules */}
|
|
<div className="px-4 py-2 flex flex-col gap-2 bg-slate-950/40 rounded-lg m-1">
|
|
<div className="flex justify-between items-center py-1">
|
|
<span>Ngôn ngữ:</span>
|
|
<select className="bg-slate-800 border border-slate-700 rounded px-1.5 py-0.5 text-white text-[11px]">
|
|
<option value="vi">Tiếng Việt</option>
|
|
<option value="en">English</option>
|
|
</select>
|
|
</div>
|
|
<div className="flex justify-between items-center py-1">
|
|
<span>Giao diện:</span>
|
|
<select className="bg-slate-800 border border-slate-700 rounded px-1.5 py-0.5 text-white text-[11px]">
|
|
<option value="dark">Tối</option>
|
|
<option value="light">Sáng</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="h-[1px] bg-slate-800 my-1 mx-2" />
|
|
<button onClick={loginRedirect} className="flex items-center gap-3 w-full px-4 py-3 text-blue-400 hover:bg-slate-800 rounded-lg text-left font-bold">
|
|
<LogIn className="w-4 h-4" /> Đăng ký / Đăng nhập
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
### Step 2: Implement the Main Profile Modification Panel (ProfileSettingsModal.tsx)
|
|
Create this secure responsive form layer. It must conform to absolute constraint logic (such as locking email changes) and include select toggles for app themes.
|
|
|
|
import React, { useState } from 'react';
|
|
import { X, Camera } from 'lucide-react';
|
|
|
|
export const ProfileSettingsModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => {
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[1000000] bg-black/70 backdrop-blur-sm flex items-end sm:items-center justify-center p-0 sm:p-4">
|
|
<div className="bg-slate-900 w-full sm:max-w-xl h-[92vh] sm:h-auto max-h-[92vh] sm:max-h-[85vh] rounded-t-2xl sm:rounded-xl flex flex-col overflow-hidden text-slate-200 text-xs">
|
|
|
|
{/* Header bar */}
|
|
<div className="px-4 py-3.5 border-b border-slate-800 flex justify-between items-center shrink-0">
|
|
<span className="font-bold text-sm text-white">Chỉnh sửa hồ sơ cá nhân</span>
|
|
<button onClick={onClose} className="p-1 bg-slate-800 hover:bg-slate-700 rounded-lg"><X className="w-4 h-4" /></button>
|
|
</div>
|
|
|
|
{/* Scrollable Form Workspace Canvas */}
|
|
<div className="flex-1 overflow-y-auto p-5 space-y-5">
|
|
|
|
{/* Avatar Upload Container Component */}
|
|
<div className="flex flex-col items-center justify-center space-y-2">
|
|
<div className="relative w-20 h-20 rounded-full border-2 border-slate-700 overflow-hidden bg-slate-800 group">
|
|
<img src="/placeholder-avatar.png" alt="Profile View" className="w-full h-full object-cover" />
|
|
<label className="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 cursor-pointer transition-opacity">
|
|
<Camera className="w-5 h-5 text-white" />
|
|
<input type="file" accept="image/*" className="hidden" />
|
|
</label>
|
|
</div>
|
|
<span className="text-[10px] text-slate-500">Chạm ảnh để tải lên hình đại diện mới</span>
|
|
</div>
|
|
|
|
{/* Text Input Row Fields */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="font-bold text-slate-400">Tên hiển thị:</label>
|
|
<input type="text" className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white focus:border-blue-500 outline-none" defaultValue="Loc Pham" />
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="font-bold text-slate-400">Số điện thoại:</label>
|
|
<input type="tel" className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white focus:border-blue-500 outline-none" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="font-bold text-slate-400">Địa chỉ liên hệ:</label>
|
|
<input type="text" className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white focus:border-blue-500 outline-none" />
|
|
</div>
|
|
|
|
{/* EMAIL COMPONENT BOUNDARY - CRITICAL REQUIREMENT: DISABLED CHANGING */}
|
|
<div className="flex flex-col gap-1.5 bg-slate-950/30 p-3 rounded-lg border border-slate-800/60">
|
|
<label className="font-bold text-slate-500">Địa chỉ Email đăng nhập (Không thể chỉnh sửa):</label>
|
|
<input type="email" disabled className="bg-slate-950/50 border border-slate-800 text-slate-500 rounded-lg p-2.5 cursor-not-allowed select-none" defaultValue="yeunhiepanh.photo@gmail.com" />
|
|
</div>
|
|
|
|
{/* Security Password Mutation Stack */}
|
|
<div className="border-t border-slate-800 pt-4 flex flex-col gap-3">
|
|
<span className="font-bold text-slate-300">Thay đổi mật khẩu đăng nhập</span>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<input type="password" placeholder="Mật khẩu hiện tại" className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white" />
|
|
<input type="password" placeholder="Mật khẩu mới" className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Core Configuration Toggles (Moved from Top-Bar into profile settings context) */}
|
|
<div className="border-t border-slate-800 pt-4 grid grid-cols-2 gap-4">
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="font-bold text-slate-400">Lựa chọn ngôn ngữ:</label>
|
|
<select className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white">
|
|
<option value="vi">Tiếng Việt (ICT)</option>
|
|
<option value="en">English (US)</option>
|
|
</select>
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<label className="font-bold text-slate-400">Lựa chọn giao diện:</label>
|
|
<select className="bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-white">
|
|
<option value="dark">Chế độ tối (Dark Mode)</option>
|
|
<option value="light">Chế độ sáng (Light Mode)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Action Bottom Save Trigger */}
|
|
<div className="p-4 bg-slate-950 border-t border-slate-800 flex justify-end gap-3 shrink-0">
|
|
<button onClick={onClose} className="px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded-lg text-slate-300">Hủy</button>
|
|
<button className="px-5 py-2 bg-blue-600 hover:bg-blue-500 font-bold text-white rounded-lg">Lưu cấu hình</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
## 4. Quality Verification & Acceptance Checklist for AI Agent
|
|
|
|
[ ] Top-Bar Streamlining: Verify that old utility layout buttons boxed inside the top-bar (image_55397c.png) are safely stripped out, leaving only the Back button, Title, and the new single Dropdown icon asset.
|
|
|
|
[ ] Cross-Platform Android Touch Targets: On mobile browser layouts, clicking the avatar must expand a screen-bottom popup block container (fixed bottom-0 left-0 right-0) ensuring an native-app tactile interaction.
|
|
|
|
[ ] Email Immutability Guard: Inspect the editing form inside ProfileSettingsModal.tsx. Confirm the input node for the user's profile email address possess the disabled state attribute flag so typing alterations are completely locked out.
|
|
|
|
[ ] System State Isolation: Confirm changes inside language and theme inputs successfully bubble triggers upwards to refresh local storage parameters instantly without dropping routing view connections. |