Fix nav active state & populate services menu
Task #9: Two header fixes on artifacts/ehsan-poc. 1. Nav active state no longer stuck on "فرص التبرع". Active page now gets a moving green outline box (Home) matching the real ehsan.sa nav. "فرص التبرع" stays a solid green CTA but only shows an active green ring when on /opportunities. Applied consistent active styling across desktop and mobile nav (Home, Opportunities, Request). 2. "خدماتنا" dropdown now lists the eight official services from the reference image, each with a lucide icon: غراس (Sprout), الزكاة (HandCoins), هدية (Gift), الأضاحي (Beef), الحملات (Megaphone), التبرع الدوري (Repeat), التبرع بالرسائل (MessageSquare), تطهير الأسهم (TrendingUp). Each routes to /opportunities (POC has no per-service pages). Kept "طلب دعم" below a separator so desktop users retain access to /request. Mobile menu mirrors the services list. Added serviceItems i18n group (AR + EN) in translations.ts. Files: src/components/layout/Header.tsx, src/lib/i18n/translations.ts. Verified: tsc --noEmit clean; e2e UI test passed (active state moves, dropdown shows all 8 items + request, routing works); console clean after HMR-restart of the web workflow. No emojis.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import {
|
||||
@@ -18,6 +19,15 @@ import {
|
||||
X,
|
||||
LogOut,
|
||||
Globe,
|
||||
Sprout,
|
||||
HandCoins,
|
||||
Gift,
|
||||
Beef,
|
||||
Megaphone,
|
||||
Repeat,
|
||||
MessageSquare,
|
||||
TrendingUp,
|
||||
HeartHandshake,
|
||||
} from "lucide-react";
|
||||
import ehsanLogo from "../../assets/ehsan-logo.png";
|
||||
|
||||
@@ -31,6 +41,17 @@ export function Header() {
|
||||
|
||||
const isActive = (path: string) => location === path;
|
||||
|
||||
const services = [
|
||||
{ key: "ghiras", label: t.serviceItems.ghiras, icon: Sprout },
|
||||
{ key: "zakat", label: t.serviceItems.zakat, icon: HandCoins },
|
||||
{ key: "gift", label: t.serviceItems.gift, icon: Gift },
|
||||
{ key: "adahi", label: t.serviceItems.adahi, icon: Beef },
|
||||
{ key: "campaigns", label: t.serviceItems.campaigns, icon: Megaphone },
|
||||
{ key: "recurring", label: t.serviceItems.recurring, icon: Repeat },
|
||||
{ key: "sms", label: t.serviceItems.sms, icon: MessageSquare },
|
||||
{ key: "stocks", label: t.serviceItems.stocks, icon: TrendingUp },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="bg-white sticky top-0 z-50 shadow-sm">
|
||||
<div className="container mx-auto px-4 h-16 flex items-center justify-between gap-4">
|
||||
@@ -43,8 +64,10 @@ export function Header() {
|
||||
<nav className="hidden lg:flex items-center gap-1 flex-1 justify-center">
|
||||
<Link
|
||||
href="/"
|
||||
className={`px-3 py-2 text-sm font-medium rounded-md transition-colors ${
|
||||
isActive("/") ? "text-primary" : "text-foreground hover:text-primary"
|
||||
className={`px-3 py-2 text-sm font-medium rounded-md border transition-colors ${
|
||||
isActive("/")
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-foreground hover:text-primary"
|
||||
}`}
|
||||
data-testid="nav-home"
|
||||
>
|
||||
@@ -57,7 +80,9 @@ export function Header() {
|
||||
|
||||
<Link
|
||||
href="/opportunities"
|
||||
className="px-4 py-2 text-sm font-bold rounded-md bg-primary text-primary-foreground inline-flex items-center gap-1 hover:opacity-90 transition-opacity"
|
||||
className={`px-4 py-2 text-sm font-bold rounded-md bg-primary text-primary-foreground inline-flex items-center gap-1 hover:opacity-90 transition-opacity ${
|
||||
isActive("/opportunities") ? "ring-2 ring-primary ring-offset-2" : ""
|
||||
}`}
|
||||
data-testid="nav-opportunities"
|
||||
>
|
||||
{t.nav.opportunities}
|
||||
@@ -74,9 +99,26 @@ export function Header() {
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="center">
|
||||
<DropdownMenuItem onClick={() => setLocation("/request")} data-testid="nav-requestSupport">
|
||||
{t.nav.requestSupport}
|
||||
<DropdownMenuContent align="center" className="w-56">
|
||||
{services.map((s) => (
|
||||
<DropdownMenuItem
|
||||
key={s.key}
|
||||
onClick={() => setLocation("/opportunities")}
|
||||
data-testid={`nav-service-${s.key}`}
|
||||
className="gap-2 cursor-pointer"
|
||||
>
|
||||
<s.icon className="w-4 h-4 text-primary" />
|
||||
<span>{s.label}</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => setLocation("/request")}
|
||||
data-testid="nav-requestSupport"
|
||||
className="gap-2 cursor-pointer"
|
||||
>
|
||||
<HeartHandshake className="w-4 h-4 text-primary" />
|
||||
<span>{t.nav.requestSupport}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -164,21 +206,42 @@ export function Header() {
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block px-3 py-2 rounded-md text-sm font-medium text-foreground hover:bg-muted"
|
||||
className={`block px-3 py-2 rounded-md text-sm font-medium hover:bg-muted ${
|
||||
isActive("/") ? "text-primary bg-muted" : "text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t.nav.home}
|
||||
</Link>
|
||||
<Link
|
||||
href="/opportunities"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block px-3 py-2 rounded-md text-sm font-bold text-primary hover:bg-muted"
|
||||
className={`block px-3 py-2 rounded-md text-sm font-bold hover:bg-muted ${
|
||||
isActive("/opportunities") ? "bg-primary text-primary-foreground" : "text-primary"
|
||||
}`}
|
||||
>
|
||||
{t.nav.opportunities}
|
||||
</Link>
|
||||
<div className="pt-1">
|
||||
<p className="px-3 py-1 text-xs font-semibold text-foreground/50">
|
||||
{t.nav.services}
|
||||
</p>
|
||||
{services.map((s) => (
|
||||
<button
|
||||
key={s.key}
|
||||
onClick={() => { setMobileOpen(false); setLocation("/opportunities"); }}
|
||||
className="flex w-full items-center gap-2 text-start px-3 py-2 rounded-md text-sm font-medium text-foreground hover:bg-muted"
|
||||
>
|
||||
<s.icon className="w-4 h-4 text-primary" />
|
||||
<span>{s.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href="/request"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block px-3 py-2 rounded-md text-sm font-medium text-foreground hover:bg-muted"
|
||||
className={`block px-3 py-2 rounded-md text-sm font-medium hover:bg-muted ${
|
||||
isActive("/request") ? "text-primary bg-muted" : "text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t.nav.requestSupport}
|
||||
</Link>
|
||||
|
||||
@@ -43,6 +43,16 @@ export const en = {
|
||||
whatsappLog: "WhatsApp Log",
|
||||
quickDonate: "Quick Donate",
|
||||
},
|
||||
serviceItems: {
|
||||
ghiras: "Ghiras",
|
||||
zakat: "Zakat",
|
||||
gift: "Gift",
|
||||
adahi: "Adahi (Sacrifices)",
|
||||
campaigns: "Campaigns",
|
||||
recurring: "Recurring Donation",
|
||||
sms: "Donate by SMS",
|
||||
stocks: "Stock Purification",
|
||||
},
|
||||
auth: {
|
||||
title: "Admin Login",
|
||||
subtitle: "Sign in to access the management dashboard",
|
||||
@@ -255,6 +265,16 @@ export const ar = {
|
||||
whatsappLog: "سجل واتساب",
|
||||
quickDonate: "تبرع سريع",
|
||||
},
|
||||
serviceItems: {
|
||||
ghiras: "غراس",
|
||||
zakat: "الزكاة",
|
||||
gift: "هدية",
|
||||
adahi: "الأضاحي",
|
||||
campaigns: "الحملات",
|
||||
recurring: "التبرع الدوري",
|
||||
sms: "التبرع بالرسائل",
|
||||
stocks: "تطهير الأسهم",
|
||||
},
|
||||
auth: {
|
||||
title: "دخول الإدارة",
|
||||
subtitle: "سجّل الدخول للوصول إلى لوحة الإدارة",
|
||||
|
||||
Reference in New Issue
Block a user