Skip to content

Commit

Permalink
Merge pull request #80 from Duri-Salon/feat(salon)/mypage-ui
Browse files Browse the repository at this point in the history
[feat] 미용사 마이페이지 api
  • Loading branch information
seungboshim authored Dec 18, 2024
2 parents 51dd878 + f487fb9 commit 23e5f4a
Show file tree
Hide file tree
Showing 30 changed files with 1,827 additions and 116 deletions.
7 changes: 6 additions & 1 deletion apps/salon/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { Global } from '@emotion/react';

import AuthPage from '@pages/Auth';
import Home from '@pages/Home';
import IncomePage from '@pages/Income/Income';
import LoginPage from '@pages/Login';
import MyPage from '@pages/My';
import ReviewPage from '@pages/My/Review';
import MyShopPage from '@pages/My/Shop';
import OnboardingPage from '@pages/Onboarding';
import OnboardingPendingPage from '@pages/Onboarding/Pending';
import StartPage from '@pages/Onboarding/StartPage';
Expand All @@ -19,7 +22,7 @@ import ReservationPage from '@pages/Quotation/ReservationPage';

import PrivateRoute from '@components/PrivateRoute';

import IncomePage from './pages/Income/Income';
import 'react-spring-bottom-sheet/dist/style.css';

