Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 60f8707
Author: Jeremy <[email protected]>
Date:   Mon Jul 31 14:41:46 2023 +0900

    feat: 레스토랑 카드 및 마커 클릭 이벤트 변경 (#198)

    * feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)

    * style: 마커 호버시 마커 강조 (#192)

    * feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)

    * refactor: 불필요한 코드 제거 (#192)

    - 레스토랑 카드 클릭시 맵 모달 이벤트 제거
    - 마커 클릭시 맵 모달 이벤트 제거

    * feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)

    * style: 레스토랑 오버레이 스타일 수정 (#192)

    * refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)

    * feat: 마커 클릭시 강조 효과 주기 (#192)

    * Squashed commit of the following:

    commit 3442d16
    Author: Minjae Kim <[email protected]>
    Date:   Fri Jul 28 15:06:21 2023 +0900

        design: 전체 카테고리에 해당하는 이미지 수정 (#195) (#196)

    commit 2112803
    Author: 황준승 <[email protected]>
    Date:   Thu Jul 27 16:45:06 2023 +0900

        feat: 음식점 리스트 중복 필터링 기능 구현 (#186)

        * refactor:  지도 boundary 타입 추가 및 음식점 카테고리 타입 일부 수정 (#180)

        * feat: 셀럽 및 음식점 카테고리 별 필터링 기능 추가 (#180)

        * refactor: getQueryString 로직 분리 및 적용 (#180)

        * feat: CelebDropDown 및 CategoryNavbar에 전체 버튼 추가 (#184)

        * fix: 필터링 클릭 시 렌더링이 한 박자 늦게되는 오류 해결 (#184)

        * refactor: Map 컴포넌트에서 사용하지 않는 props 속성 제거 (#184)

        * refactor: celeb 전체를 나타내는 상태값을 -1로 변경 (#184)

        * feat: CelebDropDown blur 기능 추가 (#184)

        * fix: 불필요한 useEffect dependency 제거 (#184)

        * feat: Restaurant_Category에 전체 옵션 추가 (#184)

        * fix: CelebId 초기값 수정 (#184)

        * fix: API 명세서 수정에 따른 데이터 타입 변경 (#184)

        * refactor: NavButton props 프로퍼티 수정으로 인한 코드 수정 (#184)

        * refactor: css 선언방식을 삼항연산자를 && 로 변경 (#184)

        * fix: NavButton 불필요한 hover 기능 제거 (#184)

        Changed:
        hover 이벤트 && 연산자를 삼항연산자 사용으로 변경

        * refactor: NavButton 컴포넌트를 NavItem 컴포넌트로 네이밍 수정 (#184)

        ---------

        Co-authored-by: d0dam <[email protected]>
        Co-authored-by: Jeremy <[email protected]>

    * feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)

    * style: 파일명 오류 수정 (#192)

    * refactor: baseURL 환경변수 설정 및 type import 분리

    * feat: RestaurantCard 컴포넌트 props 수정 (#192)

    onClick을 optional로 수정

    * refactor: getQuadrant 리팩터링 (#192)

    * style: 상태 네이밍 수정 (#192)

    mainPosition -> currentCenter

    * feat: 음식점 카드 호버시 해당 음식점 마커 강조 (#192)

    * design: 강조시 애니메이션 효과 추가 및 음식점 리스트 스타일 수정 (#192)

    * refactor: 음식점 카드 호버시 마커 강조 로직 변경 (#192)

    * refactor: 프로필 이미지 컴포넌트 Props 타입 수정 (#192)

    size: number => string

    * fix: setHoverId가 없을 때 default value 설정 (#192)
  • Loading branch information
D0Dam committed Jul 31, 2023
1 parent d11c319 commit 7dc4a3e
Show file tree
Hide file tree
Showing 16 changed files with 118 additions and 53 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import MainPage from './pages/MainPage';

export const { BASE_URL } = process.env;

function App() {
return <MainPage />;
}
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/components/@common/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useState } from 'react';
import { Wrapper, Status } from '@googlemaps/react-wrapper';
import { styled } from 'styled-components';
import OverlayMarker from './OverlayMarker';
import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import MapContent from './MapContent';
import OverlayMyLocation from './OverlayMyLocation';
import LoadingDots from '../LoadingDots';
Expand All @@ -12,11 +10,15 @@ import LeftBracket from '~/assets/icons/left-bracket.svg';
import RightBracket from '~/assets/icons/right-bracket.svg';
import Minus from '~/assets/icons/minus.svg';
import Plus from '~/assets/icons/plus.svg';
import { RestaurantData } from '~/@types/api.types';
import getQuadrant from '~/utils/getQuadrant';
import OverlayMarker from './OverlayMarker';

import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { RestaurantData } from '~/@types/api.types';

interface MapProps {
data: RestaurantData[];
hoveredId: number | null;
setBoundary: React.Dispatch<React.SetStateAction<CoordinateBoundary>>;
toggleMapExpand: () => void;
loadingData: boolean;
Expand All @@ -42,22 +44,24 @@ const StyledMapLoadingContainer = styled.section`
background-color: var(--gray-2);
`;

function Map({ data, setBoundary, toggleMapExpand, loadingData }: MapProps) {
const JamsilCampus = { lat: 37.515271, lng: 127.1029949 };

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);
const [isMapExpanded, setIsMapExpanded] = useState(false);
const [loading, setLoading] = useState(false);
const [mainPosition, setMainPosition] = useState({ lat: 37.5057482, lng: 127.050727 });
const [currentCenter, setCurrentCenter] = useState<Coordinate>(JamsilCampus);

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

const onIdle = (m: google.maps.Map) => {
setZoom(m.getZoom()!);
setMainPosition({ lat: m.getCenter().lat(), lng: m.getCenter().lng() });
setCurrentCenter({ lat: m.getCenter().lat(), lng: m.getCenter().lng() });

const lowLatitude = String(m.getBounds().getSouthWest().lat());
const highLatitude = String(m.getBounds().getNorthEast().lat());
Expand Down Expand Up @@ -103,7 +107,8 @@ function Map({ data, setBoundary, toggleMapExpand, loadingData }: MapProps) {
<OverlayMarker
restaurant={restaurant}
celeb={celebs[0]}
quadrant={getQuadrant(mainPosition, { lat, lng })}
quadrant={getQuadrant(currentCenter, { lat, lng })}
isRestaurantHovered={restaurant.id === hoveredId}
/>
);
})}
Expand Down
42 changes: 31 additions & 11 deletions frontend/src/components/@common/Map/OverlayMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,65 @@
import styled, { keyframes } from 'styled-components';
import styled, { css, keyframes } from 'styled-components';
import { useRef, useState } from 'react';
import ProfileImage from '../ProfileImage';
import Overlay from './Overlay/Overlay';
import type { Celeb } from '~/@types/celeb.types';
import { Restaurant } from '~/@types/restaurant.types';
import RestaurantCard from '~/components/RestaurantCard';
import useOnClickOutside from '~/hooks/useOnClickOutside';

import type { Quadrant } from '~/utils/getQuadrant';
import useOnClickOutside from '~/hooks/useOnClickOuside';
import type { Restaurant } from '~/@types/restaurant.types';
import type { Celeb } from '~/@types/celeb.types';

interface OverlayMarkerProps {
celeb: Celeb;
map?: google.maps.Map;
restaurant: Restaurant;
quadrant: Quadrant;
isRestaurantHovered: boolean;
}

function OverlayMarker({ celeb, restaurant, map, quadrant }: OverlayMarkerProps) {
function OverlayMarker({ celeb, restaurant, map, quadrant, isRestaurantHovered }: OverlayMarkerProps) {
const { lat, lng } = restaurant;
const [isClicked, setIsClicked] = useState(false);
const ref = useRef();
useOnClickOutside(ref, () => setIsClicked(false));

const clickMarker = () => setIsClicked(true);

return (
map && (
<Overlay position={{ lat, lng }} map={map} zIndex={isClicked ? 18 : 0}>
<StyledMarker onClick={() => setIsClicked(true)} isClicked={isClicked} ref={ref}>
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} border />
<Overlay position={{ lat, lng }} map={map} zIndex={isClicked || isRestaurantHovered ? 18 : 0}>
<StyledMarker onClick={clickMarker} isClicked={isClicked} isRestaurantHovered={isRestaurantHovered} ref={ref}>
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} border size="100%" />
</StyledMarker>
{isClicked && (
<StyledModal quadrant={quadrant}>
<RestaurantCard restaurant={restaurant} onClick={() => {}} type="map" />
<RestaurantCard restaurant={restaurant} type="map" />
</StyledModal>
)}
</Overlay>
)
);
}

const StyledMarker = styled.div<{ isClicked: boolean }>`
const scaleUp = keyframes`
0% {
transform: scale(1);
}
100% {
transform: scale(1.5);
}
`;

const StyledMarker = styled.div<{ isClicked: boolean; isRestaurantHovered: boolean }>`
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
border: ${({ isClicked }) => (isClicked ? '3px solid var(--orange-2)' : '3px solid transparent')};
border: ${({ isClicked, isRestaurantHovered }) =>
isClicked || isRestaurantHovered ? '3px solid var(--orange-2)' : '3px solid transparent'};
border-radius: 50%;
transition: transform 0.2s ease-in-out;
Expand All @@ -54,6 +68,12 @@ const StyledMarker = styled.div<{ isClicked: boolean }>`
&:hover {
transform: scale(1.5);
}
${({ isRestaurantHovered }) =>
isRestaurantHovered &&
css`
animation: ${scaleUp} 0.2s ease-in-out forwards;
`}
`;

const fadeInAnimation = keyframes`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export const Default: Story = {
args: {
name: '누군가',
imageUrl: 'https://avatars.githubusercontent.com/u/51052049?v=4',
size: 64,
size: '64px',
},
};
8 changes: 4 additions & 4 deletions frontend/src/components/@common/ProfileImage/ProfileImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ProfileImageProps extends React.HTMLAttributes<HTMLImageElement> {
name: string;
imageUrl: string;
border?: boolean;
size?: number;
size?: string;
}

function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...props }: ProfileImageProps) {
Expand All @@ -13,9 +13,9 @@ function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...prop

export default ProfileImage;

const StyledProfile = styled.img<{ size: number; border: boolean }>`
width: ${({ size }) => (size ? `${size}px` : '100%')};
height: ${({ size }) => (size ? `${size}px` : 'auto')};
const StyledProfile = styled.img<{ size: string; border: boolean }>`
width: ${({ size }) => size || 'auto'};
height: ${({ size }) => size || 'auto'};
border-radius: 50%;
background: none;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/CategoryNavbar/CategoryNavbar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState } from 'react';
import styled from 'styled-components';
import { RestaurantCategory } from '~/@types/restaurant.types';
import NavItem from '~/components/@common/NavButton/NavButton';

import isEqual from '~/utils/compare';

import type { RestaurantCategory } from '~/@types/restaurant.types';

interface Category {
label: RestaurantCategory;
icon: React.ReactNode;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CelebBanner/CelebBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function CelebBanner({
return (
<StyledContainer background={backgroundImageUrl}>
<StyledCelebInfo>
<ProfileImage name={name} imageUrl={profileImageUrl} size={172} />
<ProfileImage name={name} imageUrl={profileImageUrl} size="172px" />
<StyledName>{name}</StyledName>
<StyledDetail>
{youtubeChannelName} 구독자 {subscriberCount / 10_000}만명 ∙ 음식점 {restaurantCount}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CelebDropDown/CelebDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function CelebDropDown({ celebs, externalOnClick, isOpen = false }: DropDownProp
{celebs.map(({ id, name, profileImageUrl }) => (
<StyledDropDownOption data-id={id} onMouseDown={onSelection(name)}>
<div>
<ProfileImage name={name} imageUrl={profileImageUrl} size={20} />
<ProfileImage name={name} imageUrl={profileImageUrl} size="20px" />
{name}
</div>
{isEqual(selected, name) && <SearchIcon />}
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/components/MapModalContent/MapModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { styled } from 'styled-components';
import { RestaurantModalInfo } from '~/@types/restaurant.types';
import { BORDER_RADIUS, FONT_SIZE } from '~/styles/common';
import TextButton from '../@common/Button';
import { BASE_URL } from '~/App';

interface MapModalContentProps {
content: RestaurantModalInfo;
Expand All @@ -17,10 +18,7 @@ function MapModalContent({ content }: MapModalContentProps) {
<div>{roadAddress}</div>
<div>{phoneNumber}</div>
</div>
<StyledRestaurantImage
src={`http://3.35.157.27:3000/images-data/${images[0].name}`}
alt={`${name} 식당 이미지`}
/>
<StyledRestaurantImage src={`${BASE_URL}images-data/${images[0].name}`} alt={`${name} 식당 이미지`} />
</StyledRestaurantInfo>
<TextButton
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ export const Default: Story = {
'https://yt3.googleusercontent.com/sL5ugPfl9vvwRwhf6l5APY__BZBw8qWiwgHs-uVsMPFoD5-a4opTJIcRSyrY8aY5LEESOMWJ=s176-c-k-c0x00ffffff-no-rj',
},
],
size: 42,
size: '42px',
},
};
35 changes: 24 additions & 11 deletions frontend/src/components/RestaurantCard/RestaurantCard.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { styled } from 'styled-components';
import { BORDER_RADIUS, FONT_SIZE, paintSkeleton, truncateText } from '~/styles/common';
import ProfileImage from '../@common/ProfileImage';
import { Restaurant } from '~/@types/restaurant.types';
import { Celeb } from '~/@types/celeb.types';
import { BASE_URL } from '~/App';

import type { Celeb } from '~/@types/celeb.types';
import type { Restaurant } from '~/@types/restaurant.types';

interface RestaurantCardProps {
restaurant: Restaurant;
celebs?: Celeb[];
size?: number;
size?: string;
type?: 'list' | 'map';
onClick?: React.MouseEventHandler;
setHoveredId?: React.Dispatch<React.SetStateAction<number>>;
}

function RestaurantCard({ restaurant, celebs, size, type = 'list', onClick }: RestaurantCardProps) {
function RestaurantCard({
restaurant,
celebs,
size,
type = 'list',
onClick = () => {},
setHoveredId = () => {},
}: RestaurantCardProps) {
const { images, name, roadAddress, category } = restaurant;

const onMouseEnter = () => {
setHoveredId(restaurant.id);
};

const onMouseLeave = () => {
setHoveredId(null);
};

return (
<StyledContainer onClick={onClick}>
<StyledImage
alt={`${name} 대표 이미지`}
src={`http://3.35.157.27:3000/images-data/${images[0].name}`}
type={type}
loading="lazy"
/>
<StyledContainer onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}/images-data/${images[0].name}`} type={type} />
<section>
<StyledInfo>
<StyledCategory>{category}</StyledCategory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { styled } from 'styled-components';
import { useEffect, useState } from 'react';
import RestaurantCard from '../RestaurantCard/RestaurantCard';
import type { RestaurantData, RestaurantListData } from '~/@types/api.types';
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 }: RestaurantCardListProps) {
function RestaurantCardList({ restaurantDataList, loading, setHoveredId }: RestaurantCardListProps) {
const [prevCardNumber, setPrevCardNumber] = useState(18);

useEffect(() => {
Expand All @@ -24,7 +26,7 @@ function RestaurantCardList({ restaurantDataList, loading }: RestaurantCardListP
<StyledCardListHeader>음식점 수 {restaurantDataList.totalElementsCount}</StyledCardListHeader>
<StyledRestaurantCardList>
{restaurantDataList.content?.map(({ celebs, ...restaurant }: RestaurantData) => (
<RestaurantCard restaurant={restaurant} celebs={celebs} size={42} onClick={() => {}} />
<RestaurantCard restaurant={restaurant} celebs={celebs} size="42px" setHoveredId={setHoveredId} />
))}
</StyledRestaurantCardList>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/VideoPreview/VideoPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function VideoPreview({
</StyledVideoCover>
)}
<StyledVideoInfo>
<ProfileImage name={celebName} imageUrl={profileImageUrl} size={38} />
<ProfileImage name={celebName} imageUrl={profileImageUrl} size="38px" />
<StyledTitle>{title}</StyledTitle>
<StyledViewAndDate>
<div>{celebName}</div>
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/hooks/useOnClickOutside.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

0 comments on commit 7dc4a3e

Please sign in to comment.