diff --git a/src/App.tsx b/src/App.tsx index e2d0a5b..8bc3121 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { auth } from './firebase.ts'; import ProtectedRoute from './components/protected-route.tsx'; import Home from './routes/home.tsx'; import Profile from './routes/profile.tsx'; +import SearchResult from './routes/search-result.tsx'; import Auth from './routes/auth.tsx'; import Layout from './components/layout.tsx'; import LoadingScreen from './components/loading-screen.tsx'; @@ -26,6 +27,10 @@ const router = createBrowserRouter([ path: '/profile', element: , }, + { + path: `/search/:searchKeyword`, + element: , + }, ], }, { diff --git a/src/assets/images/i-search.svg b/src/assets/images/i-search.svg new file mode 100644 index 0000000..d7cafa1 --- /dev/null +++ b/src/assets/images/i-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/atoms.tsx b/src/atoms.tsx new file mode 100644 index 0000000..d34eab9 --- /dev/null +++ b/src/atoms.tsx @@ -0,0 +1,8 @@ +import { atom } from 'recoil'; + +export const searchKeywordAtom = atom({ + key: 'searchKeyword', + default: '', +}); + +export const tmp = ''; diff --git a/src/components/search-timeline.tsx b/src/components/search-timeline.tsx new file mode 100644 index 0000000..d88f4d4 --- /dev/null +++ b/src/components/search-timeline.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { + collection, + getDocs, + limit, + orderBy, + query, + where, +} from 'firebase/firestore'; +import { useRecoilValue } from 'recoil'; +import { db } from '../firebase.ts'; +import { searchKeywordAtom } from '../atoms.tsx'; +import Tweet from './tweet.tsx'; +import * as S from '../styles/timeline.ts'; +import ITweet from '../interfaces/ITweet.ts'; + +export default function SearchTimeline() { + const [tweets, setTweets] = useState([]); + const searchKeyword = useRecoilValue(searchKeywordAtom); + const fetchTweets = async () => { + const tweetsQuery = query( + collection(db, 'tweets'), + orderBy('tweet'), + where('tweet', '>=', searchKeyword), + where('tweet', '<=', `${searchKeyword}\uf8ff`), + limit(25), + ); + const snapshot = await getDocs(tweetsQuery); + const tweetList = snapshot.docs.map((doc) => { + const { userId, userName, tweet, createdAt, photo } = doc.data(); + return { + userId, + userName, + tweet, + createdAt, + photo, + id: doc.id, + }; + }); + tweetList.sort((a, b) => b.createdAt - a.createdAt); + setTweets(tweetList); + }; + useEffect(() => { + fetchTweets(); + }, [searchKeyword]); + return tweets.length !== 0 ? ( + + {tweets.map((tweet) => ( + + ))} + + ) : ( + 검색 결과가 없습니다. + ); +} diff --git a/src/components/search.tsx b/src/components/search.tsx index 865e07e..b68f610 100644 --- a/src/components/search.tsx +++ b/src/components/search.tsx @@ -1,26 +1,47 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { useNavigate } from 'react-router-dom'; +import { searchKeywordAtom } from '../atoms.tsx'; import WindowTop from './window-top.tsx'; import * as W from '../styles/window.ts'; import * as S from '../styles/search.ts'; +import { ReactComponent as IconSearch } from '../assets/images/i-search.svg'; export default function Search() { - const [searchKeyword, setSearchKeyword] = useState(''); + const navigate = useNavigate(); + const [searchValue, setSearchValue] = useState(''); + const [searchKeyword, setSearchKeyword] = useRecoilState(searchKeywordAtom); const onChange = (e: React.ChangeEvent) => { - setSearchKeyword(e.target.value); + setSearchValue(e.target.value); }; + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (searchValue === '') return; + setSearchKeyword(searchValue); + setSearchValue(''); + navigate(`/search/searchKeyword=${searchKeyword}`); + }; + useEffect(() => { + if (searchKeyword) { + navigate(`/search/searchKeyword=${searchKeyword}`); + } + }, [searchKeyword, navigate]); return ( - + - + + 검색하기 + + ); diff --git a/src/routes/search-result.tsx b/src/routes/search-result.tsx index 78cfb5b..0c856b5 100644 --- a/src/routes/search-result.tsx +++ b/src/routes/search-result.tsx @@ -1,3 +1,12 @@ +import SearchTimeline from '../components/search-timeline.tsx'; +import WindowTop from '../components/window-top.tsx'; +import * as W from '../styles/window.ts'; + export default function SearchResult() { - return null; + return ( + + + + + ); } diff --git a/src/styles/search.ts b/src/styles/search.ts index b173ac1..db3d287 100644 --- a/src/styles/search.ts +++ b/src/styles/search.ts @@ -1,7 +1,9 @@ import { styled } from 'styled-components'; import { Input } from './button.ts'; +import { grayColor } from './global.ts'; export const Form = styled.form` + position: relative; display: flex; flex-direction: column; gap: 10px; @@ -9,4 +11,21 @@ export const Form = styled.form` max-width: 500px; `; -export const FormInput = styled(Input)``; +export const FormInput = styled(Input)` + padding-right: 50px; +`; + +export const FormButton = styled.button` + position: absolute; + top: 3px; + right: 3px; + bottom: 3px; + height: 37px; + width: 37px; + background-color: transparent; + border-radius: 50%; + border: none; + svg { + stroke: ${grayColor}; + } +`; diff --git a/src/styles/timeline.ts b/src/styles/timeline.ts index 241ad4b..5ecd51b 100644 --- a/src/styles/timeline.ts +++ b/src/styles/timeline.ts @@ -1,8 +1,11 @@ import styled from 'styled-components'; -const TimelineWrapper = styled.ul` +export const TimelineWrapper = styled.ul` display: block; overflow-y: auto; `; -export default TimelineWrapper; +export const Text = styled.p` + margin: 20px 0; + text-align: center; +`;