Skip to content

Commit

Permalink
[Feat] 검색 페이지 및 전역 상태 관리 수정 (#60)
Browse files Browse the repository at this point in the history
* feat: 헤더 검색, 좋아요, 로그인으로 변경

* feat: 검색 바

* feat: tab 레이아웃

* feat: 검색 전체 레이아웃

* feat: gatheringCard CSS

* feat: useCustomInfiniteQuery hook

* feat: 페이지 이동 및 새로고침 시, 상태 관리 로직 추가
  • Loading branch information
he2e2 authored Nov 29, 2024
1 parent 9986b52 commit 0419250
Show file tree
Hide file tree
Showing 30 changed files with 574 additions and 147 deletions.
3 changes: 2 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ const typescriptConfig = {
},
rules: {
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unused-vars': 'error',
Expand Down
5 changes: 5 additions & 0 deletions src/app/appRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
WriteGatheringPage,
WriteArchivePage,
RegisterPage,
SearchPage,
} from '@/pages';
import { Layout } from '@/widgets';

Expand Down Expand Up @@ -43,6 +44,10 @@ const AppRouter = () => {
path: '/gathering/write',
element: <WriteGatheringPage />,
},
{
path: '/search',
element: <SearchPage />,
},
{
path: '/user',
element: <>{/** userPage */}</>,
Expand Down
11 changes: 11 additions & 0 deletions src/features/archive/archive.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@ export const getArchiveList = (sort: string, page: number, color?: Color | null)
},
})
.then(res => res.data);

export const getSearchArchive = (searchKeyword: string, page: number) =>
api
.get<GetArchiveListApiResponse>('/archive/search', {
params: {
searchKeyword,
page,
size: 9,
},
})
.then(res => res.data);
91 changes: 19 additions & 72 deletions src/features/archive/archive.hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

