Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: 스토리북에 구글 맵 적용 후 검색 창 스토리북을 개선한다 #926

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
38 changes: 28 additions & 10 deletions frontend/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Wrapper } from '@googlemaps/react-wrapper';
import type { Preview } from '@storybook/react';
import { initialize, mswDecorator } from 'msw-storybook-addon';

Expand Down Expand Up @@ -66,16 +67,33 @@ const preview: Preview = {
},
},
decorators: [
(Story) => (
<React.Fragment>
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<MemoryRouter initialEntries={['/']}>
<Story />
</MemoryRouter>
</QueryClientProvider>
</React.Fragment>
),
(Story) => {
const map = document.getElementById('map');

if (map) {
map.style.visibility = 'hidden';
}

return (
<React.Fragment>
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<MemoryRouter initialEntries={['/']}>
<Wrapper
apiKey={
process.env.NODE_ENV === 'production'
? process.env.GOOGLE_MAPS_API_KEY_PROD!
: process.env.GOOGLE_MAPS_API_KEY_DEV!
}
libraries={['marker']}
>
<Story />
</Wrapper>
</MemoryRouter>
</QueryClientProvider>
</React.Fragment>
Comment on lines +78 to +94
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<React.Fragment>
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<MemoryRouter initialEntries={['/']}>
<Wrapper
apiKey={
process.env.NODE_ENV === 'production'
? process.env.GOOGLE_MAPS_API_KEY_PROD!
: process.env.GOOGLE_MAPS_API_KEY_DEV!
}
libraries={['marker']}
>
<Story />
</Wrapper>
</MemoryRouter>
</QueryClientProvider>
</React.Fragment>
<QueryClientProvider client={queryClient}>
<GlobalStyle />
<MemoryRouter initialEntries={['/']}>
<Wrapper
apiKey={
process.env.NODE_ENV === 'production'
? process.env.GOOGLE_MAPS_API_KEY_PROD!
: process.env.GOOGLE_MAPS_API_KEY_DEV!
}
libraries={['marker']}
>
<Story />
</Wrapper>
</MemoryRouter>
</QueryClientProvider>

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

<React.Fragment> 요거슨 저번에도 얘기하긴 했지만 React import 안하면 오류 메시지 나는 것 때문에 넣어놨습니다. React.Fragment 대신 다른 방법으로 오류 메시지 없애는 방법은 없을까여?🧐

Copy link
Member

Choose a reason for hiding this comment

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

제가 풀 땡겨서 확인했을 땐 오류가 없는데 어떤 오류인가여?

Copy link
Collaborator Author

@feb-dain feb-dain Nov 12, 2023

Choose a reason for hiding this comment

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

image

이렇게 오류 메시지 뜨는 거요!
(React.Fragment 태그 지우면 저장했을 때 안 쓰는 import 사라져서 저런 오류가 납니다)

Copy link
Member

Choose a reason for hiding this comment

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

지금 제 윈도우 환경에서는 안뜨는데 맥에서도 확인해볼게여

Copy link
Member

Choose a reason for hiding this comment

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

왜 안나는지 원인을 찾았습니다. 제가 웹스톰을 써서 경고를 자동으로 처리해주는 것 같아요.

  1. 윈도우 맥 둘다 웹스톰에선 import, Fragment 둘 다 지워도 경고 안뜹니다
  2. vscode에서는 import React from 'react';만 살려두는 경우에 경고가 안뜹니다

혹시 import도 제거하셨나여?

Copy link
Collaborator Author

@feb-dain feb-dain Nov 12, 2023

Choose a reason for hiding this comment

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

넴 저장시 안 쓰는 import는 자동으로 없어지는 게 제 vscode 설정이라 React를 쓰고 있는 곳이 없으면 지워집니다. import React from 'react';를 살려두려면 일부러 저장하지 않아야 하는데, 그렇게 하는 것보다는 Fragment 태그를 두는 게 편할 것 같아서 넣어놨어요

Copy link
Member

Choose a reason for hiding this comment

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

무슨 말인지 알 것 같네요..

Storybook은 별도의 번들링 프로세스를 구성해서 모듈로 취급되는 관계로, React를 전역으로 가져오려면 import React from 'react';가 반드시 있어야 하는데, vscode의 미사용 import 제거를 기능을 사용하려다 보니 preview.tsx 내의 import도 자동으로 제거되어서 억지로 Fragment를 쓰고 있는거군요..

그런데 제 vscode에서는 다른 import 문장들과는 다르게 해당 react import 문장은 자동으로 제거되지 않는데 내일 한번 확인이 필요할 것 같아요

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

맞습니다
저만 지워지는 거 였나요😂 낼 확인해봅시다

Copy link
Member

Choose a reason for hiding this comment

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

네넹

Comment on lines +77 to +94
Copy link
Member

Choose a reason for hiding this comment

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

그냥 정말 개인적인 생각인데 컴포넌트 단위 렌더링을 위해 스토리북을 사용하는데 구글 지도가 이 과정에 포함되는게 조금 어색하다는 생각이 듭니다. 컴포넌트 단위로 렌더링을 할 수 있게 컴포넌트에서 구글 지도 api에 대한 참조를 끊어내는것도 괜찮지 않을까 하는 생각입니다! 혹시 어떻게 생각하시나요...??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요거슨 길어질 것 같으니 만나서 얘기합시다

);
},
mswDecorator,
],
};
Expand Down
13 changes: 5 additions & 8 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,13 @@
type="application/manifest+json"
/>

