Skip to content

Commit

Permalink
feat: 데이터베이스 구조개선 및 마켓 검색기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
JoStar33 committed Aug 16, 2024
1 parent 8f33c74 commit 4a188d0
Show file tree
Hide file tree
Showing 24 changed files with 494 additions and 26 deletions.
6 changes: 6 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ import Loading from './components/common/Loading';
import DarkBackground from './components/common/DarkBackground';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import queryClientDefaultOptions from './constants/queryClientDefaultOptions';
import databaseInitializer from './mocks/fakeDatabase';
import React from 'react';

export default function App() {
const queryClient = new QueryClient(queryClientDefaultOptions);
const modalWithTextValue = useRecoilValue(modalWithText);
const loadingValue = useRecoilValue(loadingState);

React.useEffect(() => {
databaseInitializer();
}, []);

return (
<QueryClientProvider client={queryClient}>
<Theme>
Expand Down
16 changes: 16 additions & 0 deletions src/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## 작성요령

API파일을 각 기능단위별로 분리해서 관리하고 있습니다.

아래 형태를 유지하며 작성해주세요.

```tsx
// 기능명은 앞글자 대문자로.
const 기능명 = {
Get: {},
Post: {},
Put: {},
Patch: {},
Delete: {},
};
```
9 changes: 9 additions & 0 deletions src/api/contents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Contents = {
Get: {},
Post: {},
Put: {},
Patch: {},
Delete: {},
};

export default Contents;
4 changes: 2 additions & 2 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ instance.interceptors.response.use(
if (shouldMissingToken) {
const accessToken = storage.getAccessTokenLocalStorageItem();
if (accessToken) {
instance.defaults.headers.common['Authorization'] = `${accessToken}`;
instance.defaults.headers.common['Authorization'] = accessToken;
const freshRequest = {
...config,
headers: { ...config.headers, Authorization: accessToken },
};
return instance.request(freshRequest);
}
window.location.href = routerPath.SIGN_IN
window.location.href = routerPath.SIGN_IN;
return;
}
return Promise.reject(error);
Expand Down
13 changes: 13 additions & 0 deletions src/components/contents/ContentCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import styled from 'styled-components';

export default function ContentCard() {
return (
<S.ContentCard>
<img />
</S.ContentCard>
);
}

const S = {
ContentCard: styled.div``,
};
4 changes: 2 additions & 2 deletions src/components/layouts/Aside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export default function Aside({ setIsOpenAside }: IProps) {
<div className="aside-body">
{[...bottomTab]
.sort((a, b) => (a.priority > b.priority ? 1 : -1))
.map((tabElement) => (
<div className="aside-body__element" onClick={() => handleClickMenuItem(tabElement.path)}>
.map((tabElement, index) => (
<div key={index} className="aside-body__element" onClick={() => handleClickMenuItem(tabElement.path)}>
{tabElement.title}
</div>
))}
Expand Down
1 change: 0 additions & 1 deletion src/components/layouts/BottomTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const S = {
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 20px;
width: 100%;
height: 80px;
background-color: ${(props) => props.theme.colors.subMain};
Expand Down
83 changes: 83 additions & 0 deletions src/components/market/MarketSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import styled from 'styled-components';
import { FaSearch } from 'react-icons/fa';
import { TfiClose } from 'react-icons/tfi';
import { useLocation, useNavigate } from 'react-router-dom';

export default function MarketSearchInput() {
const navigate = useNavigate();
const location = useLocation();
const inputRef = React.useRef<HTMLInputElement>(null);

const urlSearchParams = new URLSearchParams(location.search);
const currentInputState = urlSearchParams.get('searchKeyword');
const [isFocusedOn, setIsFocusedOn] = React.useState(() => !currentInputState);

const handleSearchMarket = () => {
if (!inputRef.current) return;
if (!inputRef.current?.value) {
urlSearchParams.delete('searchKeyword');
} else {
urlSearchParams.set('searchKeyword', inputRef.current?.value);
}
setIsFocusedOn(false);
navigate({ pathname: location.pathname, search: urlSearchParams.has('searchKeyword') ? `?${urlSearchParams}` : '' }, { replace: true });
};

const handleClearSearchMarket = () => {
if (!inputRef.current) return;
setIsFocusedOn(true);
inputRef.current.value = '';
navigate({ pathname: location.pathname, search: '' }, { replace: true });
};

const handleKeyUpMarketEvent = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
handleSearchMarket();
}
};

const handleFocusEvent = () => {
setIsFocusedOn(true);
};

const handleBlurEvent = () => {
setIsFocusedOn(!currentInputState);
};

React.useEffect(function initializeInputValue() {
if (!inputRef.current) return;
inputRef.current.value = currentInputState as string;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<S.MarketSearchInput onBlur={handleBlurEvent}>
<input ref={inputRef} onKeyDown={handleKeyUpMarketEvent} onFocus={handleFocusEvent} />
{isFocusedOn ? (
<FaSearch cursor="pointer" size={25} onClick={handleSearchMarket} />
) : (
<TfiClose size={25} onClick={handleClearSearchMarket} />
)}
</S.MarketSearchInput>
);
}

const S = {
MarketSearchInput: styled.div`
position: sticky;
z-index: 3;
height: 50px;
top: -10px;
display: flex;
align-items: center;
gap: 10px;
background-color: ${(props) => props.theme.colors.white};
input {
width: 100%;
height: 30px;
border: 1px solid ${(props) => props.theme.colors.black};
padding-left: 10px;
}
`,
};
16 changes: 12 additions & 4 deletions src/components/market/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IMarketListResponse } from '@/types/market';
import styled from 'styled-components';
import MarketCard from './MarketCard';
import MarketSearchInput from './MarketSearchInput';

interface IProps {
data: IMarketListResponse;
Expand All @@ -9,16 +10,23 @@ interface IProps {
export default function Market({ data }: IProps) {
return (
<S.Market>
{data.value.map((element) => (
<MarketCard element={element} />
))}
<MarketSearchInput />
<div className="market-card-wrapper">
{data.value.map((element) => (
<MarketCard element={element} />
))}
</div>
</S.Market>
);
}

const S = {
Market: styled.div`
display: flex;
flex-wrap: wrap;
flex-direction: column;
.market-card-wrapper {
display: flex;
flex-wrap: wrap;
}
`,
};
11 changes: 10 additions & 1 deletion src/containers/market/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ import queryKeys from '@/constants/queryKeys';
import useSimpleQuery, { IUseSimpleQuery } from '@/hooks/useSimpleQuery';
import { IMarketListResponse } from '@/types/market';
import MarketComponent from '@/components/market';
import { useLocation } from 'react-router-dom';

const TEN_MINUTES = 10 * 60 * 1000;

export default function MarketContainer() {
const location = useLocation();
const urlSearchParams = new URLSearchParams(location.search);

const searchKeyword = urlSearchParams.get('searchKeyword')?.trim();

const request: IUseSimpleQuery = {
queryKey: [queryKeys.marketList],
queryKey: [queryKeys.marketList, urlSearchParams.get('searchKeyword')],
requestAPI: Market.Get.list,
options: {
staleTime: TEN_MINUTES,
},
requestQuery: {
searchKeyword,
},
};

const { data, isLoading } = useSimpleQuery<IMarketListResponse>(request);
Expand Down
36 changes: 36 additions & 0 deletions src/mocks/contents/contentsGetHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { http } from 'msw';
import { commonUrl } from '..';
import CustomResponse from '../utils/customResponse';
import { contentsList } from '../fakeDatabase/resources/contents';

const contentsUrl = (path?: string) => `${commonUrl(`/contents${path ?? ''}`)}`;

const contentsGetHandler = [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
http.get(`${contentsUrl()}`, ({ request }) => {
// try {
// const urlObj = new URL(request.url);
// const params = urlObj.searchParams;
// const searchKeyword = params.get('searchKeyword');
// if (params.has('searchKeyword')) {
// const searchedMarketList = marketList.filter((marketElement) => {
// return (
// marketElement.title.toLowerCase().includes((searchKeyword as string).toLowerCase()) ||
// marketElement.address.toLowerCase().includes((searchKeyword as string).toLowerCase())
// );
// });
// return CustomResponse({
// value: searchedMarketList,
// message: '성공!',
// code: 200,
// });
// }
// } catch (error: any) {
// console.log(error);
// return CustomResponse({ value: undefined, message: error, code: 500 });
// }
return CustomResponse({ value: contentsList, message: '성공!', code: 200 });
}),
];

export default contentsGetHandler;
5 changes: 5 additions & 0 deletions src/mocks/contents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import contentsGetHandler from './contentsGetHandler';

const contentsHandler = [...contentsGetHandler];

export default contentsHandler;
6 changes: 4 additions & 2 deletions src/mocks/coordinate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
import { ICoordinateListResponse } from '@/types/coordinate';
import { delay, http } from 'msw';
import { commonUrl } from '.';
import { coordinateList } from './fakeDatabase/resources/coordinate';
import { marketList } from './fakeDatabase/resources/market';
import { coordinateDatabase } from './fakeDatabase/resources/coordinate';
import { marketDatabase } from './fakeDatabase/resources/market';
import CustomResponse from './utils/customResponse';

//coordinate
const coordinateUrl = (path?: string) => `${commonUrl(`/coordinate${path ?? ''}`)}`;

const coordinateHandler = [
http.get(`${coordinateUrl()}`, async () => {
const coordinateList = coordinateDatabase.Get.list().value;
await delay(2000);
const coordinateListResponse: ICoordinateListResponse = {
value: coordinateList,
Expand All @@ -27,6 +28,7 @@ const coordinateHandler = [
}),
http.get(`${coordinateUrl('/*')}`, async ({ request }) => {
await delay(2000);
const marketList = marketDatabase.Get.list().value;
const urlObj = new URL(request.url);
const pathSegments = urlObj.pathname.split('/');
const lastPathSegment = pathSegments[pathSegments.length - 1];
Expand Down
15 changes: 15 additions & 0 deletions src/mocks/fakeDatabase/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## fakeDatabase

가짜 데이터베이스이고 로컬스토리지에 정보를 저장합니다.

아래 객체의 형태로 데이터를 조회 및 조작합니다.

```tsx
const 도메인명Database = {
Get: {},
Post: {},
Put: {},
Patch: {},
Delete: {},
};
```
8 changes: 8 additions & 0 deletions src/mocks/fakeDatabase/constants/databaseKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const databaseKey = {
userList: 'user-list',
contentsList: 'contents-list',
coordinateList: 'coordinate-list',
marketList: 'market-list',
};

export default databaseKey;
11 changes: 11 additions & 0 deletions src/mocks/fakeDatabase/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { initializeContentsDatabase } from './resources/contents';
import { initializeCoordinateDatabase } from './resources/coordinate';
import { initializeMarketDatabase } from './resources/market';

const databaseInitializer = () => {
initializeMarketDatabase();
initializeCoordinateDatabase();
initializeContentsDatabase();
};

export default databaseInitializer;
Loading

0 comments on commit 4a188d0

Please sign in to comment.