diff --git a/src/Router.tsx b/src/Router.tsx index 417189f6..792ebe0c 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -80,7 +80,7 @@ const Router = () => { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/api/get.ts b/src/api/get.ts index a6fd9cbf..8e5f0829 100644 --- a/src/api/get.ts +++ b/src/api/get.ts @@ -149,6 +149,9 @@ export const getCounselorsComments = async (postId: any) => export const getCustomersComments = async (postId: any) => await getInstance(`/comments/customers/${postId}`); +export const getCounselorsIsWriteComments = async (postId: any) => + await getInstance(`/comments/counselors/authentication/${postId}`); + // Post Controller export const getOneOpenConsult = async (id: string | undefined) => await getInstance(`/posts/${id}`); @@ -175,3 +178,8 @@ export const getCustomerIsWriter = async (postId: any) => export const getOpenConsultDraft = async (postId: any) => await getInstance(`/posts/drafts/${postId}`); + +// Post Scrap Controller + +export const getPostScraps = async (params: any) => + getInstance(`/postScraps`, params); diff --git a/src/api/patch.ts b/src/api/patch.ts index a710a214..b92c690d 100644 --- a/src/api/patch.ts +++ b/src/api/patch.ts @@ -64,9 +64,23 @@ export const patchApplyPayments = async (id: any) => //SearchWord Controller //검색 결과 반환 -export const patchSearchWordsResults = async (sortType: string, body: any) => - await patchPublicInstance(`/searchWords/results?sortType=${sortType}`, body); +export const patchSearchWordsCounselorsResults = async ( + sortType: string, + body: any, +) => + await patchPublicInstance( + `/searchWords/results/counselors?sortType=${sortType}`, + body, + ); +export const patchSearchWordsPostsResults = async ( + sortType: string, + body: any, +) => + await patchPublicInstance( + `/searchWords/results/posts?sortType=${sortType}`, + body, + ); //Wishlist Controlloer //찜하기 추가 export const patchWishLists = async (counselorId: number) => @@ -80,6 +94,5 @@ export const patchAdoptComment = async (postId: any, commentId: string) => await patchInstance(`/comments/customers/${postId}?commentId=${commentId}`); //Post Controller -export const patchOpenConsult = async (body: any) => { +export const patchOpenConsult = async (body: any) => await patchInstance(`/posts`, body); -}; diff --git a/src/api/post.ts b/src/api/post.ts index 74e80e24..ef518389 100644 --- a/src/api/post.ts +++ b/src/api/post.ts @@ -50,7 +50,7 @@ export const postWishLists = async (body: any) => // 일대다상담 // Comment Controller -export const postComment = async (body) => +export const postComment = async (body: any) => await postInstance('/comments/counselors', body); // CommentLike Controller diff --git a/src/components/Buyer/BuyerCategoryResult/CategoryResultHeader.tsx b/src/components/Buyer/BuyerCategoryResult/CategoryResultHeader.tsx index 17bbd427..95563c5e 100644 --- a/src/components/Buyer/BuyerCategoryResult/CategoryResultHeader.tsx +++ b/src/components/Buyer/BuyerCategoryResult/CategoryResultHeader.tsx @@ -3,18 +3,24 @@ import { Grey1, Grey6, White } from 'styles/color'; import { Heading } from 'styles/font'; import { ReactComponent as Back } from 'assets/icons/icon-back.svg'; import { useNavigate } from 'react-router-dom'; +import { useCallback, useMemo } from 'react'; interface ResultHeaderProps { categoryType: string; } export const CategoryResultHeader = ({ categoryType }: ResultHeaderProps) => { + const url = new URL(window.location.href); + const params = useMemo(() => new URLSearchParams(url.search), [url.search]); const navigate = useNavigate(); + const handleClickBackIcon = useCallback(() => { + if (params.has('from', 'search')) { + navigate('/search'); + } else { + navigate('/share'); + } + }, [navigate, params]); return ( - { - navigate(-1); - }} - /> + {categoryType} ); diff --git a/src/components/Buyer/BuyerConsult/BuyerConsultChatSection.tsx b/src/components/Buyer/BuyerConsult/BuyerConsultChatSection.tsx index b90412ce..cbf3b5ff 100644 --- a/src/components/Buyer/BuyerConsult/BuyerConsultChatSection.tsx +++ b/src/components/Buyer/BuyerConsult/BuyerConsultChatSection.tsx @@ -9,6 +9,7 @@ import { LoadingSpinner } from 'utils/LoadingSpinner'; import { Heading } from 'styles/font'; import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; import { convertChatListDate } from 'utils/convertDate'; +import { Space } from 'components/Common/Space'; // // @@ -37,7 +38,7 @@ export const BuyerConsultChatSection = ({ * https://stackoverflow.com/questions/73896315/rxjs-subscribe-callback-doesnt-have-access-to-current-react-state-functional-c */ const cardDataRef = useRef([]); - const { stompClient, isConnected } = useStompContext(); + const { stompClient } = useStompContext(); /** * 채팅 readId, 가장 최근 unread message, 정렬 업데이트 @@ -84,6 +85,7 @@ export const BuyerConsultChatSection = ({ unreadMessageCount: 0, reviewCompleted: null, consultId: null, + consultCategory: '', }; //add roomIds for unsubscribe roomIdsRef.current.unshift(notification.chatId); @@ -94,12 +96,12 @@ export const BuyerConsultChatSection = ({ }; useEffect(() => { - if (!isConnected) { + if (!stompClient.current?.connected) { return; } const sendConnectRequest = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chat/customers/connect', {}, @@ -181,19 +183,21 @@ export const BuyerConsultChatSection = ({ // return () => { - roomIdsRef.current.forEach((value) => { - stompClient.current?.unsubscribe( - '/queue/chatMessages/customers/' + value, - ); - }); - if (userIdRef.current !== -1) { - stompClient.current?.unsubscribe( - '/queue/chattings/notifications/customers/' + userIdRef.current, - ); + if (stompClient.current?.connected) { + roomIdsRef.current.forEach((value) => { + stompClient.current?.unsubscribe( + '/queue/chatMessages/customers/' + value, + ); + }); + if (userIdRef.current !== -1) { + stompClient.current?.unsubscribe( + '/queue/chattings/notifications/customers/' + userIdRef.current, + ); + } + stompClient.current?.unsubscribe('/queue/chattings/connect/customers/'); } - stompClient.current?.unsubscribe('/queue/chattings/connect/customers/'); }; - }, [stompClient, isConnected]); + }, [stompClient, stompClient.current?.connected]); useLayoutEffect(() => { const fetchData = async () => { @@ -261,6 +265,7 @@ export const BuyerConsultChatSection = ({ /> ); })} + ); } else { @@ -279,7 +284,6 @@ const BuyerConsultChatSectionWrapper = styled.section` flex-direction: column; gap: 0.8rem; align-items: center; - padding: 1.2rem 0; `; const EmptyWrapper = styled.div` margin-top: 10vh; diff --git a/src/components/Buyer/BuyerConsult/BuyerConsultLetterSection.tsx b/src/components/Buyer/BuyerConsult/BuyerConsultLetterSection.tsx index 3f1e7b74..f0c4f329 100644 --- a/src/components/Buyer/BuyerConsult/BuyerConsultLetterSection.tsx +++ b/src/components/Buyer/BuyerConsult/BuyerConsultLetterSection.tsx @@ -7,6 +7,7 @@ import { getLettersCustomers } from 'api/get'; import { LoadingSpinner } from 'utils/LoadingSpinner'; import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; import { Heading } from 'styles/font'; +import { Space } from 'components/Common/Space'; interface BuyerConsultLetterSectionProps { sortType: number; @@ -71,6 +72,7 @@ export const BuyerConsultLetterSection = ({ {cardData.map((value) => { return ( ); })} + ) : ( @@ -102,7 +105,6 @@ const BuyerConsultLetterSectionWrapper = styled.section` flex-direction: column; gap: 0.8rem; align-items: center; - padding: 1.2rem 0; `; const EmptyIcon = styled(Empty)` diff --git a/src/components/Buyer/BuyerConsult/BuyerOpenConsultSection.tsx b/src/components/Buyer/BuyerConsult/BuyerOpenConsultSection.tsx index 8f5951e4..2fdf56f6 100644 --- a/src/components/Buyer/BuyerConsult/BuyerOpenConsultSection.tsx +++ b/src/components/Buyer/BuyerConsult/BuyerOpenConsultSection.tsx @@ -1,10 +1,10 @@ import React, { useLayoutEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import styled from 'styled-components'; -import { Green, Grey1, Grey2, Grey3, Grey6 } from 'styles/color'; +import { Grey1, Grey2, Grey3, Grey6 } from 'styles/color'; import { Body1, Body3, Caption1, Heading } from 'styles/font'; import { LoadingSpinner } from 'utils/LoadingSpinner'; -import { isBuyPopupOpenState, isConsultModalOpenState } from 'utils/atom'; +import { isBuyPopupOpenState } from 'utils/atom'; import { ReactComponent as LockIcon } from 'assets/icons/icon-lock.svg'; import { ReactComponent as HeartIcon } from 'assets/icons/icon-heart2.svg'; import { ReactComponent as SaveIcon } from 'assets/icons/icon-save2.svg'; @@ -83,7 +83,7 @@ function BuyerOpenConsultSection({ isChecked }: BuyerOpenConsultSectionProps) { {isLoading ? (
)} {!isLastElem ? ( @@ -196,7 +198,7 @@ function BuyerOpenConsultSection({ isChecked }: BuyerOpenConsultSectionProps) { const BuyerOpenConsultCardList = styled.div` display: flex; - margin: 1.2rem 2rem; + margin: 0 2rem; flex-direction: column; align-items: flex-start; gap: 1.2rem; @@ -279,14 +281,6 @@ const TimeLeft = styled.div` right: 1.6rem; `; -const CreateConsultButton = styled.button` - width: 5.8rem; - height: 5.8rem; - border-radius: 100%; - background-color: ${Green}; - box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.25); - align-self: flex-end; -`; const CreateConsultButtonWrapper = styled.div` width: 100%; padding: 0 2rem; diff --git a/src/components/Buyer/BuyerHome/HomeConsultInProgress.tsx b/src/components/Buyer/BuyerHome/HomeConsultInProgress.tsx index de0ec8a2..a1ed613f 100644 --- a/src/components/Buyer/BuyerHome/HomeConsultInProgress.tsx +++ b/src/components/Buyer/BuyerHome/HomeConsultInProgress.tsx @@ -10,7 +10,7 @@ import { useStompContext } from 'contexts/StompContext'; import { convertChatListDate } from 'utils/convertDate'; interface ConsultItem { id: number; - consultStyle: string; + consultStyle: string; status: string; opponentNickname: string; latestMessageUpdatedAt: string; @@ -25,7 +25,7 @@ interface ConsultItem { export const HomeConsultInProgress = () => { const navigate = useNavigate(); - const { stompClient, isConnected } = useStompContext(); + const { stompClient } = useStompContext(); const [consultCard, setConsultCard] = useState(); const [totalOngoing, setTotalOngoing] = useState(); @@ -39,7 +39,7 @@ export const HomeConsultInProgress = () => { * */ const connectConsultInProgress = (id: number) => { - if (stompClient.current && isConnected && isLogined) { + if (stompClient.current && stompClient.current?.connected && isLogined) { stompClient.current.subscribe( '/queue/chatMessages/customers/' + id, (message) => { @@ -85,13 +85,13 @@ export const HomeConsultInProgress = () => { fetchData(); return () => { - if (stompClient.current && isConnected && isLogined) { + if (stompClient.current && stompClient.current?.connected && isLogined) { stompClient.current?.unsubscribe( '/queue/chatMessages/customers/' + currentChatIdRef.current, ); } }; - }, [stompClient, isConnected, isLogined]); + }, [stompClient, stompClient.current?.connected, isLogined]); if (!isLogined || !totalOngoing || !consultCard) { return <>; @@ -145,10 +145,11 @@ const Wrapper = styled.div` .nav-consult { display: flex; width: 100%; + height: 4.4rem; box-sizing: border-box; padding: 2.2rem 3.2rem 1.2rem 2rem; - justify-content: space-between; align-items: center; + justify-content: space-between; cursor: pointer; margin-bottom: 0.4rem; } @@ -157,7 +158,6 @@ const NavConsult = styled.div` display: flex; align-items: center; gap: 0.8rem; - margin: 0px auto 0px 0px -`; -const MoreIcon = styled(More)` + margin: 0px auto 0px 0px; `; +const MoreIcon = styled(More)``; diff --git a/src/components/Buyer/BuyerHome/HomeConsultInReady.tsx b/src/components/Buyer/BuyerHome/HomeConsultInReady.tsx index 577e2e70..6df89ad2 100644 --- a/src/components/Buyer/BuyerHome/HomeConsultInReady.tsx +++ b/src/components/Buyer/BuyerHome/HomeConsultInReady.tsx @@ -64,7 +64,10 @@ const Wrapper = styled.div` width: 100%; height: 4.4rem; display: flex; + box-sizing: border-box; + align-items: center; justify-content: space-between; + padding: 2.2rem 3.2rem 1.2rem 2rem; cursor: pointer; margin-bottom: 0.4rem; } @@ -73,10 +76,6 @@ const NavConsult = styled.div` display: flex; align-items: center; gap: 0.8rem; - margin-top: 0.9rem; - margin-left: 2rem; -`; -const MoreIcon = styled(More)` - margin-right: 3.8rem; - margin-top: 1.5rem; + margin: 0px auto 0px 0px; `; +const MoreIcon = styled(More)``; diff --git a/src/components/Buyer/BuyerOpenConsult/HotOpenConsultList.tsx b/src/components/Buyer/BuyerOpenConsult/HotOpenConsultList.tsx index 0547c401..4be1b6ad 100644 --- a/src/components/Buyer/BuyerOpenConsult/HotOpenConsultList.tsx +++ b/src/components/Buyer/BuyerOpenConsult/HotOpenConsultList.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { ReactComponent as FireIcon } from 'assets/icons/icon-fire.svg'; -import { Body3, Body4 } from 'styles/font'; +import { Body4 } from 'styles/font'; import { Grey6 } from 'styles/color'; import { getCustomerPopularConsultList } from 'api/get'; import { useNavigate } from 'react-router-dom'; @@ -26,6 +26,7 @@ function HotOpenConsultList() { {hotConsultList.map((item) => ( { navigate(`/open-consult/${item.postId}`); }} diff --git a/src/components/Buyer/BuyerOpenConsult/OpenConsultList.tsx b/src/components/Buyer/BuyerOpenConsult/OpenConsultList.tsx index 67a52516..a1d51a1d 100644 --- a/src/components/Buyer/BuyerOpenConsult/OpenConsultList.tsx +++ b/src/components/Buyer/BuyerOpenConsult/OpenConsultList.tsx @@ -1,32 +1,23 @@ -import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import React, { useLayoutEffect, useRef, useState } from 'react'; import styled from 'styled-components'; -import { Green, Grey1, Grey2, Grey3, Grey6 } from 'styles/color'; -import { Body1, Caption1, Caption2 } from 'styles/font'; -import { LoadingSpinner } from 'utils/LoadingSpinner'; -import { ReactComponent as LockIcon } from 'assets/icons/icon-lock.svg'; +import { Grey1, Grey2, Grey6 } from 'styles/color'; +import { Body1, Caption1 } from 'styles/font'; import { ReactComponent as HeartIcon } from 'assets/icons/icon-heart2.svg'; -import { ReactComponent as HeartEmptyIcon } from 'assets/icons/icon-heart3.svg'; import { ReactComponent as SaveIcon } from 'assets/icons/icon-save2.svg'; -import { ReactComponent as SaveEmptyIcon } from 'assets/icons/icon-save3.svg'; import { ReactComponent as CommentIcon } from 'assets/icons/icon-comment.svg'; -import { ReactComponent as CheckIcon } from 'assets/icons/icon-check2.svg'; -import { ReactComponent as WriteIcon } from 'assets/icons/icon-write.svg'; import { Space } from 'components/Common/Space'; -import { BackDrop } from 'components/Common/BackDrop'; import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; -import { - getCustomerOpenConsultList, - getCustomerPublicConsultList, -} from 'api/get'; +import { getCustomerPublicConsultList } from 'api/get'; import { useNavigate } from 'react-router-dom'; import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import { LoadingSpinner } from 'utils/LoadingSpinner'; function OpenConsultList() { const [cardData, setCardData] = useState([]); const preventRef = useRef(true); const navigate = useNavigate(); const [isLastElem, setIsLastElem] = useState(false); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const onIntersect: IntersectionObserverCallback = async (entry) => { if (entry[0].isIntersecting && !isLastElem && preventRef.current) { preventRef.current = false; @@ -77,47 +68,63 @@ function OpenConsultList() { useLayoutEffect(() => { fetchOpenConsult(0, new Date().toISOString().slice(0, 19)); }, []); - return ( -
- - {/* 상담카드 부분 */} - {cardData.map((item) => ( - { - navigate(`/open-consult/${item.postId}`); - }} - > -
- {item.title} -
- -
{item.content}
-
- - - {item.totalLike} - - - - {item.totalScrap} - - - - {item.totalComment} - -
- {item.updatedAt} -
- ))} - {!isLastElem ? ( -
- ) : ( -
- )} - {/* 상담카드 부분 */} - -
- ); + if (isLoading) { + return ( +
+ +
+ ); + } else { + return ( +
+ + {/* 상담카드 부분 */} + {cardData.map((item) => ( + { + navigate(`/open-consult/${item.postId}`); + }} + key={item.postId} + > +
+ {item.title} +
+ +
{item.content}
+
+ + + {item.totalLike} + + + + {item.totalScrap} + + + + {item.totalComment} + +
+ {item.updatedAt} +
+ ))} + {!isLastElem ? ( +
+ ) : ( +
+ )} + {/* 상담카드 부분 */} + +
+ ); + } } const BuyerOpenConsultCardList = styled.div` display: flex; @@ -177,13 +184,6 @@ const HeartResizeIcon = styled(HeartIcon)` height: 2rem; `; -const PrivateSign = styled.div` - display: flex; - position: absolute; - top: 1.95rem; - right: 1.6rem; -`; - const TimeLeft = styled.div` font-size: 1.2rem; font-weight: 400; @@ -193,24 +193,4 @@ const TimeLeft = styled.div` right: 1.6rem; `; -const CreateConsultButton = styled.button` - width: 5.8rem; - height: 5.8rem; - border-radius: 100%; - background-color: ${Green}; - box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.25); - align-self: flex-end; -`; -const CreateConsultButtonWrapper = styled.div` - width: 100%; - padding: 0 3.3rem; - box-sizing: border-box; - display: flex; - position: fixed; - bottom: 3.5rem; - flex-direction: column; - @media (min-width: 768px) { - width: 375px; - } -`; export default OpenConsultList; diff --git a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedCounselorCard.tsx b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedCounselorCard.tsx index c3c7e528..ff6e5262 100644 --- a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedCounselorCard.tsx +++ b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedCounselorCard.tsx @@ -8,11 +8,16 @@ import { ReactComponent as NoneBookMark } from 'assets/icons/icon-save1.svg'; import { ReactComponent as BookMark } from 'assets/icons/icon-save2.svg'; import { ReactComponent as DownIcon } from 'assets/icons/icon-down-toggle.svg'; import { ReactComponent as UpIcon } from 'assets/icons/icon-up-toggle.svg'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { CartegoryState, ConsultTimes } from 'utils/type'; import { convertTimeToString } from 'utils/convertTimeToString'; import { deleteWishLists } from 'api/delete'; + +// +// +// + interface SavedCounselorCardProps { counselorId: number; tagList: CartegoryState[]; @@ -28,7 +33,11 @@ interface SavedCounselorCardProps { chattingPrice: number; consultStyle: number; } -//일단 toggle파트 제외하고 클릭 시 상담프로필로 navigate하게 구현 + +// +// +// + export const SavedCounselorCard = ({ counselorId, tagList, @@ -45,11 +54,12 @@ export const SavedCounselorCard = ({ consultStyle, }: SavedCounselorCardProps) => { const navigate = useNavigate(); - //toggle const [toggle, setToggle] = useState(false); const [isSending, setIsSending] = useState(false); const [isSaved, setIsSaved] = useState(true); - const handleBookmark = async (e: React.MouseEvent) => { + const handleBookmark = async ( + e: React.MouseEvent | React.MouseEvent, + ) => { e.stopPropagation(); if (isSending) { return; @@ -73,18 +83,22 @@ export const SavedCounselorCard = ({ setIsSaved(false); } }; + // + // + // if (isSaved) { return ( { - //마인더 프로필 개발되면 수정 navigate(`/profile/${counselorId}`); }} > - {tagList.map((value: any) => { - return ; + {tagList.map((value: CartegoryState) => { + return ( + + ); })} {introduction} @@ -114,7 +128,7 @@ export const SavedCounselorCard = ({ ) : ( ) => { + onClick={(e: React.MouseEvent) => { e.stopPropagation(); setIsSaved(true); }} diff --git a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultCard.tsx b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultCard.tsx new file mode 100644 index 00000000..a8f2803a --- /dev/null +++ b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultCard.tsx @@ -0,0 +1,111 @@ +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { Green, Grey1, Grey2, Grey3, Grey6 } from 'styles/color'; +import { Body1, Caption1, Caption2 } from 'styles/font'; +import { LoadingSpinner } from 'utils/LoadingSpinner'; +import { ReactComponent as LockIcon } from 'assets/icons/icon-lock.svg'; +import { ReactComponent as HeartIcon } from 'assets/icons/icon-heart2.svg'; +import { ReactComponent as HeartEmptyIcon } from 'assets/icons/icon-heart3.svg'; +import { ReactComponent as SaveIcon } from 'assets/icons/icon-save2.svg'; +import { ReactComponent as SaveEmptyIcon } from 'assets/icons/icon-save3.svg'; +import { ReactComponent as CommentIcon } from 'assets/icons/icon-comment.svg'; +import { ReactComponent as CheckIcon } from 'assets/icons/icon-check2.svg'; +import { ReactComponent as WriteIcon } from 'assets/icons/icon-write.svg'; +import { Space } from 'components/Common/Space'; +import { BackDrop } from 'components/Common/BackDrop'; +import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; +import { useNavigate } from 'react-router-dom'; +import useIntersectionObserver from 'hooks/useIntersectionObserver'; +interface SavedCounselorCardProps { + item: openConsultApiObject; +} +function SavedOpenConsultCard({ item }: SavedCounselorCardProps) { + const navigate = useNavigate(); + return ( + { + navigate(`/open-consult/${item.postId}`); + }} + > +
+ {item.title} +
+ +
{item.content}
+
+ + + {item.totalLike} + + + + {item.totalScrap} + + + + {item.totalComment} + +
+ {item.updatedAt} +
+ ); +} + +const Wrapper = styled.div` + width: 89%; + height: 14rem; + cursor: pointer; + position: relative; + background-color: ${Grey6}; + padding: 1.6rem; + box-sizing: border-box; + border-radius: 1.2rem; + .row1 { + width: calc(100% - 5rem); + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + max-height: 5rem; + overflow: hidden; + } + .row2 { + display: -webkit-box; + max-height: 4.7rem; + -webkit-box-orient: vertical; + overflow: hidden; + align-self: flex-end; + margin-bottom: 0.4rem; + -webkit-line-clamp: 2; + color: ${Grey1}; + height: 4.6rem; + text-overflow: ellipsis; + font-family: Pretendard; + font-size: 1.4rem; + font-style: normal; + font-weight: 400; + line-height: 155%; + } + .row3 { + display: flex; + gap: 1.2rem; + } +`; +const HeartResizeIcon = styled(HeartIcon)` + width: 2rem; + height: 2rem; +`; +const IconItem = styled.div` + display: flex; + align-items: center; + gap: 0.5rem; +`; +const TimeLeft = styled.div` + font-size: 1.2rem; + font-weight: 400; + color: ${Grey2}; + position: absolute; + bottom: 1.8rem; + right: 1.6rem; +`; + +export default SavedOpenConsultCard; diff --git a/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx new file mode 100644 index 00000000..a506d482 --- /dev/null +++ b/src/components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults.tsx @@ -0,0 +1,28 @@ +import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; +import React from 'react'; +import styled from 'styled-components'; +import SavedOpenConsultCard from './SavedOpenConsultCard'; + +interface SavedOpenConsultResultsProps { + openConsultList: openConsultApiObject[]; +} +function SavedOpenConsultResults({ + openConsultList, +}: SavedOpenConsultResultsProps) { + return ( + + {openConsultList.map((card) => ( + + ))} + + ); +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1.2rem; + align-items: center; + width: 100%; +`; +export default SavedOpenConsultResults; diff --git a/src/components/Buyer/BuyerSearch/SearchHeader.tsx b/src/components/Buyer/BuyerSearch/SearchHeader.tsx index caa5e843..4bd3aee9 100644 --- a/src/components/Buyer/BuyerSearch/SearchHeader.tsx +++ b/src/components/Buyer/BuyerSearch/SearchHeader.tsx @@ -5,12 +5,9 @@ import { useNavigate } from 'react-router-dom'; import { Grey1, Grey4, White } from 'styles/color'; import Input from 'components/Common/Input'; import { ChangeEvent, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; -import { searchKeywordState } from 'utils/atom'; export const SearchHeader = () => { const navigate = useNavigate(); - const setKeyword = useSetRecoilState(searchKeywordState); //input value const [input, setInput] = useState(''); //input onchagne @@ -19,8 +16,7 @@ export const SearchHeader = () => { }; const handleSubmit = (event: ChangeEvent) => { event.preventDefault(); - setKeyword(input); - navigate('/search/result'); + navigate(`/search/result?keyword=${input}`); }; return ( @@ -40,8 +36,8 @@ export const SearchHeader = () => { placeHolderColor={Grey4} height="4.4rem" width="100%" - padding="0 3.2rem 0 0" - textIndent="1rem" + isBoxSizing={true} + padding="0.8rem 3.4rem 0.8rem 1.6rem" /> @@ -50,26 +46,25 @@ export const SearchHeader = () => { }; const Wrapper = styled.div` height: 5.2rem; + gap: 0.8rem; background-color: ${White}; position: relative; display: flex; align-items: center; justify-content: center; + box-sizing: border-box; padding: 0.4rem 2rem; `; const BackIcon = styled(Back)` - position: absolute; - top: 1.4rem; - left: 2rem; cursor: pointer; `; const FormWrapper = styled.form` position: relative; - width: 79%; + width: 100%; `; const SearchIcon = styled(Search)` position: absolute; - right: -2.7rem; + right: 0.8rem; top: 0.8rem; cursor: pointer; `; diff --git a/src/components/Buyer/BuyerSearch/SearchRecent.tsx b/src/components/Buyer/BuyerSearch/SearchRecent.tsx index dfabd5ce..58c0634b 100644 --- a/src/components/Buyer/BuyerSearch/SearchRecent.tsx +++ b/src/components/Buyer/BuyerSearch/SearchRecent.tsx @@ -6,13 +6,10 @@ import { Grey1, Grey4 } from 'styles/color'; import { ReactComponent as XIcon } from 'assets/icons/icon-grey-x.svg'; import { getSearchWords } from 'api/get'; import { deleteSearchWords } from 'api/delete'; -import { useSetRecoilState } from 'recoil'; -import { searchKeywordState } from 'utils/atom'; import { useNavigate } from 'react-router-dom'; export const SearchRecent = () => { const navigate = useNavigate(); const [recentSearch, setRecentSearch] = useState([]); - const setKeyword = useSetRecoilState(searchKeywordState); const handleXonClick = async ( targetIndex: number, event: React.MouseEvent, @@ -28,8 +25,7 @@ export const SearchRecent = () => { } }; const handleRecentClick = (index: number) => { - setKeyword(recentSearch[index]); - navigate('/search/result'); + navigate(`/search/result?keyword=${recentSearch[index]}`); }; useEffect(() => { const fetchData = async () => { diff --git a/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx b/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx new file mode 100644 index 00000000..6e3e1c51 --- /dev/null +++ b/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import styled from 'styled-components'; +import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; +import SavedOpenConsultCard from '../BuyerSavedCounselor.tsx/SavedOpenConsultCard'; +interface OpenConsultResultProps { + openConsultList: openConsultApiObject[]; +} +function OpenConsultResults({ openConsultList }: OpenConsultResultProps) { + return ( + + {openConsultList.map((item) => ( + + ))} + + ); +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1.2rem; + align-items: center; + width: 100%; +`; +export default OpenConsultResults; diff --git a/src/components/Buyer/BuyerWriteOpenConsult/FianlWritePopup.tsx b/src/components/Buyer/BuyerWriteOpenConsult/FianlWritePopup.tsx index bb2b4c61..ff109a35 100644 --- a/src/components/Buyer/BuyerWriteOpenConsult/FianlWritePopup.tsx +++ b/src/components/Buyer/BuyerWriteOpenConsult/FianlWritePopup.tsx @@ -1,15 +1,10 @@ import { patchOpenConsult } from 'api/patch'; -import React from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import styled from 'styled-components'; -import { Green, Grey4, LightGreen, White } from 'styles/color'; -import { Body1, Body3 } from 'styles/font'; -import { - isBuyPopupOpenState, - isPostPopupOpenState, - isSendPopupOpenState, -} from 'utils/atom'; +import { Green, LightGreen, White } from 'styles/color'; +import { Body1 } from 'styles/font'; +import { isPostPopupOpenState } from 'utils/atom'; import { convertCategoryEnum } from 'utils/convertCategoryEnum'; interface FianlWritePopupProps { title: string; @@ -32,10 +27,17 @@ function FinalWritePopup({ title, content, category }: FianlWritePopupProps) { }; try { const res: any = await patchOpenConsult(body); - if (res?.status === 200) { + if (res.status === 200) { navigate('/consult/?type=open-consult'); - } else { - console.log(res); + } else if (res?.response.status === 400) { + alert('이미 최종 제출된 상담입니다.'); + navigate('/consult?type=open-consult'); + } else if (res?.response.status === 403) { + alert('작성권한이 없습니다.'); + navigate('/consult?type=open-consult'); + } else if (res?.response.status === 404) { + alert('존재하지 않는 일대다상담입니다.'); + navigate('/consult?type=open-consult'); } } catch (err) { alert(err); diff --git a/src/components/Buyer/Common/CartegorySearch.tsx b/src/components/Buyer/Common/CartegorySearch.tsx index 0a4753c5..812cbd5a 100644 --- a/src/components/Buyer/Common/CartegorySearch.tsx +++ b/src/components/Buyer/Common/CartegorySearch.tsx @@ -1,11 +1,11 @@ -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import styled from 'styled-components'; import { Grey6 } from 'styles/color'; import { Characters } from 'utils/Characters'; import { searchKeywordState } from 'utils/atom'; - export const CartegorySearch = () => { + const { pathname } = useLocation(); const navigate = useNavigate(); const setSearchKeyword = useSetRecoilState(searchKeywordState); return ( @@ -16,7 +16,11 @@ export const CartegorySearch = () => { number={1} onClick={() => { setSearchKeyword('연애갈등'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 연애갈등 @@ -26,7 +30,11 @@ export const CartegorySearch = () => { number={2} onClick={() => { setSearchKeyword('이별/재회'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 이별/재회 @@ -36,7 +44,11 @@ export const CartegorySearch = () => { number={3} onClick={() => { setSearchKeyword('여자심리'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 여자심리 @@ -46,7 +58,11 @@ export const CartegorySearch = () => { number={4} onClick={() => { setSearchKeyword('남자심리'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 남자심리 @@ -58,7 +74,11 @@ export const CartegorySearch = () => { number={5} onClick={() => { setSearchKeyword('썸/연애시작'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 썸/연애시작 @@ -68,7 +88,11 @@ export const CartegorySearch = () => { number={6} onClick={() => { setSearchKeyword('짝사랑'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 짝사랑 @@ -78,7 +102,11 @@ export const CartegorySearch = () => { number={7} onClick={() => { setSearchKeyword('권태기'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 권태기 @@ -88,7 +116,11 @@ export const CartegorySearch = () => { number={8} onClick={() => { setSearchKeyword('기타'); - navigate('/categorySearch'); + if (pathname === '/search') { + navigate('/categorySearch?from=search'); + } else { + navigate('/categorySearch'); + } }} /> 기타 diff --git a/src/components/Buyer/Common/ConsultCard.tsx b/src/components/Buyer/Common/ConsultCard.tsx index 6bd45fc7..9ff979fa 100644 --- a/src/components/Buyer/Common/ConsultCard.tsx +++ b/src/components/Buyer/Common/ConsultCard.tsx @@ -105,11 +105,11 @@ export const ConsultCard = ({ ) : null}
{latestMessageContent !== null ? ( - + {latestMessageContent} ) : ( - + {opponentNickname}님께 고민 내용을 남겨 주세요.{' '} {opponentNickname}님이 24시간 이내 답장을 드릴 거예요. @@ -151,7 +151,7 @@ const Unread = styled.div` justify-content: center; align-items: center; border-radius: 0.4rem; - background-color: ${Green}; + background-$color: ${Green}; width: 1.9rem; height: 1.9rem; right: 0; @@ -184,14 +184,14 @@ const ConsultStateBox = styled.div` align-items: center; } `; -export const CardText = styled.div<{ color: string }>` +export const CardText = styled.div<{ $color: string }>` width: 100%; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 2; font-family: Pretendard; - color: ${(props) => props.color}; + color: ${(props) => props.$color}; font-size: 1.4rem; font-weight: 400; line-height: 155%; diff --git a/src/components/Buyer/Common/OpenConsultSortModal.tsx b/src/components/Buyer/Common/OpenConsultSortModal.tsx new file mode 100644 index 00000000..bab47d48 --- /dev/null +++ b/src/components/Buyer/Common/OpenConsultSortModal.tsx @@ -0,0 +1,147 @@ +import { ReactComponent as CheckIcon } from 'assets/icons/icon-modal-check.svg'; +import { SetStateAction } from 'react'; +import { SetURLSearchParams } from 'react-router-dom'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import styled, { keyframes } from 'styled-components'; +import { Green, Grey1, Grey4, Grey6 } from 'styles/color'; +import { Body1 } from 'styles/font'; +import { isSortModalOpenState, scrollLockState } from 'utils/atom'; +interface SortModalProps { + sortType: number; + setSortType: React.Dispatch>; + setPostId: React.Dispatch>; + searchParams: URLSearchParams; + setSearchParams: SetURLSearchParams; +} +//최근순 인기순 별점순 모달 +export const OpenConsultSortModal = ({ + sortType, + setSortType, + setPostId, + searchParams, + setSearchParams, +}: SortModalProps) => { + //modal 여부 + const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); + //scorll 막기 + const setScrollLock = useSetRecoilState(scrollLockState); + + return ( + +
+ +
+
{ + setSortType(0); + setPostId(0); + searchParams.set('open-sort', 'recent'); + setSearchParams(searchParams); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 0 ? ( + <> + 최근순 + + + ) : ( + 최근순 + )} +
+
{ + setSortType(1); + setPostId(0); + searchParams.set('open-sort', 'likes'); + setSearchParams(searchParams); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 1 ? ( + <> + 공감 많은 순 + + + ) : ( + 공감 많은 순 + )} +
+
{ + setSortType(2); + setPostId(0); + searchParams.set('open-sort', 'comments'); + setSearchParams(searchParams); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 2 ? ( + <> + 댓글 많은 순 + + + ) : ( + 댓글 많은 순 + )} +
+
+ ); +}; +const slideIn = keyframes` + from{ + transform : translateY(100%); + } + to{ + transform : translateY(0%); + } +`; +const slideOut = keyframes` + from{ + transform : translateY(0%); + } + to{ + transform : translateY(100%); + } +`; +const Wrapper = styled.div<{ visible: boolean }>` + @media (max-width: 767px) { + width: 100vw; + } + @media (min-width: 768px) { + width: 37.5rem; + } + position: fixed; + height: 22.7rem; + background-color: ${Grey6}; + bottom: 0; + border-radius: 2rem 2rem 0 0; + box-shadow: 0px -4px 10px rgba(0, 0, 0, 0.1); + z-index: 2002; + animation: ${({ visible }) => (visible ? slideIn : slideOut)} 0.3s ease-in-out; + + .bar-wrapper { + height: 4.5rem; + display: flex; + justify-content: center; + } + .row { + display: flex; + padding: 1rem 2rem 0 2rem; + height: 4.4rem; + justify-content: space-between; + cursor: pointer; + } +`; +const Bar = styled.div` + margin-top: 1.2rem; + width: 3.1rem; + height: 0.3rem; + background-color: ${Grey4}; +`; diff --git a/src/components/Buyer/Common/SortModal.tsx b/src/components/Buyer/Common/SortModal.tsx index 4837cc22..b53d5f06 100644 --- a/src/components/Buyer/Common/SortModal.tsx +++ b/src/components/Buyer/Common/SortModal.tsx @@ -1,5 +1,6 @@ import { ReactComponent as CheckIcon } from 'assets/icons/icon-modal-check.svg'; import { SetStateAction } from 'react'; +import { SetURLSearchParams } from 'react-router-dom'; import { useRecoilState, useSetRecoilState } from 'recoil'; import styled, { keyframes } from 'styled-components'; import { Green, Grey1, Grey4, Grey6 } from 'styles/color'; @@ -9,12 +10,16 @@ interface SortModalProps { sortType: number; setSortType: React.Dispatch>; setPageNum: React.Dispatch>; + searchParams: URLSearchParams; + setSearchParams: SetURLSearchParams; } //최근순 인기순 별점순 모달 export const SortModal = ({ sortType, setSortType, setPageNum, + searchParams, + setSearchParams, }: SortModalProps) => { //modal 여부 const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); @@ -32,6 +37,8 @@ export const SortModal = ({ setSortType(0); setPageNum(0); setIsModalOpen(false); + searchParams.set('sort', 'recent'); + setSearchParams(searchParams); setScrollLock(false); }} > @@ -49,6 +56,8 @@ export const SortModal = ({ onClick={() => { setSortType(1); setPageNum(0); + searchParams.set('sort', 'popular'); + setSearchParams(searchParams); setIsModalOpen(false); setScrollLock(false); }} @@ -67,6 +76,8 @@ export const SortModal = ({ onClick={() => { setSortType(2); setPageNum(0); + searchParams.set('sort', 'rating'); + setSearchParams(searchParams); setIsModalOpen(false); setScrollLock(false); }} diff --git a/src/components/Common/Divider2.tsx b/src/components/Common/Divider2.tsx new file mode 100644 index 00000000..f55820b0 --- /dev/null +++ b/src/components/Common/Divider2.tsx @@ -0,0 +1,77 @@ +import styled from 'styled-components'; +import { Green, Grey4, Grey6 } from 'styles/color'; +import { ReactComponent as UnderLineBuyer } from 'assets/icons/underline-buyer.svg'; +import { ReactComponent as UnderLineBuyerBig } from 'assets/icons/underline-big.svg'; +import { Subtitle } from 'styles/font'; + +// +// +// + +interface Divder2Props { + tabState: number; + setTabState: (tabState: number) => void; +} + +// +// +// + +function Divider2({ tabState, setTabState }: Divder2Props) { + return ( + + { + setTabState(1); + }} + > + {tabState === 1 ? ( + <> + 상담사 + + + ) : ( + 상담사 + )} + + { + setTabState(2); + }} + > + {tabState === 2 ? ( + <> + 공개상담 + + + ) : ( + 공개상담 + )} + + + ); +} + +const Wrapper = styled.nav` + width: 100%; + box-sizing: border-box; + padding-top: 0.8rem; + height: 4.4rem; + display: flex; + justify-content: center; + border-bottom: 1px solid ${Grey6}; + background-color: white; + z-index: 999; +`; + +const TabButton = styled.div<{ tabState: number }>` + display: flex; + flex-direction: column; + width: 50%; + align-items: center; + cursor: pointer; +`; + +export default Divider2; diff --git a/src/components/Common/Header.tsx b/src/components/Common/Header.tsx index 05d0513e..fc513db9 100644 --- a/src/components/Common/Header.tsx +++ b/src/components/Common/Header.tsx @@ -57,7 +57,7 @@ const Logo = styled.div` cursor: pointer; `; const StyledSearch = styled(Search)` - margin-top: 1rem; + margin-top: 1.4rem; margin-right: 2.4rem; cursor: pointer; `; diff --git a/src/components/Common/Profile.tsx b/src/components/Common/Profile.tsx index ec3c2f0a..697c9ab1 100644 --- a/src/components/Common/Profile.tsx +++ b/src/components/Common/Profile.tsx @@ -11,7 +11,7 @@ import { Characters } from 'utils/Characters'; interface ProfileProps { isBuyer: boolean; isVerified?: undefined | boolean; - profileIdentifier: number | undefined; + profileIdentifier: number | null | undefined; name: string | undefined; levelStatus: string | undefined; isPass?: boolean | undefined; @@ -29,7 +29,8 @@ export const Profile = ({ return ( <> - + {/* 프로플 만들지 않았을 때, 기본 프로필을 4번으로 */} + {isBuyer ? ( {name} @@ -100,6 +101,10 @@ export const Profile = ({ onClick={() => { if (isPass) { navigate('/minder/education/first'); + } else { + alert( + '마지막 인증 시험을 본 시간에서 24시간 이후에 시험을 응시할 수 있어요.', + ); } }} width="100%" diff --git a/src/components/Common/TabA1.tsx b/src/components/Common/TabA1.tsx index 64a1f2db..4f152323 100644 --- a/src/components/Common/TabA1.tsx +++ b/src/components/Common/TabA1.tsx @@ -21,6 +21,9 @@ export const TabA1 = ({ isBuyer, initState }: TabA1Props) => { if (res.status === 200) { localStorage.setItem('randomConsult', JSON.stringify(res.data)); navigate(`/minder/open-consult/${res.data[0]}`); + setTabState(3); + } else if (res?.response.status === 403) { + alert('공개 상담 페이지에 접근할 권한이 없습니다.'); } } catch (err) { alert(err); @@ -33,11 +36,11 @@ export const TabA1 = ({ isBuyer, initState }: TabA1Props) => { } else { setColor(Green); } - }, []); + }, [initState, isBuyer]); return ( { setTabState(1); if (isBuyer) { @@ -57,7 +60,7 @@ export const TabA1 = ({ isBuyer, initState }: TabA1Props) => { )} { setTabState(2); if (isBuyer) { @@ -77,9 +80,8 @@ export const TabA1 = ({ isBuyer, initState }: TabA1Props) => { )} { - setTabState(3); if (isBuyer) { navigate('/open-consult'); } else { @@ -96,7 +98,7 @@ export const TabA1 = ({ isBuyer, initState }: TabA1Props) => { 공개상담 )} - + {tabState === 4 ? ( <> 내 정보 @@ -132,15 +134,15 @@ const Wrapper = styled.nav` background-color: white; z-index: 999; `; -const TabButton = styled.div<{ tabState: number }>` +const TabButton = styled.div<{ $tabState: number }>` display: flex; flex-direction: column; width: ${(props) => - props?.tabState === 1 + props?.$tabState === 1 ? '8.8rem' - : props?.tabState === 2 + : props?.$tabState === 2 ? '8.2rem' - : props?.tabState === 3 + : props?.$tabState === 3 ? '10.9rem' : '9.6rem'}; align-items: center; diff --git a/src/components/Common/TagA2Cartegory.tsx b/src/components/Common/TagA2Cartegory.tsx index e2816598..215cc540 100644 --- a/src/components/Common/TagA2Cartegory.tsx +++ b/src/components/Common/TagA2Cartegory.tsx @@ -2,12 +2,20 @@ import styled from 'styled-components'; import { Green, Grey6, Red, White } from 'styles/color'; import { Caption2 } from 'styles/font'; import { CartegoryState } from 'utils/type'; -//bgcolor type이 1이면 white 2면 해당 색상, 3이면 grey6 + +// +// +// + interface TagA2CartegoryProps { tagType: CartegoryState; bgColorType: number; } +// +// +// + export const TagA2Cartegory = ({ tagType, bgColorType, diff --git a/src/components/Seller/Common/BankSelectModal.tsx b/src/components/Seller/Common/BankSelectModal.tsx index a7ced60f..bf54b9b4 100644 --- a/src/components/Seller/Common/BankSelectModal.tsx +++ b/src/components/Seller/Common/BankSelectModal.tsx @@ -1,4 +1,4 @@ -import React, { SetStateAction } from 'react'; +import React from 'react'; import { useRecoilState } from 'recoil'; import styled, { keyframes } from 'styled-components'; import { Grey4, Grey6 } from 'styles/color'; @@ -7,7 +7,7 @@ import { BankIcon } from 'utils/BankIcon'; import { isBankModalOpenState } from 'utils/atom'; import { bankNameList } from 'utils/constant'; interface BankSelectModalProps { - setSelectBankType: React.Dispatch>; + setSelectBankType: React.Dispatch>; } function BankSelectModal({ setSelectBankType }: BankSelectModalProps) { const [isBankModalOpen, setIsBankModalOpen] = diff --git a/src/components/Seller/SellerCalculateManagement/CompleteApplyPopup.tsx b/src/components/Seller/SellerCalculateManagement/CompleteApplyPopup.tsx index 63ac89d1..9a8003ee 100644 --- a/src/components/Seller/SellerCalculateManagement/CompleteApplyPopup.tsx +++ b/src/components/Seller/SellerCalculateManagement/CompleteApplyPopup.tsx @@ -4,20 +4,34 @@ import React from 'react'; import styled from 'styled-components'; import { Grey4 } from 'styles/color'; import { Body1, Body3 } from 'styles/font'; + +// +// +// + interface CompleteApplyPopupProps { name: string; date: string; + consultType: string; setIsCompleteApplyManage: React.Dispatch>; } + +// +// +// + function CompleteApplyPopup({ name = '김고민', - date = '2023.02.02', + date = '2001.12.15', + consultType = '무슨', setIsCompleteApplyManage, }: CompleteApplyPopupProps) { return ( 정산 신청이 완료되었어요! - {name}과의 편지 상담 2023.12.23 + + {name}과의 {consultType} 상담 {date} +
- {consultType === 'letter' ? ( - - ) : consultType === 'chat' ? ( - - ) : ( - - )} +
+ {consultType === 'letter' ? ( + + ) : consultType === 'chat' ? ( + + ) : ( + + )} + +
); }; -const ConsultSortingMenu = styled.div` +const ConsultSortingMenu = styled.section` display: flex; flex-direction: column; - margin: 0.8rem 2rem 1.2rem; + padding: 0.8rem 2rem 1.2rem; gap: 1.2rem; + position: sticky; + top: 10.4rem; + z-index: 10; + background-color: white; .row1 { display: flex; gap: 1.2rem; @@ -135,7 +153,7 @@ const ConsultSortingMenu = styled.div` const ConsultType = styled.div` display: flex; - width: ${(props) => (props.isLong ? '8rem' : '5.6rem')}; + width: ${(props) => (props.$isLong ? '8rem' : '5.6rem')}; height: 3.4rem; cursor: pointer; justify-content: center; @@ -145,8 +163,8 @@ const ConsultType = styled.div` font-style: normal; font-weight: 400; line-height: 110%; - background: ${(props) => (props.isActive ? LightGreen : Grey6)}; - color: ${(props) => (props.isActive ? Green : Black)}; + background: ${(props) => (props.$isActive ? LightGreen : Grey6)}; + color: ${(props) => (props.$isActive ? Green : Black)}; margin-top: 0.2rem; `; const SortingType = styled.div` diff --git a/src/components/Seller/SellerConsult/SellerOpenConsultList.tsx b/src/components/Seller/SellerConsult/SellerOpenConsultList.tsx index 142fa4b8..32cdc1e7 100644 --- a/src/components/Seller/SellerConsult/SellerOpenConsultList.tsx +++ b/src/components/Seller/SellerConsult/SellerOpenConsultList.tsx @@ -4,9 +4,7 @@ import { Green, Grey1, Grey2, Grey3, Grey6, Red, White } from 'styles/color'; import { Body1, Caption1, Caption2, Heading } from 'styles/font'; import { ReactComponent as LockIcon } from 'assets/icons/icon-lock.svg'; import { ReactComponent as HeartIcon } from 'assets/icons/icon-heart2.svg'; -import { ReactComponent as HeartEmptyIcon } from 'assets/icons/icon-heart3.svg'; import { ReactComponent as SaveIcon } from 'assets/icons/icon-save2.svg'; -import { ReactComponent as SaveEmptyIcon } from 'assets/icons/icon-save3.svg'; import { ReactComponent as CommentIcon } from 'assets/icons/icon-comment.svg'; import { ReactComponent as CheckIcon } from 'assets/icons/icon-check2.svg'; import { Space } from 'components/Common/Space'; diff --git a/src/components/Seller/SellerHome/ConsultReviewsSection.tsx b/src/components/Seller/SellerHome/ConsultReviewsSection.tsx index 30219c54..602326cd 100644 --- a/src/components/Seller/SellerHome/ConsultReviewsSection.tsx +++ b/src/components/Seller/SellerHome/ConsultReviewsSection.tsx @@ -1,14 +1,12 @@ import { ContentTag } from 'pages/Seller/SellerHome'; import styled from 'styled-components'; import { Black, Grey3, Grey6, Red } from 'styles/color'; -import { Body1, Body2, Body3, Body4, Heading, Subtitle } from 'styles/font'; +import { Body1, Body2, Body3, Subtitle } from 'styles/font'; import { ReactComponent as RightArrow } from 'assets/icons/right-arrow.svg'; -import { ReactComponent as ReviewHeart } from 'assets/icons/review-heart.svg'; import { useNavigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; import { getMinderReviewsHome } from 'api/get'; import { HeartRate } from 'utils/HeartRate'; -// 섹션 안에서 axios 요청 interface ReviewData { comment: string; @@ -31,7 +29,7 @@ export const ConsultReviewSection = () => { <> { - navigate('/minder/mypage/review'); + navigate('/minder/mypage/review?from=home'); }} > {reviewData?.length === 0 ? ( @@ -55,6 +53,7 @@ export const ConsultReviewSection = () => { ) : ( reviewData?.map((item) => ( { navigate('/minder/mypage/review'); }} diff --git a/src/components/Seller/SellerMyPageReview/SellerReviewHeader.tsx b/src/components/Seller/SellerMyPageReview/SellerReviewHeader.tsx index 3ebcbfcc..69eea73a 100644 --- a/src/components/Seller/SellerMyPageReview/SellerReviewHeader.tsx +++ b/src/components/Seller/SellerMyPageReview/SellerReviewHeader.tsx @@ -1,19 +1,25 @@ -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { White } from 'styles/color'; import { ReactComponent as LeftArrowIcon } from 'assets/icons/left-arrow.svg'; import { Heading } from 'styles/font'; +import { useCallback, useMemo } from 'react'; export const SellerReviewHeader = () => { + const url = new URL(window.location.href); + const params = useMemo(() => new URLSearchParams(url.search), [url.search]); const navigate = useNavigate(); + const handleClickBackIcon = useCallback(() => { + if (params.has('from', 'home')) { + navigate('/minder'); + } else { + navigate('/minder/mypage'); + } + }, [navigate, params]); return (
- { - navigate('/minder/mypage'); - }} - /> +
받은 리뷰
diff --git a/src/components/Seller/SellerOpenConsult/BottomSection.tsx b/src/components/Seller/SellerOpenConsult/BottomSection.tsx index a7482684..f1c89765 100644 --- a/src/components/Seller/SellerOpenConsult/BottomSection.tsx +++ b/src/components/Seller/SellerOpenConsult/BottomSection.tsx @@ -1,12 +1,13 @@ import { Button } from 'components/Common/Button'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import reactTextareaAutosize from 'react-textarea-autosize'; import styled from 'styled-components'; import { ReactComponent as SendIcon } from 'assets/icons/icon-send.svg'; import { Green, Grey3, Grey6, LightGreen, White } from 'styles/color'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { isSendPopupOpenState } from 'utils/atom'; import { useNavigate, useParams } from 'react-router-dom'; +import { getCounselorsIsWriteComments } from 'api/get'; interface BottomSectionProps { isReplying: boolean; setIsReplying: React.Dispatch>; @@ -21,10 +22,12 @@ function BottomSection({ }: BottomSectionProps) { const navigate = useNavigate(); const setIsSendPopupOpen = useSetRecoilState(isSendPopupOpenState); - const { consultid } = useParams(); + const { consultid } = useParams() as { consultid: string }; + const [isAlreadyWrite, setIsAlreadyWrite] = useState(false); const handleNavigateRandomConsult = () => { - if (localStorage.getItem('randomConsult')) { - const randomNumList = JSON.parse(localStorage.getItem('randomConsult')); + const randomNumListString = localStorage.getItem('randomConsult'); + if (randomNumListString != null) { + const randomNumList = JSON.parse(randomNumListString); const navigateId = randomNumList[ (randomNumList.indexOf(parseInt(consultid)) + 1) % @@ -34,6 +37,18 @@ function BottomSection({ } // 그냥 open-consult id쳐서 들어왔을 경우 추후 예외처리.. }; + useEffect(() => { + const fetchIsAlreadyWrite = async () => { + const res: any = await getCounselorsIsWriteComments(consultid); + if (res.status === 200) { + setIsAlreadyWrite(res.data); + } else if (res.response.status === 404) { + alert('존재하지 않는 상담입니다.'); + navigate('/minder/consult?type=open-consult'); + } + }; + fetchIsAlreadyWrite(); + }); return ( {isReplying ? ( @@ -70,10 +85,13 @@ function BottomSection({
diff --git a/src/components/Seller/SellerOpenConsult/IsSendPopup.tsx b/src/components/Seller/SellerOpenConsult/IsSendPopup.tsx index 268f3503..52467063 100644 --- a/src/components/Seller/SellerOpenConsult/IsSendPopup.tsx +++ b/src/components/Seller/SellerOpenConsult/IsSendPopup.tsx @@ -23,6 +23,7 @@ function IsSendPopup({ text, setText, setIsReplying }: IsSendPopupProps) { }; try { const res: any = await postComment(body); + console.log(res); if (res?.status === 200 || res.status === 201) { setText(''); setIsSendPopupOpen(false); diff --git a/src/contexts/StompContext.tsx b/src/contexts/StompContext.tsx index 42232f61..630183a0 100644 --- a/src/contexts/StompContext.tsx +++ b/src/contexts/StompContext.tsx @@ -8,7 +8,7 @@ import React, { useState, } from 'react'; import SockJs from 'sockjs-client'; -import { CompatClient, Stomp } from '@stomp/stompjs'; +import { CompatClient, IFrame, Stomp } from '@stomp/stompjs'; import { useRecoilState } from 'recoil'; import { isCustomerState } from 'utils/atom'; import { useLocation } from 'react-router-dom'; @@ -82,6 +82,7 @@ export const StompProvider: React.FC<{ children: ReactNode }> = ({ * */ const connectChat = () => { + setIsConnected(false); const socket = new SockJs(process.env.REACT_APP_CHAT_URL + '/chat'); stompClient.current = Stomp.over(() => { return socket; @@ -92,7 +93,7 @@ export const StompProvider: React.FC<{ children: ReactNode }> = ({ Authorization: getCookie('accessToken'), isCustomer: isCustomer, }, - (frame: any) => { + (frame: IFrame) => { console.log('Connected: ' + frame); setIsConnected(true); }, @@ -108,7 +109,7 @@ export const StompProvider: React.FC<{ children: ReactNode }> = ({ }, ); - stompClient.current.reconnect_delay = 100; + stompClient.current.reconnectDelay = 100; }; // diff --git a/src/hooks/useSearchPageParams.ts b/src/hooks/useSearchPageParams.ts new file mode 100644 index 00000000..90e18d58 --- /dev/null +++ b/src/hooks/useSearchPageParams.ts @@ -0,0 +1,93 @@ +import { ChangeEvent, useCallback, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +// +// +// + +export const useSearchPageParams = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + /** get query-string */ + const keywordParam = searchParams.get('keyword'); + const typeParam = searchParams.get('searchType'); + const sortParam = searchParams.get('sort'); + const openSortParam = searchParams.get('open-sort'); + + /** get initial values */ + const initialKeyword = keywordParam ?? ''; + const initialSearchType = typeParam ?? 'counselor'; + const initialSortType = + sortParam === 'rating' ? 2 : sortParam === 'popular' ? 1 : 0; + const initialOpenSortType = + openSortParam === 'comments' ? 2 : openSortParam === 'likes' ? 1 : 0; + const initialTabState = typeParam === 'open-consult' ? 2 : 1; + + /** states with iniitial value */ + const [input, setInput] = useState(initialKeyword); + const [keyword, setKeyword] = useState(initialKeyword); + const [searchType, setSearchType] = useState(initialSearchType); + const [openSortType, setOpenSortType] = useState(initialOpenSortType); + const [sortType, setSortType] = useState(initialSortType); + const [tabState, setTabState] = useState(initialTabState); + + /** event handler*/ + const handleClickOpenConsult = useCallback(() => { + setSearchType('open-consult'); + searchParams.set('searchType', 'open-consult'); + setTabState(2); + setSearchParams(searchParams); + }, [searchParams, setSearchParams]); + const handleClickCounselor = useCallback(() => { + setSearchType('counselor'); + searchParams.set('searchType', 'counselor'); + setSearchParams(searchParams); + setTabState(1); + }, [searchParams, setSearchParams]); + + const handleClickTab = useCallback( + (tabState: number) => { + if (tabState === 1) { + handleClickCounselor(); + } else { + handleClickOpenConsult(); + } + }, + [handleClickCounselor, handleClickOpenConsult], + ); + const handleChangeInput = useCallback( + (event: ChangeEvent) => { + setInput(event.target.value); + }, + [], + ); + const handleSubmit = useCallback( + ( + event: React.FormEvent | React.MouseEvent, + ) => { + event.preventDefault(); + setKeyword(input); + searchParams.set('keyword', input); + setSearchParams(searchParams); + }, + [input, searchParams, setSearchParams], + ); + + return { + handleClickTab, + searchParams, + openSortType, + setOpenSortType, + setSearchParams, + input, + tabState, + setTabState, + keyword, + setKeyword, + handleSubmit, + searchType, + sortType, + setSortType, + handleChangeInput, + }; +}; diff --git a/src/pages/Buyer/BuyerAvailCounselor.tsx b/src/pages/Buyer/BuyerAvailCounselor.tsx index 7b91101c..709ad55d 100644 --- a/src/pages/Buyer/BuyerAvailCounselor.tsx +++ b/src/pages/Buyer/BuyerAvailCounselor.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import styled from 'styled-components'; import { Grey1, Grey3, Grey6, White } from 'styles/color'; import { Button2, Heading } from 'styles/font'; @@ -18,7 +18,17 @@ import useIntersectionObserver from 'hooks/useIntersectionObserver'; export const BuyerAvailCounselor = () => { //0 : 최신순 1:인기순 2: 별점순 // 바뀔 때마다 useEffect로 request - const [sortType, setSortType] = useState(0); + // using query params to store sorting type + const [searchParams, setSearchParams] = useSearchParams(); + + // sortType - 0 : 최신순 1:인기순 2: 별점순, 기본값: 0, 최신순 + const initialSortType = + searchParams.get('sort') === 'rating' + ? 2 + : searchParams.get('sort') === 'popular' + ? 1 + : 0; + const [sortType, setSortType] = useState(initialSortType); // Modal 여부(recoil) const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); @@ -92,7 +102,7 @@ export const BuyerAvailCounselor = () => { { - navigate(-1); + navigate('/share'); }} /> 들을 준비가 된 마인더들 @@ -125,6 +135,8 @@ export const BuyerAvailCounselor = () => { }} /> { const navigate = useNavigate(); - //0 : 최신순 1:인기순 2: 별점순 - // 바뀔 때마다 useEffect로 request - const [sortType, setSortType] = useState(0); - // Modal 여부(recoil) + + // using query params to store sorting type + const [searchParams, setSearchParams] = useSearchParams(); + + // sortType - 0 : 최신순 1:인기순 2: 별점순, 기본값: 0, 최신순 + const initialSortType = + searchParams.get('sort') === 'rating' + ? 2 + : searchParams.get('sort') === 'popular' + ? 1 + : 0; + const [sortType, setSortType] = useState(initialSortType); + + // state with opening Modal, lock scroll const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); - //scorll 막기 const setScrollLock = useSetRecoilState(scrollLockState); - //const 카테고리 정보, 검색과 동일하게 searchKeyword로 넘겨줌 + + // state with store keyword and serach result const searchKeyword = useRecoilValue(searchKeywordState); - //결과저장 const [searchData, setSearchData] = useState([]); - //무한스크롤 위한 page num + + //page num for infinite scrol const [pageNum, setPageNum] = useState(0); const [isLastElem, setIsLastElem] = useState(false); - const [isLoading, setIsLoading] = useState(true); //로딩 state - const preventRef = useRef(true); // 중복 방지 옵션 - //fetch 함수 + + // loding spinner + const [isLoading, setIsLoading] = useState(true); + const preventRef = useRef(true); + + // functio with fetch API const fetchSearchResults = async (pageIndex: number) => { try { const body = { @@ -137,6 +154,8 @@ export const BuyerCategoryResult = () => { }} /> { const navigate = useNavigate(); @@ -129,7 +130,7 @@ export const BuyerChat = () => { ) { //새 메세지 도착으로 분류 newMessageRef.current = true; - setTime('10:00'); + setTime(CHAT_START_REQUEST_TIME); setMessages((prevMessages) => [ ...prevMessages, { @@ -258,7 +259,7 @@ export const BuyerChat = () => { } }; const sendMessage = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chatMessages/customers/' + chatId, {}, @@ -268,7 +269,7 @@ export const BuyerChat = () => { }; const sendChatStartResponse = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chat/customers/' + chatId, {}, @@ -277,14 +278,14 @@ export const BuyerChat = () => { } }; - const sentExitResponse = () => { - if (stompClient.current) { + const sendExitResponse = () => { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send('app/api/v1/chat/customers/exit/' + chatId, {}); } }; const sendChatFinishRequest = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chat/customers/' + chatId, {}, @@ -351,8 +352,13 @@ export const BuyerChat = () => { }); useEffect(() => { + if (!stompClient.current?.connected) { + return; + } // 컴포넌트가 마운트되었을 때 실행 + connectChat(); + //채팅 불러오기 getChatMessages(0); // @@ -360,7 +366,7 @@ export const BuyerChat = () => { //관측 가능 preventRef.current = true; return () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current?.connected) { stompClient.current.unsubscribe('/queue/chattings/customers/' + chatId); stompClient.current.unsubscribe( '/queue/chattings/status/customers/' + chatId, @@ -371,10 +377,10 @@ export const BuyerChat = () => { stompClient.current.unsubscribe( '/queue/chatMessages/customers/' + chatId, ); - sentExitResponse(); + sendExitResponse(); } }; - }, []); + }, [stompClient.current?.connected]); //useEffects //보내기 버튼 색상처리 diff --git a/src/pages/Buyer/BuyerConsult.tsx b/src/pages/Buyer/BuyerConsult.tsx index 36b784fd..1d7d586a 100644 --- a/src/pages/Buyer/BuyerConsult.tsx +++ b/src/pages/Buyer/BuyerConsult.tsx @@ -30,6 +30,7 @@ export interface consultApiObject { } export interface openConsultApiObject { postId: number; + postScrapId: number; title: string; content: string; isPublic: boolean; @@ -39,6 +40,7 @@ export interface openConsultApiObject { publishedAt: string; isChosen: boolean; isScrapped: boolean; + scrappedAt: string; totalScrap: number; totalComment: number; updatedAt: string; @@ -48,9 +50,7 @@ export interface openConsultApiObject { export const BuyerConsult = () => { const navigate = useNavigate(); - const sortList = ['최근순', '읽지않은순']; - const { consultType, sortType, @@ -83,7 +83,7 @@ export const BuyerConsult = () => {
@@ -91,7 +91,7 @@ export const BuyerConsult = () => { @@ -99,7 +99,7 @@ export const BuyerConsult = () => { @@ -161,10 +161,14 @@ export const BuyerConsult = () => { }; const Wrapper = styled.div` .options { - padding: 0.8rem 2rem 0rem; + padding: 0.8rem 2rem 1.6rem; display: flex; flex-direction: column; gap: 1.2rem; + position: sticky; + top: 10.5rem; + background-color: white; + z-index: 10; } .select { display: flex; @@ -175,7 +179,7 @@ const Wrapper = styled.div` .select-wrapper { display: flex; gap: 0.4rem; - cursor: pointer; + cursor: pointer; } .select-button { display: flex; @@ -186,8 +190,8 @@ const Wrapper = styled.div` gap: 0.4rem; } `; -const SelectButton = styled.div<{ isSelected: boolean; isLong?: boolean }>` - background-color: ${(props) => (props.isSelected ? LightGreen : Grey6)}; +const SelectButton = styled.div<{ $isSelected: boolean; $isLong?: boolean }>` + background-color: ${(props) => (props.$isSelected ? LightGreen : Grey6)}; cursor: pointer; padding: 0.8rem 1.6rem; border-radius: 1.2rem; diff --git a/src/pages/Buyer/BuyerHome.tsx b/src/pages/Buyer/BuyerHome.tsx index 47051b72..6152b1a6 100644 --- a/src/pages/Buyer/BuyerHome.tsx +++ b/src/pages/Buyer/BuyerHome.tsx @@ -11,27 +11,26 @@ import { useEffect, useState } from 'react'; import { patchCounselorsAll } from 'api/patch'; export const BuyerHome = () => { const navigate = useNavigate(); - //결과저장 const [searchData, setSearchData] = useState([]); - const fectchSearchResults = async () => { - try { - const body = { - index: 0, - }; - const res: any = await patchCounselorsAll('POPULARITY', body); - if (res.status === 200) { - setSearchData(res.data); - } else if (res.response.status === 404) { - alert('유효하지 않은 정렬 방식입니다.'); - navigate('/share'); - } - } catch (e) { - console.log(e); - } - }; useEffect(() => { + const fectchSearchResults = async () => { + try { + const body = { + index: 0, + }; + const res: any = await patchCounselorsAll('POPULARITY', body); + if (res.status === 200) { + setSearchData(res.data); + } else if (res.response.status === 404) { + alert('유효하지 않은 정렬 방식입니다.'); + navigate('/share'); + } + } catch (e) { + alert(e); + } + }; fectchSearchResults(); - }, []); + }, [navigate]); return (
{ cost: 500, isPublic: false, }; - await postOpenConsult(body); - navigate('/paymentComplete'); + try { + const res: any = await postOpenConsult(body); + if (res.status === 201) { + console.log(res); + navigate('/paymentComplete'); + } else if (res?.response.status === 404) { + alert('존재하지 않는 회원입니다.'); + } + } catch (err) { + alert(err); + } }; return ( diff --git a/src/pages/Buyer/BuyerPayment.tsx b/src/pages/Buyer/BuyerPayment.tsx index 48b18fc5..536743ae 100644 --- a/src/pages/Buyer/BuyerPayment.tsx +++ b/src/pages/Buyer/BuyerPayment.tsx @@ -2,12 +2,13 @@ import { getPaymentsCustomers } from 'api/get'; import { PaymentCard } from 'components/Buyer/BuyerPayment/PaymentCard'; import { PaymentModal } from 'components/Buyer/BuyerPayment/PaymentModal'; import { BackIcon, HeaderWrapper } from 'components/Buyer/Common/Header'; +import { Space } from 'components/Common/Space'; import useIntersectionObserver from 'hooks/useIntersectionObserver'; import { useLayoutEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useRecoilState, useSetRecoilState } from 'recoil'; import styled from 'styled-components'; -import { Green, Grey1, Grey5, White } from 'styles/color'; +import { Green, Grey1, Grey5, Grey6, White } from 'styles/color'; import { Button2, Heading } from 'styles/font'; import { isPaymentModalOpenState, scrollLockState } from 'utils/atom'; import { PaymentInfo } from 'utils/type'; @@ -190,6 +191,7 @@ export const BuyerPayment = () => { ); })} + {!isLastElem ? (
{ ) : null} - ); + ); } }; @@ -232,12 +234,18 @@ const CardWrapper = styled.div` flex-direction: column; align-items: center; gap: 0.8rem; + height: calc(100% - 11.6rem); + overflow: scroll; margin-top: 1.2rem; `; const ToggleWrapper = styled.div` display: flex; - margin: 0.8rem 2rem; + padding: 0.8rem 2rem; gap: 0.8rem; + background-color: white; + border-bottom: 1px solid ${Grey6}; + position: sticky; + top: 5.3rem; `; const ToggleButton = styled.div<{ focus: boolean }>` width: 8.1rem; diff --git a/src/pages/Buyer/BuyerSavedCounselor.tsx b/src/pages/Buyer/BuyerSavedCounselor.tsx index 3d45e494..d08fa37d 100644 --- a/src/pages/Buyer/BuyerSavedCounselor.tsx +++ b/src/pages/Buyer/BuyerSavedCounselor.tsx @@ -10,15 +10,22 @@ import { WishlistDataType } from 'utils/type'; import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; import styled from 'styled-components'; import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import Divider2 from 'components/Common/Divider2'; +import { openConsultApiObject } from './BuyerConsult'; +import { getPostScraps } from 'api/get'; +import SavedOpenConsultResults from 'components/Buyer/BuyerSavedCounselor.tsx/SavedOpenConsultResults'; +import { LoadingSpinner } from 'utils/LoadingSpinner'; // TODO: 찜한 마인더 없을 시 페이지 추후 백 연동 시 구현 export const BuyerSavedCounselor = () => { const navigate = useNavigate(); const [isInitialLoading, setIsInitialLoading] = useState(true); const [wishlistData, setWishlistData] = useState([]); + const [openConsultData, setOpenConsultData] = useState< + openConsultApiObject[] + >([]); const [isLastElem, setIsLastElem] = useState(false); - + const [tabState, setTabState] = useState(1); const preventRef = useRef(true); - const onIntersect: IntersectionObserverCallback = async (entry) => { if ( entry[0].isIntersecting && @@ -27,10 +34,18 @@ export const BuyerSavedCounselor = () => { preventRef.current ) { preventRef.current = false; - await fetchWishlistData( - wishlistData[wishlistData.length - 1].wishlistId, - wishlistData[wishlistData.length - 1].updatedAt, - ); + if (tabState === 1) { + await fetchWishlistData( + wishlistData[wishlistData.length - 1].wishlistId, + wishlistData[wishlistData.length - 1].updatedAt, + ); + } else if (tabState === 2) { + await fetchOpenConsultData( + openConsultData[openConsultData.length - 1].postScrapId, + openConsultData[openConsultData.length - 1].scrappedAt, + ); + } + preventRef.current = true; } }; @@ -46,12 +61,12 @@ export const BuyerSavedCounselor = () => { wishlistId: lastId, updatedAt: lastUpdateAt, }; - try { const res: any = await postWishLists(body); if (res.status === 200) { if (res.data.length !== 0) { if (lastId === 0) { + setIsInitialLoading(true); setWishlistData(res.data); } else { const updatedReviews = [...wishlistData, ...res.data]; @@ -71,9 +86,44 @@ export const BuyerSavedCounselor = () => { } } }; + const fetchOpenConsultData = async (lastId: number, lastUpdateAt: string) => { + try { + const params = { + postScrapId: lastId, + scrappedAt: lastUpdateAt, + }; + const res: any = await getPostScraps({ params }); + if (res.status === 200) { + if (res.data.length !== 0) { + if (lastId === 0) { + setIsInitialLoading(true); + setOpenConsultData(res.data); + } else { + const updatedOpenConsultList = [...openConsultData, ...res.data]; + setOpenConsultData(updatedOpenConsultList); + } + } else { + setIsLastElem(true); + } + } else if (res.response.status !== 401) { + // navigate('/mypage'); + } + } catch (err) { + alert(err); + } finally { + if (lastId === 0) { + setIsInitialLoading(false); + } + } + }; useLayoutEffect(() => { - fetchWishlistData(0, ''); - }, []); + setIsInitialLoading(true); + if (tabState === 1) { + fetchWishlistData(0, ''); + } else if (tabState === 2) { + fetchOpenConsultData(0, new Date().toISOString().slice(0, 19)); + } + }, [tabState]); if (isInitialLoading) { return ( <> @@ -85,51 +135,112 @@ export const BuyerSavedCounselor = () => { /> 찜 목록 + +
+ +
); } else { - if (wishlistData.length !== 0) { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - -
- - {!isLastElem ? ( -
- ) : ( -
- )} -
- - ); - } else { - return ( - <> - - { - navigate('/mypage'); - }} - /> - 찜 목록 - - - - 아직 후기가 없어요. - - - ); + if (tabState === 1) { + if (wishlistData.length !== 0) { + return ( + <> + + { + navigate('/mypage'); + }} + /> + 찜 목록 + + + +
+ + {!isLastElem ? ( +
+ ) : ( +
+ )} +
+ + ); + } else { + return ( + <> + + { + navigate('/mypage'); + }} + /> + 찜 목록 + + + + + 아직 후기가 없어요. + + + ); + } + } else if (tabState === 2) { + if (openConsultData.length !== 0) { + return ( + <> + + { + navigate('/mypage'); + }} + /> + 찜 목록 + + + +
+ + {!isLastElem ? ( +
+ ) : ( +
+ )} +
+ + ); + } else { + return ( + <> + + { + navigate('/mypage'); + }} + /> + 찜 목록 + + + + + 아직 저장한 일대다상담이 없어요. + + + ); + } } } }; diff --git a/src/pages/Buyer/BuyerSearchResult.tsx b/src/pages/Buyer/BuyerSearchResult.tsx index ad24a046..53bfc8f2 100644 --- a/src/pages/Buyer/BuyerSearchResult.tsx +++ b/src/pages/Buyer/BuyerSearchResult.tsx @@ -7,43 +7,67 @@ import { ReactComponent as Search } from 'assets/icons/search.svg'; import { ReactComponent as Down } from 'assets/icons/icon-drop-down.svg'; import { SearchResults } from 'components/Buyer/BuyerSearchResult/SearchResults'; import { SortModal } from 'components/Buyer/Common/SortModal'; -import { ChangeEvent, useLayoutEffect, useRef, useState } from 'react'; -import { sortList } from 'utils/constant'; +import { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import { openSortList, sortList } from 'utils/constant'; import { useRecoilState, useSetRecoilState } from 'recoil'; -import { - isLoadingState, - isSortModalOpenState, - scrollLockState, - searchKeywordState, -} from 'utils/atom'; +import { isSortModalOpenState, scrollLockState } from 'utils/atom'; import Input from 'components/Common/Input'; -import { patchSearchWordsResults } from 'api/patch'; +import { + patchSearchWordsCounselorsResults, + patchSearchWordsPostsResults, +} from 'api/patch'; import { SearchResultData } from 'utils/type'; import { ConverSortType } from 'utils/convertSortType'; import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; import { LoadingSpinner } from 'utils/LoadingSpinner'; import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import Divider2 from 'components/Common/Divider2'; +import OpenConsultResults from 'components/Buyer/BuyerSearchResult/OpenConsultResults'; +import { openConsultApiObject } from './BuyerConsult'; +import { OpenConsultSortModal } from 'components/Buyer/Common/OpenConsultSortModal'; +import { ConvertOpenSortType } from 'utils/convertOpenSortType'; +import { useSearchPageParams } from 'hooks/useSearchPageParams'; export const BuyerSearchResult = () => { const navigate = useNavigate(); - //0 : 최신순 1:인기순 2: 별점순 - // 바뀔 때마다 useEffect로 request - const [sortType, setSortType] = useState(0); - // Modal 여부(recoil) + + // state with modal, scrollLock const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); - //scorll 막기 const setScrollLock = useSetRecoilState(scrollLockState); - //검색된 value - const [keyword, setKeyword] = useRecoilState(searchKeywordState); - //input value - const initInput = keyword; - const [input, setInput] = useState(initInput); - //결과저장 + + // state for saving search api result const [searchData, setSearchData] = useState([]); - //무한스크롤 위한 page num + const [openConsultSearchData, setOpenConsultSearchData] = useState< + openConsultApiObject[] + >([]); + + // state with inifinite scroll - page number and last id const [pageNum, setPageNum] = useState(0); + const [lastId, setLastId] = useState(0); + + // state for trigger fetch api (infinite scroll) const [isLastElem, setIsLastElem] = useState(false); - const preventRef = useRef(true); // 중복 방지 옵션 + const [isLoading, setIsLoading] = useState(true); + + // state for blocking fetch api (infinite scroll) when the previous request has not been completed + const preventRef = useRef(true); + + const { + handleClickTab, + searchParams, + setSearchParams, + openSortType, + setOpenSortType, + input, + keyword, + tabState, + handleSubmit, + sortType, + setSortType, + handleChangeInput, + } = useSearchPageParams(); + + // function to run in intersection observer const onIntersect: IntersectionObserverCallback = async (entry) => { if ( entry[0].isIntersecting && @@ -52,67 +76,113 @@ export const BuyerSearchResult = () => { preventRef.current ) { preventRef.current = false; - await fetchSearchResults(keyword, pageNum); + if (tabState === 1) { + await fetchSearchResults(keyword, pageNum); + } else if (tabState === 2) { + await fetchOpenSearchResults(keyword, lastId); + } + preventRef.current = true; } }; - //현재 대상 및 option을 props로 전달 const { setTarget } = useIntersectionObserver({ root: null, rootMargin: '0px', threshold: 0.8, onIntersect, }); - //input onchagne - const handleOnChange = (event: ChangeEvent) => { - setInput(event.target.value); - }; - const handleSubmit: any = (event: ChangeEvent) => { - event.preventDefault(); - setKeyword(input); - }; - //로딩 state - const [isLoading, setIsLoading] = useState(true); - const fetchSearchResults = async (searchWord: string, pageIndex: number) => { - try { - const body = { - word: searchWord, - index: pageIndex, - }; - const sortTypeString: string = ConverSortType(sortType); - const res: any = await patchSearchWordsResults(sortTypeString, body); - if (res.status === 200) { - if (res.data.length !== 0) { - if (pageIndex === 0) { - setSearchData(res.data); - setPageNum(pageNum + 1); + // function for fetching search result + const fetchSearchResults = useCallback( + async (searchWord: string, pageIndex: number) => { + try { + const body = { + word: searchWord, + index: pageIndex, + }; + const sortTypeString: string = ConverSortType(sortType); + const res: any = await patchSearchWordsCounselorsResults( + sortTypeString, + body, + ); + if (res.status === 200) { + if (res.data.length !== 0) { + if (pageIndex === 0) { + setSearchData(res.data); + setPageNum(pageNum + 1); + } else { + const updatedSearchs = [...searchData, ...res.data]; + setSearchData(updatedSearchs); + setPageNum(pageNum + 1); + } } else { - const updatedSearchs = [...searchData, ...res.data]; - setSearchData(updatedSearchs); - setPageNum(pageNum + 1); + setIsLastElem(true); } - } else { - setIsLastElem(true); + } else if (res.response.status === 400) { + alert('검색어는 2~20자 사이여야 합니다.'); + } + } catch (e) { + alert(e); + } finally { + if (pageIndex === 0) { + setIsLoading(false); } - } else if (res.response.status === 400) { - alert('검색어는 2~20자 사이여야 합니다.'); } - } catch (e) { - alert(e); - } finally { - if (pageIndex === 0) { - setIsLoading(false); + }, + [pageNum, searchData, sortType], + ); + const fetchOpenSearchResults = useCallback( + async (searchWord: string, postId: number) => { + try { + const body = { + word: searchWord, + postId: postId, + }; + const sortTypeString: string = ConvertOpenSortType(openSortType); + const res: any = await patchSearchWordsPostsResults( + sortTypeString, + body, + ); + if (res.status === 200) { + if (res.data.length !== 0) { + if (postId === 0) { + setOpenConsultSearchData(res.data); + setPageNum(pageNum + 1); + } else { + const updatedSearchs = [...openConsultSearchData, ...res.data]; + setLastId(res.data.postId); + setOpenConsultSearchData(updatedSearchs); + } + setIsLastElem(true); + } else { + setOpenConsultSearchData([]); + } + } else if (res.response.status === 400) { + alert('검색어는 2~20자 사이여야 합니다.'); + } + } catch (e) { + alert(e); + } finally { + if (postId === 0) { + setIsLoading(false); + } } - } - }; + }, + [openConsultSearchData, openSortType, pageNum], + ); + useLayoutEffect(() => { setIsLastElem(false); setPageNum(0); setSearchData([]); setIsLoading(true); - fetchSearchResults(keyword, 0); - }, [keyword, sortType]); + if (tabState === 1) { + fetchSearchResults(keyword, 0); + } else if (tabState === 2) { + fetchOpenSearchResults(keyword, 0); + } + }, [keyword, sortType, tabState, openSortType]); + if (isLoading) { return ( <> @@ -122,32 +192,10 @@ export const BuyerSearchResult = () => { navigate('/consult'); }} /> - -
- -
- - ); - } else { - return ( - - - { - navigate('/search'); - }} - /> { placeHolderColor={Grey4} height="4.4rem" width="100%" - padding="0 3.2rem 0 0" - textIndent="1rem" + isBoxSizing={true} + padding="0.8rem 3.4rem 0.8rem 1.6rem" /> -
-
{ - setIsModalOpen(true); - setScrollLock(true); - }} - > - {sortList[sortType]} - -
-
- {searchData.length !== 0 ? ( - <> - - {!isLastElem ? ( -
- ) : ( -
- )} - - ) : ( - - - - 검색 결과가 없어요. - - 마인더명, 제목, 상담 스타일 등 - 더 간단한 단어로 검색해보세요. - - )} - {isModalOpen ? ( - <> - { - //여기서 api 호출 - setIsModalOpen(false); - setScrollLock(false); - }} - /> - - - ) : null} - +
+ +
+ ); + } else { + if (tabState === 1) { + return ( + + + + { + navigate('/search'); + }} + /> + + + + + + +
+
{ + setIsModalOpen(true); + setScrollLock(true); + }} + > + {sortList[sortType]} + +
+
+
+ + {searchData.length !== 0 ? ( + <> + + {!isLastElem ? ( +
+ ) : ( +
+ )} + + ) : ( + + + + 검색 결과가 없어요. + + 마인더명, 제목, 상담 스타일 등 + 더 간단한 단어로 검색해보세요. + + )} + + {isModalOpen ? ( + <> + { + //여기서 api 호출 + setIsModalOpen(false); + setScrollLock(false); + }} + /> + + + ) : null} + + ); + } else if (tabState === 2) { + return ( + + + + { + navigate('/search'); + }} + /> + + + + + + +
+
{ + setIsModalOpen(true); + setScrollLock(true); + }} + > + {openSortList[openSortType]} + +
+
+
+ + {openConsultSearchData.length !== 0 ? ( + <> + + {!isLastElem ? ( +
+ ) : ( +
+ )} + + ) : ( + + + + 검색 결과가 없어요. + + 마인더명, 제목, 상담 스타일 등 + 더 간단한 단어로 검색해보세요. + + )} + + {isModalOpen ? ( + <> + { + //여기서 api 호출 + setIsModalOpen(false); + setScrollLock(false); + }} + /> + + + ) : null} + + ); + } } }; const Wrapper = styled.div` .select { display: flex; + background-color: white; height: 4.4rem; padding: 0.4rem 2rem; align-items: center; justify-content: flex-end; cursor: pointer; + z-index: 9; } .select-wrapper { display: flex; gap: 0.4rem; } `; -const HeaderWrapper = styled.div` +const HeaderWrapper = styled.header` height: 5.2rem; + gap: 0.8rem; background-color: ${White}; position: relative; display: flex; align-items: center; + width: 100%; justify-content: center; + box-sizing: border-box; padding: 0.4rem 2rem; `; const FormWrapper = styled.form` position: relative; - width: 79%; + width: 100%; `; const BackIcon = styled(Back)` - position: absolute; - top: 1.4rem; - left: 2rem; cursor: pointer; `; const SearchIcon = styled(Search)` position: absolute; - right: -2.7rem; + right: 0.8rem; top: 0.8rem; cursor: pointer; `; @@ -276,3 +455,11 @@ const EmptyWrapper = styled.div` flex-direction: column; align-items: center; `; + +const FixedContainer = styled.section` + position: sticky; + width: 100%; + top: 0; + background-color: white; + z-index: 99; +`; diff --git a/src/pages/Buyer/BuyerWriteOpenConsult.tsx b/src/pages/Buyer/BuyerWriteOpenConsult.tsx index a7b352c5..a7578621 100644 --- a/src/pages/Buyer/BuyerWriteOpenConsult.tsx +++ b/src/pages/Buyer/BuyerWriteOpenConsult.tsx @@ -66,7 +66,7 @@ function BuyerWriteOpenConsult() { } }; fetchDraftedData(); - }, []); + }, [postId]); useEffect(() => { if (input === '' || titleInput === '') { // 비어 있으면 전송못함 diff --git a/src/pages/Common/Service.tsx b/src/pages/Common/Service.tsx index 779119d8..4c9d5141 100644 --- a/src/pages/Common/Service.tsx +++ b/src/pages/Common/Service.tsx @@ -1,6 +1,6 @@ import { BackIcon, HeaderWrapper } from 'components/Buyer/Common/Header'; import { Space } from 'components/Common/Space'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled, { keyframes } from 'styled-components'; import { Green, Grey2, Grey3, Grey6, LightGreen } from 'styles/color'; @@ -19,7 +19,7 @@ import { Button } from 'components/Common/Button'; import { ReactComponent as HeartIcon } from 'assets/icons/icon-heart2.svg'; import { LoadingSpinner } from 'utils/LoadingSpinner'; import { Characters } from 'utils/Characters'; -import { consultStyleToCharNum } from 'utils/convertStringToCharNum'; + const slideArray = [ '연애갈등', '이별/재회', @@ -44,7 +44,7 @@ function Service() { { - navigate('/share'); + navigate(-1); }} /> 서비스 소개 diff --git a/src/pages/Seller/SellerCalculateManagement.tsx b/src/pages/Seller/SellerCalculateManagement.tsx index 6b61ee80..d64b8130 100644 --- a/src/pages/Seller/SellerCalculateManagement.tsx +++ b/src/pages/Seller/SellerCalculateManagement.tsx @@ -1,9 +1,9 @@ import { ManagementHeader } from 'components/Seller/SellerCalculateManagement/ManagementHeader'; import styled from 'styled-components'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { ManagementStatusSelector } from 'components/Seller/SellerCalculateManagement/ManagementStatusSelector'; import { Heading, Subtitle } from 'styles/font'; -import { Green, Red } from 'styles/color'; +import { Red } from 'styles/color'; import { SellerCalulateCard } from 'components/Seller/SellerCalculateManagement/SellerCalculateCard'; import { NoCalculationGraphic } from 'components/Seller/SellerCalculateManagement/NoCalculationGraphic'; import { useRecoilState, useSetRecoilState } from 'recoil'; @@ -12,16 +12,23 @@ import { SellerManagementModal } from 'components/Seller/SellerCalculateManageme import { getPaymentsMinder } from 'api/get'; import { useNavigate } from 'react-router-dom'; import { LoadingSpinner } from 'utils/LoadingSpinner'; +import useIntersectionObserver from 'hooks/useIntersectionObserver'; +/// +/// +/// const manageStatusMap = { '정산 중': 'SETTLEMENT_ONGOING', '정산 예정': 'SETTLEMENT_WAITING', 완료: 'SETTLEMENT_COMPLETE', }; - const sortTypeMap = ['WEEK', 'MONTH', 'ALL']; + +/// +/// +/// interface ManageItem { - paymentId: string; + paymentId: number; nickname: string; isChat: boolean; profit: number; @@ -32,11 +39,14 @@ interface ManageItem { total: number; } type ManageList = ManageItem[]; + +/// +/// +/// + export const SellerCaculateManagement = () => { // 상단 탭의 상태 const [manageStatus, setManageStatus] = useState('완료'); - // 내역 없음 여부 - const [isNoCalculatoin, setIsNoCalculation] = useState(false); // 드롭다운 메뉴, 최근 일주일 : 0 , 최근 1개월 : 1, 전체 : 2 const [sortType, setSortType] = useState(0); const [isModalOpen, setIsModalOpen] = useRecoilState( @@ -44,38 +54,80 @@ export const SellerCaculateManagement = () => { ); //scorll 막기 const setScrollLock = useSetRecoilState(scrollLockState); + const preventRef = useRef(true); + + const onIntersect: IntersectionObserverCallback = async (entry) => { + if (entry[0].isIntersecting && !isLastElem && preventRef.current) { + preventRef.current = false; + await fetchManagements( + managementList[managementList.length - 1].paymentId, + ); + preventRef.current = true; + } + }; + const { setTarget } = useIntersectionObserver({ + root: null, + rootMargin: '0px', + threshold: 0.8, + onIntersect, + }); + const [isLastElem, setIsLastElem] = useState(false); const [managementList, setManagementList] = useState([]); const [toatlMoney, setTotalMoney] = useState(0); const [isCompleteApplyManage, setIsCompleteApplyManage] = useState(false); const [isLoading, setIsLoading] = useState(true); const navigate = useNavigate(); - useEffect(() => { - const fetchManagements = async () => { - setIsLoading(true); - const params = { - status: manageStatusMap[manageStatus], - sort: sortTypeMap[sortType], - paymentId: 0, - }; - const res: any = await getPaymentsMinder({ params }); + const fetchManagements = async (lastId: number) => { + const params = { + status: manageStatusMap[manageStatus], + sort: sortTypeMap[sortType], + paymentId: lastId, + }; + const res: any = await getPaymentsMinder({ params }); + try { if (res?.status === 200) { - setManagementList(res?.data); - let totalEarnMoney = 0; - res?.data?.forEach((item: ManageItem) => { - totalEarnMoney += Number(item?.profit); - }); - setTotalMoney(totalEarnMoney); - setTimeout(() => { - setIsLoading(false); - }, 500); + if (res.data.length !== 0) { + if (lastId === 0) { + setManagementList(res.data); + let totalEarnMoney = 0; + res?.data?.forEach((item: ManageItem) => { + totalEarnMoney += Number(item?.profit); + }); + setTotalMoney(totalEarnMoney); + } else { + const updatedManages = [...managementList, ...res.data]; + setManagementList(updatedManages); + let totalEarnMoney = 0; + res?.data?.forEach((item: ManageItem) => { + totalEarnMoney += Number(item?.profit); + }); + setTotalMoney(totalEarnMoney); + } + } else { + setManagementList(res.data); + setIsLastElem(true); + let totalEarnMoney = 0; + res?.data?.forEach((item: ManageItem) => { + totalEarnMoney += Number(item?.profit); + }); + setTotalMoney(totalEarnMoney); + } } else { alert('판매 정보가 아직 등록되지 않았어요!'); navigate('/minder'); } - }; - fetchManagements(); - }, [manageStatus, sortType, isCompleteApplyManage]); + } catch (err) { + alert(err); + } finally { + if (lastId === 0) { + setIsLoading(false); + } + } + }; + useEffect(() => { + fetchManagements(0); + }, [manageStatus, sortType, isCompleteApplyManage, navigate]); return ( <> @@ -104,27 +156,34 @@ export const SellerCaculateManagement = () => { {managementList?.length === 0 ? ( ) : ( - - {/* //정산 예정일일 경우 calculateActivate false true로~*/} - {managementList?.map((item) => ( - - ))} - + <> + + {/* //정산 예정일일 경우 calculateActivate false true로~*/} + {managementList?.map((item) => ( + + ))} + + {!isLastElem ? ( +
+ ) : ( +
+ )} + )} )} @@ -152,7 +211,7 @@ const TotalEarnMoney = styled.div` display: flex; align-items: center; gap: 0.8rem; - margin: 2.8rem 2rem 1.2rem; + margin: 1.2rem 2rem 1.2rem; `; const SellerCalculateCardList = styled.div` diff --git a/src/pages/Seller/SellerChat.tsx b/src/pages/Seller/SellerChat.tsx index 0929ca6c..eb2ccbc3 100644 --- a/src/pages/Seller/SellerChat.tsx +++ b/src/pages/Seller/SellerChat.tsx @@ -18,6 +18,7 @@ import { useStompContext } from 'contexts/StompContext'; import useChatRequestTime from 'hooks/Chat/useChatRequestTime'; import SellerChatFooter from 'components/Seller/SellerChat/SellerChatFooter'; import SellerChatSection from 'components/Seller/SellerChat/SellerChatSection'; +import { CHAT_START_REQUEST_TIME } from 'utils/constant'; // // @@ -144,7 +145,7 @@ const SellerChat = () => { ) { //구매자와 달리 현재 채팅 status를 업데이트하는 방향으로 구현해야할듯 //새 메세지 도착으로 분류 - setTime('10:00'); + setTime(CHAT_START_REQUEST_TIME); setChatStatus('상담 시작 요청'); } else if ( arrivedMessage.chatWebsocketStatus === @@ -244,7 +245,7 @@ const SellerChat = () => { }; const sendMessage = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chatMessages/counselors/' + chatId, {}, @@ -254,7 +255,7 @@ const SellerChat = () => { }; const sendChatStartRequest = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chat/counselors/' + chatId, {}, @@ -263,14 +264,14 @@ const SellerChat = () => { } }; - const sentExitResponse = () => { - if (stompClient.current) { + const sendExitResponse = () => { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send('app/api/v1/chat/counselors/exit/' + chatId, {}); } }; const sendChatFinishRequest = () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current.connected) { stompClient.current.send( '/app/api/v1/chat/counselors/' + chatId, {}, @@ -339,8 +340,12 @@ const SellerChat = () => { }); useEffect(() => { - // 컴포넌트가 마운트되었을 때 실행 + if (!stompClient.current?.connected) { + return; + } + connectChat(); + //채팅 불러오기 getChatMessages(0); //채팅 status, 상대이름 가져오기 @@ -349,7 +354,7 @@ const SellerChat = () => { preventRef.current = true; // 언마운트 시에 소켓 연결 해제 return () => { - if (stompClient.current) { + if (stompClient.current && stompClient.current?.connected) { stompClient.current.unsubscribe( '/queue/chattings/counselors/' + chatId, ); @@ -362,10 +367,10 @@ const SellerChat = () => { stompClient.current.unsubscribe( '/queue/chatMessages/counselors/' + chatId, ); - sentExitResponse(); + sendExitResponse(); } }; - }, []); + }, [chatId, stompClient, stompClient.current?.connected]); //useEffects //보내기 버튼 색상처리 diff --git a/src/pages/Seller/SellerConsult.tsx b/src/pages/Seller/SellerConsult.tsx index a264a6bd..9a127cbb 100644 --- a/src/pages/Seller/SellerConsult.tsx +++ b/src/pages/Seller/SellerConsult.tsx @@ -14,12 +14,8 @@ export const SellerConsult = () => { }} /> -
- -
+ + ); }; diff --git a/src/pages/Seller/SellerLetterWrite.tsx b/src/pages/Seller/SellerLetterWrite.tsx index 53c3ab64..74b2f256 100644 --- a/src/pages/Seller/SellerLetterWrite.tsx +++ b/src/pages/Seller/SellerLetterWrite.tsx @@ -1,16 +1,13 @@ -import { getDraftsLetter, getLetterRecentType } from 'api/get'; import { LetterWriteHeader } from 'components/Seller/SellerLetterWrite/LetterWriteHeader'; import { LetterWriteMainSection } from 'components/Seller/SellerLetterWrite/LetterWriteMainSection'; import { LetterWriteSuccess } from 'components/Seller/SellerLetterWrite/LetterWriteSuccess'; -import { useEffect, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useState } from 'react'; export const SellerLetterWrite = () => { // 편지 전송 완료 페이지 띄울지 여부 const [isSend, setIsSend] = useState(false); // 셰어로부터 질문 한눈에 보기 여부 const [isViewQuestion, setIsViewQuestion] = useState(false); - const navigate = useNavigate(); return ( <> { const category = useCustomSelect('category'); const style = useCustomSelect('style'); const type = useCustomSelect('type'); - // 시간 설정은 나중에....ㅠㅠ + const availableTime = useCustomSelect('time'); const letterPrice = useInput(''); diff --git a/src/pages/Seller/SellerMyPageViewProfile.tsx b/src/pages/Seller/SellerMyPageViewProfile.tsx index 56af4aba..30806828 100644 --- a/src/pages/Seller/SellerMyPageViewProfile.tsx +++ b/src/pages/Seller/SellerMyPageViewProfile.tsx @@ -33,9 +33,12 @@ export const SellerMypageViewProfile = () => { setIsEvaluationPending(true); } const profileRes: any = await getProfiles(); - if (profileRes?.response?.status === 404) { + if (profileRes?.response?.status === 403) { alert('마인더 인증을 통과한 뒤 판매 정보를 등록할 수 있습니다.'); navigate('/minder/mypage'); + } else if (profileRes?.response?.status === 404) { + alert('등록되지 않은 회원 정보입니다.'); + navigate('/minder/mypage'); } setProfile(profileRes.data); setIsLoading(false); diff --git a/src/pages/Seller/SellerMypage.tsx b/src/pages/Seller/SellerMypage.tsx index 375fa2f1..8875d659 100644 --- a/src/pages/Seller/SellerMypage.tsx +++ b/src/pages/Seller/SellerMypage.tsx @@ -12,7 +12,7 @@ interface UserInfo { nickname: string; level: string; isEducated: boolean; - consultStlyle: string; + consultStyle: string; profilesStatus: string; } export const SellerMypage = () => { diff --git a/src/pages/Seller/SellerRefundBankAccount.tsx b/src/pages/Seller/SellerRefundBankAccount.tsx index cf742394..b8b386f1 100644 --- a/src/pages/Seller/SellerRefundBankAccount.tsx +++ b/src/pages/Seller/SellerRefundBankAccount.tsx @@ -11,15 +11,15 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useRecoilState } from 'recoil'; import styled from 'styled-components'; -import { Grey1, Grey3, Grey6 } from 'styles/color'; -import { Body1, Body3, Heading } from 'styles/font'; +import { Grey1, Grey6 } from 'styles/color'; +import { Body1, Heading } from 'styles/font'; import { BankIcon } from 'utils/BankIcon'; import { isBankModalOpenState } from 'utils/atom'; function SellerRefundBankAccount() { const navigate = useNavigate(); - const [accountNum, setAccountNum] = useState(''); - const [bankType, setBankType] = useState(null); + const [accountNum, setAccountNum] = useState(''); + const [bankType, setBankType] = useState(null); const [owner, setOwner] = useState(''); // 은행 모달 const [isBankModalOpen, setIsBankModalOpen] = @@ -27,14 +27,9 @@ function SellerRefundBankAccount() { const [isActiveFisnishButton, setIsActiveFinishButton] = useState(false); useEffect(() => { - // API 요청 - // setAccountNum('12345678900'); - // setBankType('우리은행'); - // setOwner('김고민'); - // const res= getCounselorsAccount() const fetchAccountData = async () => { try { - const res:any = await getCounselorsAccount(); + const res: any = await getCounselorsAccount(); if (res.status === 200) { setAccountNum(res.data.account); setBankType(res.data.bank); diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 909cea7b..5ee750b7 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -13,6 +13,7 @@ export const categories = [ '조언', '팩폭', ]; +export const openSortList = ['최근순', '공감 많은 순', '댓글 많은 순']; export const sortList = ['최근순', '인기순', '별점순']; export const quizList = [ @@ -102,3 +103,6 @@ export const quitRecommends = [ '상담사 선택, 취소, 상담하기가 어려우신가요?\n셰어마인드에 대해 이해하기 쉽게 정리해 보았는데 이용해보시는건 어떨까요?', '혹시 놓치고 있던 상담사가 있지 않을까요?\n더 많은 상담사를 찾아보고 상담을 받아보시는건 어떠세요?', ]; + +/** 상담 시작 요청 시간 */ +export const CHAT_START_REQUEST_TIME = '10:00'; diff --git a/src/utils/convertOpenSortType.ts b/src/utils/convertOpenSortType.ts new file mode 100644 index 00000000..8ff6b394 --- /dev/null +++ b/src/utils/convertOpenSortType.ts @@ -0,0 +1,11 @@ +export const ConvertOpenSortType = (typeNum: number) => { + if (typeNum === 0) { + return 'LATEST'; + } else if (typeNum === 1) { + return 'DESC_TOTAL_LIKE'; + } else if (typeNum === 2) { + return 'DESC_TOTAL_COMMENT'; + } else { + return ''; + } +}; diff --git a/src/utils/isIncludeSpecialLetter.ts b/src/utils/isIncludeSpecialLetter.ts index 891c06c3..af11775f 100644 --- a/src/utils/isIncludeSpecialLetter.ts +++ b/src/utils/isIncludeSpecialLetter.ts @@ -9,7 +9,7 @@ export const isIncludeSpecialLetter = (str: string) => { }; export const isIncludeSpecialLetterOneLiner = (str: string) => { - var specialCharacterRegex = /[\{\}\[\]\/,;|\)*~`^\-_+<>@\#$%&\\\=\(\'\"]/g; + var specialCharacterRegex = /[\{\}\[\]\/;|\)*~`^\-_+<>@\#$%&\\\=\(\'\"]/g; if (specialCharacterRegex.test(str)) { return true; } else {