diff --git a/package.json b/package.json index 7f46d344..9429b6ef 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/react-dom": "^18.0.0", "axios": "^0.27.2", "jotai": "^1.11.0", + "qs": "^6.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.36.1", @@ -69,5 +70,6 @@ "stylelint": "^14.9.1", "stylelint-config-standard": "^26.0.0", "stylelint-config-standard-scss": "^4.0.0" - } + }, + "proxy": "https://nid.naver.com" } diff --git a/src/App.tsx b/src/App.tsx index bbce53e9..484ce982 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,9 @@ import Withdrawal from 'pages/Setting/Withdrawal'; import Inquiry from 'pages/Inquiry'; import Myinquiry from 'pages/Inquiry/Myinquiry'; import Notice from 'pages/Notice'; +import KakaoLogin from 'pages/Auth/OAuth/KakaoLogin'; +import NaverLogin from 'pages/Auth/OAuth/NaverLogin'; +import GoogleLogin from 'pages/Auth/OAuth/GoogleLogin'; export default function App(): JSX.Element { return ( @@ -50,6 +53,9 @@ export default function App(): JSX.Element { } /> } /> } /> + } /> + } /> + } /> diff --git a/src/assets/images/home/location-marker.png b/src/assets/images/home/location-marker.png new file mode 100644 index 00000000..0e5ef3f0 Binary files /dev/null and b/src/assets/images/home/location-marker.png differ diff --git a/src/assets/images/home/selected-marker.png b/src/assets/images/home/selected-marker.png new file mode 100644 index 00000000..2f0a2d5d Binary files /dev/null and b/src/assets/images/home/selected-marker.png differ diff --git a/src/assets/images/search/defaultImg.png b/src/assets/images/search/defaultImg.png new file mode 100644 index 00000000..fac42a72 Binary files /dev/null and b/src/assets/images/search/defaultImg.png differ diff --git a/src/assets/svg/home/verticalDot.svg b/src/assets/svg/home/verticalDot.svg new file mode 100644 index 00000000..8a38c725 --- /dev/null +++ b/src/assets/svg/home/verticalDot.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/config/constants.ts b/src/config/constants.ts index 055db90a..eb03ad73 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -25,4 +25,4 @@ export const KAKAO_REDIRECT_URL = `https://kauth.kakao.com/oauth/authorize?clien // 네이버 OAuth export const NAVER_CLIENT_ID = checkEnvVar('REACT_APP_NAVER_CLIENT_ID'); -export const NAVER_REDIRECT_URL = `https://nid.naver.com/oauth2.0/authorize?client_id=${NAVER_CLIENT_ID}&redirect_uri=${SERVER_LOGIN_REDIRECT_URL}/naver&response_type=code`; +export const NAVER_REDIRECT_URL = `https://nid.naver.com/oauth2.0/authorize?client_id=${NAVER_CLIENT_ID}&redirect_uri=${SERVER_LOGIN_REDIRECT_URL}/naver&response_type=code&state=1`; diff --git a/src/pages/Auth/OAuth/GoogleLogin.tsx b/src/pages/Auth/OAuth/GoogleLogin.tsx new file mode 100644 index 00000000..7f3e52f2 --- /dev/null +++ b/src/pages/Auth/OAuth/GoogleLogin.tsx @@ -0,0 +1,44 @@ +import axios from 'axios'; +import { API_PATH } from 'config/constants'; +import { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useUpdateAuth } from 'store/auth'; + +export default function GoogleLogin() { + const location = useLocation(); + const updateAuth = useUpdateAuth(); + const navigate = useNavigate(); + const snsLogin = async () => { + try { + const code = location.search.slice(6, location.search.length).split('&')[0]; + const clientId = process.env.REACT_APP_GOOGLE_CLIENT_ID; + const clientSecret = process.env.REACT_APP_GOOGLE_CLIENT_SECRET; + const redirectUri = process.env.REACT_APP_SERVER_LOGIN_REDIRECT_URL; + const requestBody = `code=${code}&client_id=${clientId}&client_secret=${clientSecret}&redirect_uri=${redirectUri}/google&grant_type=authorization_code`; + + const googleResponse = await axios.post('https://oauth2.googleapis.com/token', requestBody); + const googleIdToken = googleResponse.data.id_token; + const googleLogin = await axios.post(`${API_PATH}/login/GOOGLE`, {}, { + headers: { + Authorization: googleIdToken, + }, + }); + sessionStorage.setItem('accessToken', googleLogin.data.accessToken); + localStorage.setItem('refreshToken', googleLogin.data.refreshToken); + updateAuth(); + } catch (e) { + // console.log(e); + } + }; + useEffect(() => { + if (location !== undefined) { + snsLogin(); + } else { + // url 오류로 인한 콜백 + navigate('/login'); + } + }); + return ( +
+ ); +} diff --git a/src/pages/Auth/OAuth/KakaoLogin.tsx b/src/pages/Auth/OAuth/KakaoLogin.tsx new file mode 100644 index 00000000..49059090 --- /dev/null +++ b/src/pages/Auth/OAuth/KakaoLogin.tsx @@ -0,0 +1,57 @@ +import { useLocation, useNavigate } from 'react-router-dom'; +import { useEffect } from 'react'; +import { API_PATH } from 'config/constants'; +import axios, { AxiosResponse } from 'axios'; +import qs from 'qs'; +import { useUpdateAuth } from 'store/auth'; + +interface KakaoResponse { + access_token: string, + expires_in: number, + refresh_token: string, + refresh_token_expires_in: number, + scope: string, + token_type: string, +} + +export default function KakaoLogin() { + const location = useLocation(); + const updateAuth = useUpdateAuth(); + const navigate = useNavigate(); + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }; + const data = { + grant_type: 'authorization_code', + client_id: process.env.REACT_APP_KAKAO_CLIENT_ID, + redirect_uri: `${process.env.REACT_APP_SERVER_LOGIN_REDIRECT_URL}/kakao`, + code: location.search.slice(6, location.search.length), + }; + const snsLogin = async () => { + try { + const response: AxiosResponse = await axios.post('https://kauth.kakao.com/oauth/token', qs.stringify(data), { headers }); + const kakaoAccessToken = response.data.access_token; + + const kakaoLogin = await axios.post(`${API_PATH}/login/KAKAO`, {}, { + headers: { + Authorization: kakaoAccessToken, + }, + }); + sessionStorage.setItem('accessToken', kakaoLogin.data.accessToken); + localStorage.setItem('refreshToken', kakaoLogin.data.refreshToken); + await updateAuth(); + } catch { + // + } + }; + useEffect(() => { + if (location !== undefined) { + snsLogin(); + } else { + navigate('/login'); + } + }); + return ( +
+ ); +} diff --git a/src/pages/Auth/OAuth/NaverLogin.tsx b/src/pages/Auth/OAuth/NaverLogin.tsx new file mode 100644 index 00000000..b58e5985 --- /dev/null +++ b/src/pages/Auth/OAuth/NaverLogin.tsx @@ -0,0 +1,40 @@ +import axios from 'axios'; +import { API_PATH } from 'config/constants'; +import { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useUpdateAuth } from 'store/auth'; + +export default function NaverLogin() { + const location = useLocation(); + const updateAuth = useUpdateAuth(); + const navigate = useNavigate(); + const clientId = process.env.REACT_APP_NAVER_CLIENT_ID; + const clientSecret = process.env.REACT_APP_NAVER_CLIENT_SECRET; + const snsLogin = async () => { + try { + const naverResponse = await axios.post(`/oauth2.0/token?grant_type=authorization_code&client_secret=${clientSecret}&client_id=${clientId}&code=${location.search.split('&')[0].slice(6)}&state=1`); + const headers = { + authorization: naverResponse.data.access_token, + }; + const result = await axios.post(`${API_PATH}/login/NAVER`, {}, { + headers, + }); + sessionStorage.setItem('accessToken', result.data.accessToken); + localStorage.setItem('refreshToken', result.data.refreshToken); + await updateAuth(); + } catch (e) { + // + } + }; + useEffect(() => { + if (location !== undefined) { + snsLogin(); + } else { + // url 오류로 인한 콜백 + navigate('/login'); + } + }); + return ( +
+ ); +} diff --git a/src/pages/Home/components/Map/components/MarkerHtml/MarkerHtml.module.scss b/src/pages/Home/components/Map/components/MarkerHtml/MarkerHtml.module.scss index 238a9c6f..8810ca79 100644 --- a/src/pages/Home/components/Map/components/MarkerHtml/MarkerHtml.module.scss +++ b/src/pages/Home/components/Map/components/MarkerHtml/MarkerHtml.module.scss @@ -1,119 +1,142 @@ @use "src/utils/styles/mediaQuery" as media; -.marker { +.container { display: flex; justify-content: center; + flex-direction: column; align-items: center; - border: 2px solid #ff7f23; - border-radius: 33.5px; - background-color: #ffffff; - width: 100%; - height: 68px; - font-weight: 500; +} - &::after { - content: ""; - position: absolute; - border-top: 14px solid #ff7f23; - border-left: 14px solid transparent; - border-right: 14px solid transparent; - top: 68px; - left: 22px; - z-index: -1; +.marker { + width: 69px; + height: 69px; + + @include media.media-breakpoint-down(mobile) { + width: 48px; + height: 48px; } &--clicked { + position: absolute; + left: 20px; display: flex; + flex-direction: column; justify-content: center; align-items: center; - border: 2px solid #ff7f23; - border-radius: 33.5px; - background-color: #ff7f23; - width: 100%; - height: 68px; - color: white; - font-weight: 500; + width: 175px; + height: 69px; - &::after { - content: ""; - position: absolute; - border-top: 14px solid #ff7f23; - border-left: 14px solid transparent; - border-right: 14px solid transparent; - top: 68px; - left: 22px; + @include media.media-breakpoint-down(mobile) { + width: 125px; + height: 45px; } } } .bubble { display: flex; + width: 100%; + height: 100%; align-items: center; box-sizing: content-box; &__photo img { - display: flex; - align-items: center; - width: 62px; - height: 62px; + width: 43px; + height: 43px; border-radius: 89.5px; + position: absolute; + top: 10px; + left: 29px; + + @include media.media-breakpoint-down(mobile) { + width: 30px; + height: 30px; + top: 7px; + left: 35px; + } } - &__name { - margin-left: 10px; - padding-right: 10px; + &__photo--clicked img { + width: 56.007px; + height: 55.723px; + border-radius: 89.5px; + position: absolute; + top: 2px; + left: 22px; @include media.media-breakpoint-down(mobile) { - margin: 0; - padding: 0; - position: absolute; - top: 85px; - right: 0; - width: 100%; - display: flex; - justify-content: center; - font-size: 15px; - font-weight: 500; - line-height: 15px; - color: #222222; - // stylelint-disable-next-line - text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, - 1px 1px 0 white; + width: 36px; + height: 36px; + top: 2px; + left: 21px; } } - &--clicked { - display: flex; - align-items: center; + &__name { + width: 100px; + font-size: 18px; + font-weight: 500; + text-align: center; - &__photo img { - display: flex; - align-items: center; - width: 65px; - height: 65px; - border-radius: 89.5px; + @include media.media-breakpoint-down(mobile) { + font-size: 12px; + font-weight: 700; + color: #222222; } - &__name { - margin-left: 10px; - padding-right: 10px; + &--clicked { + display: flex; + justify-content: center; + align-items: center; + width: 100px; + height: 40px; + position: absolute; + text-align: center; + top: 10px; + left: 75px; + font-size: 20px; + font-weight: 700; + color: #fe7f23; @include media.media-breakpoint-down(mobile) { - padding: 0; - display: none; + width: 70px; + height: 34px; + top: 3px; + left: 55px; + font-size: 14px; } } } -} -.dummy { - &::after { - content: ""; + &__index { + color: #ffffff; position: absolute; - top: 67px; - left: 23px; - border-top: 12px solid #ffffff; - border-left: 12px solid transparent; - border-right: 12px solid transparent; + top: 18px; + left: 43px; + font-size: 24px; + font-weight: 700; + z-index: 999; + + @include media.media-breakpoint-down(mobile) { + font-size: 16px; + top: 13px; + left: 46px; + } + + &--clidked { + color: #ffffff; + position: absolute; + left: 43px; + font-size: 24px; + font-weight: 700; + z-index: 999; + top: 15px; + + @include media.media-breakpoint-down(mobile) { + font-size: 16px; + top: 10px; + left: 35px; + } + } } } diff --git a/src/pages/Home/components/Map/components/MarkerHtml/index.tsx b/src/pages/Home/components/Map/components/MarkerHtml/index.tsx index 6ada6e67..6bf8c871 100644 --- a/src/pages/Home/components/Map/components/MarkerHtml/index.tsx +++ b/src/pages/Home/components/Map/components/MarkerHtml/index.tsx @@ -1,33 +1,43 @@ +import Default from 'assets/images/search/defaultImg.png'; +import MarkerLogo from 'assets/images/home/location-marker.png'; +import Selected from 'assets/images/home/selected-marker.png'; import styles from './MarkerHtml.module.scss'; -export function MarkerHtml(defaultImg:string, name:string) { +export function MarkerHtml(thumbnail:string | null, name:string, index:number) { return ` -
+
+ marker
- 음식 이미지 + +
+
+ ${!thumbnail ? index : ''} +
${name}
-
-
+
`; } -export function ClickedMarkerHtml(defaultImg:string, name:string) { +export function ClickedMarkerHtml(thumbnail:string | null, name:string, index:number) { return ` -
-
-
- 음식 이미지 +
+ +
+
+
-
+
+ ${!thumbnail ? index : ''} +
+
${name}
-
`; } diff --git a/src/pages/Home/components/Map/components/MobileOptions/MobileOptions.module.scss b/src/pages/Home/components/Map/components/MobileOptions/MobileOptions.module.scss index eba18f57..bd779712 100644 --- a/src/pages/Home/components/Map/components/MobileOptions/MobileOptions.module.scss +++ b/src/pages/Home/components/Map/components/MobileOptions/MobileOptions.module.scss @@ -1,5 +1,8 @@ -.top-options { +.nav { position: absolute; + display: grid; + grid-template-columns: 1fr auto; + gap: 50px; top: 20px; left: 50%; transform: translate(-50%); @@ -19,38 +22,62 @@ color: #666666; padding-left: 18px; padding-right: 15px; - margin-bottom: 10px; + } + + &__filter { + width: 48px; + height: 48px; + background-color: #ffffff; + border-radius: 37px; + opacity: 0.8; + box-shadow: 2px 3px 12px 1px rgba(0 0 0 / 10%); + border: 1px solid #eeeeee; + position: absolute; + top: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; + + &--clicked { + svg path { + stroke: #ff7f23; + } + } } &__list { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 4px; - justify-items: center; + display: flex; + float: right; + flex-direction: column; + justify-content: space-around; + width: 118px; + height: 78px; + border-radius: 15px; + opacity: 0.8; + border: 1px solid #eeeeee; + background-color: #ffffff; + box-shadow: 2px 3px 12px 1px rgba(0 0 0 / 10%); } &__text { display: flex; - justify-content: center; align-items: center; - box-sizing: border-box; width: 100%; height: 30px; - background: rgb(251 251 250 / 90%); - border: 1px solid #c4c4c4; - box-shadow: 2px 3px 12px 1px rgb(0 0 0 / 10%); - border-radius: 37px; + margin-left: 10px; font-weight: 500; font-size: 12px; color: #595959; cursor: pointer; + background-color: transparent; + border: none; svg { padding-right: 5px; } - &--selected { - border: 1px solid #ff7f23; + &--clicked { color: #ff7f23; svg path { diff --git a/src/pages/Home/components/Map/components/MobileOptions/index.tsx b/src/pages/Home/components/Map/components/MobileOptions/index.tsx index 2dcc749f..cee53828 100644 --- a/src/pages/Home/components/Map/components/MobileOptions/index.tsx +++ b/src/pages/Home/components/Map/components/MobileOptions/index.tsx @@ -1,13 +1,15 @@ import { ReactComponent as LensIcon } from 'assets/svg/home/lens.svg'; -import { ReactComponent as StoreFrontIcon } from 'assets/svg/home/storefront.svg'; import { ReactComponent as GroupIcon } from 'assets/svg/home/group.svg'; import { ReactComponent as BookMarkIcon } from 'assets/svg/home/bookmark.svg'; +import { ReactComponent as VerticalDot } from 'assets/svg/home/verticalDot.svg'; import { Link } from 'react-router-dom'; import cn from 'utils/ts/classNames'; import { useState } from 'react'; +import useBooleanState from 'utils/hooks/useBooleanState'; import styles from './MobileOptions.module.scss'; export default function MobileOptions(): JSX.Element { + const [filter, , ,toggle] = useBooleanState(false); const [selected, setSelected] = useState(''); const handleClick = (type: string) => { setSelected(type); @@ -15,30 +17,30 @@ export default function MobileOptions(): JSX.Element { }; return (
-
+
- + 검색어를 입력해주세요.
- + + {filter && ( +
- - +
+ )}
); diff --git a/src/pages/Home/components/Map/index.tsx b/src/pages/Home/components/Map/index.tsx index 23a63c43..dae9ae33 100644 --- a/src/pages/Home/components/Map/index.tsx +++ b/src/pages/Home/components/Map/index.tsx @@ -1,7 +1,6 @@ import { useEffect, useRef } from 'react'; import useGeolocation from 'utils/hooks/useGeolocation'; import MARKER from 'pages/Home/static/marker'; -import defaultImage from 'assets/images/search/default-image.png'; import useMediaQuery from 'utils/hooks/useMediaQuery'; import styles from './Map.module.scss'; import OptionButtons from './components/OptionButtons'; @@ -12,6 +11,7 @@ interface MarkerType { latitude: number; longitude: number; placeName: string; + index: number; } const options = { maximumAge: 1000, @@ -26,14 +26,19 @@ export default function Map(): JSX.Element { naver.maps.Event.addListener(markerCur, 'click', () => { if (selectedMarker.current) { selectedMarker.current.setIcon({ - content: MarkerHtml(defaultImage, selectedMarker.current.getTitle()), + content: MarkerHtml( + '', + selectedMarker.current.getTitle(), + selectedMarker.current.getZIndex(), + ), size: new naver.maps.Size(50, 52), anchor: new naver.maps.Point(25, 26), }); } markerCur.setIcon({ - content: ClickedMarkerHtml(defaultImage, item.placeName), + // 추후 각 마커벼로 이미지파일이 주어지면 첫번째 인자로 해당 이미지를 넘겨주도록 해야함 + content: ClickedMarkerHtml('', item.placeName, item.index), size: new naver.maps.Size(50, 52), anchor: new naver.maps.Point(25, 26), }); @@ -77,8 +82,9 @@ export default function Map(): JSX.Element { position: new naver.maps.LatLng(item.latitude, item.longitude), title: item.placeName, map: mapRef.current, + zIndex: item.index, icon: { - content: MarkerHtml(defaultImage, item.placeName), + content: MarkerHtml('', item.placeName, item.index), size: new naver.maps.Size(50, 52), anchor: new naver.maps.Point(25, 26), }, diff --git a/src/pages/Home/static/marker.ts b/src/pages/Home/static/marker.ts index dc52674b..4aa5d29b 100644 --- a/src/pages/Home/static/marker.ts +++ b/src/pages/Home/static/marker.ts @@ -1,43 +1,51 @@ const MARKER = [ { - placeName: '장소1', - latitude: 36.862341, - longitude: 127.209635, + placeName: '박순자 아우내순대', + latitude: 36.761724, + longitude: 127.299092, + index: 1, }, { - placeName: '장소2', - latitude: 36.762341, - longitude: 125.254645, + placeName: '집', + latitude: 36.343087, + longitude: 127.448879, + index: 2, }, { - placeName: '장소3', + placeName: '3', latitude: 36.722341, longitude: 127.269665, + index: 3, }, { - placeName: '장소4', + placeName: '4', latitude: 37.762341, longitude: 125.189675, + index: 4, }, { - placeName: '장소5', + placeName: '5', latitude: 36.862341, longitude: 127.254631, + index: 5, }, { - placeName: '장소6', + placeName: '6', latitude: 36.962341, longitude: 127.218645, + index: 6, }, { - placeName: '장소7', + placeName: '7', latitude: 36.562341, longitude: 127.299565, + index: 7, }, { - placeName: '장소8', + placeName: '8', latitude: 36.552341, longitude: 126.259775, + index: 8, }, ]; diff --git a/yarn.lock b/yarn.lock index 6a0cf345..61d68ccb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7589,6 +7589,13 @@ qs@6.10.3: dependencies: side-channel "^1.0.4" +qs@^6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"