6.4 검색 바 및 결과 페이지 라우팅 o
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
||||||
|
import { SearchBar } from "@/app/components/SearchBar";
|
||||||
|
|
||||||
export function AppHeader() {
|
export function AppHeader() {
|
||||||
return (
|
return (
|
||||||
<header style={{ display: "flex", justifyContent: "space-between", padding: 12 }}>
|
<header style={{ display: "flex", justifyContent: "space-between", padding: 12 }}>
|
||||||
<div>msg App</div>
|
<div>msg App</div>
|
||||||
<nav style={{ display: "flex", gap: 12 }}>
|
<nav style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
||||||
<a href="/">홈</a>
|
<a href="/">홈</a>
|
||||||
<a href="/boards">게시판</a>
|
<a href="/boards">게시판</a>
|
||||||
|
<SearchBar />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ type Resp = {
|
|||||||
|
|
||||||
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||||
|
|
||||||
export function PostList({ boardId, sort = "recent" }: { boardId?: string; sort?: "recent" | "popular" }) {
|
export function PostList({ boardId, sort = "recent", q }: { boardId?: string; sort?: "recent" | "popular"; q?: string }) {
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
const getKey = (index: number, prev: Resp | null) => {
|
const getKey = (index: number, prev: Resp | null) => {
|
||||||
if (prev && prev.items.length === 0) return null;
|
if (prev && prev.items.length === 0) return null;
|
||||||
const page = index + 1;
|
const page = index + 1;
|
||||||
const sp = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort });
|
const sp = new URLSearchParams({ page: String(page), pageSize: String(pageSize), sort });
|
||||||
if (boardId) sp.set("boardId", boardId);
|
if (boardId) sp.set("boardId", boardId);
|
||||||
|
if (q) sp.set("q", q);
|
||||||
return `/api/posts?${sp.toString()}`;
|
return `/api/posts?${sp.toString()}`;
|
||||||
};
|
};
|
||||||
const { data, size, setSize, isLoading } = useSWRInfinite<Resp>(getKey, fetcher);
|
const { data, size, setSize, isLoading } = useSWRInfinite<Resp>(getKey, fetcher);
|
||||||
@@ -37,8 +38,18 @@ export function PostList({ boardId, sort = "recent" }: { boardId?: string; sort?
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
|
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
|
||||||
<span>정렬:</span>
|
<span>정렬:</span>
|
||||||
<a href={`/?sort=recent`} style={{ textDecoration: sort === "recent" ? "underline" : "none" }}>최신</a>
|
<a
|
||||||
<a href={`/?sort=popular`} style={{ textDecoration: sort === "popular" ? "underline" : "none" }}>인기</a>
|
href={`${q ? "/search" : "/"}?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); if (boardId) p.set("boardId", boardId); p.set("sort", "recent"); return p.toString(); })()}`}
|
||||||
|
style={{ textDecoration: sort === "recent" ? "underline" : "none" }}
|
||||||
|
>
|
||||||
|
최신
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={`${q ? "/search" : "/"}?${(() => { const p = new URLSearchParams(); if (q) p.set("q", q); if (boardId) p.set("boardId", boardId); p.set("sort", "popular"); return p.toString(); })()}`}
|
||||||
|
style={{ textDecoration: sort === "popular" ? "underline" : "none" }}
|
||||||
|
>
|
||||||
|
인기
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<ul style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
<ul style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
{items.map((p) => (
|
{items.map((p) => (
|
||||||
|
|||||||
28
src/app/components/SearchBar.tsx
Normal file
28
src/app/components/SearchBar.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export function SearchBar() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [term, setTerm] = useState("");
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const q = term.trim();
|
||||||
|
router.push(q ? `/search?q=${encodeURIComponent(q)}` : "/search");
|
||||||
|
}}
|
||||||
|
style={{ display: "flex", gap: 8, alignItems: "center" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
value={term}
|
||||||
|
onChange={(e) => setTerm(e.target.value)}
|
||||||
|
placeholder="검색어 입력"
|
||||||
|
style={{ padding: "6px 8px", border: "1px solid #ddd", borderRadius: 6 }}
|
||||||
|
/>
|
||||||
|
<button type="submit">검색</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
14
src/app/search/page.tsx
Normal file
14
src/app/search/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { PostList } from "@/app/components/PostList";
|
||||||
|
|
||||||
|
export default function SearchPage({ searchParams }: { searchParams?: { q?: string; sort?: "recent" | "popular" } }) {
|
||||||
|
const q = searchParams?.q ?? "";
|
||||||
|
const sort = searchParams?.sort ?? "recent";
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 style={{ marginBottom: 12 }}>검색: {q || "전체"}</h2>
|
||||||
|
<PostList q={q} sort={sort} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
6.1 Hero/공지 배너 컴포넌트(자동/수동 슬라이드) o
|
6.1 Hero/공지 배너 컴포넌트(자동/수동 슬라이드) o
|
||||||
6.2 최근/인기 글 리스트 및 무한스크롤 연동 o
|
6.2 최근/인기 글 리스트 및 무한스크롤 연동 o
|
||||||
6.3 권한 기반 빠른 액션 노출 제어 o
|
6.3 권한 기반 빠른 액션 노출 제어 o
|
||||||
6.4 검색 바 및 결과 페이지 라우팅
|
6.4 검색 바 및 결과 페이지 라우팅 o
|
||||||
6.5 개인화 위젯(최근 본 글/알림 요약)
|
6.5 개인화 위젯(최근 본 글/알림 요약)
|
||||||
|
|
||||||
[게시판/컨텐츠]
|
[게시판/컨텐츠]
|
||||||
|
|||||||
Reference in New Issue
Block a user