navigation.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. "use client"
  2. import { useState, useEffect } from "react"
  3. import { useRouter, usePathname } from "next/navigation"
  4. import { Button } from "@/components/ui/button"
  5. import { MoonIcon, SunIcon, MenuIcon, XIcon, GlobeIcon, UserIcon, LogOutIcon, SettingsIcon } from "lucide-react"
  6. import { useTheme } from "next-themes"
  7. import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from "@/components/ui/dropdown-menu"
  8. import { useTranslations } from "next-intl"
  9. import { useToast } from "@/hooks/use-toast"
  10. import Image from "next/image"
  11. import { useSession, signOut } from 'next-auth/react'
  12. import { useAuth } from "./providers"
  13. interface NavigationProps {
  14. locale: string
  15. }
  16. interface User {
  17. id: string
  18. email: string
  19. username: string | null
  20. isEmailVerified: boolean
  21. credits: number
  22. }
  23. export default function Navigation({ locale }: NavigationProps) {
  24. const { theme, setTheme } = useTheme()
  25. const [isMenuOpen, setIsMenuOpen] = useState(false)
  26. const { user, isLoading, refreshUser } = useAuth()
  27. const router = useRouter()
  28. const pathname = usePathname()
  29. const t = useTranslations("navigation")
  30. const tAuth = useTranslations("dashboard")
  31. const tErrors = useTranslations("auth.errors")
  32. const { toast } = useToast()
  33. const toggleMenu = () => {
  34. setIsMenuOpen(!isMenuOpen)
  35. }
  36. const handleLogout = async () => {
  37. try {
  38. await signOut({ redirect: false })
  39. await refreshUser() // 刷新认证状态
  40. router.push(`/${locale}`)
  41. toast({
  42. title: t("logoutSuccess"),
  43. description: t("logoutSuccessDesc"),
  44. })
  45. } catch (error) {
  46. toast({
  47. title: t("logoutFailed"),
  48. description: t("logoutFailedDesc"),
  49. variant: "destructive",
  50. })
  51. }
  52. }
  53. const scrollToSection = (sectionId: string) => {
  54. // 检查当前路径是否为首页
  55. const isHomePage = pathname === `/${locale}` || pathname === `/${locale}/`
  56. if (isHomePage) {
  57. // 如果在首页,直接滚动到对应部分
  58. const element = document.getElementById(sectionId)
  59. if (element) {
  60. element.scrollIntoView({ behavior: "smooth" })
  61. }
  62. } else {
  63. // 如果不在首页,导航到首页的对应锚点
  64. router.push(`/${locale}#${sectionId}`)
  65. }
  66. setIsMenuOpen(false)
  67. }
  68. const scrollToTop = () => {
  69. const isHomePage = pathname === `/${locale}` || pathname === `/${locale}/`
  70. if (isHomePage) {
  71. window.scrollTo({ top: 0, behavior: "smooth" })
  72. } else {
  73. router.push(`/${locale}`)
  74. }
  75. setIsMenuOpen(false)
  76. }
  77. const switchLanguage = (newLocale: string) => {
  78. const newPath = pathname.replace(`/${locale}`, `/${newLocale}`)
  79. router.push(newPath)
  80. }
  81. return (
  82. <nav className="fixed top-0 w-full bg-background/80 backdrop-blur-md border-b z-50">
  83. <div className="container mx-auto px-4 sm:px-6 lg:px-8">
  84. <div className="flex justify-between items-center h-16">
  85. {/* Logo */}
  86. <div className="flex-shrink-0">
  87. <div className="flex items-center space-x-2 cursor-pointer" onClick={scrollToTop}>
  88. <Image
  89. src="/images/logo.png"
  90. alt="Aiartools Logo"
  91. width={32}
  92. height={32}
  93. className="w-8 h-8"
  94. />
  95. <h1 className="text-2xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">
  96. Aiartools
  97. </h1>
  98. </div>
  99. </div>
  100. {/* Desktop Navigation */}
  101. <div className="hidden md:block">
  102. <div className="ml-10 flex items-baseline space-x-4">
  103. <button
  104. onClick={scrollToTop}
  105. className="text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium transition-colors"
  106. >
  107. {t("home")}
  108. </button>
  109. <button
  110. onClick={() => scrollToSection("features")}
  111. className="text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium transition-colors"
  112. >
  113. {t("features")}
  114. </button>
  115. <button
  116. onClick={() => scrollToSection("demo")}
  117. className="text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium transition-colors"
  118. >
  119. {t("demo")}
  120. </button>
  121. <button
  122. onClick={() => scrollToSection("pricing")}
  123. className="text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium transition-colors"
  124. >
  125. {t("pricing")}
  126. </button>
  127. <button
  128. onClick={() => scrollToSection("blog")}
  129. className="text-foreground hover:text-primary px-3 py-2 rounded-md text-sm font-medium transition-colors"
  130. >
  131. {t("blog")}
  132. </button>
  133. </div>
  134. </div>
  135. {/* Right Side Actions */}
  136. <div className="flex items-center space-x-2">
  137. {/* Language Switcher */}
  138. <DropdownMenu>
  139. <DropdownMenuTrigger asChild>
  140. <Button variant="ghost" size="icon">
  141. <GlobeIcon className="h-5 w-5" />
  142. </Button>
  143. </DropdownMenuTrigger>
  144. <DropdownMenuContent align="end">
  145. <DropdownMenuItem onClick={() => switchLanguage("en")}>English</DropdownMenuItem>
  146. <DropdownMenuItem onClick={() => switchLanguage("zh")}>中文</DropdownMenuItem>
  147. </DropdownMenuContent>
  148. </DropdownMenu>
  149. {/* Theme Toggle */}
  150. <Button variant="ghost" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
  151. <SunIcon className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
  152. <MoonIcon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
  153. </Button>
  154. {/* User Auth Section */}
  155. {!isLoading && (
  156. <>
  157. {user ? (
  158. // 已登录用户菜单
  159. <DropdownMenu>
  160. <DropdownMenuTrigger asChild>
  161. <Button variant="ghost" size="sm" className="flex items-center space-x-2">
  162. <UserIcon className="h-4 w-4" />
  163. <span className="hidden sm:inline-block">
  164. {user.username || user.email.split('@')[0]}
  165. </span>
  166. </Button>
  167. </DropdownMenuTrigger>
  168. <DropdownMenuContent align="end" className="w-56">
  169. <div className="px-2 py-2">
  170. <p className="text-sm font-medium">{user.username || tAuth('welcome')}</p>
  171. <p className="text-xs text-muted-foreground">{user.email}</p>
  172. {!user.isEmailVerified && (
  173. <p className="text-xs text-orange-600 mt-1">{tAuth('unverified')}</p>
  174. )}
  175. </div>
  176. <DropdownMenuSeparator />
  177. <DropdownMenuItem onClick={() => router.push(`/${locale}/dashboard`)}>
  178. <SettingsIcon className="mr-2 h-4 w-4" />
  179. {t('dashboard')}
  180. </DropdownMenuItem>
  181. <DropdownMenuSeparator />
  182. <DropdownMenuItem onClick={handleLogout}>
  183. <LogOutIcon className="mr-2 h-4 w-4" />
  184. {t('logout')}
  185. </DropdownMenuItem>
  186. </DropdownMenuContent>
  187. </DropdownMenu>
  188. ) : (
  189. // 未登录用户按钮
  190. <div className="hidden sm:flex items-center space-x-2">
  191. <Button
  192. variant="ghost"
  193. size="sm"
  194. onClick={() => router.push(`/${locale}/auth/login`)}
  195. >
  196. {t('login')}
  197. </Button>
  198. <Button
  199. size="sm"
  200. onClick={() => router.push(`/${locale}/auth/register`)}
  201. >
  202. {t('register')}
  203. </Button>
  204. </div>
  205. )}
  206. </>
  207. )}
  208. {/* Mobile Menu Button */}
  209. <div className="md:hidden">
  210. <Button variant="ghost" size="icon" onClick={() => setIsMenuOpen(!isMenuOpen)}>
  211. {isMenuOpen ? <XIcon className="h-6 w-6" /> : <MenuIcon className="h-6 w-6" />}
  212. </Button>
  213. </div>
  214. </div>
  215. </div>
  216. {/* Mobile Navigation */}
  217. {isMenuOpen && (
  218. <div className="md:hidden">
  219. <div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-background border-t">
  220. <button
  221. onClick={scrollToTop}
  222. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  223. >
  224. {t("home")}
  225. </button>
  226. <button
  227. onClick={() => scrollToSection("features")}
  228. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  229. >
  230. {t("features")}
  231. </button>
  232. <button
  233. onClick={() => scrollToSection("demo")}
  234. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  235. >
  236. {t("demo")}
  237. </button>
  238. <button
  239. onClick={() => scrollToSection("pricing")}
  240. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  241. >
  242. {t("pricing")}
  243. </button>
  244. <button
  245. onClick={() => scrollToSection("blog")}
  246. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  247. >
  248. {t("blog")}
  249. </button>
  250. {/* Mobile Auth Section */}
  251. {!isLoading && (
  252. <>
  253. <div className="border-t border-border my-2"></div>
  254. {user ? (
  255. <>
  256. <div className="px-3 py-2">
  257. <p className="text-sm font-medium text-foreground">{user.username || tAuth('welcome')}</p>
  258. <p className="text-xs text-muted-foreground">{user.email}</p>
  259. {!user.isEmailVerified && (
  260. <p className="text-xs text-orange-600 mt-1">{tAuth('unverified')}</p>
  261. )}
  262. </div>
  263. <button
  264. onClick={() => {
  265. router.push(`/${locale}/dashboard`)
  266. setIsMenuOpen(false)
  267. }}
  268. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  269. >
  270. {t('dashboard')}
  271. </button>
  272. <button
  273. onClick={() => {
  274. handleLogout()
  275. setIsMenuOpen(false)
  276. }}
  277. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  278. >
  279. {t('logout')}
  280. </button>
  281. </>
  282. ) : (
  283. <>
  284. <button
  285. onClick={() => {
  286. router.push(`/${locale}/auth/login`)
  287. setIsMenuOpen(false)
  288. }}
  289. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  290. >
  291. {t('login')}
  292. </button>
  293. <button
  294. onClick={() => {
  295. router.push(`/${locale}/auth/register`)
  296. setIsMenuOpen(false)
  297. }}
  298. className="text-foreground hover:text-primary block px-3 py-2 rounded-md text-base font-medium w-full text-left transition-colors"
  299. >
  300. {t('register')}
  301. </button>
  302. </>
  303. )}
  304. </>
  305. )}
  306. </div>
  307. </div>
  308. )}
  309. </div>
  310. </nav>
  311. )
  312. }