11"use client" ;
22
3- import { useState } from "react" ;
3+ import { useState , useCallback , useEffect } from "react" ;
4+ import { useSearchParams , useRouter } from "next/navigation" ;
45import Category from "@/components/page/community/Category" ;
56import PostList from "@/components/page/community/PostList" ;
67import PopularPostsHighlight from "@/components/page/community/PopularPostsHighlight" ;
7- import PageTitle from "@/components/commons/PageTitle" ;
8- import { Button } from "@/components/ui/button" ;
98import { Plus , Search , X } from "lucide-react" ;
109import PostCreateModal from "@/components/page/community/PostCreateModal" ;
1110
1211type SortType = "latest" | "popular" | "mostLiked" ;
1312
1413function CommunityPage ( ) {
14+ const searchParams = useSearchParams ( ) ;
15+ const router = useRouter ( ) ;
16+ const keyword = searchParams . get ( "q" ) || "" ;
17+
1518 const [ selectedBoardId , setSelectedBoardId ] = useState < number | undefined > (
1619 undefined ,
1720 ) ;
1821 const [ sortOption , setSortOption ] = useState < SortType > ( "latest" ) ;
1922 const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
20- const [ searchInput , setSearchInput ] = useState ( "" ) ;
21- const [ keyword , setKeyword ] = useState ( "" ) ;
22-
23- const handleSearch = ( e : React . FormEvent ) => {
24- e . preventDefault ( ) ;
25- setKeyword ( searchInput . trim ( ) ) ;
26- } ;
23+ const [ mobileSearchInput , setMobileSearchInput ] = useState ( keyword ) ;
24+
25+ // URL의 검색어가 변경되면 모바일 input 동기화
26+ useEffect ( ( ) => {
27+ setMobileSearchInput ( keyword ) ;
28+ } , [ keyword ] ) ;
29+
30+ const handleMobileSearch = useCallback (
31+ ( e : React . FormEvent ) => {
32+ e . preventDefault ( ) ;
33+ const trimmedInput = mobileSearchInput . trim ( ) ;
34+ if ( trimmedInput ) {
35+ router . push ( `/community?q=${ encodeURIComponent ( trimmedInput ) } ` ) ;
36+ } else {
37+ router . push ( "/community" ) ;
38+ }
39+ } ,
40+ [ mobileSearchInput , router ]
41+ ) ;
2742
28- const handleClearSearch = ( ) => {
29- setSearchInput ( "" ) ;
30- setKeyword ( " ") ;
31- } ;
43+ const handleClearMobileSearch = useCallback ( ( ) => {
44+ setMobileSearchInput ( "" ) ;
45+ router . push ( "/community ") ;
46+ } , [ router ] ) ;
3247
3348 return (
34- < main className = "flex flex-col gap-6 pb-8" >
35- < PageTitle title = "커뮤니티" />
49+ < main className = "flex flex-col gap-6 pb-24" >
50+ { /* Mobile Search Bar - Only visible on mobile */ }
51+ < form onSubmit = { handleMobileSearch } className = "md:hidden" >
52+ < div className = "relative" >
53+ < Search className = "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500" />
54+ < input
55+ type = "text"
56+ value = { mobileSearchInput }
57+ onChange = { ( e ) => setMobileSearchInput ( e . target . value ) }
58+ placeholder = "게시글 검색..."
59+ className = "w-full pl-10 pr-10 py-2.5 text-sm border border-gray-200 dark:border-navy-600 rounded-xl bg-white dark:bg-navy-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[var(--color-ds-primary)]/20 focus:border-[var(--color-ds-primary)] transition-colors"
60+ />
61+ { mobileSearchInput && (
62+ < button
63+ type = "button"
64+ onClick = { handleClearMobileSearch }
65+ className = "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
66+ >
67+ < X className = "w-4 h-4" />
68+ </ button >
69+ ) }
70+ </ div >
71+ </ form >
3672
3773 { /* Category Filter */ }
3874 < Category
@@ -43,68 +79,29 @@ function CommunityPage() {
4379 { /* Popular Posts Highlight */ }
4480 < PopularPostsHighlight boardId = { selectedBoardId } />
4581
46- { /* Search Bar */ }
47- < form onSubmit = { handleSearch } className = "relative" >
48- < div className = "flex gap-2" >
49- < div className = "relative flex-1" >
50- < Search className = "absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
51- < input
52- type = "text"
53- value = { searchInput }
54- onChange = { ( e ) => setSearchInput ( e . target . value ) }
55- placeholder = "게시글 검색..."
56- className = "w-full pl-10 pr-10 py-2.5 border border-warm-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-forest-500/20 focus:border-forest-500 bg-white"
57- />
58- { searchInput && (
59- < button
60- type = "button"
61- onClick = { handleClearSearch }
62- className = "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
63- >
64- < X className = "w-4 h-4" />
65- </ button >
66- ) }
67- </ div >
68- < Button type = "submit" className = "bg-forest-500 hover:bg-forest-600 rounded-xl px-6" >
69- 검색
70- </ Button >
71- </ div >
72- </ form >
73-
7482 { /* Search Result Info */ }
7583 { keyword && (
76- < div className = "flex items-center gap-2 text-sm text-gray-600" >
84+ < div className = "flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 " >
7785 < span > "{ keyword } " 검색 결과</ span >
78- < button
79- onClick = { handleClearSearch }
80- className = "text-forest-500 hover:text-forest-600 underline"
81- >
82- 검색 초기화
83- </ button >
8486 </ div >
8587 ) }
8688
87- { /* Actions Bar */ }
88- < div className = "flex justify-between items-center" >
89+ { /* Sort Options */ }
90+ < div className = "flex items-center" >
8991 < select
9092 value = { sortOption }
9193 onChange = { ( e ) => setSortOption ( e . target . value as SortType ) }
92- className = "border border-warm-300 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-forest-500 /20 focus:border-forest-500 bg-white text-warm -700"
94+ className = "border border-gray-200 dark:border-navy-600 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-ds-primary)] /20 focus:border-[var(--color-ds-primary)] bg-white dark:bg-navy-700 text-gray -700 dark:text-gray-200 "
9395 >
9496 < option value = "latest" > 최신순</ option >
9597 < option value = "popular" > 인기순</ option >
9698 < option value = "mostLiked" > 좋아요순</ option >
9799 </ select >
98-
99- < Button className = "gap-2 bg-forest-500 hover:bg-forest-600 rounded-xl" onClick = { ( ) => setIsModalOpen ( true ) } >
100- < Plus className = "w-4 h-4" />
101- 글쓰기
102- </ Button >
103100 </ div >
104101
105102 { /* Info: 인기순/좋아요순은 게시판 선택 필요 */ }
106103 { ( sortOption === "popular" || sortOption === "mostLiked" ) && ! selectedBoardId && (
107- < div className = "text-sm text-amber-600 bg-amber-50 px-4 py-2 rounded-lg" >
104+ < div className = "text-sm text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 px-4 py-2 rounded-lg" >
108105 인기순/좋아요순은 게시판을 선택해야 적용됩니다.
109106 </ div >
110107 ) }
@@ -116,6 +113,15 @@ function CommunityPage() {
116113 sortType = { sortOption }
117114 />
118115
116+ { /* FAB - Write Post Button */ }
117+ < button
118+ onClick = { ( ) => setIsModalOpen ( true ) }
119+ className = "fixed bottom-20 right-4 md:bottom-6 md:right-6 z-40 w-14 h-14 bg-[var(--color-ds-primary)] hover:bg-[var(--color-ds-primary-hover)] text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center hover:scale-105 active:scale-95"
120+ aria-label = "글쓰기"
121+ >
122+ < Plus className = "w-6 h-6" />
123+ </ button >
124+
119125 { /* Post Create Modal */ }
120126 < PostCreateModal
121127 isOpen = { isModalOpen }
0 commit comments