Files
alireza-hassaniandClaude Sonnet 5 f16b47966b Initial commit: Negin Dental Clinic website
Next.js (App Router) clone of negindentalclinic.ir, wired to the
Meshkee Website API (blog, contact form, favicon), with on-page SEO
and a scroll-reveal animation sequence on the homepage.

See context.md for full project context.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 18:28:54 +03:30

194 lines
5.6 KiB
JavaScript

"use client";
import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { CUSTOMER_PORTAL_URL, ADMIN_DASHBOARD_URL } from "@/lib/meshkee";
import { useMeshkeeSession, isAdminUser } from "@/lib/auth-client";
import styles from "./Header.module.css";
const NAV = [
{ href: "/services", label: "خدمات کلینیک" },
{ href: "/blog", label: "مقالات" },
{ href: "/virtual-tour", label: "تور مجازی" },
{ href: "/about-us", label: "درباره ما" },
{ href: "/contact-us", label: "تماس با ما" },
];
const CLINIC_PHONE_LABEL = "۰۲۵ - ۳۲۹۴۵۰۷۷";
const CLINIC_PHONE_TEL = "tel:+982532945077";
function PhoneIcon() {
return (
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 5c0-.6.4-1 1-1h3l2 5-2.5 1.5a12 12 0 0 0 5.5 5.5L16 15l5 2v3c0 .6-.4 1-1 1A17 17 0 0 1 4 5z" />
</svg>
);
}
function UserIcon() {
return (
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="8" r="4" />
<path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8" />
</svg>
);
}
function ChevronIcon() {
return (
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>
);
}
function userDisplayName(user) {
return (
user?.firstName ||
user?.name ||
user?.fullName ||
(user?.cellNumber ? user.cellNumber : "کاربر")
);
}
function AccountMenu() {
const { status, user, logout } = useMeshkeeSession();
const [open, setOpen] = useState(false);
const rootRef = useRef(null);
useEffect(() => {
if (!open) return;
const onDocClick = (e) => {
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
};
document.addEventListener("mousedown", onDocClick);
return () => document.removeEventListener("mousedown", onDocClick);
}, [open]);
if (status !== "in") {
// Signed out (or still loading): the icon just sends the shopper to
// the customer account portal to sign in / register.
return (
<a
className={styles.userBtn}
href={CUSTOMER_PORTAL_URL}
aria-label="ورود کاربران"
title="ورود کاربران"
>
<UserIcon />
</a>
);
}
const admin = isAdminUser(user);
return (
<div className={styles.account} ref={rootRef}>
<button
type="button"
className={styles.accountBtn}
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
>
<span className={styles.accountAvatar}>
<UserIcon />
</span>
<span className={styles.accountName}>{userDisplayName(user)} عزیز</span>
<ChevronIcon />
</button>
{open ? (
<div className={styles.accountMenu} role="menu">
<a href={`${CUSTOMER_PORTAL_URL}/profile`} role="menuitem">
پروفایل من
</a>
{admin ? (
<a href={ADMIN_DASHBOARD_URL} role="menuitem">
مدیریت وبسایت
</a>
) : null}
<button type="button" role="menuitem" onClick={logout}>
خروج
</button>
</div>
) : null}
</div>
);
}
export default function Header() {
const pathname = usePathname();
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 20);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
setOpen(false);
}, [pathname]);
return (
<header className={`${styles.header} ${scrolled ? styles.headerScrolled : ""}`}>
<div className={`container ${styles.inner}`}>
<Link href="/" className={styles.brand} aria-label="کلینیک دندانپزشکی نگین">
<Image
src="/assets/img/Header/header.png"
alt="کلینیک دندانپزشکی نگین"
width={190}
height={57}
priority
/>
</Link>
<nav className={`${styles.nav} ${open ? styles.navOpen : ""}`}>
{NAV.map((item) => {
const active = pathname.startsWith(item.href);
return (
<Link
key={item.href}
href={item.href}
className={`${styles.link} ${active ? styles.linkActive : ""}`}
>
{item.label}
</Link>
);
})}
</nav>
<div className={styles.actions}>
<a
className={styles.phone}
href={CLINIC_PHONE_TEL}
dir="ltr"
aria-label={`تماس با کلینیک ${CLINIC_PHONE_LABEL}`}
>
<PhoneIcon />
<span className={styles.phoneNum}>{CLINIC_PHONE_LABEL}</span>
</a>
<AccountMenu />
<button
type="button"
className={styles.burger}
aria-expanded={open}
aria-label="باز و بسته کردن منو"
onClick={() => setOpen((v) => !v)}
>
<span />
<span />
<span />
</button>
</div>
</div>
</header>
);
}