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,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu";
|
} from "../ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +19,15 @@ import {
|
|||||||
X,
|
X,
|
||||||
LogOut,
|
LogOut,
|
||||||
Globe,
|
Globe,
|
||||||
|
Sprout,
|
||||||
|
HandCoins,
|
||||||
|
Gift,
|
||||||
|
Beef,
|
||||||
|
Megaphone,
|
||||||
|
Repeat,
|
||||||
|
MessageSquare,
|
||||||
|
TrendingUp,
|
||||||
|
HeartHandshake,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import ehsanLogo from "../../assets/ehsan-logo.png";
|
import ehsanLogo from "../../assets/ehsan-logo.png";
|
||||||
|
|
||||||
@@ -31,6 +41,17 @@ export function Header() {
|
|||||||
|
|
||||||
const isActive = (path: string) => location === path;
|
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 (
|
return (
|
||||||
<header className="bg-white sticky top-0 z-50 shadow-sm">
|
<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">
|
<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">
|
<nav className="hidden lg:flex items-center gap-1 flex-1 justify-center">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className={`px-3 py-2 text-sm font-medium rounded-md transition-colors ${
|
className={`px-3 py-2 text-sm font-medium rounded-md border transition-colors ${
|
||||||
isActive("/") ? "text-primary" : "text-foreground hover:text-primary"
|
isActive("/")
|
||||||
|
? "border-primary text-primary"
|
||||||
|
: "border-transparent text-foreground hover:text-primary"
|
||||||
}`}
|
}`}
|
||||||
data-testid="nav-home"
|
data-testid="nav-home"
|
||||||
>
|
>
|
||||||
@@ -57,7 +80,9 @@ export function Header() {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/opportunities"
|
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"
|
data-testid="nav-opportunities"
|
||||||
>
|
>
|
||||||
{t.nav.opportunities}
|
{t.nav.opportunities}
|
||||||
@@ -74,9 +99,26 @@ export function Header() {
|
|||||||
<ChevronDown className="w-3.5 h-3.5" />
|
<ChevronDown className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="center">
|
<DropdownMenuContent align="center" className="w-56">
|
||||||
<DropdownMenuItem onClick={() => setLocation("/request")} data-testid="nav-requestSupport">
|
{services.map((s) => (
|
||||||
{t.nav.requestSupport}
|
<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>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@@ -164,21 +206,42 @@ export function Header() {
|
|||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
onClick={() => setMobileOpen(false)}
|
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}
|
{t.nav.home}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/opportunities"
|
href="/opportunities"
|
||||||
onClick={() => setMobileOpen(false)}
|
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}
|
{t.nav.opportunities}
|
||||||
</Link>
|
</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
|
<Link
|
||||||
href="/request"
|
href="/request"
|
||||||
onClick={() => setMobileOpen(false)}
|
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}
|
{t.nav.requestSupport}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ export const en = {
|
|||||||
whatsappLog: "WhatsApp Log",
|
whatsappLog: "WhatsApp Log",
|
||||||
quickDonate: "Quick Donate",
|
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: {
|
auth: {
|
||||||
title: "Admin Login",
|
title: "Admin Login",
|
||||||
subtitle: "Sign in to access the management dashboard",
|
subtitle: "Sign in to access the management dashboard",
|
||||||
@@ -255,6 +265,16 @@ export const ar = {
|
|||||||
whatsappLog: "سجل واتساب",
|
whatsappLog: "سجل واتساب",
|
||||||
quickDonate: "تبرع سريع",
|
quickDonate: "تبرع سريع",
|
||||||
},
|
},
|
||||||
|
serviceItems: {
|
||||||
|
ghiras: "غراس",
|
||||||
|
zakat: "الزكاة",
|
||||||
|
gift: "هدية",
|
||||||
|
adahi: "الأضاحي",
|
||||||
|
campaigns: "الحملات",
|
||||||
|
recurring: "التبرع الدوري",
|
||||||
|
sms: "التبرع بالرسائل",
|
||||||
|
stocks: "تطهير الأسهم",
|
||||||
|
},
|
||||||
auth: {
|
auth: {
|
||||||
title: "دخول الإدارة",
|
title: "دخول الإدارة",
|
||||||
subtitle: "سجّل الدخول للوصول إلى لوحة الإدارة",
|
subtitle: "سجّل الدخول للوصول إلى لوحة الإدارة",
|
||||||
|
|||||||
Reference in New Issue
Block a user