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

feat: 레스토랑 카드에 loading 상태 추가 및 skeleton 반영 #202

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
462bbb7
feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)
shackstack Jul 27, 2023
161d6fe
style: 마커 호버시 마커 강조 (#192)
shackstack Jul 27, 2023
0838f20
feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)
shackstack Jul 27, 2023
d09af48
refactor: 불필요한 코드 제거 (#192)
shackstack Jul 27, 2023
11cf5f8
feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)
shackstack Jul 27, 2023
8748b91
style: 레스토랑 오버레이 스타일 수정 (#192)
shackstack Jul 28, 2023
11ba021
refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)
shackstack Jul 28, 2023
43ca047
feat: 마커 클릭시 강조 효과 주기 (#192)
shackstack Jul 28, 2023
fdeb97a
Squashed commit of the following:
shackstack Jul 28, 2023
f731c1d
feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)
shackstack Jul 28, 2023
b4ad157
Merge branch 'develop-frontend' into 192-feat-레스토랑-카드-및-마커-클릭-이벤트-변경
shackstack Jul 28, 2023
f2df719
feat: 음식점 카드에 사용하는 컴포넌트 스켈레톤 구현 (#199)
D0Dam Jul 29, 2023
e1b1f3c
refactor: 음식점 카드 리스트 컴포넌트 분리, 로딩 상태 추가 (#199)
D0Dam Jul 29, 2023
7a3cf54
refactor: 이미지에 대해 loading lazy 속성 추가 (#199)
D0Dam Jul 29, 2023
389520b
refactor: 로딩 애니메이션을 자연스럽게 수정 (#199)
D0Dam Jul 29, 2023
3a8a10e
refactor: Map에 data 로딩 상태 추가 (#199)
D0Dam Jul 29, 2023
e60b230
feat: 전 음식점 갯수만큼 음식점 스켈레톤을 표시하도록 수정 (#199)
D0Dam Jul 29, 2023
d11c319
refactor: Map이 로딩 상태일 때 스타일 추가 (#199)
D0Dam Jul 29, 2023
7dc4a3e
Squashed commit of the following:
D0Dam Jul 31, 2023
fbc4ff1
Merge branch 'develop-frontend' into 199-feat-레스토랑-카드에-loading-상태-추가-…
D0Dam Jul 31, 2023
cfb505f
fix: 충돌 해결 간 생긴 에러 및 lint 에러 수정 (#201)
D0Dam Jul 31, 2023
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
9 changes: 9 additions & 0 deletions frontend/src/@types/api.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
export interface RestaurantListData {
content: RestaurantData[];
currentElementsCount: number;
currentPage: number;
pageSize: number;
totalElementsCount: number;
totalPage: number;
}

export interface RestaurantData {
id: number;
name: string;
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/@common/LoadingDots/LoadingDots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default LoadingDots;

const StyledLoadingDots = styled.div`
display: flex;
gap: 0 0.6rem;
gap: 0 1.4rem;
& > div:nth-child(2) {
animation-delay: 0.14s;
Expand All @@ -30,13 +30,13 @@ const pulseAnimation = keyframes`
transform: scale(0);
}
90%, 100% {
transform: scale(1);
transform: scale(10);
}
`;

const StyledLoadingDot = styled.div`
width: 12px;
height: 12px;
width: 1.2px;
height: 1.2px;
border-radius: 50%;
background-color: var(--black);
Expand Down
30 changes: 22 additions & 8 deletions frontend/src/components/@common/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Wrapper, Status } from '@googlemaps/react-wrapper';
import { styled } from 'styled-components';
import OverlayMarker from './OverlayMarker';
import MapContent from './MapContent';
import OverlayMyLocation from './OverlayMyLocation';
import LoadingDots from '../LoadingDots';
Expand All @@ -12,6 +11,7 @@
import Minus from '~/assets/icons/minus.svg';
import Plus from '~/assets/icons/plus.svg';
import getQuadrant from '~/utils/getQuadrant';
import OverlayMarker from './OverlayMarker';

import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { RestaurantData } from '~/@types/api.types';
Expand All @@ -21,18 +21,33 @@
hoveredId: number | null;
setBoundary: React.Dispatch<React.SetStateAction<CoordinateBoundary>>;
toggleMapExpand: () => void;
loadingData: boolean;
}

const render = (status: Status) => {
if (status === Status.FAILURE)
return <div>지도를 불러올 수 없습니다. 페이지를 새로고침 하거나 네트워크 연결을 다시 한 번 확인해주세요.</div>;
return <LoadingDots />;
return (
<StyledMapLoadingContainer>
<LoadingDots />
</StyledMapLoadingContainer>
);
};

const StyledMapLoadingContainer = styled.section`
display: flex;
justify-content: center;
align-items: center;

height: 100%;

background-color: var(--gray-2);
`;

const JamsilCampus = { lat: 37.515271, lng: 127.1029949 };

function Map({ data, setBoundary, toggleMapExpand, hoveredId }: MapProps) {
const [center, setCenter] = useState<Coordinate>(JamsilCampus);
function Map({ data, setBoundary, toggleMapExpand, loadingData, hoveredId }: MapProps) {
const [center, setCenter] = useState<Coordinate>({ lat: 37.5057482, lng: 127.050727 });
const [clicks, setClicks] = useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = useState(16);
const [myPosition, setMyPosition] = useState<Coordinate | null>(null);
Expand All @@ -41,11 +56,11 @@
const [currentCenter, setCurrentCenter] = useState<Coordinate>(JamsilCampus);

const onClick = (e: google.maps.MapMouseEvent) => {
setClicks([...clicks, e.latLng!]);

Check warning on line 59 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
};

const onIdle = (m: google.maps.Map) => {
setZoom(m.getZoom()!);

Check warning on line 63 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
setCurrentCenter({ lat: m.getCenter().lat(), lng: m.getCenter().lng() });

const lowLatitude = String(m.getBounds().getSouthWest().lat());
Expand Down Expand Up @@ -86,7 +101,7 @@
zoom={zoom}
center={center}
>
{data.map(({ celebs, ...restaurant }) => {
{data?.map(({ celebs, ...restaurant }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

data가 존재하지 않았을 어떤 UI를 보여줄 지 같이 고려를 해보면 좋을 거 같아요!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

맞아요.. 고려해야합니다..
이 부분은 새로 이슈를 파서 추가해보도록 할게요~!

const { lat, lng } = restaurant;
return (
<OverlayMarker
Expand All @@ -98,7 +113,7 @@
);
})}
{myPosition && <OverlayMyLocation position={myPosition} />}
{loading && (
{(loadingData || loading) && (
<LoadingUI>
<LoadingDots />
</LoadingUI>
Expand Down Expand Up @@ -132,8 +147,7 @@
right: calc(50% - 41px);

width: 82px;

padding: 1.6rem 2.4rem;
height: 40px;
`;

const StyledMyPositionButtonUI = styled.button`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { styled } from 'styled-components';
import { paintSkeleton } from '~/styles/common';

interface ProfileImageSkeletonProps {
size: number;
}

function ProfileImageSkeleton({ size }: ProfileImageSkeletonProps) {
return <StyledProfileImageSkeleton size={size} />;
}

export default ProfileImageSkeleton;

const StyledProfileImageSkeleton = styled.div<{ size: number }>`
${paintSkeleton}
width: ${({ size }) => (size ? `${size}px` : '100%')};
height: ${({ size }) => (size ? `${size}px` : 'auto')};
border-radius: 50%;
background: none;
`;
7 changes: 4 additions & 3 deletions frontend/src/components/RestaurantCard/RestaurantCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { styled } from 'styled-components';
import { BORDER_RADIUS, FONT_SIZE, truncateText } from '~/styles/common';
import { BORDER_RADIUS, FONT_SIZE, paintSkeleton, truncateText } from '~/styles/common';
import ProfileImage from '../@common/ProfileImage';
import { BASE_URL } from '~/App';

Expand Down Expand Up @@ -35,7 +35,7 @@ function RestaurantCard({

return (
<StyledContainer onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}images-data/${images[0].name}`} type={type} />
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}/images-data/${images[0].name}`} type={type} />
<section>
<StyledInfo>
<StyledCategory>{category}</StyledCategory>
Expand Down Expand Up @@ -71,11 +71,12 @@ const StyledContainer = styled.div`
`;

const StyledImage = styled.img<{ type: 'list' | 'map' }>`
${paintSkeleton}
width: 100%;
aspect-ratio: 1.05 / 1;
border-radius: ${({ type }) =>
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0`};
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0 `};
object-fit: cover;
`;
Expand Down
92 changes: 92 additions & 0 deletions frontend/src/components/RestaurantCard/RestaurantCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { styled } from 'styled-components';
import ProfileImageSkeleton from '../@common/ProfileImage/ProfileImageSkeleton';
import { BORDER_RADIUS, paintSkeleton } from '~/styles/common';

function RestaurantCardSkeleton() {
return (
<StyledContainer>
<StyledImage />
<section>
<StyledInfo>
<StyledCategory />
<StyledName />
<StyledAddress />
<StyledAddress />
</StyledInfo>
<StyledProfileImageSection>
<ProfileImageSkeleton size={42} />
</StyledProfileImageSection>
</section>
</StyledContainer>
);
}

export default RestaurantCardSkeleton;

const StyledContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: start;
gap: 0.8rem;

width: 100%;
height: 100%;

& > section {
display: flex;
justify-content: space-between;
}

cursor: pointer;
`;

const StyledImage = styled.div`
${paintSkeleton}
width: 100%;
aspect-ratio: 1.05 / 1;

object-fit: cover;

border-radius: ${BORDER_RADIUS.md};
`;

const StyledInfo = styled.div`
display: flex;
flex: 1;
flex-direction: column;
gap: 0.4rem;

position: relative;

width: 100%;

padding: 0.4rem;
`;

const StyledName = styled.h5`
${paintSkeleton}
width: 100%;
height: 20px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledAddress = styled.span`
${paintSkeleton}
width: 50%;
height: 12px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledCategory = styled.span`
${paintSkeleton}
width: 40%;
height: 12px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledProfileImageSection = styled.div`
align-self: flex-end;
`;
56 changes: 56 additions & 0 deletions frontend/src/components/RestaurantCardList/RestaurantCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { styled } from 'styled-components';
import { useEffect, useState } from 'react';
import RestaurantCard from '../RestaurantCard/RestaurantCard';
import { FONT_SIZE } from '~/styles/common';
import RestaurantCardListSkeleton from './RestaurantCardListSkeleton';

import type { RestaurantData, RestaurantListData } from '~/@types/api.types';

interface RestaurantCardListProps {
restaurantDataList: RestaurantListData | null;
loading: boolean;
setHoveredId: React.Dispatch<React.SetStateAction<number>>;
}

function RestaurantCardList({ restaurantDataList, loading, setHoveredId }: RestaurantCardListProps) {
const [prevCardNumber, setPrevCardNumber] = useState(18);
Copy link
Collaborator

Choose a reason for hiding this comment

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

restaurant List 개수에 따라 스켈레톤 UI 개수를 지정하는 로직 이군요!!
디테일 너무 좋습니다!!

해당 로직의 경우 커스텀 훅으로 분리를 해도 좋을 거 같아요!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

저도 커스텀 훅 분리를 좋아하는 사람으로써 분리를 하라면 하겠지만, 계속 살펴보니 분리를 해서 얻을 수 있는 장점이 크게 없어 보여요..!
그렇게 생각한 이유는...

  • 로직이 아직까지는 짧아서 한눈에 전체 코드를 볼 수 있어요.
  • 커스텀 훅을 사용해서 네이밍 직관성을 올려줄 수 있겠지만, 지금 상태 네이밍인 prevCardNumber도 충분히 직관적이라고 생각해요.
  • 위 로직은 restaurantDataList 라는 어찌보면 큰 정보를 의존하고 있어요.
    • 커스텀 훅으로 분리한다면 restaurantDataList 를 전달해야 하는데, restaurantDataList가 기존 코드와 훅 두 로직에 쓰이면서 오히려 과한 인수 전달이 이루어지는 것 같아요.

이에 대해서 푸만능은 어떻게 생각하시나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

다시 보니 저 매직넘버 18 은 상수로 분리할 필요는 있어보이네요! ㅎㅎ


useEffect(() => {
if (restaurantDataList) setPrevCardNumber(restaurantDataList.currentElementsCount);
}, [restaurantDataList?.currentElementsCount]);

if (!restaurantDataList || loading) return <RestaurantCardListSkeleton cardNumber={prevCardNumber} />;

return (
<div>
<StyledCardListHeader>음식점 수 {restaurantDataList.totalElementsCount}</StyledCardListHeader>
<StyledRestaurantCardList>
{restaurantDataList.content?.map(({ celebs, ...restaurant }: RestaurantData) => (
<RestaurantCard restaurant={restaurant} celebs={celebs} size="42px" setHoveredId={setHoveredId} />
))}
</StyledRestaurantCardList>
</div>
);
}

export default RestaurantCardList;

const StyledCardListHeader = styled.p`
margin: 3.2rem 2.4rem;
font-size: ${FONT_SIZE.md};
`;

const StyledRestaurantCardList = styled.div`
display: grid;
gap: 4rem 2.4rem;
height: 100%;
margin: 0 2.4rem;
grid-template-columns: 1fr 1fr 1fr;
@media screen and (width <= 1240px) {
grid-template-columns: 1fr 1fr;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { styled } from 'styled-components';
import RestaurantCardSkeleton from '../RestaurantCard/RestaurantCardSkeleton';
import { BORDER_RADIUS, paintSkeleton } from '~/styles/common';

interface RestaurantCardListSkeletonProps {
cardNumber: number;
}

function RestaurantCardListSkeleton({ cardNumber }: RestaurantCardListSkeletonProps) {
return (
<div>
<StyledCardListHeader />
<StyledRestaurantCardList>
{Array.from({ length: cardNumber }, () => (
<RestaurantCardSkeleton />
))}
</StyledRestaurantCardList>
</div>
);
}

export default RestaurantCardListSkeleton;

const StyledCardListHeader = styled.p`
${paintSkeleton}
width: 35%;
height: 16px;
margin: 3.2rem 2.4rem;
border-radius: ${BORDER_RADIUS.xs};
`;

const StyledRestaurantCardList = styled.div`
display: grid;
gap: 4rem 2.4rem;
height: 100%;
margin: 0 2.4rem;
grid-template-columns: 1fr 1fr 1fr;
@media screen and (width <= 1240px) {
grid-template-columns: 1fr 1fr;
}
`;
3 changes: 3 additions & 0 deletions frontend/src/components/RestaurantCardList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RestaurantCardList from './RestaurantCardList';

export default RestaurantCardList;
19 changes: 19 additions & 0 deletions frontend/src/hooks/useOnClickOuside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, RefObject } from 'react';

export default function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,
handler: (event?: Event | MouseEvent) => void,
) {
useEffect(() => {
function onClickHandler(event: Event | MouseEvent) {
if (!ref?.current || ref?.current.contains(event?.target as Node)) {
return;
}
handler(event);
}
window.addEventListener('click', onClickHandler);
return () => {
window.removeEventListener('click', onClickHandler);
};
}, [ref, handler]);
}
Loading