import {
deleteArchive,
Expand All @@ -22,7 +21,7 @@ import type {
} from './archive.dto';
import type { Color } from './colors.type';

import { useIntersectionObserver } from '@/shared/hook';
import { useCustomInfiniteQuery } from '@/shared/hook';

export const useCreateArchive = () =>
useMutation({
Expand All @@ -45,42 +44,12 @@ export const useArchive = (archiveId: number) =>
queryFn: () => getArchive(archiveId),
});

export const useComments = (archiveId: number, enabled: boolean = false) => {
const { data, fetchNextPage, isLoading, isError, isFetchingNextPage } = useInfiniteQuery<
GetCommentsApiResponse,
Error
>({
queryKey: ['/archive', archiveId, 'comment'],
queryFn: ({ pageParam = 0 }) => getComments(archiveId, 10, pageParam as number),
enabled,
getNextPageParam: (lastPage, allPages) => {
if (Array.isArray(lastPage.data)) {
const isLastPage = lastPage.data?.length < 10;
return isLastPage ? null : allPages.length;
}
return null;
},
initialPageParam: 0,
});

const items = useMemo(() => {
const temp: Comment[] = [];
data?.pages.forEach(page => {
page.data?.forEach(comment => {
temp.push(comment);
});
});
return temp;
}, [data]);

const ref = useIntersectionObserver(
() => {
void fetchNextPage();
},
{ threshold: 1.0 },
export const useComments = (archiveId: number) => {
return useCustomInfiniteQuery<GetCommentsApiResponse, Comment, Error>(
['/archive', archiveId, 'comment'],
({ pageParam }) => getComments(archiveId, 10, pageParam),
10,
);

return { items, isFetchingNextPage, isLoading, isError, ref, fetchNextPage };
};

export const useCreateComment = (archiveId: number) =>
Expand Down Expand Up @@ -133,40 +102,18 @@ export const usePopularArchiveList = () =>
queryFn: () => getPopularlityArchiveList(),
});

export const useArchiveList = (sort: string, color?: Color | 'default') => {
const { data, fetchNextPage, isLoading, isError, isFetchingNextPage } = useInfiniteQuery<
GetArchiveListApiResponse,
Error
>({
queryKey: ['/archive', sort, color],
queryFn: ({ pageParam = 0 }) =>
getArchiveList(sort, pageParam as number, color === 'default' ? null : color),
getNextPageParam: (lastPage, allPages) => {
if (Array.isArray(lastPage.data)) {
const isLastPage = lastPage.data?.length < 9;
return isLastPage ? null : allPages.length;
}
return null;
},
initialPageParam: 0,
});

const items = useMemo(() => {
const temp: ArchiveCardDTO[] = [];
data?.pages.forEach(page => {
page.data?.forEach(archive => {
temp.push(archive);
});
});
return temp;
}, [data]);

const ref = useIntersectionObserver(
() => {
void fetchNextPage();
},
{ threshold: 1.0 },
export const useArchiveList = (sort: string, color: Color | 'default') => {
return useCustomInfiniteQuery<GetArchiveListApiResponse, ArchiveCardDTO, Error>(
['/archive', sort, color],
({ pageParam }) => getArchiveList(sort, pageParam, color === 'default' ? null : color),
9,
);
};

return { items, isFetchingNextPage, isLoading, isError, ref, fetchNextPage };
export const useSearchArchive = (searchKeyword: string) => {
return useCustomInfiniteQuery<GetArchiveListApiResponse, ArchiveCardDTO, Error>(
['/archive/search', searchKeyword],
({ pageParam }) => getArchiveList(searchKeyword, pageParam),
9,
);
};
93 changes: 56 additions & 37 deletions src/features/archive/model/archive.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { produce } from 'immer';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

import type { BaseArchiveDTO } from '../archive.dto';
import type { Color } from '../colors.type';
Expand All @@ -13,6 +14,7 @@ interface ArchiveStore {
resetArchiveData: () => void;
color: Color | null;
setColor: (color: Color) => void;
clearStorage: () => void;
}

export const initialArchiveState: BaseArchiveDTO = {
Expand All @@ -24,44 +26,61 @@ export const initialArchiveState: BaseArchiveDTO = {
imageUrls: [{ url: 'https://source.unsplash.com/random/800x600' }],
};

export const useArchiveStore = create<ArchiveStore>(set => ({
archiveId: 0,
setArchiveId: id => {
set(() => ({
archiveId: id,
}));
},
export const useArchiveStore = create(
persist<ArchiveStore>(
set => ({
archiveId: 0,
setArchiveId: id => {
set(() => ({
archiveId: id,
}));
},

archiveData: initialArchiveState,
setArchiveData: newData => {
set(() => ({
archiveData: newData,
}));
},
archiveData: initialArchiveState,
setArchiveData: newData => {
set(() => ({
archiveData: newData,
}));
},

updateArchiveData: (key, value) => {
set(
produce((state: ArchiveStore) => {
state.archiveData[key] = value;
}),
);
},
updateArchiveData: (key, value) => {
set(
produce((state: ArchiveStore) => {
state.archiveData[key] = value;
}),
);
},

resetArchiveData: () => {
set(() => ({
archiveData: initialArchiveState,
}));
},
resetArchiveData: () => {
set(() => ({
archiveData: initialArchiveState,
}));
},

color: null,
setColor: color => {
set(
produce((state: ArchiveStore) => {
state.archiveData.type = color;
}),
);
set(() => ({
color,
}));
},

color: null,
setColor: color => {
set(
produce((state: ArchiveStore) => {
state.archiveData.type = color;
}),
);
set(() => ({
color,
}));
},
}));
clearStorage: () => {
set(() => ({
archiveId: 0,
archiveData: initialArchiveState,
color: null,
}));
useArchiveStore.persist.clearStorage();
},
}),
{
name: 'archive-storage',
storage: createJSONStorage(() => sessionStorage),
},
),
);
2 changes: 1 addition & 1 deletion src/features/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './archive';
export * from './gathering';

export * from './search';
1 change: 1 addition & 0 deletions src/features/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ui';
17 changes: 17 additions & 0 deletions src/features/search/ui/SearchBar.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.container {
display: flex;
align-items: center;
justify-content: space-between;
width: 40%;
min-width: 300px;
padding: 0.8rem 1.3rem;
color: $secondary-color;
background-color: $primary-color;
border-radius: 20px;

input {
flex: 1;
padding: 0.5rem;
color: $secondary-color;
}
}
26 changes: 26 additions & 0 deletions src/features/search/ui/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

import styles from './SearchBar.module.scss';

export const SearchBar = ({
searchText,
setSearchText,
}: {
searchText: string;
setSearchText: (t: string) => void;
}) => {
return (
<div className={styles.container}>
<input
onChange={e => {
setSearchText(e.target.value);
}}
placeholder='검색어를 입력해주세요'
type='text'
value={searchText}
/>
<FontAwesomeIcon className={styles.icon} icon={faSearch} />
</div>
);
};
1 change: 1 addition & 0 deletions src/features/search/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SearchBar';
4 changes: 2 additions & 2 deletions src/pages/ArchiveListPage/ArchiveListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import styles from './ArchiveListPage.module.scss';

import type { Color } from '@/features';
import { ColorSelect, useArchiveList } from '@/features';
import { Button, SelectBtn } from '@/shared/ui';
import { Button, SelectBtn, TripleDot } from '@/shared/ui';
import { ArchiveGrid } from '@/widgets';

export const ArchiveListPage = () => {
Expand Down Expand Up @@ -45,7 +45,7 @@ export const ArchiveListPage = () => {
</div>
<ArchiveGrid archives={archives} />
<div className={styles.loading} ref={ref}>
{isFetchingNextPage && '로딩중...'}
{isFetchingNextPage && <TripleDot />}
</div>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/pages/DetailArchivePage/DetailArchivePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styles from './DetailArchivePage.module.scss';
import { worker } from '../../mocks/browser';

import { MarkdownPreview, WriteComment, CommentItem, useArchive, useComments } from '@/features';
import { TripleDot } from '@/shared/ui';
import { DetailHeader } from '@/widgets';

export const DetailArchivePage = () => {
Expand Down Expand Up @@ -48,7 +49,7 @@ export const DetailArchivePage = () => {
items.map(comment => (
<CommentItem archiveId={Number(archiveId)} comment={comment} key={comment.commentId} />
))}
{archive && <div ref={ref}>{isFetchingNextPage && <p>Loading more comments...</p>}</div>}
{archive && <div ref={ref}>{isFetchingNextPage && <TripleDot />}</div>}
</div>
</div>
);
Expand Down
12 changes: 12 additions & 0 deletions src/pages/SearchPage/SearchPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.wrapper {
display: flex;
flex: 1;
flex-direction: column;
gap: 2rem;
align-items: center;
padding: 4rem 8rem;

@media (width <= 768px) {
padding: 4rem;
}
}
18 changes: 18 additions & 0 deletions src/pages/SearchPage/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from 'react';

import styles from './SearchPage.module.scss';

import { SearchBar } from '@/features';
import { SearchTap } from '@/widgets';

export const SearchPage = () => {
const [searchText, setSearchText] = useState('');
const [activeTab, setActiveTab] = useState('전체');

return (
<div className={styles.wrapper}>
<SearchBar searchText={searchText} setSearchText={setSearchText} />
<SearchTap activeTab={activeTab} setActiveTab={setActiveTab} />
</div>
);
};
Loading

1 comment on commit 0419250

@github-actions
Copy link

Choose a reason for hiding this comment

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

⚡ Lighthouse report for http://localhost:3000/

Category Score
🔴 Performance 40
🟢 Accessibility 95
🟢 Best Practices 100
🟠 SEO 83

Detailed Metrics

Metric Value
🔴 First Contentful Paint 37.1 s
🔴 Largest Contentful Paint 63.7 s
🟠 Total Blocking Time 270 ms
🟢 Cumulative Layout Shift 0
🔴 Speed Index 47.9 s

Please sign in to comment.