From adb1f76f9d8f6c298f37ac72e643ddd9d9d682d0 Mon Sep 17 00:00:00 2001 From: Eunseo Sim <55528304+simeunseo@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:51:28 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=A4=EB=B2=84=20=ED=83=AD=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=8F=20mds=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20(#1705)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 참여한 프로젝트, 모임이 없는 경우 해당 항목 전부 비노출 * feat: 본인 프로필에 커피챗 오픈 버튼 추가 * chore: makers-ui 버전 업 * feat: 개선된 Select에 맞추어 레이아웃 변경 * feat: 멤버 정렬 Select를 mds로 교체 현재 Select의 오픈 상태에 따른 스타일링을 반영할 수 없어, mds 수정 사항 요청 상태 * feat: ProfileSection mds 반영 * feat: 활동 팀 mds Tag로 변경 * feat: soptActivitySection 모바일 레이아웃 변경 * feat: 본인 프로필이면서 활동 내역에 등록된 프로젝트가 없을 경우 프로젝트 등록 페이지로 이동하는 버튼 추가 * refactor: activity 필드 내부 값에 대한 가드 * feat: 멤버 순서 정렬 Select mds 수정사항 반영하여 교체 * fix: 프로필 기본정보 gap 수정 * feat: BottomSheetSelect 수정 * feat: 멤버탭 모바일 Select를 BottomSheetSelect로 교체 * feat: BottomSheetSelect에서 icon prop과 스타일 커스텀 추가 * feat: 멤버 탭에서 모바일 정렬 Select를 BottomSheetSelect로 교체 * fix: coffeechatform의 career 항목에 대해 string[] 타입 비허용 * feat: 쪽지 보내기에 커피챗 뱃지 추가 * fix: 멤버 상세 activity section에서 프로젝트가 없을 경우 내용을 세로 가운데 정렬 * fix: 멤버 상세 activity section 내 간격 조정 * fix: 멤버 상세 career section 커리어-스킬 사이 마진 수정 * fix: 멤버 상세 페이지 하단 마진값 수정 * fix: 멤버 상세 연락처 정보 마진 수정 * feat: BottomSheetSelect width 정의 방식 변경 * feat: mds ui 버전업 --- package.json | 4 +- src/components/coffeechat/detail/index.tsx | 2 +- .../BottomSheetSelect/index.tsx | 57 +- .../members/detail/ActivityBadge.tsx | 31 +- .../detail/ActivitySection/MemberDetail.tsx | 6 +- .../ActivitySection/MemberDetailSection.ts | 3 + .../detail/DetailinfoSection/index.tsx | 48 +- .../members/detail/GroupSection/index.tsx | 18 +- .../members/detail/InterestSection/index.tsx | 12 +- .../detail/MessageSection/MessageModal.tsx | 8 +- .../members/detail/MessageSection/index.tsx | 106 ++- src/components/members/detail/PartItem.tsx | 97 ++- .../members/detail/ProfileSection/index.tsx | 275 ++++--- .../members/detail/ProjectSection/index.tsx | 16 +- .../detail/SoptActivitySection/index.tsx | 6 +- .../MemberList/filters/MemberListOrder.tsx | 42 ++ .../members/main/MemberList/index.tsx | 246 +++--- yarn.lock | 712 +----------------- 18 files changed, 585 insertions(+), 1104 deletions(-) create mode 100644 src/components/members/main/MemberList/filters/MemberListOrder.tsx diff --git a/package.json b/package.json index 8321b5dc4..14c7e2ba3 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "@radix-ui/react-tooltip": "^1.0.5", "@sopt-makers/colors": "^3.0.2", "@sopt-makers/fonts": "^1.0.0", - "@sopt-makers/icons": "^1.0.5", - "@sopt-makers/ui": "^2.7.6", + "@sopt-makers/icons": "^1.1.0", + "@sopt-makers/ui": "^2.8.6", "@tanstack/react-query": "^5.4.3", "@toss/emotion-utils": "^1.1.10", "@toss/error-boundary": "^1.4.6", diff --git a/src/components/coffeechat/detail/index.tsx b/src/components/coffeechat/detail/index.tsx index 4b4d7d1db..5414b27e5 100644 --- a/src/components/coffeechat/detail/index.tsx +++ b/src/components/coffeechat/detail/index.tsx @@ -79,7 +79,7 @@ export default function CoffeechatDetail({ memberId }: CoffeechatDetailProp) { isMine={profile.isMine} isCoffeechatTap /> - + SOPT 활동 정보 diff --git a/src/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect/index.tsx b/src/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect/index.tsx index 5bc99965b..5521d98c3 100644 --- a/src/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect/index.tsx +++ b/src/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect/index.tsx @@ -3,7 +3,7 @@ import { colors } from '@sopt-makers/colors'; import { fonts } from '@sopt-makers/fonts'; import { IconCheck, IconChevronDown } from '@sopt-makers/icons'; import { Button } from '@sopt-makers/ui'; -import { useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { zIndex } from '@/styles/zIndex'; @@ -14,11 +14,23 @@ interface Option { interface BottomSheetSelectProps { options: Option[]; - value: string | string[] | null | undefined; + defaultOption?: Option; + value: string | null | undefined; placeholder: string; onChange: (value: string) => void; + icon?: ReactNode; + className?: string; } -const BottomSheetSelect = ({ options, value, placeholder, onChange }: BottomSheetSelectProps) => { + +const BottomSheetSelect = ({ + options, + defaultOption, + value, + placeholder, + onChange, + icon, + className, +}: BottomSheetSelectProps) => { const [open, setOpen] = useState(false); const [selectedValue, setSelectedValue] = useState(value); const [temporaryValue, setTemporaryValue] = useState(value); @@ -32,7 +44,7 @@ const BottomSheetSelect = ({ options, value, placeholder, onChange }: BottomShee const handleConfirm = () => { setSelectedValue(temporaryValue); - if (temporaryValue !== '') onChange(temporaryValue as string); + onChange(temporaryValue as string); handleClose(); }; @@ -49,18 +61,24 @@ const BottomSheetSelect = ({ options, value, placeholder, onChange }: BottomShee }; }, [open]); + const getSelectedLabel = (value: string) => { + return options.find((option) => option.value === value)?.label; + }; + return ( - - {selectedValue !== null ?

{selectedValue}

:

{placeholder}

} - + + {selectedValue ?

{getSelectedLabel(selectedValue)}

:

{placeholder}

} + {icon || ( + + )}
{open && ( @@ -68,6 +86,12 @@ const BottomSheetSelect = ({ options, value, placeholder, onChange }: BottomShee + {defaultOption && ( + handleOptionSelect(defaultOption.value)}> + {defaultOption.label} + {temporaryValue === defaultOption.value && } + + )} {options.map((option) => ( handleOptionSelect(option.value)}> {option.label} @@ -88,11 +112,11 @@ export default BottomSheetSelect; const Container = styled.div` position: relative; - width: 100%; `; const InputField = styled.div` display: flex; + gap: 12px; align-items: center; justify-content: space-between; border-radius: 10px; @@ -100,6 +124,8 @@ const InputField = styled.div` cursor: pointer; padding: 11px 16px; ${fonts.BODY_16_M}; + + width: 100%; `; const Overlay = styled.div` @@ -115,6 +141,7 @@ const Overlay = styled.div` const BottomSheet = styled.section` position: fixed; bottom: 0; + left: 20px; z-index: ${zIndex.헤더}; margin-bottom: 12px; border-radius: 16px; diff --git a/src/components/members/detail/ActivityBadge.tsx b/src/components/members/detail/ActivityBadge.tsx index 4050a5bff..cb74eba94 100644 --- a/src/components/members/detail/ActivityBadge.tsx +++ b/src/components/members/detail/ActivityBadge.tsx @@ -2,8 +2,8 @@ import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; import { FC } from 'react'; +import Text from '@/components/common/Text'; import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; -import { textStyles } from '@/styles/typography'; interface ActivityBadgeProps { category?: string; @@ -11,21 +11,20 @@ interface ActivityBadgeProps { } const ActivityBadge: FC = ({ category, name }) => { - return {`${category} ${name}`}; + return ( + + {category} + {name} + + ); }; const Container = styled.div` display: flex; align-items: center; - transition: background-color 0.2s; - border-radius: 13px; + border-radius: 28px; background-color: ${colors.gray700}; padding: 6px 14px; - line-height: 100%; - letter-spacing: -0.01em; - color: ${colors.gray10}; - - ${textStyles.SUIT_14_M} &:hover { background-color: ${colors.gray600}; @@ -38,4 +37,18 @@ const Container = styled.div` } `; +const Category = styled(Text)` + display: flex; + align-items: center; + + ::after { + display: inline-block; + margin: 0 10px; + background-color: ${colors.gray400}; + width: 1px; + height: 14px; + content: ''; + } +`; + export default ActivityBadge; diff --git a/src/components/members/detail/ActivitySection/MemberDetail.tsx b/src/components/members/detail/ActivitySection/MemberDetail.tsx index 514593ecb..6b71e3743 100644 --- a/src/components/members/detail/ActivitySection/MemberDetail.tsx +++ b/src/components/members/detail/ActivitySection/MemberDetail.tsx @@ -61,9 +61,9 @@ const MemberDetail: FC = ({ memberId }) => { - {!profile.isMine && } + - + { - // FIXME: 서버쪽에 YYYY-MM-DD 형태로 무조건 업로드시 전송해줘야 하는 이슈가 있어서, - // 생년월일을 보내지 않았을 경우에 DEFAULT_DATE를 전송하도록 임시처리 해 두었습니다. 이를 클라에서 보여주기 위해 대응합니다. - if (birthday) { - const isDefaultDay = dayjs(birthday).isSame(dayjs(DEFAULT_DATE)); - return isDefaultDay ? '' : dayjs(birthday).format('YYYY-MM-DD'); - } - return ''; -}; - -const DetailInfoSection = ({ profile, isCoffeechat = false }: DetailInfoSectionProps) => { +const DetailInfoSection = ({ profile }: DetailInfoSectionProps) => { const hasProfileInfo = profile.birthday || profile.university || profile.major || profile.address; return hasProfileInfo ? ( - {!isCoffeechat && profile.birthday && ( - - )} {profile.university && {profile.university}} {profile.major && {profile.major}} {profile.address && ( {profile.address.split(',').map((address) => ( - - {address} - + + {address} + ))} @@ -54,18 +37,19 @@ export default DetailInfoSection; const StyledAddressBadgeWrapper = styled.div` display: flex; flex-wrap: wrap; - gap: 8px; align-items: center; - - @media ${MOBILE_MEDIA_QUERY} { - gap: 10px; - } `; -const AddressBadge = styled.div` - border-radius: 13px; - background-color: ${colors.gray700}; - padding: 6px 14px; - line-height: 16px; - color: ${colors.gray10}; +const AddressItem = styled.div` + display: flex; + align-items: center; + + &:not(:last-child)::after { + display: inline-block; + margin: 0 10px; + background-color: ${colors.gray600}; + width: 1px; + height: 16px; + content: ''; + } `; diff --git a/src/components/members/detail/GroupSection/index.tsx b/src/components/members/detail/GroupSection/index.tsx index 14ee030dd..3e591b419 100644 --- a/src/components/members/detail/GroupSection/index.tsx +++ b/src/components/members/detail/GroupSection/index.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; +import { fonts } from '@sopt-makers/fonts'; import axios from 'axios'; import Link from 'next/link'; @@ -13,7 +14,6 @@ import { playgroundLink } from '@/constants/links'; import useEnterScreen from '@/hooks/useEnterScreen'; import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; import { safeParseInt } from '@/utils'; -import { fonts } from '@sopt-makers/fonts'; interface GroupSectionProps { profile: ProfileDetail; @@ -40,9 +40,9 @@ const GroupSection = ({ profile, meId, memberId }: GroupSectionProps) => { } return ( - + <> {meetingList.length > 0 ? ( - <> + {profile.name}님이 참여한 {meetingList.length}개의 모임이에요! @@ -56,11 +56,11 @@ const GroupSection = ({ profile, meId, memberId }: GroupSectionProps) => { ))} - + ) : ( <> - {String(meId) === memberId ? ( - <> + {String(meId) === memberId && ( + 아직 참여한 모임이 없어요 @@ -72,13 +72,11 @@ const GroupSection = ({ profile, meId, memberId }: GroupSectionProps) => { - - ) : ( - 아직 {profile.name}님이 참여한 모임이 없어요 + )} )} - + ); }; diff --git a/src/components/members/detail/InterestSection/index.tsx b/src/components/members/detail/InterestSection/index.tsx index e5ca3e0d7..1f45941a7 100644 --- a/src/components/members/detail/InterestSection/index.tsx +++ b/src/components/members/detail/InterestSection/index.tsx @@ -59,13 +59,7 @@ interface InterestSectionProps { balanceGame: BalanceGame; selfIntroduction?: string; } -const InterestSection: FC = ({ - mbti, - sojuCapacity, - balanceGame, - interest, - selfIntroduction, -}) => { +const InterestSection: FC = ({ mbti, sojuCapacity, balanceGame, interest, selfIntroduction }) => { const balanceGameResults = getBalanceGameResults(balanceGame); const isBalanceGameAvailable = balanceGame && Object.values(balanceGame).some((value) => value !== null); @@ -165,7 +159,7 @@ const BalanceGameWrapper = styled.div` @media ${MOBILE_MEDIA_QUERY} { margin-top: 12px; - max-width: 236px; + max-width: 265px; } `; @@ -176,5 +170,5 @@ const BalanceGameItem = styled.div` line-height: 16px; color: ${colors.gray10}; - ${textStyles.SUIT_14_M}; + ${textStyles.SUIT_16_SB}; `; diff --git a/src/components/members/detail/MessageSection/MessageModal.tsx b/src/components/members/detail/MessageSection/MessageModal.tsx index f57a85dfe..7ce8eacc7 100644 --- a/src/components/members/detail/MessageSection/MessageModal.tsx +++ b/src/components/members/detail/MessageSection/MessageModal.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled'; import { yupResolver } from '@hookform/resolvers/yup'; import { colors } from '@sopt-makers/colors'; import { IconUser } from '@sopt-makers/icons'; +import { Button } from '@sopt-makers/ui'; import { FC, useState } from 'react'; import { useForm } from 'react-hook-form'; import * as yup from 'yup'; @@ -17,9 +18,9 @@ import TextArea from '@/components/common/TextArea'; import Modal, { ModalProps } from '@/components/members/detail/MessageSection/Modal'; import { MB_BIG_MEDIA_QUERY, MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; import { zIndex } from '@/styles/zIndex'; -import { Button } from '@sopt-makers/ui'; export enum MessageCategory { + COFFEE_CHAT = '커피챗', NETWORK = '친목', APPJAM_TEAM_BUILDING = '앱잼 팀 빌딩', PROJECT_SUGGESTION = '프로젝트 제안', @@ -30,6 +31,10 @@ interface Category { value: MessageCategory; } const CATEGORY: Category[] = [ + { + icon: '/icons/icon-coffeechat.svg', + value: MessageCategory.COFFEE_CHAT, + }, { icon: '/icons/icon-network.svg', value: MessageCategory.NETWORK, @@ -247,7 +252,6 @@ const StyledCategory = styled.section` align-items: center; justify-content: center; margin-top: 40px; - max-width: 224px; `; const StyledCategoryItem = styled.div<{ isSelected: boolean }>` diff --git a/src/components/members/detail/MessageSection/index.tsx b/src/components/members/detail/MessageSection/index.tsx index c71ff01bc..113c14bd7 100644 --- a/src/components/members/detail/MessageSection/index.tsx +++ b/src/components/members/detail/MessageSection/index.tsx @@ -49,44 +49,74 @@ export default function MessageSection({ memberId, profile }: MessageSectionProp router.push(playgroundLink.coffeechatDetail(memberId)); }; - return ( - <> - - - {name}님과 나누고 싶은 이야기가 있나요? - - 궁금한 점에 대해 편하게 소통해보세요! - - - - {isCoffeeChatActivate && ( - - 커피챗 보러가기 - - )} - - 쪽지 보내기 - - - - {isOpenMessageModal && ( - - logSubmitEvent('sendMessage', { - category: options?.category?.toString() ?? '', - receiverId: +memberId, - referral: 'memberDetail', - }) - } - /> - )} - - ); + const handleClickCoffeeChatOpenButton = () => { + router.push(playgroundLink.coffeechatUpload()); + }; + + const Mine = () => { + return ( + <> + {!profile.isCoffeeChatActivate && ( + + + SOPT 회원들과 나누고 싶은 이야기가 있나요? + + 어떤 내용이라도 좋아요. 편하게 오픈해 보세요! + + + + + 커피챗 오픈하러 가기 + + + + )} + + ); + }; + + const Others = () => { + return ( + <> + + + {name}님과 나누고 싶은 이야기가 있나요? + + 궁금한 점에 대해 편하게 소통해보세요! + + + + {isCoffeeChatActivate && ( + + 커피챗 보러가기 + + )} + + 쪽지 보내기 + + + + {isOpenMessageModal && ( + + logSubmitEvent('sendMessage', { + category: options?.category?.toString() ?? '', + receiverId: +memberId, + referral: 'memberDetail', + }) + } + /> + )} + + ); + }; + + return profile.isMine ? : ; } const StyledMemberDetailSection = styled(MemberDetailSection)` diff --git a/src/components/members/detail/PartItem.tsx b/src/components/members/detail/PartItem.tsx index 16a5502bd..01651662a 100644 --- a/src/components/members/detail/PartItem.tsx +++ b/src/components/members/detail/PartItem.tsx @@ -1,8 +1,12 @@ import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; +import { IconPlus } from '@sopt-makers/icons'; +import { Tag } from '@sopt-makers/ui'; import Link from 'next/link'; +import { playgroundLink } from 'playground-common/export'; import { FC } from 'react'; +import Text from '@/components/common/Text'; import ActivityBadge from '@/components/members/detail/ActivityBadge'; import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; import { textStyles } from '@/styles/typography'; @@ -14,46 +18,62 @@ type PartItemProps = { part: string; teams?: string[]; activities: { type: string; name: string; href: string }[]; + isMine?: boolean; }; -const PartItem: FC = ({ generation, part, teams, activities }) => { +const PartItem: FC = ({ generation, part, teams, activities, isMine }) => { const partLabel = `${part} ${NORMAL_PARTS.includes(part) ? '파트' : ''}`; const soptLogoSrc = Number(generation) < 12 ? '/icons/logo/time=1-11.svg' : `/icons/logo/time=${generation}.svg`; + const hasActivities = !(activities.length === 0 && !isMine); return ( - + {`${generation}기 {generation}기 - {partLabel} {teams?.map((team) => `| ${team}`)} - - - {activities.map((activity, idx) => ( - - - + {partLabel}{' '} + {teams?.map((team) => ( + + {team} + ))} - + + {hasActivities && ( + + {activities.map((activity, idx) => ( + + + + ))} + {isMine && activities.length === 0 && ( + + + + 프로젝트 추가하기 + + + )} + + )} ); }; -const Container = styled.div` +const Container = styled.div<{ hasActivities: boolean }>` display: grid; - grid: - [row1-start] 'thumbnail generation belongs' 1fr [row1-end] - [row2-start] 'thumbnail activities activities' 1fr [row2-end] - / auto auto 1fr; + grid-template-areas: + 'thumbnail generation belongs' + ${({ hasActivities }) => hasActivities && "'thumbnail activities activities'"}; + grid-template-columns: auto auto 1fr; align-items: center; @media ${MOBILE_MEDIA_QUERY} { - grid: - [row1-start] 'thumbnail generation' 1fr [row1-end] - [row2-start] 'thumbnail belongs' 1fr [row2-end] - [row2-start] 'activities activities' auto [row2-end] - / auto 1fr; + grid-template-areas: + 'thumbnail generation belongs' + ${({ hasActivities }) => hasActivities && "'activities activities activities'"}; + grid-template-columns: auto auto 1fr; } `; @@ -87,23 +107,20 @@ const Generation = styled.div` ${textStyles.SUIT_18_B} @media ${MOBILE_MEDIA_QUERY} { - align-self: end; - margin-bottom: 2px; - ${textStyles.SUIT_16_B} } `; const BelongArea = styled.div` + display: flex; grid-area: belongs; + gap: 8px; + align-items: center; color: ${colors.gray100}; ${textStyles.SUIT_18_M} @media ${MOBILE_MEDIA_QUERY} { - align-self: start; - margin-top: 2px; - ${textStyles.SUIT_16_M} } `; @@ -114,11 +131,35 @@ const Badges = styled.div` grid-area: activities; gap: 8px; align-self: start; - margin-top: 2px; + margin-top: 16px; + + @media ${MOBILE_MEDIA_QUERY} { + margin-top: 10px; + } +`; + +const AddProject = styled(Text)` + display: flex; + gap: 6px; + align-items: center; + border-radius: 28px; + background-color: ${colors.gray700}; + padding: 6px 14px; + + &:hover { + background-color: ${colors.gray600}; + } @media ${MOBILE_MEDIA_QUERY} { - margin-top: 12px; + margin: 0; + width: fit-content; + white-space: nowrap; } `; +const StyledIconPlus = styled(IconPlus)` + width: 14px; + height: 14px; +`; + export default PartItem; diff --git a/src/components/members/detail/ProfileSection/index.tsx b/src/components/members/detail/ProfileSection/index.tsx index 6994f3edc..156da06d2 100644 --- a/src/components/members/detail/ProfileSection/index.tsx +++ b/src/components/members/detail/ProfileSection/index.tsx @@ -1,15 +1,14 @@ import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; import { fonts } from '@sopt-makers/fonts'; -import { IconAlertTriangle, IconUser, IconUserX } from '@sopt-makers/icons'; +import { IconAlertTriangle, IconBirthdaySecondary, IconMail, IconPhone, IconUser, IconUserX } from '@sopt-makers/icons'; import { Flex } from '@toss/emotion-utils'; +import dayjs from 'dayjs'; import { uniq } from 'lodash-es'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { playgroundLink } from 'playground-common/export'; -import CallIcon from 'public/icons/icon-call.svg'; import EditIcon from 'public/icons/icon-edit.svg'; -import MailIcon from 'public/icons/icon-mail.svg'; import { ProfileDetail } from '@/api/endpoint_LEGACY/members/type'; import ResizedImage from '@/components/common/ResizedImage'; @@ -18,6 +17,7 @@ import useEventLogger from '@/components/eventLogger/hooks/useEventLogger'; import FeedDropdown from '@/components/feed/common/FeedDropdown'; import { useBlockMember } from '@/components/members/hooks/useBlockMember'; import { useReportMember } from '@/components/members/hooks/useReportMember'; +import { DEFAULT_DATE } from '@/components/members/upload/constants'; import IconCoffee from '@/public/icons/icon-coffee.svg'; import IconMore from '@/public/icons/icon-dots-vertical.svg'; import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; @@ -28,6 +28,51 @@ interface ProfileSectionProps { memberId: string; } +const convertBirthdayFormat = (birthday?: string) => { + // FIXME: 서버쪽에 YYYY-MM-DD 형태로 무조건 업로드시 전송해줘야 하는 이슈가 있어서, + // 생년월일을 보내지 않았을 경우에 DEFAULT_DATE를 전송하도록 임시처리 해 두었습니다. 이를 클라에서 보여주기 위해 대응합니다. + if (birthday) { + const isDefaultDay = dayjs(birthday).isSame(dayjs(DEFAULT_DATE)); + return isDefaultDay ? '' : dayjs(birthday).format('YYYY.MM.DD'); + } + return ''; +}; + +const ContactSection = ({ profile }: { profile: ProfileDetail }) => { + return ( + + {(profile.birthday || profile.phone) && ( + + {profile.birthday && ( + + +
{convertBirthdayFormat(profile.birthday)}
+
+ )} + {profile.phone && ( + + + +
{profile.phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')}
+
+ + )} +
+ )} +
+ {profile.email && ( + + + +
{profile.email}
+
+ + )} +
+
+ ); +}; + const ProfileSection = ({ profile, memberId }: ProfileSectionProps) => { const router = useRouter(); const { logClickEvent } = useEventLogger(); @@ -36,101 +81,102 @@ const ProfileSection = ({ profile, memberId }: ProfileSectionProps) => { const { handleBlockMember } = useBlockMember(); return ( - - - {profile.profileImage ? ( - - ) : ( - - - - - - + + +
+ + {profile.profileImage ? ( + + ) : ( + + + + + + + + + )} + {profile.isCoffeeChatActivate && ( + + + + )} + + + +
{profile.name}
+
{uniq(profile.soptActivities.map(({ part }) => part)).join('/')}
+
+
{profile.introduction}
+ + - - )} - {profile.isCoffeeChatActivate && ( - - - +
+
+ {profile.isMine ? ( + { + router.push(playgroundLink.memberEdit()); + logClickEvent('editProfile'); + }} + > + + + ) : ( + + }> + { + handleReportMember(safeParseInt(memberId) ?? undefined); + }} + > + + + 신고 + + + { + handleBlockMember(safeParseInt(memberId) ?? undefined); + }} + > + + 차단 + + + + )} -
- - -
{profile.name}
-
{uniq(profile.soptActivities.map(({ part }) => part)).join('/')}
-
-
{profile.introduction}
- - {profile.phone && ( - -
- -
{profile.phone}
-
- - )} - {profile.email && ( - -
- -
{profile.email}
-
- - )} -
-
- - {profile.isMine ? ( - { - router.push(playgroundLink.memberEdit()); - logClickEvent('editProfile'); - }} - > - - - ) : ( - - }> - { - handleReportMember(safeParseInt(memberId) ?? undefined); - }} - > - - - 신고 - - - { - handleBlockMember(safeParseInt(memberId) ?? undefined); - }} - > - - 차단 - - - - - )} -
+ + + + + ); }; export default ProfileSection; -const ProfileContainer = styled.div` +const DefaultWrapper = styled.div` display: flex; - position: relative; - gap: 33px; - align-items: center; + justify-content: space-between; width: 100%; - letter-spacing: -0.01em; - font-weight: 500; + + & > div:first-child { + display: flex; + position: relative; + gap: 33px; + align-items: center; + width: 100%; + letter-spacing: -0.01em; + font-weight: 500; + } +`; +const ProfileSectionWrapper = styled.div` + display: flex; + flex-direction: column; @media ${MOBILE_MEDIA_QUERY} { gap: 20px; align-items: flex-start; @@ -168,12 +214,10 @@ const ProfileImage = styled(ResizedImage)` const ProfileContents = styled.div` display: flex; flex-direction: column; - justify-content: space-between; width: 100%; height: 128px; .intro { - margin-top: 16px; color: #c0c5c9; ${fonts.BODY_16_M} @@ -185,16 +229,12 @@ const ProfileContents = styled.div` } @media ${MOBILE_MEDIA_QUERY} { - width: 171px; height: auto; } `; const EditButton = styled.div` display: flex; - position: absolute; - top: 22px; - right: 0; align-items: center; justify-content: center; border-radius: 50%; @@ -248,15 +288,17 @@ const NameWrapper = styled.div` } `; -const ContactWrapper = styled.div<{ shouldDivide: boolean }>` +const ContactWrapper = styled.div` display: flex; + gap: 12px; + margin-top: 16px; line-height: 100%; color: #808388; font-size: 14px; & > div { display: flex; - gap: 4px; + gap: 12px; align-items: center; @media ${MOBILE_MEDIA_QUERY} { @@ -265,10 +307,6 @@ const ContactWrapper = styled.div<{ shouldDivide: boolean }>` } .phone { - box-sizing: border-box; - margin-right: 13px; - border-right: ${({ shouldDivide }) => (shouldDivide ? '1.5px solid #3c3d40' : 'none')}; - padding-right: 17px; @media ${MOBILE_MEDIA_QUERY} { margin: 0; border: 0; @@ -288,8 +326,8 @@ const ContactWrapper = styled.div<{ shouldDivide: boolean }>` @media ${MOBILE_MEDIA_QUERY} { flex-direction: column; - gap: 4px; - margin-top: 16px; + gap: 6px; + margin-top: 0; } `; @@ -329,7 +367,7 @@ const MoreIconContainer = styled.div` text-align: right; @media ${MOBILE_MEDIA_QUERY} { - width: 100%; + align-self: baseline; height: auto; } `; @@ -344,3 +382,32 @@ const StyledIconMore = styled(IconMore)` padding-top: 4px; } `; + +const StyledIconPhone = styled(IconPhone)` + width: 20px; + height: 20px; + color: ${colors.gray300}; +`; + +const StyledIconMail = styled(IconMail)` + width: 20px; + height: 20px; + color: ${colors.gray300}; +`; + +const StyledIconBirth = styled(IconBirthdaySecondary)` + width: 20px; + height: 20px; + color: ${colors.gray300}; +`; + +const ContactTopWrapper = styled.div` + display: flex; + gap: 12px; +`; + +const ContactItem = styled.div` + display: flex; + gap: 4px; + align-items: center; +`; diff --git a/src/components/members/detail/ProjectSection/index.tsx b/src/components/members/detail/ProjectSection/index.tsx index e3c94cd36..2dcbbd55b 100644 --- a/src/components/members/detail/ProjectSection/index.tsx +++ b/src/components/members/detail/ProjectSection/index.tsx @@ -21,9 +21,9 @@ const ProjectSection = ({ profile, memberId, meId }: ProjectActivitySectionProps const { logClickEvent } = useEventLogger(); return ( - + <> {profile.projects.length > 0 ? ( - <> + {profile.name}님이 참여한 {profile.projects.length}개의 프로젝트예요! @@ -32,11 +32,11 @@ const ProjectSection = ({ profile, memberId, meId }: ProjectActivitySectionProps ))} - + ) : ( <> - {String(meId) === memberId ? ( - <> + {String(meId) === memberId && ( + 아직 등록한 프로젝트가 없어요 @@ -51,13 +51,11 @@ const ProjectSection = ({ profile, memberId, meId }: ProjectActivitySectionProps - - ) : ( - 아직 {profile.name}님이 참여한 프로젝트가 없어요 + )} )} - + ); }; diff --git a/src/components/members/detail/SoptActivitySection/index.tsx b/src/components/members/detail/SoptActivitySection/index.tsx index 07e997387..77ee5e0af 100644 --- a/src/components/members/detail/SoptActivitySection/index.tsx +++ b/src/components/members/detail/SoptActivitySection/index.tsx @@ -8,11 +8,12 @@ import { Category } from '@/components/projects/types'; interface SoptActivitySectionProps { soptActivities: SoptActivity[]; + isMine?: boolean; } -export default function SoptActivitySection({ soptActivities }: SoptActivitySectionProps) { +export default function SoptActivitySection({ soptActivities, isMine }: SoptActivitySectionProps) { return ( - + {soptActivities.map(({ generation, part, projects, team }, idx) => ( ))} diff --git a/src/components/members/main/MemberList/filters/MemberListOrder.tsx b/src/components/members/main/MemberList/filters/MemberListOrder.tsx new file mode 100644 index 000000000..21ebcee15 --- /dev/null +++ b/src/components/members/main/MemberList/filters/MemberListOrder.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled'; +import { colors } from '@sopt-makers/colors'; +import { IconSwitchVertical } from '@sopt-makers/icons'; +import { SelectV2 } from '@sopt-makers/ui'; + +import { Option } from '@/components/members/main/MemberList/filters/constants'; + +interface MemberListOrderProps { + className?: string; + value?: Option; + options: Option[]; + onChange?: (value: string) => void; +} + +export function MemberListOrder({ className, value, options, onChange }: MemberListOrderProps) { + return ( + + + } /> + + + {options.map((option) => ( + + ))} + + + ); +} + +const StyledIconSwitchVertical = styled(IconSwitchVertical)` + width: 20px; + height: 20px; + color: ${colors.gray300}; +`; + +const StyledTriggerContent = styled(SelectV2.TriggerContent)` + background-color: transparent; + + & > p { + color: ${colors.gray300}; + } +`; diff --git a/src/components/members/main/MemberList/index.tsx b/src/components/members/main/MemberList/index.tsx index e349b27ad..5b63b7c7a 100644 --- a/src/components/members/main/MemberList/index.tsx +++ b/src/components/members/main/MemberList/index.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; -import { IconChevronDown, IconSwitchVertical } from '@sopt-makers/icons'; +import { IconSwitchVertical } from '@sopt-makers/icons'; import { SearchField } from '@sopt-makers/ui'; import { debounce, uniq } from 'lodash-es'; import Link from 'next/link'; @@ -9,11 +9,11 @@ import { useRouter } from 'next/router'; import React, { ChangeEvent, FC, ReactNode, useEffect, useMemo, useState } from 'react'; import { Profile } from '@/api/endpoint_LEGACY/members/type'; +import BottomSheetSelect from '@/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect'; import EmptyView from '@/components/common/EmptyView'; import Responsive from '@/components/common/Responsive'; import Text from '@/components/common/Text'; import useEventLogger from '@/components/eventLogger/hooks/useEventLogger'; -import OrderBySelect from '@/components/members/common/select/OrderBySelect'; import MessageModal, { MessageCategory } from '@/components/members/detail/MessageSection/MessageModal'; import { DESKTOP_ONE_MEDIA_QUERY, DESKTOP_TWO_MEDIA_QUERY } from '@/components/members/main/contants'; import { useMemberProfileQuery } from '@/components/members/main/hooks/useMemberProfileQuery'; @@ -31,7 +31,7 @@ import { TEAM_OPTIONS, } from '@/components/members/main/MemberList/filters/constants'; import MemberListFilter from '@/components/members/main/MemberList/filters/MemberListFilter'; -import MemberListFilterSheet from '@/components/members/main/MemberList/filters/MemberListFilterSheet'; +import { MemberListOrder } from '@/components/members/main/MemberList/filters/MemberListOrder'; import { LATEST_GENERATION } from '@/constants/generation'; import { playgroundLink } from '@/constants/links'; import useIntersectionObserver from '@/hooks/useIntersectionObserver'; @@ -66,7 +66,7 @@ const MemberList: FC = ({ banner }) => { const [employed, setEmployed] = useState