feat: restore and integrate AppDownloadBanner under Top-Bar

This commit is contained in:
2026-06-27 20:50:00 +07:00
parent 7ab1342690
commit 175668da35
6 changed files with 185 additions and 74 deletions
+110
View File
@@ -0,0 +1,110 @@
# To AI Agent: Restore Smart Android App Download Banner Below Top-Bar Header
## 1. Context & Feature Objective
Previously, the codebase had a promotional banner encouraging mobile users to download the native Android `.apk` file. However, during the recent overhaul of the Top-Bar header and Member Dropdown Menu configurations, this banner component was accidentally unmounted or hidden.
**Objective:** Restore and refactor this promotional frame (`AppDownloadBanner.tsx`). It must **ONLY** appear when a user accesses the web application via a **Mobile Android Browser** (Chrome, Samsung Internet, Opera Mobile, etc.). It must be positioned dynamically as a small, clean horizontal frame pinned directly underneath the main Top-Bar header layout, without conflicting with the new absolute Profile Dropdown Menu.
---
## 2. Visual & Behavioral Layout Specifications
- **Placement Context:** Directly underneath the Top-Bar layer, shifting the core page content (Map Canvas or Landing Hero) down proportionally (`relative` or `sticky` stack). It must not overlay or block map navigation tools.
- **Conditional Trigger Logic:** The banner must evaluate `navigator.userAgent`. If the user agent includes `'android'` **AND** the user is running inside a standard web browser (not inside the compiled wrapper app itself), display the banner.
- **Dismissible Interaction:** Include a small close button (`X`). Clicking it should temporarily store a flag in `sessionStorage` or `localStorage` to prevent the banner from bugging the user repeatedly during their session.
---
## 3. Technical Implementation Blueprint
### Step 1: Create the Responsive Banner Component (`AppDownloadBanner.tsx`)
Create or re-engineer the banner layout within `frontend/src/components/layout/AppDownloadBanner.tsx`:
```typescript
import React, { useState, useEffect } from 'react';
import { X, Download } from 'lucide-react';
export const AppDownloadBanner: React.FC = () => {
const [isVisible, setIsVisible] = useState(false);
const APK_DOWNLOAD_URL = `${import.meta.env.VITE_BACKEND_URL || window.location.origin}/downloads/yotrip-latest.apk`;
useEffect(() => {
const userAgent = navigator.userAgent.toLowerCase();
const isAndroidBrowser = userAgent.includes('android') && !window.location.origin.includes('capacitor://') && !window.location.origin.includes('localhost:80');
const isBannerDismissed = localStorage.getItem('yotrip_apk_banner_dismissed') === 'true';
// ✅ Target condition match: User is on Android mobile browser and hasn't closed it yet
if (isAndroidBrowser && !isBannerDismissed) {
setIsVisible(true);
}
}, []);
const handleDismiss = () => {
localStorage.setItem('yotrip_apk_banner_dismissed', 'true');
setIsVisible(false);
};
if (!isVisible) return null;
return (
<div className="w-full bg-gradient-to-r from-blue-900 to-indigo-950 border-b border-blue-800 px-4 py-2 flex items-center justify-between text-white text-[11px] font-medium z-40 relative animate-fade-in shrink-0">
<div className="flex items-center gap-2.5 flex-1 min-w-0">
<div className="p-1 bg-blue-500/20 rounded-lg text-blue-400 shrink-0">
<Download className="w-3.5 h-3.5" />
</div>
<p className="truncate text-slate-200">
Trải nghiệm mượt mà hơn với ứng dụng <span className="text-white font-bold">YoTrip cho Android</span>!
</p>
</div>
<div className="flex items-center gap-3 ml-2 shrink-0">
{/* Direct Download Trigger Target Link */}
<a
href={APK_DOWNLOAD_URL}
download="yotrip.apk"
className="bg-blue-600 hover:bg-blue-500 px-2.5 py-1 rounded font-bold text-white transition-colors shadow-sm active:scale-95"
>
Tải APK
</a>
{/* Close Button Trigger */}
<button
onClick={handleDismiss}
className="p-1 hover:bg-slate-800 rounded text-slate-400 hover:text-slate-200 transition-colors"
title="Đóng thông báo"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
</div>
);
};
### Step 2: Integrate into Layout Hierarchies (LandingPage.tsx & ExplorerMap.tsx)
Mount the verified download banner right below your primary header or top-bar row item array context:
{/* ❌ BEFORE STRUCTURAL INTEGRATION */}
<div className="w-screen h-screen flex flex-col">
<TopBarHeader />
<MapCanvasWorkspace />
</div>
{/* ✅ AFTER STRUCTURAL INTEGRATION: Stacked relative context */}
<div className="w-screen h-screen flex flex-col overflow-hidden">
{/* 1. Main Application Header */}
<TopBarHeader />
{/* 2. THE RESTORED ANDROID APP PROMOTIONAL BANNER */}
<AppDownloadBanner />
{/* 3. Primary Workspace Area - Shifts down cleanly when banner initializes */}
<div className="flex-1 relative min-h-0 w-full">
<MapCanvasWorkspace />
</div>
</div>
## 4. Verification & Quality Acceptance Criteria for AI Agent
[ ] Targeted UserAgent Isolation: Emulate a desktop display width (iPhone or Desktop layouts). Verify the banner remains completely hidden. Toggle the network responsive preview device model to Android (e.g., Pixel 7) and refresh. Confirm the download bar pops up seamlessly.
[ ] Absolute Dropdown Overlay Preservation: Expand the Member Avatar menu row dropdown list while the banner is visible. Confirm that the dropdown box displays layered ON TOP of the banner context, without layout shifting or text clipping.
[ ] State Dismissal Memory: Click the X button on the banner, then refresh the browser session. Confirm the banner remains hidden and respects the localStorage condition toggle.