diff --git a/dashboard/src/app/blog/page.tsx b/dashboard/src/app/blog/page.tsx index ff7b5a2..1c91384 100644 --- a/dashboard/src/app/blog/page.tsx +++ b/dashboard/src/app/blog/page.tsx @@ -1,6 +1,7 @@ export const dynamic = 'force-dynamic' import Link from 'next/link' +import SearchComponent from '@components/search' import { getHomepagePosts } from '@cms/content' import type { Metadata } from 'next' import { notFound } from 'next/navigation' @@ -31,12 +32,13 @@ function formatDate(dateStr: string | undefined, language: 'zh' | 'en'): string } type PageProps = { - searchParams: { page?: string } + searchParams?: { page?: string } | Promise<{ page?: string }> } export default async function BlogPage({ searchParams }: PageProps) { const posts = await getHomepagePosts() - const { page } = searchParams ?? {} + const resolvedSearchParams = await Promise.resolve(searchParams ?? {}) + const { page } = resolvedSearchParams ?? {} const postsPerPage = 10 const currentPage = parseInt(page || '1', 10) const totalPages = Math.max(1, Math.ceil(posts.length / postsPerPage)) @@ -57,6 +59,7 @@ export default async function BlogPage({ searchParams }: PageProps) {

Latest updates, releases, and insights from the Cloud-Neutral community.

+ {posts.length === 0 ? ( diff --git a/dashboard/src/components/Navbar.tsx b/dashboard/src/components/Navbar.tsx index eed92f7..0741327 100644 --- a/dashboard/src/components/Navbar.tsx +++ b/dashboard/src/components/Navbar.tsx @@ -2,15 +2,15 @@ import Link from 'next/link' import Image from 'next/image' import { usePathname } from 'next/navigation' -import { FormEvent, useEffect, useMemo, useRef, useState } from 'react' -import { Mail, Search } from 'lucide-react' +import { useEffect, useMemo, useRef, useState } from 'react' +import { Mail } from 'lucide-react' import { useLanguage } from '../i18n/LanguageProvider' import { translations } from '../i18n/translations' import LanguageToggle from './LanguageToggle' import ReleaseChannelSelector, { ReleaseChannel } from './ReleaseChannelSelector' import { getFeatureToggleInfo } from '@lib/featureToggles' import { useUser } from '@lib/userStore' -import { AskAIDialog } from './AskAIDialog' +import SearchComponent from './search' const CHANNEL_ORDER: ReleaseChannel[] = ['stable', 'beta', 'develop'] const DEFAULT_CHANNELS: ReleaseChannel[] = ['stable'] @@ -231,13 +231,8 @@ export default function Navbar() { download: isChinese ? '下载' : 'Download', openSource: isChinese ? '开源项目' : 'Open source', moreServices: isChinese ? '更多服务' : 'More services', - searchPlaceholder: isChinese ? '请输入关键字搜索内容' : 'Ask anything about your docs', } - const [searchValue, setSearchValue] = useState('') - const [askDialogOpen, setAskDialogOpen] = useState(false) - const [pendingQuestion, setPendingQuestion] = useState<{ key: number; text: string } | null>(null) - useEffect(() => { if (typeof window === 'undefined') { return @@ -281,15 +276,6 @@ export default function Navbar() { { key: 'xscopehub', label: 'XScopeHub', href: '/xscopehub' }, ] - const handleSearchSubmit = (event: FormEvent) => { - event.preventDefault() - const trimmed = searchValue.trim() - if (!trimmed) return - setPendingQuestion({ key: Date.now(), text: trimmed }) - setAskDialogOpen(true) - setSearchValue('') - } - if (isHiddenRoute) { return null } @@ -397,22 +383,7 @@ export default function Navbar() {
-
- setSearchValue(event.target.value)} - placeholder={labels.searchPlaceholder} - className="w-full rounded-full border border-brand-border bg-brand-surface/60 py-2 pl-4 pr-10 text-sm text-brand-heading transition focus:border-brand focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand/20" - /> - -
+ {user ? (
- +
{mainLinks.map((link) => ( setMenuOpen(false)}> @@ -632,15 +592,6 @@ export default function Navbar() {
- setAskDialogOpen(false)} - onEnd={() => { - setAskDialogOpen(false) - setPendingQuestion(null) - }} - initialQuestion={pendingQuestion ?? undefined} - /> ) } diff --git a/dashboard/src/components/search/index.tsx b/dashboard/src/components/search/index.tsx new file mode 100644 index 0000000..d9e2cfe --- /dev/null +++ b/dashboard/src/components/search/index.tsx @@ -0,0 +1,87 @@ +'use client' + +import { FormEvent, useState } from 'react' +import clsx from 'clsx' +import { Search } from 'lucide-react' + +import { AskAIDialog } from '../AskAIDialog' +import { useLanguage } from '../i18n/LanguageProvider' + +type SearchComponentProps = { + className?: string + inputClassName?: string + buttonClassName?: string + variant?: 'default' | 'hero' +} + +type PendingQuestion = { key: number; text: string } + +const baseWrapperClass = 'relative w-full max-w-xs' +const heroWrapperClass = 'relative w-full max-w-2xl' + +const baseInputClass = + 'w-full rounded-full border border-brand-border bg-brand-surface/60 py-2 pl-4 pr-10 text-sm text-brand-heading transition focus:border-brand focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand/20' +const heroInputClass = + 'w-full rounded-full border border-brand-border bg-white/80 py-3 pl-5 pr-12 text-base text-brand-heading shadow-sm transition focus:border-brand focus:bg-white focus:outline-none focus:ring-2 focus:ring-brand/20' + +const baseButtonClass = + 'absolute right-2 top-1/2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full bg-brand text-white transition hover:bg-brand-light' +const heroButtonClass = + 'absolute right-3 top-1/2 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-brand text-white shadow-sm transition hover:bg-brand-light' + +export default function SearchComponent({ + className, + inputClassName, + buttonClassName, + variant = 'default' +}: SearchComponentProps) { + const { language } = useLanguage() + const [searchValue, setSearchValue] = useState('') + const [askDialogOpen, setAskDialogOpen] = useState(false) + const [pendingQuestion, setPendingQuestion] = useState(null) + + const placeholder = + language === 'zh' ? '请输入关键字搜索内容' : 'Ask anything about your docs' + + const handleSearchSubmit = (event: FormEvent) => { + event.preventDefault() + const trimmed = searchValue.trim() + if (!trimmed) return + setPendingQuestion({ key: Date.now(), text: trimmed }) + setAskDialogOpen(true) + setSearchValue('') + } + + const wrapperClasses = clsx(variant === 'hero' ? heroWrapperClass : baseWrapperClass, className) + const inputClasses = clsx(variant === 'hero' ? heroInputClass : baseInputClass, inputClassName) + const submitButtonClasses = clsx( + variant === 'hero' ? heroButtonClass : baseButtonClass, + buttonClassName + ) + + return ( + <> +
+ setSearchValue(event.target.value)} + placeholder={placeholder} + className={inputClasses} + /> + +
+ setAskDialogOpen(false)} + onEnd={() => { + setAskDialogOpen(false) + setPendingQuestion(null) + }} + initialQuestion={pendingQuestion ?? undefined} + /> + + ) +}