udpate for coming soon
This commit is contained in:
+21
-39
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import type { CommonContent } from "@/content/types";
|
||||
import { locales, type Locale } from "@/lib/i18n";
|
||||
import { type Locale } from "@/lib/i18n";
|
||||
import { isComingSoonMode } from "@/lib/site";
|
||||
|
||||
type BottomNavProps = {
|
||||
locale: Locale;
|
||||
@@ -11,67 +11,49 @@ type BottomNavProps = {
|
||||
};
|
||||
|
||||
export default function BottomNav({ locale, common }: BottomNavProps) {
|
||||
const pathname = usePathname();
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
if (isComingSoonMode()) {
|
||||
return (
|
||||
<nav aria-label={common.navLabel} className="bottom-nav">
|
||||
<div className="container bottom-nav-inner">
|
||||
<div className="bottom-nav-links">
|
||||
<Link href={`/${locale}`} className="active" aria-current="page">
|
||||
<span aria-hidden className="nav-icon">
|
||||
○
|
||||
</span>
|
||||
{common.nav.home}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedPath = pathname !== "/" && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
||||
const homePath = `/${locale}`;
|
||||
const aboutPath = `/${locale}/about`;
|
||||
const contactPath = `/${locale}/contact`;
|
||||
|
||||
const isHome = normalizedPath === homePath;
|
||||
const isAbout = normalizedPath === aboutPath || normalizedPath.startsWith(`${aboutPath}/`);
|
||||
const isContact = normalizedPath === contactPath || normalizedPath.startsWith(`${contactPath}/`);
|
||||
const targetLocale: Locale = locale === "ar" ? "en" : "ar";
|
||||
|
||||
const switchPath = () => {
|
||||
const nextSegments = [...segments];
|
||||
|
||||
if (nextSegments.length === 0) {
|
||||
return `/${targetLocale}`;
|
||||
}
|
||||
|
||||
if (locales.includes(nextSegments[0] as Locale)) {
|
||||
nextSegments[0] = targetLocale;
|
||||
} else {
|
||||
nextSegments.unshift(targetLocale);
|
||||
}
|
||||
|
||||
return `/${nextSegments.join("/")}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<nav aria-label={common.navLabel} className="bottom-nav">
|
||||
<div className="container bottom-nav-inner">
|
||||
<div className="bottom-nav-links">
|
||||
<Link href={homePath} className={isHome ? "active" : undefined} aria-current={isHome ? "page" : undefined}>
|
||||
<Link href={homePath} className="active" aria-current="page">
|
||||
<span aria-hidden className="nav-icon">
|
||||
○
|
||||
</span>
|
||||
{common.nav.home}
|
||||
</Link>
|
||||
<Link href={aboutPath} className={isAbout ? "active" : undefined} aria-current={isAbout ? "page" : undefined}>
|
||||
<Link href={aboutPath}>
|
||||
<span aria-hidden className="nav-icon">
|
||||
□
|
||||
</span>
|
||||
{common.nav.about}
|
||||
</Link>
|
||||
<Link
|
||||
href={contactPath}
|
||||
className={isContact ? "active" : undefined}
|
||||
aria-current={isContact ? "page" : undefined}
|
||||
>
|
||||
<Link href={contactPath}>
|
||||
<span aria-hidden className="nav-icon">
|
||||
◇
|
||||
</span>
|
||||
{common.nav.contact}
|
||||
</Link>
|
||||
<Link href={switchPath()} className="lang-toggle" aria-label={common.languageSwitcherLabel}>
|
||||
<span aria-hidden className="nav-icon">
|
||||
⇄
|
||||
</span>
|
||||
{targetLocale.toUpperCase()}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
+21
-10
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import type { CommonContent, HomeContent } from "@/content/types";
|
||||
import type { CommonContent, HomeContent, HomeVariantContent } from "@/content/types";
|
||||
import type { Locale } from "@/lib/i18n";
|
||||
import { getEmailHref, isComingSoonMode } from "@/lib/site";
|
||||
|
||||
type HeroSectionProps = {
|
||||
locale: Locale;
|
||||
@@ -9,23 +10,33 @@ type HeroSectionProps = {
|
||||
};
|
||||
|
||||
export default function HeroSection({ locale, home, common }: HeroSectionProps) {
|
||||
const activeHome: HomeVariantContent = isComingSoonMode() ? home.comingSoon : home.full;
|
||||
const emailHref = getEmailHref();
|
||||
|
||||
return (
|
||||
<section className="hero panel">
|
||||
<p className="eyebrow">{home.kicker}</p>
|
||||
<h1>{home.title}</h1>
|
||||
<p className="lead">{home.description}</p>
|
||||
<p className="availability-badge">{activeHome.badge}</p>
|
||||
<p className="eyebrow">{activeHome.kicker}</p>
|
||||
<h1>{activeHome.title}</h1>
|
||||
<p className="lead">{activeHome.description}</p>
|
||||
|
||||
<div className="cta-row">
|
||||
<Link href={`/${locale}/contact`} className="cta-btn">
|
||||
{home.primaryCta}
|
||||
</Link>
|
||||
<Link href={`/${locale}/about`} className="ghost-btn">
|
||||
{common.nav.about}
|
||||
{emailHref ? (
|
||||
<a href={emailHref} className="cta-btn">
|
||||
{activeHome.primaryCta}
|
||||
</a>
|
||||
) : (
|
||||
<span className="cta-btn is-disabled" aria-disabled="true">
|
||||
{activeHome.primaryCta}
|
||||
</span>
|
||||
)}
|
||||
<Link href={isComingSoonMode() ? `/${locale}` : `/${locale}/about`} className="ghost-btn">
|
||||
{activeHome.secondaryCta}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="stat-grid">
|
||||
{home.highlights.map((item) => (
|
||||
{activeHome.highlights.map((item) => (
|
||||
<article key={item.label} className="stat-card">
|
||||
<span className="stat-value">{item.value}</span>
|
||||
<span className="stat-label">{item.label}</span>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function LanguageSwitcher({ locale, label }: LanguageSwitcherProp
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="language-switcher" aria-label={label}>
|
||||
<div className="language-switcher" role="group" aria-label={label}>
|
||||
{locales.map((item) => {
|
||||
const active = item === locale;
|
||||
|
||||
|
||||
@@ -12,7 +12,10 @@ export default function SiteFooter({ locale, common }: SiteFooterProps) {
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
<div className="container footer-content">
|
||||
<p>{common.footerRights.replace("{year}", String(year))}</p>
|
||||
<div className="footer-copy">
|
||||
<p>{common.footerRights.replace("{year}", String(year))}</p>
|
||||
<p>{common.footerBuiltWith}</p>
|
||||
</div>
|
||||
<p className="locale-badge">{locale.toUpperCase()}</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import Link from "next/link";
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher";
|
||||
import ThemeToggle from "@/components/ThemeToggle";
|
||||
import type { Locale } from "@/lib/i18n";
|
||||
import type { CommonContent } from "@/content/types";
|
||||
import { isComingSoonMode } from "@/lib/site";
|
||||
|
||||
type SiteHeaderProps = {
|
||||
locale: Locale;
|
||||
@@ -9,13 +11,34 @@ type SiteHeaderProps = {
|
||||
};
|
||||
|
||||
export default function SiteHeader({ locale, common }: SiteHeaderProps) {
|
||||
const isComingSoon = isComingSoonMode();
|
||||
|
||||
return (
|
||||
<header className="site-header">
|
||||
<div className="container bar">
|
||||
<Link href={`/${locale}`} className="brand">
|
||||
{common.siteTitle}
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
<div className="brand-block">
|
||||
<Link href={`/${locale}`} className="brand">
|
||||
{common.siteTitle}
|
||||
</Link>
|
||||
<span className="brand-tagline">{common.siteTagline}</span>
|
||||
</div>
|
||||
|
||||
{!isComingSoon ? (
|
||||
<nav className="header-nav" aria-label={common.navLabel}>
|
||||
<Link href={`/${locale}`}>{common.nav.home}</Link>
|
||||
<Link href={`/${locale}/about`}>{common.nav.about}</Link>
|
||||
<Link href={`/${locale}/contact`}>{common.nav.contact}</Link>
|
||||
</nav>
|
||||
) : null}
|
||||
|
||||
<div className="header-actions">
|
||||
<LanguageSwitcher locale={locale} label={common.languageSwitcherLabel} />
|
||||
<ThemeToggle
|
||||
label={common.themeToggleLabel}
|
||||
lightLabel={common.themeLight}
|
||||
darkLabel={common.themeDark}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -6,11 +6,17 @@ type Theme = "dark" | "light";
|
||||
|
||||
const STORAGE_KEY = "theme";
|
||||
|
||||
type ThemeToggleProps = {
|
||||
label: string;
|
||||
lightLabel: string;
|
||||
darkLabel: string;
|
||||
};
|
||||
|
||||
function isTheme(value: string | null): value is Theme {
|
||||
return value === "dark" || value === "light";
|
||||
}
|
||||
|
||||
export default function ThemeToggle() {
|
||||
export default function ThemeToggle({ label, lightLabel, darkLabel }: ThemeToggleProps) {
|
||||
const [theme, setTheme] = useState<Theme>("dark");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,9 +40,9 @@ export default function ThemeToggle() {
|
||||
type="button"
|
||||
onClick={handleToggle}
|
||||
className="theme-toggle"
|
||||
aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} mode`}
|
||||
aria-label={`${label}: ${theme === "dark" ? lightLabel : darkLabel}`}
|
||||
>
|
||||
<span className="theme-toggle-label">{theme === "dark" ? "Light" : "Dark"}</span>
|
||||
<span className="theme-toggle-label">{theme === "dark" ? lightLabel : darkLabel}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user