Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/search/GroupSearchResult.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ export const Tab = styled.button<{ selected?: boolean }>`
cursor: pointer;
`;

export const Content = styled.div`
export const Content = styled.div<{ isRefetching?: boolean }>`
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 0 20px 60px;
opacity: ${({ isRefetching }) => (isRefetching ? 0.45 : 1)};
transition: opacity 0.15s ease;
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 opacity로 전환되는 패턴 너무 좋은 것 같아요! 👍🏻


&::-webkit-scrollbar {
display: none;
Expand Down
10 changes: 7 additions & 3 deletions src/components/search/GroupSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ const GroupSearchResult = ({
onClickRoom,
}: Props) => {
const mapped = useMemo(() => rooms.map(mapToGroupCardModel), [rooms]);
const isEmpty = !isLoading && mapped.length === 0;
// searching 중에는 아직 debounce 대기 중일 수 있으므로 빈 결과 화면을 표시하지 않음
const isEmpty = !isLoading && mapped.length === 0 && type !== 'searching';
// 기존 결과를 유지한 채 재검색 중인 상태 (필터·카테고리 변경 시)
const isRefetching = isLoading && mapped.length > 0;

return (
<>
Expand All @@ -87,7 +90,8 @@ const GroupSearchResult = ({

{(showTabs || type === 'searched') && (
<GroupCardHeader>
<GroupNum>전체 {mapped.length}</GroupNum>
{/* 재검색 중엔 이전 카운트를 유지하고, 초기 검색 완료 시 카운트를 표시 */}
<GroupNum>{isLoading && !isRefetching ? '' : `전체 ${mapped.length}`}</GroupNum>
<Filter
filters={FILTER}
selectedFilter={selectedFilter}
Expand All @@ -96,7 +100,7 @@ const GroupSearchResult = ({
</GroupCardHeader>
)}

<Content>
<Content isRefetching={isRefetching}>
{error && <ErrorText>{error}</ErrorText>}

{isEmpty ? (
Expand Down
6 changes: 5 additions & 1 deletion src/components/search/RecentSearchTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ interface RecentSearchTabsProps {
recentSearches: string[];
handleDelete: (term: string) => void;
handleRecentSearchClick: (term: string) => void;
isLoading?: boolean;
}

const RecentSearchTabs = ({
recentSearches,
handleDelete,
handleRecentSearchClick,
isLoading = false,
}: RecentSearchTabsProps) => {
return (
<Container>
<Title>최근 검색어</Title>
<TabContainer>
{recentSearches.length === 0 ? (
{isLoading ? (
<Text>최근 검색어를 불러오고 있습니다.</Text>
) : recentSearches.length === 0 ? (
<Text>최근 검색어가 아직 없어요.</Text>
) : (
recentSearches.map(recentSearch => (
Expand Down
9 changes: 9 additions & 0 deletions src/pages/feed/MyFeedPage.styled.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import styled from '@emotion/styled';
import { colors } from '@/styles/global/global';

export const Container = styled.div`
min-width: 320px;
max-width: 767px;
margin: 0 auto;
`;

export const LoadingScreen = styled.div`
min-width: 320px;
max-width: 767px;
margin: 0 auto;
min-height: 100dvh;
background-color: ${colors.black.main};
`;
11 changes: 9 additions & 2 deletions src/pages/feed/MyFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import OtherFeed from '@/components/feed/OtherFeed';
import { getOtherFeed, type OtherFeedItem } from '@/api/feeds/getOtherFeed';
import { getOtherProfile } from '@/api/users/getOtherProfile';
import type { OtherProfileData } from '@/types/profile';
import { Container } from './MyFeedPage.styled';
import { Container, LoadingScreen } from './MyFeedPage.styled';

const MyFeedPage = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -53,7 +53,14 @@ const MyFeedPage = () => {
}, [userId]);

if (loading) {
return <></>;
return (
<LoadingScreen>
<TitleHeader
leftIcon={<img src={leftArrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
</LoadingScreen>
);
}

if (error) {
Expand Down
11 changes: 9 additions & 2 deletions src/pages/feed/OtherFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import OtherFeed from '@/components/feed/OtherFeed';
import { getOtherFeed, type OtherFeedItem } from '@/api/feeds/getOtherFeed';
import { getOtherProfile } from '@/api/users/getOtherProfile';
import type { OtherProfileData } from '@/types/profile';
import { Container } from './MyFeedPage.styled';
import { Container, LoadingScreen } from './MyFeedPage.styled';

const OtherFeedPage = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -53,7 +53,14 @@ const OtherFeedPage = () => {
}, [userId]);

if (loading) {
return <></>;
return (
<LoadingScreen>
<TitleHeader
leftIcon={<img src={leftArrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
</LoadingScreen>
);
}

if (error) {
Expand Down
9 changes: 9 additions & 0 deletions src/pages/feed/UserSearch.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ export const SearchBarContainer = styled.div`
export const Content = styled.div`
margin-top: 132px;
`;

export const LoadingMessage = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: ${colors.white};
font-size: 16px;
`;
17 changes: 13 additions & 4 deletions src/pages/feed/UserSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useNavigate } from 'react-router-dom';
import { useUserSearch } from '@/hooks/useUserSearch';
import { getRecentSearch, type RecentSearchData } from '@/api/recentsearch/getRecentSearch';
import { deleteRecentSearch } from '@/api/recentsearch/deleteRecentSearch';
import { Content, SearchBarContainer, Wrapper } from './UserSearch.styled';
import { Content, SearchBarContainer, Wrapper, LoadingMessage } from './UserSearch.styled';

const UserSearch = () => {
const navigate = useNavigate();
Expand All @@ -25,10 +25,16 @@ const UserSearch = () => {
});

const [recentSearches, setRecentSearches] = useState<RecentSearchData[]>([]);
const [isRecentLoading, setIsRecentLoading] = useState(false);

const fetchRecentSearches = async () => {
const response = await getRecentSearch('USER');
setRecentSearches(response.data.recentSearchList);
setIsRecentLoading(true);
try {
const response = await getRecentSearch('USER');
setRecentSearches(response.data.recentSearchList);
} finally {
setIsRecentLoading(false);
}
};

useEffect(() => {
Expand Down Expand Up @@ -100,7 +106,9 @@ const UserSearch = () => {
<Content>
{isSearching ? (
<>
{isSearched ? (
{userList.length === 0 && (loading || !isSearched) ? (
<LoadingMessage>검색 중...</LoadingMessage>
) : isSearched ? (
<UserSearchResult
type={'searched'}
searchedUserList={userList}
Expand All @@ -124,6 +132,7 @@ const UserSearch = () => {
recentSearches={recentSearches.map(item => item.searchTerm)}
handleDelete={handleDeleteWrapper}
handleRecentSearchClick={handleRecentSearchClick}
isLoading={isRecentLoading}
/>
</>
)}
Expand Down
7 changes: 2 additions & 5 deletions src/pages/feed/UserSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ export function UserSearchResult({
hasMore,
onLoadMore,
}: UserSearchResultProps) {
const isEmptySearchedUserList = () => {
if (searchedUserList.length === 0) return true;
else return false;
};
const isEmpty = searchedUserList.length === 0 && type !== 'searching';

const observerRef = useRef<HTMLDivElement>(null);

Expand All @@ -47,7 +44,7 @@ export function UserSearchResult({
<List>
{type === 'searching' ? <></> : <ResultHeader>전체 {searchedUserList.length}</ResultHeader>}

{isEmptySearchedUserList() ? (
{isEmpty ? (
<EmptyWrapper>{loading ? '사용자 찾는 중...' : '찾는 사용자가 없어요.'}</EmptyWrapper>
) : (
<>
Expand Down
48 changes: 39 additions & 9 deletions src/pages/groupSearch/GroupSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const GroupSearch = () => {
const [category, setCategory] = useState<string>('');

const [recentSearches, setRecentSearches] = useState<RecentSearchData[]>([]);
const [isRecentLoading, setIsRecentLoading] = useState(false);
const [searchTimeoutId, setSearchTimeoutId] = useState<NodeJS.Timeout | null>(null);

const [showTabs, setShowTabs] = useState(false);
Expand All @@ -46,11 +47,14 @@ const GroupSearch = () => {

useEffect(() => {
(async () => {
setIsRecentLoading(true);
try {
const response = await getRecentSearch('ROOM');
setRecentSearches(response.isSuccess ? response.data.recentSearchList : []);
} catch {
setRecentSearches([]);
} finally {
setIsRecentLoading(false);
}
})();
}, []);
Expand All @@ -62,11 +66,14 @@ const GroupSearch = () => {
}, [searchStatus]);

const fetchRecentSearches = async () => {
setIsRecentLoading(true);
try {
const response = await getRecentSearch('ROOM');
setRecentSearches(response.isSuccess ? response.data.recentSearchList : []);
} catch {
setRecentSearches([]);
} finally {
setIsRecentLoading(false);
}
};

Expand All @@ -77,12 +84,15 @@ const GroupSearch = () => {
status: 'searching' | 'searched',
categoryParam: string,
isAllCategory: boolean = false,
keepPrevious: boolean = false,
) => {
setIsLoading(true);
setError(null);
setRooms([]);
setNextCursor(null);
setIsLast(true);
if (!keepPrevious) {
setRooms([]);
setNextCursor(null);
setIsLast(true);
}

try {
const isFinalized = status === 'searched';
Expand Down Expand Up @@ -142,7 +152,7 @@ const GroupSearch = () => {
setSearchStatus('searching');
setShowTabs(false);
const id = setTimeout(() => {
searchFirstPage(trimmed, toSortKey(selectedFilter), 'searching', category);
searchFirstPage(trimmed, toSortKey(selectedFilter), 'searching', category, false, true);
}, 300);
setSearchTimeoutId(id);
};
Expand Down Expand Up @@ -195,12 +205,30 @@ const GroupSearch = () => {
useEffect(() => {
if (searchStatus !== 'searched') return;

const term = searchTerm.trim();
const term = searchTermRef.current.trim();
const currentCategory = categoryRef.current;
const isAllCategory = !term && currentCategory === '';

searchFirstPage(
term,
toSortKey(selectedFilterRef.current),
'searched',
currentCategory,
isAllCategory,
true,
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchStatus, searchTerm]);

useEffect(() => {
if (searchStatusRef.current !== 'searched') return;

const term = searchTermRef.current.trim();
const isAllCategory = !term && category === '';

searchFirstPage(term, toSortKey(selectedFilter), 'searched', category, isAllCategory);
searchFirstPage(term, toSortKey(selectedFilter), 'searched', category, isAllCategory, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedFilter, category, searchStatus, searchTerm]);
}, [selectedFilter, category]);

useEffect(() => {
const term = searchTerm.trim();
Expand All @@ -213,7 +241,7 @@ const GroupSearch = () => {

const id = setTimeout(() => {
const currentCategory = categoryRef.current;
searchFirstPage(term, toSortKey(selectedFilter), 'searching', currentCategory);
searchFirstPage(term, toSortKey(selectedFilter), 'searching', currentCategory, false, true);
}, 300);
setSearchTimeoutId(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -320,7 +348,8 @@ const GroupSearch = () => {

{searchStatus !== 'idle' ? (
<>
{isLoading && rooms.length === 0 ? (
{(isLoading && rooms.length === 0) ||
(searchStatus === 'searching' && rooms.length === 0) ? (
<LoadingMessage>검색 중...</LoadingMessage>
) : (
<GroupSearchResult
Expand Down Expand Up @@ -353,6 +382,7 @@ const GroupSearch = () => {
}
}}
handleRecentSearchClick={handleRecentSearchClick}
isLoading={isRecentLoading}
/>
<AllRoomsButton onClick={handleAllRoomsClick}>
<p>전체 모임방 둘러보기</p>
Expand Down