<link href="https://fonts.googleapis.com" rel="preload" as="font" />
<link crossorigin href="https://fonts.gstatic.com" rel="preload" as="font" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect" />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=fallback"
rel="preload"
as="style"
/>
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap"
rel="stylesheet"
as="font"
onload="this.onload=null; this.rel='stylesheet'"
Comment on lines +47 to +53
Copy link
Member

Choose a reason for hiding this comment

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

좋네요

/>
<title>카페인: 편리한 전기차 충전소 지도</title>
</head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const CongestionStatistics = ({ stationId }: CongestionStatisticsProps) => {
{isError ? (
<Error
title="앗"
message="데이터를 불러오는데 실패했어요"
message="충전소 사용량을 가져오지 못 했어요"
subMessage="잠시 후 다시 시도해주세요"
handleRetry={handleRetry}
minHeight={40.4}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { css } from 'styled-components';
import { useEffect, useState } from 'react';

import { memberInfoStore } from '@stores/login/memberInfoStore';
import { memberTokenStore } from '@stores/login/memberTokenStore';

import { useCreateReply } from '@hooks/tanstack-query/station-details/reviews/useCreateReply';

import Box from '@common/Box';
import ButtonNext from '@common/ButtonNext';
import FlexBox from '@common/FlexBox';
import Text from '@common/Text';

import ContentField from '@ui/StationDetailsWindow/reviews/common/ContentField';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { useState } from 'react';

import { useModifyReview } from '@hooks/tanstack-query/station-details/reviews/useModifyReview';

import Box from '@common/Box';
import ButtonNext from '@common/ButtonNext';
import FlexBox from '@common/FlexBox';

import ContentField from '@ui/StationDetailsWindow/reviews/common/ContentField';
import HeaderWithRating from '@ui/StationDetailsWindow/reviews/common/HeaderWithRating';

import { MAX_REVIEW_CONTENT_LENGTH, MIN_REVIEW_CONTENT_LENGTH } from '@constants';

import type { Review } from '@type';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ const meta = {
title: 'UI/SearchResult',
tags: ['autodocs'],
component: SearchResult,
decorators: [
(Story) => (
<Container>
<Story />
</Container>
),
],
args: {
cities: [
{
Expand Down Expand Up @@ -80,48 +87,36 @@ export default meta;
// TODO: 스토리북 빌드 실패로 임시로 조치해뒀으니 수정 바랍니다.

export const Default = ({ ...args }: SearchResultProps) => {
return (
<Container>
<SearchResult {...args} />
</Container>
);
return <SearchResult {...args} />;
};

export const NoResult = () => {
return (
<SubContainer>
<SearchResult
cities={[]}
stations={[]}
closeResult={() => null}
isError={false}
isLoading={false}
showStationDetails={() => null}
/>
</SubContainer>
<SearchResult
cities={[]}
stations={[]}
closeResult={() => null}
isError={false}
isLoading={false}
showStationDetails={() => null}
/>
);
};

export const Error = () => {
return (
<Container>
<SearchResult
cities={[]}
stations={[]}
closeResult={() => null}
isError={true}
isLoading={false}
showStationDetails={() => null}
/>
</Container>
<SearchResult
cities={[]}
stations={[]}
closeResult={() => null}
isError={true}
isLoading={false}
showStationDetails={() => null}
/>
);
};

const Container = styled.div`
width: 34rem;
height: 16rem;
`;

const SubContainer = styled(Container)`
height: 24rem;
height: 34rem;
`;
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import type { Meta } from '@storybook/react';
import { styled } from 'styled-components';

import type { ChangeEvent, FocusEvent, FormEvent, MouseEvent } from 'react';
import { useState } from 'react';

import { useQueryClient } from '@tanstack/react-query';

import { fetchSearchedStations, useSearchStations } from '@hooks/tanstack-query/useSearchStations';
import { useDebounce } from '@hooks/useDebounce';

import Loader from '@common/Loader';

import { useNavigationBar } from '@ui/Navigator/NavigationBar/hooks/useNavigationBar';
import StationDetailsWindow from '@ui/StationDetailsWindow';

import { MOBILE_BREAKPOINT } from '@constants';
import { QUERY_KEY_SEARCHED_STATION, QUERY_KEY_STATION_MARKERS } from '@constants/queryKeys';

import { pillStyle } from '../../../style';
import type { StationPosition } from '../../../types';
import Button from '../../common/Button';
import SearchResult from './SearchResult';
import StationSearchBar from './StationSearchBar';

const meta = {
title: 'UI/StationSearchBar',
Expand All @@ -36,140 +16,15 @@ const meta = {

export default meta;

// TODO: addon으로 googleMap 관련 함수 제외하기
export const Default = () => {
const [isFocused, setIsFocused] = useState(false);

const [searchWord, setSearchWord] = useState('');
const [debouncedSearchWord, setDebouncedSearchWord] = useState(searchWord);
const queryClient = useQueryClient();
const { openLastPanel } = useNavigationBar();

useDebounce(
() => {
setDebouncedSearchWord(searchWord);
},
[searchWord],
400
);

const {
data: searchResult,
isLoading,
isError,
isFetching,
} = useSearchStations(debouncedSearchWord);

const handleOpenResult = (event: MouseEvent<HTMLInputElement> | FocusEvent<HTMLInputElement>) => {
event.stopPropagation();
setIsFocused(true);
};

const handleCloseResult = () => {
setIsFocused(false);
};

const handleSubmitSearchWord = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
handleCloseResult();

const { stations } = await fetchSearchedStations(searchWord);

if (stations !== undefined && stations.length > 0) {
const [{ stationId, latitude, longitude }] = stations;
showStationDetails({ stationId, latitude, longitude });
}

queryClient.invalidateQueries({ queryKey: [QUERY_KEY_SEARCHED_STATION] });
};

const showStationDetails = ({ stationId, latitude, longitude }: StationPosition) => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY_STATION_MARKERS] });
openLastPanel(<StationDetailsWindow stationId={stationId} />);
};

const handleChangeSearchWord = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
const searchWord = encodeURIComponent(value);

setIsFocused(true);
setSearchWord(searchWord);
};

return (
<S.Container>
<S.Form role="search" onSubmit={handleSubmitSearchWord}>
<label htmlFor="station-search-bar" aria-hidden>
<S.Search
id="station-search-bar"
type="search"
role="searchbox"
placeholder="충전소명 또는 지역명을 입력해 주세요"
autoComplete="off"
onChange={handleChangeSearchWord}
onFocus={handleOpenResult}
onClick={handleOpenResult}
/>
<Button type="submit" aria-label="검색하기">
{isFetching ? (
<Loader size="md" />
) : (
<MagnifyingGlassIcon width="2.4rem" stroke="#767676" />
)}
</Button>
</label>
</S.Form>
{isFocused && searchResult && (
<SearchResult
cities={searchResult.cities}
stations={searchResult.stations}
isLoading={isLoading}
isError={isError}
showStationDetails={showStationDetails}
closeResult={handleCloseResult}
/>
)}
</S.Container>
);
return <StationSearchBar />;
};

const S = {
Container: styled.div`
width: 30rem;

@media screen and (max-width: ${MOBILE_BREAKPOINT}px) {
width: 100%;
}
`,

Form: styled.form`
position: relative;
min-width: 30rem;

@media screen and (max-width: ${MOBILE_BREAKPOINT}px) {
min-width: 100%;
}
`,

Search: styled.input`
${pillStyle};

background: #fcfcfc;
border: 1px solid #d0d2d8;

width: 100%;
padding: 1.9rem 4.6rem 2rem 1.8rem;
font-size: 1.3rem;

& + button {
position: absolute;
right: 2rem;
top: 50%;
transform: translateY(-50%);
}

&:focus {
box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.2);
outline: 0;
}
position: fixed;
top: 24px;
left: 8rem;
z-index: 9999;
`,
};
Loading
Loading