From 2ebb3dd5a2fe5e79e75b52dc174f0e7ab5507997 Mon Sep 17 00:00:00 2001 From: HeeSeok-kim <106604926+HeeSeok-kim@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:43:28 +0900 Subject: [PATCH] =?UTF-8?q?[Deploy]:=20=EB=B0=B0=ED=8F=AC=20=20(#235)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 마이페이지 로컬주소 해결 (#206) * fix: 마운트시 나타나는 데이터 제거 (#224) * feat: silder 바 제작 * likeAcademy 버그 수정 (#229) * fix: 마운트시 나타나는 데이터 제거 * fix: get요청 확인 * fix: 머지 미반영 코드 추가 * fix: likeAcademy page 수정 * fix: 안 쓰는 변수 제거 * fix: scroll 없애고, 전체표시 * fix: 아코디언 가운데정렬, 메모 스크롤 적용 * fix: 불필요한 border제거 * hotfix: 바텀시트 수정 * hotfix: 좋아요 api 추가 및 ReviewPercent 타입 변경 * hotfix: 맵 페이지 검색바 로직 분리 및 지도가 보이지 않는 문제 수정 * hotfix: 스토리북 에러 수정 * hotfix: 검색결과 클릭시 이동 후 마커 표시되도록 수정 * feat: toast ui 생성 (#230) * hotfix: 필터 페이지 수정 * hotfix: 바텀시트 스토리북 삭제 * hotfix: 코드리뷰 반영 * fix: schedule detail api get요청 수정 반영 * hotfix: 코드리뷰 반영 * fix: 안 쓰는 변수 삭제 * fix: delete api수정 * fix: homepage settingpage잘 작동 (#232) --------- Co-authored-by: Eugene Kim Co-authored-by: sincerity developer <85999976+jisung24@users.noreply.github.com> Co-authored-by: Eugene Kim <67894159+eugene028@users.noreply.github.com> --- src/App.tsx | 2 +- .../BottomSheet/BottomSheetContent.tsx | 38 +++- .../BottomSheet/BottomSheetHeader.tsx | 19 +- .../bottomsheet/BottomSheet.stories.tsx | 56 ------ .../common/bottomsheet/BottomSheet.tsx | 42 ++-- src/components/common/header/Header.tsx | 3 - .../common/progressBar/ProgressBar.tsx | 4 - .../common/slider/Slider.stories.tsx | 2 +- src/components/common/slider/Slider.tsx | 154 ++++++++++---- src/components/common/toast/index.tsx | 4 +- src/components/layout/Layout.tsx | 3 + src/components/map/MapSearchBar.tsx | 132 ++++++++++++ src/components/map/NaverMap.tsx | 175 +++++++++------- src/components/review/ReviewBottomSheet.tsx | 8 +- .../scheduleDetail/ScheduleDetailApi.ts | 23 +-- .../scheduleDetail/ScheduleDetailType.ts | 9 +- src/libs/api/index.ts | 7 +- src/libs/api/mapapi/mapApi.ts | 12 ++ src/libs/api/mapapi/mapApiType.ts | 18 +- src/libs/api/mypage/myPageApi.ts | 2 +- src/libs/store/likeacademyAtom.tsx | 8 +- src/libs/store/mapFilterAtom.ts | 19 +- src/libs/store/mapInfoAtom.ts | 9 + src/pages/academy/AcademyDashboard.tsx | 13 +- .../academy/academyDetail/AcademySetting.tsx | 23 ++- src/pages/academy/addSchedule/index.tsx | 5 +- .../detailSchedule/DetailSchedulePage.tsx | 110 +++++----- src/pages/academy/editAcademy/index.tsx | 5 +- src/pages/calendar/data.ts | 29 --- src/pages/filter/FilterPage.tsx | 23 ++- src/pages/home/HomePage.tsx | 26 ++- src/pages/likeAcademy/LikeAcademy.tsx | 21 +- src/pages/map/MapPage.tsx | 189 +++--------------- src/pages/mypage/MyPage.tsx | 24 ++- src/pages/onboarding/OnbardingPage.tsx | 12 +- src/pages/schedule/new/index.tsx | 12 +- src/pages/setting/SettingPage.tsx | 2 +- 37 files changed, 712 insertions(+), 531 deletions(-) delete mode 100644 src/components/common/bottomsheet/BottomSheet.stories.tsx create mode 100644 src/components/map/MapSearchBar.tsx delete mode 100644 src/pages/calendar/data.ts diff --git a/src/App.tsx b/src/App.tsx index 74b06dac..44fde67e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import './styles/index.css' - import { RouterProvider } from 'react-router-dom' import { router } from './routes' + function App() { return } diff --git a/src/components/BottomSheet/BottomSheetContent.tsx b/src/components/BottomSheet/BottomSheetContent.tsx index 3728af1a..84e4dca9 100644 --- a/src/components/BottomSheet/BottomSheetContent.tsx +++ b/src/components/BottomSheet/BottomSheetContent.tsx @@ -17,9 +17,8 @@ const BottomSheetContent = ({ address, detailInfo }: BottomSheetContentProps) => { - const { lessonGetResponses, reviewPercentGetResponse, areaOfExpertise } = + const { lessonGetResponses, reviewPercentGetResponse, categories } = detailInfo - console.log(detailInfo) return (
@@ -27,7 +26,7 @@ const BottomSheetContent = ({
{`☎ ${number}`}
- +
{expanded && (
@@ -116,7 +115,7 @@ const BottomSheetContent = ({ className={ 'absolute text-center subHead-18 z-50 ml-[13px] mt-[7px]' }> - {'상담사 선생님이 친절해요'} + {'선생님이 친절해요 👨‍🏫'}
- {'시설이 좋아요'} + {'시설이 좋아요 🏫'}
- {'가격이 착해요'} + {'교육비가 저렴해요 💰'}
- {'학습 관리가 꼼꼼해요'} + {'교육 관리가 철저해요 📝'}
- {'선생님이 좋아요'} + {'학생에 대한 애정 가득 💓'} +
+ +
+
+
+
+ {'등하원이 편리해요 🚌'}
diff --git a/src/components/BottomSheet/BottomSheetHeader.tsx b/src/components/BottomSheet/BottomSheetHeader.tsx index ded7c85d..16a30a8d 100644 --- a/src/components/BottomSheet/BottomSheetHeader.tsx +++ b/src/components/BottomSheet/BottomSheetHeader.tsx @@ -1,26 +1,39 @@ import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' import { LikeBlank, LikeFilled } from '@/assets/icon' +import { postLike } from '@/libs/api/mapapi/mapApi.ts' const BottomSheetHeader = ({ title, - isLike + isLike, + academyId }: { title: string isLike: boolean + academyId: number }) => { + const likeMutation = useMutation({ + mutationFn: (academyId: number) => postLike({ academyId: academyId }), + onSuccess: () => {}, + onSettled: () => { + setLiked(!liked) + } + }) + const [liked, setLiked] = useState(isLike) //TODO: 좋아요 API 로직 추가 + return (

{title}

{liked ? ( setLiked(!liked)} + onClick={() => likeMutation.mutate(academyId)} /> ) : ( setLiked(!liked)} + onClick={() => likeMutation.mutate(academyId)} /> )}
diff --git a/src/components/common/bottomsheet/BottomSheet.stories.tsx b/src/components/common/bottomsheet/BottomSheet.stories.tsx deleted file mode 100644 index 58266fea..00000000 --- a/src/components/common/bottomsheet/BottomSheet.stories.tsx +++ /dev/null @@ -1,56 +0,0 @@ -// Button.stories.ts|tsx - -import type { Meta, StoryObj } from '@storybook/react' - -import BottomSheet from './BottomSheet' - -const meta: Meta = { - title: 'Components/BottomSheet', - component: BottomSheet, - argTypes: { - title: { - control: 'text' - } - } -} - -export default meta -type Story = StoryObj - -export const Default: Story = { - render: (args) => ( - - ) -} diff --git a/src/components/common/bottomsheet/BottomSheet.tsx b/src/components/common/bottomsheet/BottomSheet.tsx index 3112043e..dcfcefc5 100644 --- a/src/components/common/bottomsheet/BottomSheet.tsx +++ b/src/components/common/bottomsheet/BottomSheet.tsx @@ -1,8 +1,10 @@ import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' import BottomSheetContent from '@/components/BottomSheet/BottomSheetContent' import BottomSheetHeader from '@/components/BottomSheet/BottomSheetHeader' +import Loading from '@/components/Loading/Loading.tsx' import Spacing from '@/components/common/spacing/Spacing.tsx' -import { DetailAcademyResponse } from '@/libs/api/mapapi/mapApiType.ts' +import { getAcademyDetail } from '@/libs/api/mapapi/mapApi.ts' /** * @param title BottomSheet에 들어갈 Title을 입력합니다. @@ -13,15 +15,24 @@ interface BottomSheetProps { title: string address: string number: string - detailInfo: DetailAcademyResponse + academyId: number } const BottomSheet = ({ title = '학원명 입력', address, number, - detailInfo + academyId }: BottomSheetProps) => { const [expanded, setExpanded] = useState(false) + const { data: detailAcademy, isLoading } = useQuery({ + queryKey: ['academy', academyId], + queryFn: () => + getAcademyDetail({ + academyId: academyId + }), + enabled: academyId > -1 + }) + return ( <>
-
- - -
+ {isLoading && } + {detailAcademy && ( +
+ + +
+ )} ) diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index b619079d..45dea6c0 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -52,9 +52,6 @@ const Header = ({
-
alert('알림보기!')}> - -
diff --git a/src/components/common/progressBar/ProgressBar.tsx b/src/components/common/progressBar/ProgressBar.tsx index 0c74a880..34a89681 100644 --- a/src/components/common/progressBar/ProgressBar.tsx +++ b/src/components/common/progressBar/ProgressBar.tsx @@ -9,10 +9,6 @@ const ProgressBar = ({ fullStepNum: number step: number }) => { - if (step > fullStepNum) { - alert(`스텝은 전체 스텝 개수인 ${fullStepNum}보다 클 수 없습니다.`) - return <> - } const progressBarWidth = (step / fullStepNum) * 100 return (
diff --git a/src/components/common/slider/Slider.stories.tsx b/src/components/common/slider/Slider.stories.tsx index 188055bd..3758d8af 100644 --- a/src/components/common/slider/Slider.stories.tsx +++ b/src/components/common/slider/Slider.stories.tsx @@ -12,7 +12,7 @@ const meta: Meta = { render: function Render() { return (
- + console.log('')} />
) } diff --git a/src/components/common/slider/Slider.tsx b/src/components/common/slider/Slider.tsx index 701d0bd5..ed7ee524 100644 --- a/src/components/common/slider/Slider.tsx +++ b/src/components/common/slider/Slider.tsx @@ -1,49 +1,117 @@ import { useState } from 'react' -const Silder = ({ - minNum = 0, - maxNum = 100_000 -}: { - minNum: number - maxNum: number -}) => { - const [value, setValue] = useState(1) +const Silder = ({ onChange }: { onChange: () => void }) => { + const [value, setValue] = useState(0) + const parseAcademyFee = (value: number) => { + switch (value) { + case 0: { + return '0 ~ 10만원' + } + case 100_000: { + return '10만원 ~ 20만원' + } + case 200_000: { + return '20만원 ~ 30만원' + } + case 300_000: { + return '30만원 ~ 40만원' + } + case 400_000: { + return '40만원 ~ 50만원' + } + case 500_000: { + return '50만원 ~ 60만원' + } + case 600_000: { + return '60만원 ~ 70만원' + } + case 700_000: { + return '70만원 이상' + } + } + } return ( -
-
{value}
-
-
-
-
-
- { - const newValue = Number.parseInt(e.target.value, 10) - setValue(newValue) - }} - /> +
+
+ {parseAcademyFee(value)} +
+
+
+
{'0'}
+
+
+ {'20'} +
+
+ {'10'} +
+
+
+ {'30'} +
+
+
+ {'40'} +
+
+
+ {'50'} +
+
+
+ {'60'} +
+
+ {'70~'} +
+ { + const newValue = Number.parseInt(e.target.value, 10) + onChange() + setValue(newValue * 1000) + }} + /> +
) } diff --git a/src/components/common/toast/index.tsx b/src/components/common/toast/index.tsx index 7a23a19f..5d15f848 100644 --- a/src/components/common/toast/index.tsx +++ b/src/components/common/toast/index.tsx @@ -2,7 +2,7 @@ import { ToastContainer } from 'react-toastify' const Toast = () => { return ( { toastStyle={{ borderRadius: '12px', backgroundColor: 'white', - margin: '6px' + margin: '10px' }} bodyStyle={{ color: 'ccc', lineHeight: 1.5 }} /> diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index a612165d..c29b8cad 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,7 +1,9 @@ import { useEffect } from 'react' import { Outlet } from 'react-router-dom' +import useToastify from '@/libs/hooks/useToastify' const Layout = () => { + const { Toast } = useToastify() useEffect(() => { const vh = window.innerHeight * 0.01 document.documentElement.style.setProperty('--vh', `${vh}px`) @@ -17,6 +19,7 @@ const Layout = () => {
+
diff --git a/src/components/map/MapSearchBar.tsx b/src/components/map/MapSearchBar.tsx new file mode 100644 index 00000000..4e9e87fe --- /dev/null +++ b/src/components/map/MapSearchBar.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from 'react' +import { useInView } from 'react-intersection-observer' +import { useNavigate } from 'react-router-dom' +import { useQuery } from '@tanstack/react-query' +import { useAtom } from 'jotai' +import Icon from '@/components/common/icon/Icon.tsx' +import Input from '@/components/common/inputbox/input/Input.tsx' +import { getAcademiesSearchResult } from '@/libs/api/academy/AcademyApi.ts' +import { SearchAcademiesResponse } from '@/libs/api/mapapi/mapApiType.ts' +import { useDebounce } from '@/libs/hooks/useDebounce.ts' +import { selectSearchAcademyAtom } from '@/libs/store/mapInfoAtom.ts' + +const MapSearchBar = () => { + const navigate = useNavigate() + const [searchValue, setSearchValue] = useState('') + const [page, setPage] = useState(0) + const debounceValue = useDebounce(searchValue, 300) + const [searchList, setSearchList] = useState([]) + const [_, setSelectValue] = useAtom(selectSearchAcademyAtom) + const [isHidden, setHidden] = useState(false) + + const { ref, inView } = useInView({ + threshold: 1 + }) + + const { data: searchData } = useQuery({ + queryKey: ['searchData', debounceValue, page], + queryFn: () => getAcademiesSearchResult(debounceValue, page), + enabled: debounceValue !== '' + }) + + useEffect(() => { + if (searchData && page === 0) { + // 검색어만 바뀌었을때 + setSearchList([...searchData.content]) + } else if (searchData && page > 0) { + // 페이지가 바뀌었을때 + setSearchList((prevList) => [...prevList, ...searchData.content]) + } else { + // debounceValue가 ''일때 + setSearchList([]) + } + }, [searchData, page, debounceValue]) + + const isLast = searchData?.last || false + const updatePage = () => { + setPage(page + 1) + } + + //검색어가 바뀔때마다 페이지도 0으로 맞춰 api를 호출 + const updateSearchValue = (value: string) => { + setSearchValue(value) + setHidden(true) + setPage(0) + } + + const observer = ( +
+ ) + + useEffect(() => { + if (isLast) { + return + } else if (inView) { + updatePage() + } + }, [inView]) + + return ( +
+
+ { + updateSearchValue(e.target.value) + }} + /> +
0 && isHidden + ? 'rounded-lg border-blue-350 border mb-4 mt-2' + : '' + }`}> + {isHidden && searchList.length > 0 && ( +
+ {searchList.map((data, index) => ( +
{ + setSelectValue(data) + setHidden(false) + }}> + +
+
+ {data.academyName} +
+
+ {data.address} +
+
+
+ ))} + {observer} +
+ )} +
+
+
navigate('/map/filter')}> + + {'필터'} +
+
+ ) +} + +export default MapSearchBar diff --git a/src/components/map/NaverMap.tsx b/src/components/map/NaverMap.tsx index 85fde460..294f3c14 100644 --- a/src/components/map/NaverMap.tsx +++ b/src/components/map/NaverMap.tsx @@ -1,17 +1,18 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { useQuery } from '@tanstack/react-query' +import { useNavigate } from 'react-router-dom' import { useAtom } from 'jotai/index' -import { SetLocationProps } from '../../types/mapPage.ts' -import BottomSheet from '@/components/common/bottomsheet/BottomSheet.tsx' import Icon from '@/components/common/icon/Icon.tsx' import { DefaultMapOption, initSelectAcademy, Marker } from '@/components/map/constants.ts' -import { getAcademyDetail } from '@/libs/api/mapapi/mapApi.ts' import { Academy } from '@/libs/api/mapapi/mapApiType.ts' -import { selectAcademyAtom } from '@/libs/store/mapInfoAtom.ts' +import { + mapInfoAtom, + selectAcademyAtom, + selectSearchAcademyAtom +} from '@/libs/store/mapInfoAtom.ts' import throttle from '@/libs/utils/throttle.ts' /** @@ -19,45 +20,17 @@ import throttle from '@/libs/utils/throttle.ts' * * **/ interface NaverMapProps { - latitude: number - longitude: number academyList: Academy[] - setLocation: ({ latitude, longitude }: SetLocationProps) => void - searchAcademy: number //나중에 제외해야합니다. } -const NaverMap = ({ - latitude, - longitude, - academyList, - setLocation, - searchAcademy -}: NaverMapProps) => { +const NaverMap = ({ academyList }: NaverMapProps) => { const mapRef = useRef(null) + const markerRef = useRef([]) + const [mapInfo, setMapInfo] = useAtom(mapInfoAtom) const [selectAcademy, setSelectAcademy] = useAtom(selectAcademyAtom) + const [selectValue, _] = useAtom(selectSearchAcademyAtom) const [isNewLocation, setIsNewLocation] = useState(false) - - useEffect(() => { - if (searchAcademy > -1) { - setSelectAcademy((prev) => ({ - ...prev, - isBottomSheet: true - })) - console.log(selectAcademy) - } - }, [searchAcademy]) - - const { data: detailAcademy } = useQuery({ - queryKey: ['academy', selectAcademy], - queryFn: () => - getAcademyDetail({ - academyId: - selectAcademy.academy.academyId > -1 - ? selectAcademy.academy.academyId - : searchAcademy - }), - enabled: selectAcademy.academy.academyId > -1 || searchAcademy > -1 - }) + const navigate = useNavigate() const currentLocation = useCallback(() => { if (!navigator.geolocation || !mapRef.current) { @@ -68,13 +41,12 @@ const NaverMap = ({ (position) => { const { latitude, longitude } = position.coords const center = new naver.maps.LatLng(latitude, longitude) - console.log(center) mapRef.current?.panTo(center, { duration: 500, easing: 'easeOutCubic' }) }, - () => console.log('test'), + () => navigate('/'), { enableHighAccuracy: false, timeout: 1000 @@ -84,17 +56,54 @@ const NaverMap = ({ const updateLocation = useCallback(() => { const location = mapRef.current?.getCenter() - setLocation({ + createMap({ latitude: location?.y as number, longitude: location?.x as number }) + + setMapInfo((prev) => ({ + ...prev, + latitude: location?.y as number, + longitude: location?.x as number + })) + setIsNewLocation(false) }, []) + const createMap = ({ + latitude, + longitude + }: { + latitude: number + longitude: number + }) => { + const center: naver.maps.LatLng = new naver.maps.LatLng(latitude, longitude) + const naverMapOption = { + center: center, + ...DefaultMapOption + } + + mapRef.current = new naver.maps.Map('map', naverMapOption) + + naver.maps.Event.addListener(mapRef.current, 'click', () => + setSelectAcademy({ + isBottomSheet: false, + academy: initSelectAcademy.academy + }) + ) + + naver.maps.Event.addListener( + mapRef.current, + 'dragend', + throttle(() => { + setIsNewLocation(true) + }, 100) + ) + } + useEffect(() => { if (mapRef.current) { academyList.map((data) => { - // console.log(Marker({ value: data.academyName, select: false })) const isSelected = data.academyId === selectAcademy.academy.academyId const marker = new naver.maps.Marker({ position: new naver.maps.LatLng(data.latitude, data.longitude), @@ -103,7 +112,7 @@ const NaverMap = ({ content: Marker({ value: data.academyName, select: isSelected }) } }) - + markerRef.current.push(marker) naver.maps.Event.addListener(marker, 'click', () => { setSelectAcademy((prev) => ({ isBottomSheet: !isSelected, @@ -118,31 +127,59 @@ const NaverMap = ({ }, [academyList, selectAcademy]) useEffect(() => { - const center: naver.maps.LatLng = new naver.maps.LatLng(latitude, longitude) - const naverMapOption = { - center: center, - ...DefaultMapOption - } + const { latitude, longitude } = mapInfo + createMap({ latitude: latitude, longitude: longitude }) + if (selectValue.academyId > -1) { + const marker = new naver.maps.Marker({ + position: new naver.maps.LatLng( + selectValue.latitude, + selectValue.longitude + ), + map: mapRef.current as naver.maps.Map, + icon: { + content: Marker({ value: selectValue.academyName, select: true }) + } + }) + markerRef.current = [marker] + naver.maps.Event.addListener(marker, 'click', () => { + setSelectAcademy(() => ({ + isBottomSheet: true, + academy: { + academyId: selectValue.academyId, + academyName: selectValue.academyName, + address: selectValue.address, + contact: '', + areaOfExpertise: '', + latitude: selectValue.latitude, + longitude: selectValue.longitude + } + })) + }) - if (!mapRef.current) { - mapRef.current = new naver.maps.Map('map', naverMapOption) + setSelectAcademy(() => ({ + isBottomSheet: true, + academy: { + academyId: selectValue.academyId, + academyName: selectValue.academyName, + address: selectValue.address, + contact: '', + areaOfExpertise: '', + latitude: selectValue.latitude, + longitude: selectValue.longitude + } + })) } + }, [mapInfo]) - naver.maps.Event.addListener(mapRef.current, 'click', () => - setSelectAcademy({ - isBottomSheet: false, - academy: initSelectAcademy.academy - }) - ) - - naver.maps.Event.addListener( - mapRef.current, - 'dragend', - throttle(() => { - setIsNewLocation(true) - }, 100) - ) - }, []) + useEffect(() => { + if (selectValue.academyId > -1) { + setMapInfo((prev) => ({ + ...prev, + longitude: selectValue.longitude, + latitude: selectValue.latitude + })) + } + }, [selectValue]) return (
@@ -164,14 +201,6 @@ const NaverMap = ({ )} - {selectAcademy.isBottomSheet && detailAcademy && ( - - )}
) } diff --git a/src/components/review/ReviewBottomSheet.tsx b/src/components/review/ReviewBottomSheet.tsx index 55512347..61505700 100644 --- a/src/components/review/ReviewBottomSheet.tsx +++ b/src/components/review/ReviewBottomSheet.tsx @@ -6,6 +6,8 @@ import Spacing from '@/components/common/spacing/Spacing' import { postReview } from '@/libs/api/review/reviewApi' import { AcademyReview } from '@/libs/api/review/reviewType' import { ReviewRequestType } from '@/libs/api/review/reviewType' +import useToastify from '@/libs/hooks/useToastify' + const ReviewBottomSheet = ({ academyTitle, academyId, @@ -15,6 +17,7 @@ const ReviewBottomSheet = ({ academyId: number setBottomSheetClose: React.Dispatch> }) => { + const { setToast } = useToastify() const [reviewState, setReviewState] = useState({ academyId: academyId, KINDNESS: false, @@ -28,7 +31,7 @@ const ReviewBottomSheet = ({ mutationFn: (reviewState: ReviewRequestType) => postReview(reviewState), onSuccess: () => { setBottomSheetClose(false) - alert('리뷰 남기기 성공!') + setToast({ comment: '리뷰를 성공적으로 남겼어요.', type: 'success' }) } }) const handleMemo = (value: keyof ReviewRequestType) => { @@ -47,7 +50,8 @@ const ReviewBottomSheet = ({ useEffect(() => { const valueAry = Object.values(reviewState) const count = valueAry.filter(Boolean).length - if (count >= 4) alert('안돼') + if (count >= 4) + setToast({ comment: '리뷰는 4개 이상 남길 수 없어요.', type: 'warning' }) }, [reviewState]) return ( <> diff --git a/src/libs/api/academy/scheduleDetail/ScheduleDetailApi.ts b/src/libs/api/academy/scheduleDetail/ScheduleDetailApi.ts index ad45e77a..4a159fa7 100644 --- a/src/libs/api/academy/scheduleDetail/ScheduleDetailApi.ts +++ b/src/libs/api/academy/scheduleDetail/ScheduleDetailApi.ts @@ -7,27 +7,24 @@ import request from '@/libs/api' // /academy-schedules/detail?requestedDate=2023-11-06&lessonId=109347&childId=208&scheduleId=117 export const getAcademiesScheduleDetail = async ({ - requestedDate, lessonId, childId, scheduleId }: ScheduleDetailRequest): Promise => { - const scheduleDetail = await request.get(`/academy-schedules/detail`, { - params: { requestedDate, lessonId, childId, scheduleId } - }) + const scheduleDetail = await request.get( + `/academy-schedules/detail/${scheduleId}`, + { + params: { lessonId, childId } + } + ) return scheduleDetail.data } export const deleteAcademySchedule = async ({ academyScheduleId, - isAllDeleted, - requestDate + isAllDeleted }: DeleteScheduleRequest) => { - await request.delete('/academy-schedules', { - data: { - academyScheduleId, - isAllDeleted, - requestDate - } - }) + await request.delete( + `/academy-schedules/${academyScheduleId}?isAllDeleted=${isAllDeleted}` + ) } diff --git a/src/libs/api/academy/scheduleDetail/ScheduleDetailType.ts b/src/libs/api/academy/scheduleDetail/ScheduleDetailType.ts index d0efa3ab..365732e8 100644 --- a/src/libs/api/academy/scheduleDetail/ScheduleDetailType.ts +++ b/src/libs/api/academy/scheduleDetail/ScheduleDetailType.ts @@ -3,7 +3,7 @@ interface AcademyInfo { address: string } -interface LessonTimes { +interface LessonTime { startTime: string endTime: string } @@ -11,7 +11,7 @@ interface LessonInfo { lessonName: string capacity: number totalFee: number - lessonTimes: LessonTimes[] + lessonTimes: LessonTime periodicity: string } @@ -27,11 +27,11 @@ export interface ScheduleDetailResponse { date: string academyInfo: AcademyInfo lessonInfo: LessonInfo - childrenInfos: ChildrenInfo[] + childrenInfo: ChildrenInfo + categories: string[] } export interface ScheduleDetailRequest { - requestedDate: string lessonId: number childId: number scheduleId: number @@ -40,5 +40,4 @@ export interface ScheduleDetailRequest { export interface DeleteScheduleRequest { academyScheduleId: number isAllDeleted: boolean - requestDate: string } diff --git a/src/libs/api/index.ts b/src/libs/api/index.ts index 2769448c..9e6dcfea 100644 --- a/src/libs/api/index.ts +++ b/src/libs/api/index.ts @@ -1,6 +1,7 @@ import axios from 'axios' import { logoutApi } from './autorization/logout/LogoutApi' import { refreshApi } from './autorization/refresh/refreshApi' +import useToastify from '@/libs/hooks/useToastify' const request = axios.create({ baseURL: import.meta.env.VITE_API_ENDPOINT, @@ -31,13 +32,17 @@ request.interceptors.response.use( }, async (error) => { if (error.response.status === 403) { + const { setToast } = useToastify() try { const getRefreshToken = await refreshApi() const prevRequest = error.config prevRequest.headers.Authorization = `Bearer ${getRefreshToken.appToken}` return request(prevRequest) } catch { - alert('로그인이 풀리셨습니다... 로그인을 다시 진행해주세요😁') + setToast({ + comment: '로그인을 다시 진행해주세요.', + type: 'info' + }) await logoutApi() throw new Error('failed to request refresh token') } diff --git a/src/libs/api/mapapi/mapApi.ts b/src/libs/api/mapapi/mapApi.ts index 80b04c2f..b1bfbd4c 100644 --- a/src/libs/api/mapapi/mapApi.ts +++ b/src/libs/api/mapapi/mapApi.ts @@ -7,6 +7,7 @@ import { GetAcademysParams, GetLocationParam, GetTownParam, + LikeResponse, LocationResponse, ProvinceResponse, TownResponse @@ -64,3 +65,14 @@ export const getAcademyDetail = async ({ return res.data } + +export const postLike = async ({ + academyId +}: { + academyId: number +}): Promise => { + const res = await request.post(`/likes`, { + academyId: academyId + }) + return res.data +} diff --git a/src/libs/api/mapapi/mapApiType.ts b/src/libs/api/mapapi/mapApiType.ts index 65bcf8e4..d82e2096 100644 --- a/src/libs/api/mapapi/mapApiType.ts +++ b/src/libs/api/mapapi/mapApiType.ts @@ -49,6 +49,15 @@ export interface Academy { longitude: number } +export interface SearchAcademy { + academyId: number + academyName: string + address: string + areaOfExpertise: string + latitude: number + longitude: number +} + export interface AcademyResponse { academiesByLocationResponse: Academy[] } @@ -71,6 +80,7 @@ interface ReviewPercent { cheapFeePercent: number goodManagementPercent: number lovelyTeachingPercent: number + shuttleAvailabilityCount: number } export interface DetailAcademyResponse { @@ -80,7 +90,7 @@ export interface DetailAcademyResponse { shuttleAvailability: string expectedFee: number updatedDate: string - areaOfExpertise: string + categories: string[] lessonGetResponses: { lessons: Lesson[] } @@ -108,3 +118,9 @@ export interface InfiniteScrollPage { paged: boolean unpaged: boolean } + +export interface LikeResponse { + likeId: number + memberId: number + academyId: number +} diff --git a/src/libs/api/mypage/myPageApi.ts b/src/libs/api/mypage/myPageApi.ts index 854698a6..c2097527 100644 --- a/src/libs/api/mypage/myPageApi.ts +++ b/src/libs/api/mypage/myPageApi.ts @@ -3,7 +3,7 @@ import request from '@/libs/api' export const myPageApi = async (): Promise => { try { - const req = await request.get('http://3.114.43.57:8080/members') + const req = await request.get('/members') return req.data } catch { throw new Error('cannot get api from myPage') diff --git a/src/libs/store/likeacademyAtom.tsx b/src/libs/store/likeacademyAtom.tsx index 80da2dce..9f49eba2 100644 --- a/src/libs/store/likeacademyAtom.tsx +++ b/src/libs/store/likeacademyAtom.tsx @@ -2,9 +2,7 @@ import { atom } from 'jotai' import { GetLikeAcademyResponse } from '@/libs/api/likeacademy/LikeAcademyType' const initialLikeAcademyAtom: GetLikeAcademyResponse = { - likeAcademyInfos: [ - { likeId: 0, academyId: 0, academyName: '', expectedFee: 0 } - ], + likeAcademyInfos: [], totalFee: 0 } @@ -14,3 +12,7 @@ const checkGroup: boolean[] = [] export const totalAtom = atom(total) export const checkGroupAtom = atom(checkGroup) export const likeAcademyAtom = atom(initialLikeAcademyAtom) + +// onChange로직 다시 +// 아이 이름 입력 시 특수문자 => 에러메시지 거르자 +// 디폴트 이미지 => diff --git a/src/libs/store/mapFilterAtom.ts b/src/libs/store/mapFilterAtom.ts index 45d6e8f8..bf7f95b1 100644 --- a/src/libs/store/mapFilterAtom.ts +++ b/src/libs/store/mapFilterAtom.ts @@ -2,16 +2,15 @@ import { atom } from 'jotai' const initMapFilter: SubjectList = { subjectList: [ - { title: '예능', filter: '예능(대)', color: 'default' }, - { title: '국제화', filter: '국제화', color: 'default' }, - { title: '입시', filter: '입시, 검정 및 보습', color: 'default' }, - { title: '직업기술', filter: '직업기술', color: 'default' }, - { title: '종합', filter: '종합(대)', color: 'default' }, - { title: '독서실', filter: '독서실', color: 'default' }, - { title: '기예', filter: '기예(대)', color: 'default' }, - { title: '기타', filter: '기타(대)', color: 'default' }, - { title: '인문사회', filter: '인문사회(대)', color: 'default' }, - { title: '정보', filter: '정보', color: 'default' } + { title: '수학', filter: '수학', color: 'default' }, + { title: '과학', filter: '과학', color: 'default' }, + { title: '국어', filter: '국어', color: 'default' }, + { title: '영어', filter: '영어', color: 'default' }, + { title: '컴퓨터', filter: '컴퓨터', color: 'default' }, + { title: '예체능', filter: '예체능', color: 'default' }, + { title: '외국어', filter: '외국어', color: 'default' }, + { title: '보습', filter: '보습', color: 'default' }, + { title: '기타', filter: '기타', color: 'default' } ], maxMoney: 500_000, minMoney: 1 diff --git a/src/libs/store/mapInfoAtom.ts b/src/libs/store/mapInfoAtom.ts index 60bd0a30..478cfdd6 100644 --- a/src/libs/store/mapInfoAtom.ts +++ b/src/libs/store/mapInfoAtom.ts @@ -1,6 +1,7 @@ import { atom } from 'jotai' import { MapInfoAtomType } from '../../types/selectcity.ts' import { InitSelectAcademyType } from '@/components/map/naverMapType.ts' +import { SearchAcademiesResponse } from '@/libs/api/mapapi/mapApiType.ts' export const mapInfoAtom = atom({ selectProvince: '', @@ -22,3 +23,11 @@ export const selectAcademyAtom = atom({ longitude: -1 } }) + +export const selectSearchAcademyAtom = atom({ + academyId: -1, + academyName: '', + address: '', + latitude: -1, + longitude: -1 +}) diff --git a/src/pages/academy/AcademyDashboard.tsx b/src/pages/academy/AcademyDashboard.tsx index 0b348328..8014f570 100644 --- a/src/pages/academy/AcademyDashboard.tsx +++ b/src/pages/academy/AcademyDashboard.tsx @@ -16,6 +16,7 @@ import { patchToggleDashboardState } from '@/libs/api/dashboard/DashBoardApi' import { getAllDashboards } from '@/libs/api/dashboard/DashBoardApi' import { GetAllDashBoardResponse } from '@/libs/api/dashboard/DashBoardType' import useSidebar from '@/libs/hooks/useSidebar' +import useToastify from '@/libs/hooks/useToastify' import { childAtom } from '@/libs/store/childInfoAtom' import { getWeekday } from '@/libs/utils/weekParse' @@ -24,6 +25,7 @@ const AcademyDashboard = () => { const [dashboardData, setDashboardData] = useState( [] ) + const { setToast } = useToastify() const navigate = useNavigate() const { toggleOpen } = useSidebar() @@ -127,10 +129,17 @@ const AcademyDashboard = () => { } handleDelete={() => { if (data.isActive) { - alert('다니고 있는 학원은 삭제가 불가능합니다!') + setToast({ + comment: + '다니고 있는 학원은 삭제가 불가능해요. 먼저 미등록 상태로 변경해주세요.', + type: 'warning' + }) } else { deleteDashboardInfo(data.dashboardId) - alert('삭제 완료!') + setToast({ + comment: '삭제가 완료되었어요.', + type: 'success' + }) } }} onClick={(e) => { diff --git a/src/pages/academy/academyDetail/AcademySetting.tsx b/src/pages/academy/academyDetail/AcademySetting.tsx index 2a9b2286..c45818a2 100644 --- a/src/pages/academy/academyDetail/AcademySetting.tsx +++ b/src/pages/academy/academyDetail/AcademySetting.tsx @@ -6,8 +6,10 @@ import { deleteDashboard } from '@/libs/api/dashboard/DashBoardApi' import { patchToggleDashboardState } from '@/libs/api/dashboard/DashBoardApi' import { GetAllDashBoardResponse } from '@/libs/api/dashboard/DashBoardType' import { queryClient } from '@/libs/api/queryClient' +import useToastify from '@/libs/hooks/useToastify' const AcademySetting = ({ data }: { data: GetAllDashBoardResponse }) => { const navigate = useNavigate() + const { setToast } = useToastify() const [isbottomSheetOpen, setBottomSheetOpen] = useState(false) // eslint-disable-next-line unicorn/consistent-function-scoping const deleteDashboardInfo = async (dashboardId: number) => { @@ -45,7 +47,10 @@ const AcademySetting = ({ data }: { data: GetAllDashBoardResponse }) => { queryClient.invalidateQueries({ queryKey: ['dashboard', data.dashboardId] }) - alert('성공적으로 처리했음.') + setToast({ + comment: '학원 정보를 삭제했어요.', + type: 'success' + }) }} />
@@ -60,7 +65,10 @@ const AcademySetting = ({ data }: { data: GetAllDashBoardResponse }) => { queryClient.invalidateQueries({ queryKey: ['dashboard', data.dashboardId] }) - alert('성공적으로 처리했음.') + setToast({ + comment: '학원을 등록했어요.', + type: 'success' + }) }} />
diff --git a/src/pages/onboarding/OnbardingPage.tsx b/src/pages/onboarding/OnbardingPage.tsx index 40637a45..a4845523 100644 --- a/src/pages/onboarding/OnbardingPage.tsx +++ b/src/pages/onboarding/OnbardingPage.tsx @@ -16,11 +16,13 @@ import { onboardingApi } from '@/libs/api/onboarding/onboardingApi' import { PostOnboardingRequest } from '@/libs/api/onboarding/onboardingType' +import useToastify from '@/libs/hooks/useToastify' import { onboardingPageDataAtom } from '@/libs/store/onboardingAtom' import { getItem, setItem } from '@/libs/utils/storage' const Onboarding = () => { const navigate = useNavigate() + const { setToast } = useToastify() const [currentPage, setCurrentPage] = useState(0) const [isError, setIsError] = useState(false) const inputRef = useRef(null) @@ -70,7 +72,10 @@ const Onboarding = () => { const cntOfChild = async () => { const children = await getChildrenInfo() if (children.length === 5) { - alert('5명이 다 차있습니다!') + setToast({ + comment: '아이는 최대 5명까지만 등록이 가능해요.', + type: 'warning' + }) navigate('/') } setCurrentPage(children.length + 2) @@ -178,7 +183,10 @@ const Onboarding = () => { inputRef.current?.value === '' || selectRef.current?.value === '' ) { - alert('값을 입력해주세요😁👍') + setToast({ + comment: '값을 빠짐없이 입력해주세요.', + type: 'warning' + }) return } // ❗️ 값을 입력하고, child버튼일 때(자식입력 필드에서 존재하는 버튼 2개 diff --git a/src/pages/schedule/new/index.tsx b/src/pages/schedule/new/index.tsx index 3d783932..60df8782 100644 --- a/src/pages/schedule/new/index.tsx +++ b/src/pages/schedule/new/index.tsx @@ -1,3 +1,4 @@ +import { useNavigate } from 'react-router-dom' import { useMutation } from '@tanstack/react-query' import { useAtom } from 'jotai' import Button from '@/components/common/button/Button' @@ -5,17 +6,20 @@ import ListRowSelect from '@/components/common/listRowSelect/ListRowSelect' import Spacing from '@/components/common/spacing/Spacing' import { postScheduleApi } from '@/libs/api/schedule/scheduleApi' import { PostScheduleType } from '@/libs/api/schedule/scheduleType' +import useToastify from '@/libs/hooks/useToastify' import { scheduleAtom } from '@/libs/store/scheduleInfo' import AddScheduleAcademy from '@/pages/schedule/new/AddScheduleAcademy' import AddScheduleMemo from '@/pages/schedule/new/AddScheduleMemo' import AddScheduleTime from '@/pages/schedule/new/AddScheduleTime' - const NewSchedule = () => { + const navigate = useNavigate() const [scheduleInfo, setScheduleInfo] = useAtom(scheduleAtom) + const { setToast } = useToastify() + const postNewScheduleMutation = useMutation({ - onSuccess: () => { - alert('일정 생성 완료!') - // navigate(`/schedule/${data.academyTimeTemplateIds}`) + onSuccess: (data) => { + setToast({ comment: '일정이 생성되었어요.', type: 'success' }) + navigate(`/schedule/${data.academyTimeTemplateIds[0]}`) }, mutationFn: (scheduleInfo: PostScheduleType) => postScheduleApi(scheduleInfo) diff --git a/src/pages/setting/SettingPage.tsx b/src/pages/setting/SettingPage.tsx index 3f17606e..62bf3a06 100644 --- a/src/pages/setting/SettingPage.tsx +++ b/src/pages/setting/SettingPage.tsx @@ -16,7 +16,7 @@ const SettingPage = ({ isOpen }: SettingPage) => { {() => (