Files
travelplanning/CHANGE_BUTTON.md
T

14 KiB

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.

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  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  / Đă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ồ   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.