function App() {
return (
Expand Down Expand Up @@ -49,6 +52,8 @@ function App() {
path="/portfolio/:groomerId/:portfolioId"
element={<PortfolioDetailPage />}
/>
<Route path="/my/shop" element={<MyShopPage />} />
<Route path="/my/review" element={<ReviewPage />} />

<Route path="/income" element={<IncomePage />} />
</Route>
Expand Down
104 changes: 104 additions & 0 deletions apps/salon/src/components/my/shop/DesignerInfoArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useEffect, useRef } from 'react';

import {
DesignerInfo,
Flex,
HeightFitFlex,
Pencil,
Text,
theme,
WidthFitFlex,
} from '@duri-fe/ui';
import styled from '@emotion/styled';

interface DesignerInfoAreaProps {
id: number;
name: string;
age: number;
gender: string;
history: number;
license: string[];
image: string;
onEdit: boolean;
setOnEdit: React.Dispatch<React.SetStateAction<boolean>>;
}

const DesignerInfoArea = ({
id,
name,
age,
gender,
history,
license,
image,
onEdit,
setOnEdit,
}: DesignerInfoAreaProps) => {
const designerInfoRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
designerInfoRef.current &&
!designerInfoRef.current.contains(e.target as Node)
) {
setOnEdit(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [designerInfoRef]);

return (
<HeightFitFlex
direction="column"
margin="18px 0"
align="flex-start"
gap={24}
>
<Text typo="Title3">디자이너 소개</Text>
<DesignerInfoWrapper
justify="flex-start"
ref={designerInfoRef}
onClick={() => setOnEdit(true)}
>
<DesignerInfo
version="vertical"
designerId={id}
name={name}
age={age}
gender={gender === 'F' ? '여성' : '남성'}
experience={history}
roles={license}
imageUrl={image}
isNavigate={false}
/>
{onEdit && (
<ShopEditArea backgroundColor={theme.palette.Black} borderRadius={12}>
<Pencil width={16} />
<Text typo="Label3" colorCode={theme.palette.White}>
수정하기
</Text>
</ShopEditArea>
)}
</DesignerInfoWrapper>
</HeightFitFlex>
);
};

const DesignerInfoWrapper = styled(WidthFitFlex)`
position: relative;
`;

const ShopEditArea = styled(Flex)`
position: absolute;
top: -4px;
left: -4px;
width: calc(100% + 8px);
height: calc(100% + 8px);
background-color: rgba(17, 17, 17, 0.5);
`;

export default DesignerInfoArea;
51 changes: 51 additions & 0 deletions apps/salon/src/components/my/shop/ReviewPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Flex,
HeightFitFlex,
RatingStars,
Text,
WidthFitFlex,
} from '@duri-fe/ui';
import { MyShopReviewType } from '@duri-fe/utils';

import { ShopReviewBox } from './ShopReviewBox';

interface ReviewPreviewProps {
shopRating: number;
reviewCnt: number;
reviewData: MyShopReviewType[];
}

const ReviewPreview = ({
shopRating,
reviewCnt,
reviewData,
}: ReviewPreviewProps) => {
return (
<HeightFitFlex
direction="column"
align="flex-start"
margin="32px 0 0 0"
gap={24}
>
<WidthFitFlex gap={7}>
<Text typo="Title3">리뷰</Text>
<WidthFitFlex>
<Text typo="Label3">{shopRating}</Text>
<RatingStars score={shopRating} size={14} />
<Text typo="Label3">({reviewCnt})</Text>
</WidthFitFlex>
</WidthFitFlex>
{reviewData && reviewData.length > 0 ? (
reviewData.map((review) => (
<ShopReviewBox key={review.reviewId} review={review} />
))
) : (
<Flex height={48}>
<Text>아직 등록된 리뷰가 없습니다.</Text>
</Flex>
)}
</HeightFitFlex>
);
};

export default ReviewPreview;
172 changes: 172 additions & 0 deletions apps/salon/src/components/my/shop/ReviewUserInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

import {
Button,
Flex,
Menu,
Modal,
ProfileImage,
RatingStars,
Text,
theme,
WidthFitFlex,
} from '@duri-fe/ui';
import { useDeleteReview, useModal } from '@duri-fe/utils';
import styled from '@emotion/styled';

interface ReviewUserInfoProps {
reviewId: number;
userImageURL: string;
userName: string;
rating: number;
createdAt: string;
}

export const ReviewUserInfo = ({
reviewId,
createdAt,
rating,
userImageURL,
userName,
}: ReviewUserInfoProps) => {
const navigate = useNavigate();

const { isOpenModal, toggleModal } = useModal();

const [isOpen, setIsOpen] = useState<boolean>(false);

const { mutateAsync: deleteReview } = useDeleteReview(() => {
navigate('/my/review', { replace: true });
});

const handleClickMenu = () => {
setIsOpen(!isOpen);
};

const handleClickModifyButton = () => {
navigate('/my/review/modify', { state: reviewId });
};

const handleClickDeleteButton = () => {
//삭제 모달 띄우기
toggleModal();
};

const handleClickDeleteConfirmButton = () => {
deleteReview(reviewId);
};

return (
<Wrapper justify="space-between">
{/* 사용자 프로필, 평점 */}
<WidthFitFlex justify="flex-start" gap={15.58}>
<ProfileImage
width={34}
height={34}
borderRadius={34}
src={userImageURL}
/>
<WidthFitFlex direction="column" gap={2} align="start">
<Text typo="Body3">{userName}</Text>
<Flex>
<RatingStars size={12} score={rating} />
</Flex>
</WidthFitFlex>
</WidthFitFlex>

{/* 오른쪽 버튼, 작성일자 */}
<WidthFitFlex gap={8}>
<SingleLineText typo="Caption5" colorCode={theme.palette.Gray300}>
{createdAt}
</SingleLineText>
<WidthFitFlex onClick={handleClickMenu}>
<Menu width={23} height={23} />
</WidthFitFlex>
</WidthFitFlex>
{isOpen && (
<MenuCard
direction="column"
borderRadius={8}
width={114}
height={67}
gap={8}
>
<MenuItem onClick={handleClickModifyButton}>
<Text typo="Label3">수정하기</Text>
</MenuItem>
<MenuItem onClick={handleClickDeleteButton}>
<Text typo="Label3">삭제하기</Text>
</MenuItem>
</MenuCard>
)}
{isOpenModal && (
<Modal
isOpen={isOpenModal}
toggleModal={toggleModal}
title="후기를 삭제하시겠습니까?"
closeIcon={false}
>
<Flex direction="column" gap={24}>
<Flex direction="column">
<Text typo="Caption2" colorCode={theme.palette.Gray400}>
후기 삭제 후
</Text>
<Text typo="Caption2" colorCode={theme.palette.Gray400}>
복구할 수 없습니다.
</Text>
</Flex>
<Flex>
<Button
width="104px"
height="47px"
padding="10px"
bg={theme.palette.Gray20}
typo="Body3"
onClick={toggleModal}
>
아니오
</Button>
<Button
width="145px"
height="47px"
padding="10px"
bg={theme.palette.Alert}
typo="Body3"
onClick={handleClickDeleteConfirmButton}
>
</Button>
</Flex>
</Flex>
</Modal>
)}
</Wrapper>
);
};

const Wrapper = styled(Flex)`
position: relative;
`;
const MenuCard = styled(Flex)`
position: absolute;
top: 37.4px;
right: 9px;
background-color: ${theme.palette.White};
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.1);
`;
const SingleLineText = styled(Text)`
word-break: no-wrap;
`;

const MenuItem = styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
cursor: pointer;
padding: 0 10px; // 좌우 여백을 추가하여 텍스트가 너무 붙지 않도록 조정
&:hover {
background-color: ${theme.palette.Gray_White};
}
`;
Loading

0 comments on commit 23e5f4a

Please sign in to comment.