| 1 | import Link from "next/link"; |
| 2 | import Image from "next/image"; |
| 3 | import type { Locale } from "@/lib/i18n/config"; |
| 4 | import { getChrome } from "@/lib/i18n/dictionaries"; |
| 5 | import { navLinks, secondaryNavLinks, REPO_URL, APP_LOGIN_URL, APP_SIGNUP_URL } from "@/lib/i18n/links"; |
| 6 | import { fetchRepoStats, formatStars } from "@/lib/github"; |
| 7 | import { getEnv } from "@/lib/kv"; |
| 8 | import { LocaleSwitcher } from "./locale-switcher"; |
| 9 | import { MobileMenu } from "./mobile-menu"; |
| 10 | import { NavLinks } from "./nav-links"; |
| 11 | import { ThemeToggle } from "./theme-toggle"; |
| 12 | |
| 13 | /** Masthead + primary nav — the Tideline topbar on the web. */ |
| 14 | export async function Nav({ locale = "en" }: { locale?: Locale }) { |
| 15 | const chrome = getChrome(locale); |
| 16 | const links = navLinks(locale, chrome); |
| 17 | const moreLinks = secondaryNavLinks(locale, chrome); |
| 18 | const homeHref = `/${locale}`; |
| 19 | |
| 20 | // Live star count — cached by fetchRepoStats. Falls back to a plain GitHub |
| 21 | // label when the API is unreachable at build time. |
| 22 | let stars = 0; |
| 23 | try { |
| 24 | const env = await getEnv(); |
| 25 | stars = (await fetchRepoStats(env.GITHUB_TOKEN)).stars; |
| 26 | } catch { |
| 27 | /* keep fallback label */ |
| 28 | } |
| 29 | |
| 30 | return ( |
| 31 | <header className="site-nav paper-nav"> |
| 32 | <div className="site-nav-inner paper-nav-inner"> |
| 33 | <Link href={homeHref} className="site-wordmark paper-wordmark" aria-label={chrome.navHomeAria}> |
| 34 | <div className="paper-wordmark-text"> |
| 35 | <Image src="/brand/mark-gradient.svg" width={22} height={22} alt="" className="paper-wordmark-mark" unoptimized /> |
| 36 | <img |
| 37 | className="paper-wordmark-logo" |
| 38 | src="/brand/wordmark.svg" |
| 39 | alt="" |
| 40 | width={142} |
| 41 | height={20} |
| 42 | /> |
| 43 | </div> |
| 44 | </Link> |
| 45 | |
| 46 | <NavLinks links={links} primaryAria={chrome.navPrimaryAria} /> |
| 47 | |
| 48 | <div className="site-nav-actions"> |
| 49 | <ThemeToggle |
| 50 | autoLabel={chrome.themeAuto} |
| 51 | lightLabel={chrome.themeLight} |
| 52 | darkLabel={chrome.themeDark} |
| 53 | ariaTemplate={chrome.themeAria} |
| 54 | titleLabel={chrome.themeTitle} |
| 55 | /> |
| 56 | <LocaleSwitcher current={locale} /> |
| 57 | <Link |
| 58 | href={REPO_URL} |
| 59 | className="site-github-link paper-star-badge" |
| 60 | aria-label={chrome.starsAria} |
| 61 | > |
| 62 | <svg viewBox="0 0 16 16" aria-hidden fill="currentColor" className="brand-mark"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg> |
| 63 | ★ {stars > 0 ? formatStars(stars) : chrome.githubFallback} |
| 64 | </Link> |
| 65 | <span className="paper-auth" role="group" aria-label={chrome.authGroupAria}> |
| 66 | <Link href={APP_LOGIN_URL} className="paper-auth-signin hidden lg:inline-flex" data-usage="login"> |
| 67 | {chrome.authSignIn} |
| 68 | </Link> |
| 69 | <Link href={APP_SIGNUP_URL} className="paper-auth-register hidden lg:inline-flex" data-usage="signup"> |
| 70 | {chrome.authRegister} |
| 71 | </Link> |
| 72 | </span> |
| 73 | <Link |
| 74 | href={`/${locale}/install`} |
| 75 | className="paper-install-cta hidden xl:inline-flex" |
| 76 | > |
| 77 | {chrome.installCta} |
| 78 | </Link> |
| 79 | <MobileMenu |
| 80 | installHref={`/${locale}/install`} |
| 81 | installLabel={chrome.installCta} |
| 82 | signInHref={APP_LOGIN_URL} |
| 83 | signInLabel={chrome.authSignIn} |
| 84 | registerHref={APP_SIGNUP_URL} |
| 85 | registerLabel={chrome.authRegister} |
| 86 | links={links} |
| 87 | moreLinks={moreLinks} |
| 88 | openLabel={chrome.menuOpen} |
| 89 | closeLabel={chrome.menuClose} |
| 90 | navAria={chrome.navPrimaryAria} |
| 91 | /> |
| 92 | </div> |
| 93 | </div> |
| 94 | </header> |
| 95 | ); |
| 96 | } |
| 97 |