From cae9158148bf5ec15274394bd8d50b1a0fba529d Mon Sep 17 00:00:00 2001 From: "SeungJu, Lee" <84257439+SeungJL@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:41:34 +0900 Subject: [PATCH] V5 241111 (#243) --- components/BottomNav.tsx | 15 +- components/Icons/CircleIcons.tsx | 8 +- components/Icons/DotIcons.tsx | 27 ++- components/Icons/HeartIcons.tsx | 10 +- components/Icons/MathIcons.tsx | 17 +- components/Icons/UserIcons.tsx | 4 +- components/atoms/BottomCommentInput.tsx | 56 +++++ components/atoms/BottomFixedButton.tsx | 27 +++ components/atoms/Select.tsx | 52 ++++- components/atoms/buttons/MenuButton.tsx | 9 +- components/layouts/Header.tsx | 14 +- components/molecules/BlurredLink.tsx | 31 +-- components/molecules/CommentSection.tsx | 54 +++++ components/molecules/ContentHeartBar.tsx | 37 ++- components/molecules/InfoBoxCol.tsx | 8 +- components/molecules/PlaceImage.tsx | 12 +- components/molecules/UserComment.tsx | 113 ++++----- components/molecules/UserCommentBlock.tsx | 56 ++--- components/molecules/UserCommentInput.tsx | 101 +++++--- .../molecules/cards/GroupThumbnailCard.tsx | 175 ++++++++++++++ .../molecules/cards/ProfileCommentCard.tsx | 4 +- components/molecules/groups/ButtonGroups.tsx | 19 +- components/molecules/navs/TabNav.tsx | 5 +- components/organisms/drawer/RightDrawer.tsx | 5 +- .../organisms/sliders/ImageTileSlider.tsx | 86 +++++-- constants/keys/localStorage.ts | 2 + .../studyConstants/studyVoteMapConstants.ts | 2 +- hooks/common/mutations.ts | 45 ++-- hooks/custom/CustomHooks.tsx | 27 ++- hooks/groupStudy/queries.ts | 15 ++ libs/comment/commentLib.ts | 20 ++ libs/study/getLocationByCoordinates.ts | 9 +- modals/common/CommentEditModal.tsx | 8 +- .../gather/detail/GatherComments.tsx | 20 +- .../gather/detail/GatherParticipation.tsx | 1 + pageTemplates/group/GroupMine.tsx | 91 ++++---- pageTemplates/group/detail/GroupBottomNav.tsx | 31 +-- pageTemplates/group/detail/GroupComment.tsx | 90 ++++---- .../group/detail/GroupContent/ContentFeed.tsx | 4 +- .../group/detail/GroupContent/ContentInfo.tsx | 8 +- .../detail/GroupContent/GroupStudyContent.tsx | 60 ----- pageTemplates/group/detail/GroupCover.tsx | 12 +- pageTemplates/group/detail/GroupHeader.tsx | 28 ++- .../group/detail/GroupParticipation.tsx | 51 +---- pageTemplates/group/detail/GroupTitle.tsx | 44 +--- .../profile/profileOverview/ProfileInfo.tsx | 2 +- .../SecretSquare/SecretSquareComments.tsx | 55 +++-- pageTemplates/square/SquareLoungeSection.tsx | 2 +- pageTemplates/study/StudyDateBar.tsx | 2 +- pageTemplates/study/StudyHeader.tsx | 2 +- .../StudyPageDrawerFilterBar.tsx | 2 +- .../vote/voteDrawer/VoteDrawerPlace.tsx | 8 +- pages/chat/[uid].tsx | 9 +- pages/group/[id]/admin.tsx | 5 +- pages/group/[id]/index.tsx | 174 ++++++++++---- pages/group/index.tsx | 215 +++++++++++------- pages/ranking/index.tsx | 1 - pages/study/[id]/[date]/index.tsx | 23 +- pages/studyPage.tsx | 2 +- theme.ts | 6 +- types/models/groupTypes/group.ts | 2 +- 61 files changed, 1280 insertions(+), 743 deletions(-) create mode 100644 components/atoms/BottomCommentInput.tsx create mode 100644 components/atoms/BottomFixedButton.tsx create mode 100644 components/molecules/CommentSection.tsx create mode 100644 components/molecules/cards/GroupThumbnailCard.tsx create mode 100644 libs/comment/commentLib.ts delete mode 100644 pageTemplates/group/detail/GroupContent/GroupStudyContent.tsx diff --git a/components/BottomNav.tsx b/components/BottomNav.tsx index 149e6dc62..250690a86 100644 --- a/components/BottomNav.tsx +++ b/components/BottomNav.tsx @@ -7,6 +7,7 @@ import styled from "styled-components"; import { useHandleMove } from "../@natives/useHandleBottomNav"; import { USER_LOCATION } from "../constants/keys/localStorage"; +import { useTypeToast } from "../hooks/custom/CustomToast"; import { getStudyStandardDate } from "../libs/study/date/getStudyStandardDate"; import { slideDirectionState } from "../recoils/navigationRecoils"; import { ActiveLocation } from "../types/services/locationTypes"; @@ -69,12 +70,24 @@ export default function BottomNav() { } function NavButton({ text, url, activeIcon, defaultIcon, isActive, idx }: INavButton) { + const { data: session } = useSession(); + const typeToast = useTypeToast(); + const isGuest = session?.user.role === "guest"; const setSlideDirection = useSetRecoilState(slideDirectionState); const handleMove = useHandleMove(setSlideDirection); + const onClick = (e) => { + if (isGuest && text === "스터디") { + e.preventDefault(); + typeToast("guest"); + return; + } + handleMove(); + }; + return ( diff --git a/components/Icons/DotIcons.tsx b/components/Icons/DotIcons.tsx index 3136e43c4..b36dd4dc2 100644 --- a/components/Icons/DotIcons.tsx +++ b/components/Icons/DotIcons.tsx @@ -1,10 +1,19 @@ -export function EllipsisIcon() { - return - - +export function EllipsisIcon({ size }: { size: "sm" | "md" }) { + const width = size === "sm" ? 12 : 20; + return ( + + + + ); } diff --git a/components/Icons/HeartIcons.tsx b/components/Icons/HeartIcons.tsx index 9d89b4256..03fe12cd0 100644 --- a/components/Icons/HeartIcons.tsx +++ b/components/Icons/HeartIcons.tsx @@ -5,7 +5,7 @@ export function HeartIcon({ }: { size?: "sm" | "md"; fill?: boolean; - color?: "gray" | "red" | "orange"; + color: "gray" | "red" | "orange"; }) { const width = size === "sm" ? 12 : 16; return ( @@ -19,8 +19,8 @@ export function HeartIcon({ ? color === "gray" ? "var(--gray-300)" : color === "red" - ? "var(--color-red)" - : "var(--color-orange)" + ? "var(--color-red)" + : "var(--color-orange)" : "none" } > @@ -38,8 +38,8 @@ export function HeartIcon({ ? color === "gray" ? "var(--gray-300)" : color === "red" - ? "var(--color-red)" - : "var(--color-orange)" + ? "var(--color-red)" + : "var(--color-orange)" : "white" } strokeWidth="1" diff --git a/components/Icons/MathIcons.tsx b/components/Icons/MathIcons.tsx index 955992518..233d55d46 100644 --- a/components/Icons/MathIcons.tsx +++ b/components/Icons/MathIcons.tsx @@ -1,11 +1,20 @@ -export function PlusIcon() { - return ( - +export function PlusIcon({ color, size }: { color: "white" | "mint"; size: "sm" | "md" }) { + return size === "sm" ? ( + + + ) : ( + + ); diff --git a/components/Icons/UserIcons.tsx b/components/Icons/UserIcons.tsx index 14492ff3f..3223b136a 100644 --- a/components/Icons/UserIcons.tsx +++ b/components/Icons/UserIcons.tsx @@ -24,8 +24,8 @@ export function UserPlusIcon() { return ( diff --git a/components/atoms/BottomCommentInput.tsx b/components/atoms/BottomCommentInput.tsx new file mode 100644 index 000000000..59cccfab9 --- /dev/null +++ b/components/atoms/BottomCommentInput.tsx @@ -0,0 +1,56 @@ +import { Box } from "@chakra-ui/react"; + +import { SECRET_USER_SUMMARY } from "../../constants/serviceConstants/userConstants"; +import { useKeypadHeight } from "../../hooks/custom/useKeypadHeight"; +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; +import { DispatchType } from "../../types/hooks/reactTypes"; +import { IUserSummary } from "../../types/models/userTypes/userInfoTypes"; +import { iPhoneNotchSize } from "../../utils/validationUtils"; +import UserCommentInput from "../molecules/UserCommentInput"; + +interface BottomCommentInputProps { + isFixed?: boolean; + onSubmit: (value: string) => void; + type?: "comment" | "message"; + replyName: string; + user: IUserSummary; + setReplyProps: DispatchType; +} + +function BottomCommentInput({ + isFixed = true, + onSubmit, + type = "comment", + replyName, + user, + setReplyProps, +}: BottomCommentInputProps) { + const keypadHeight = useKeypadHeight(); + + return ( + + + + + + ); +} + +export default BottomCommentInput; diff --git a/components/atoms/BottomFixedButton.tsx b/components/atoms/BottomFixedButton.tsx new file mode 100644 index 000000000..e4789764e --- /dev/null +++ b/components/atoms/BottomFixedButton.tsx @@ -0,0 +1,27 @@ +import { Box, Button } from "@chakra-ui/react"; + +import { iPhoneNotchSize } from "../../utils/validationUtils"; + +interface BottomFixedButton { + text: string; + func: () => void; +} + +function BottomFixedButton({ text, func }: BottomFixedButton) { + return ( + + + + ); +} + +export default BottomFixedButton; diff --git a/components/atoms/Select.tsx b/components/atoms/Select.tsx index 68b20b3b3..19cd19acd 100644 --- a/components/atoms/Select.tsx +++ b/components/atoms/Select.tsx @@ -13,7 +13,7 @@ interface ISelect { setValue: DispatchType | DispatchType; isBorder?: boolean; type?: "location"; - size: "sm" | "md" | "lg"; + size: "xs" | "sm" | "md" | "lg"; isEllipse?: boolean; isFullSize?: boolean; isActive?: boolean; @@ -26,7 +26,7 @@ export default function Select({ setValue: setParentValue, isBorder = true, type, - size = "sm", + size, isFullSize, isEllipse = true, isActive = true, @@ -53,7 +53,7 @@ export default function Select({ const adjustWidth = () => { if (selectRef.current) { const textLength = selectRef.current.selectedOptions[0].text.length; - const addSize = size === "sm" ? 44 : size === "md" ? 60 : 0; + const addSize = size === "xs" ? 44 : size === "sm" ? 48 : size === "md" ? 60 : 0; selectRef.current.style.width = `${textLength * 6.5 + addSize}px`; } }; @@ -64,8 +64,9 @@ export default function Select({ @@ -75,34 +76,61 @@ export default function Select({ } ref={selectRef} focusBorderColor="#00c2b3" - size={size === "sm" ? "xs" : size === "md" ? "md" : "lg"} + size={size} color="primary" value={value} onChange={onChange} borderRadius={ - !isEllipse ? undefined : size === "sm" ? "9999px" : size === "md" ? "20px" : "12px" + !isEllipse + ? undefined + : size === "xs" + ? "9999px" + : size === "sm" + ? "8px" + : size === "md" + ? "20px" + : "12px" } border={!isBorder ? "none" : undefined} borderColor="var(--gray-200)" bgColor="white" fontSize={ - size === "sm" && !isBorder ? "12px" : size === "sm" || size === "md" ? "11px" : "13px" + size === "sm" + ? "12px" + : size === "xs" && !isBorder + ? "12px" + : size === "xs" || size === "md" + ? "11px" + : "13px" } outline={size === "md" ? "1px solid var(--gray-100)" : undefined} - fontWeight={size === "sm" ? 500 : isThick ? 600 : 500} + fontWeight={isThick ? 600 : 500} isDisabled={!isActive} - height={size === "sm" ? (isBorder ? "24px" : "16px") : size === "md" ? "32px" : "52px"} + height={ + size === "xs" + ? isBorder + ? "24px" + : "16px" + : size === "sm" + ? "28px" + : size === "md" + ? "32px" + : "52px" + } width={!isFullSize ? "max-content" : "100%"} - mr={size === "sm" && !isBorder && "-6px"} + mr={size === "xs" && !isBorder && "-6px"} sx={{ paddingInlineStart: !isEllipse ? "12px" - : size === "sm" + : size === "xs" ? "8px" + : size === "sm" + ? "12px" : size === "md" ? "16px" : "20px", // padding-left - paddingInlineEnd: !isEllipse ? "16px" : "20px", // padding-right (아이콘 오른쪽에 여유 공간) + paddingInlineEnd: size === "sm" ? "4px" : !isEllipse ? "16px" : "20px", // padding-right (아이콘 오른쪽에 여유 공간) + paddingBottom: 0, }} _focus={{ outline: isBorder ? "var(--border)" : "none", diff --git a/components/atoms/buttons/MenuButton.tsx b/components/atoms/buttons/MenuButton.tsx index 656325c20..d1f025a2e 100644 --- a/components/atoms/buttons/MenuButton.tsx +++ b/components/atoms/buttons/MenuButton.tsx @@ -22,12 +22,17 @@ function MenuButton({ menuArr }: MenuButtonProps) { <> - + {menuArr.map((menu) => ( - + {menu?.text} {menu?.kakaoOptions && } diff --git a/components/layouts/Header.tsx b/components/layouts/Header.tsx index eccf6eb7b..86678af8f 100644 --- a/components/layouts/Header.tsx +++ b/components/layouts/Header.tsx @@ -44,8 +44,11 @@ export default function Header({ )} - {isCenter && {title}} - {isCenter && !children && } + {isCenter && ( + + {title} + + )}
{children}
); @@ -82,10 +85,3 @@ const HeaderContainer = styled.header<{ max-width: var(--max-width); margin: 0 auto; `; - -const CenterTitle = styled.div` - flex: 1; - font-weight: "bold"; - color: var(--gray-800); /* text-gray-1 - 색상은 예시입니다 */ - text-align: center; -`; diff --git a/components/molecules/BlurredLink.tsx b/components/molecules/BlurredLink.tsx index 42cded1ee..8db55af7a 100644 --- a/components/molecules/BlurredLink.tsx +++ b/components/molecules/BlurredLink.tsx @@ -10,33 +10,16 @@ interface BlurredLinkProps { function BlurredLink({ isBlur, url }: BlurredLinkProps) { return ( - - 오픈채팅방 주소(참여 인원 전용) -
- - - {url} - - -
-
+ + + {url} + + ); } -const KakaoLink = styled.div` - display: flex; - flex-direction: column; - padding: var(--gap-2) var(--gap-3); - padding-bottom: var(--gap-1); - background-color: var(--gray-100); - - > div { - padding: var(--gap-1) 0; - } -`; - -const CustomExternalLink = styled(ExternalLink)<{ isBlur: boolean }>` - pointer-events: ${({ isBlur }) => (isBlur ? "none" : "unset")}; +const CustomExternalLink = styled(ExternalLink)<{ isblur: "true" | "false" }>` + pointer-events: ${({ isblur }) => (isblur === "true" ? "none" : "unset")}; `; export default BlurredLink; diff --git a/components/molecules/CommentSection.tsx b/components/molecules/CommentSection.tsx new file mode 100644 index 000000000..2cfed9754 --- /dev/null +++ b/components/molecules/CommentSection.tsx @@ -0,0 +1,54 @@ +import { Box } from "@chakra-ui/react"; +import { useSession } from "next-auth/react"; + +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; +import { UserCommentProps } from "../../types/components/propTypes"; +import { DispatchType } from "../../types/hooks/reactTypes"; +import UserCommentBlock from "./UserCommentBlock"; + +interface CommentSectionProps { + commentArr: UserCommentProps[]; + setCommentArr: DispatchType; + id: string; + hasAuthority?: boolean; + setReplyProps: DispatchType; +} + +function CommentSection({ + setCommentArr, + commentArr, + id, + hasAuthority, + setReplyProps, +}: CommentSectionProps) { + const { data: session } = useSession(); + console.log(52, commentArr); + + return ( + <> + + 댓글 {commentArr?.length}개 + + {commentArr.map((comment, idx) => ( + + ))} + + ); +} + +export default CommentSection; diff --git a/components/molecules/ContentHeartBar.tsx b/components/molecules/ContentHeartBar.tsx index 5f06a7a52..fb357667e 100644 --- a/components/molecules/ContentHeartBar.tsx +++ b/components/molecules/ContentHeartBar.tsx @@ -1,6 +1,6 @@ import { Box, Button, Flex } from "@chakra-ui/react"; import dayjs from "dayjs"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { useSession } from "next-auth/react"; import { Fragment, useEffect, useState } from "react"; @@ -8,6 +8,8 @@ import { useCommentMutation, useSubCommentMutation } from "../../hooks/common/mu import { useTypeToast } from "../../hooks/custom/CustomToast"; import { useFeedLikeMutation } from "../../hooks/feed/mutations"; import { useUserInfoQuery } from "../../hooks/user/queries"; +import { getCommentArr } from "../../libs/comment/commentLib"; +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; import { UserCommentProps } from "../../types/components/propTypes"; import { FeedComment } from "../../types/models/feed"; import { IUserSummary } from "../../types/models/userTypes/userInfoTypes"; @@ -32,11 +34,8 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont const { data: session } = useSession(); const { data: userInfo } = useUserInfoQuery(); const isGuest = session ? session.user.name === "guest" : undefined; - const router = useRouter(); const searchParams = useSearchParams(); - const urlSearchParams = new URLSearchParams(searchParams); - const drawerType = searchParams.get("drawer"); const [keyboardHeight, setKeyboardHeight] = useState(0); @@ -44,6 +43,7 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont const [heartProps, setHeartProps] = useState({ isMine: false, users: likeUsers, cnt: likeCnt }); const [commentArr, setCommentArr] = useState(comments || []); const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); + const [replyProps, setReplyProps] = useState(); const { mutate } = useFeedLikeMutation({ onSuccess() { @@ -144,6 +144,12 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont }; const onSubmit = async (value: string) => { + if (replyProps) { + writeSubComment({ comment: value, commentId: replyProps.commentId }); + setCommentArr(getCommentArr(value, replyProps.commentId, commentArr, userInfo)); + return; + } + await writeComment({ comment: value }); setCommentArr((old) => [...old, addNewComment(userInfo, value)]); }; @@ -160,8 +166,8 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont if (type === "like") { setModalType("like"); } - urlSearchParams.append("drawer", type); - router.push(`/gather?${urlSearchParams.toString()}`); + // urlSearchParams.append("drawer", type); + // router.push(`/gather?${urlSearchParams.toString()}`); }; return ( @@ -214,7 +220,7 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont ) : null} {modalType === "like" && ( - router.back()}> + setModalType(null)}> {likeUsers.map((who, idx) => ( @@ -225,7 +231,7 @@ function ContentHeartBar({ feedId, likeUsers, likeCnt, comments, refetch }: Cont )} {modalType === "comment" && ( - router.back()}> + setModalType(null)}> comment._id === item._id)} setCommentArr={setCommentArr} - writeSubComment={writeSubComment} + setReplyProps={setReplyProps} + hasAuthority={(item.user as IUserSummary).uid !== userInfo?.uid} /> ))} - + )} diff --git a/components/molecules/InfoBoxCol.tsx b/components/molecules/InfoBoxCol.tsx index fbed1a600..b6500699c 100644 --- a/components/molecules/InfoBoxCol.tsx +++ b/components/molecules/InfoBoxCol.tsx @@ -3,17 +3,18 @@ import { Box, Flex } from "@chakra-ui/react"; export interface InfoBoxProps { category: string; text: string; - color?: "mint" | "red" ; + color?: "mint" | "red"; rightChildren?: React.ReactNode; } interface InfoBoxColProps { infoBoxPropsArr: InfoBoxProps[]; + size?: "sm" | "md"; } -function InfoBoxCol({ infoBoxPropsArr }: InfoBoxColProps) { +function InfoBoxCol({ infoBoxPropsArr, size = "sm" }: InfoBoxColProps) { return ( - + {infoBoxPropsArr.map((props, idx) => { const isNotLast = idx !== infoBoxPropsArr.length - 1; @@ -25,6 +26,7 @@ function InfoBoxCol({ infoBoxPropsArr }: InfoBoxColProps) { borderBottom={isNotLast && "var(--border)"} key={idx} align="center" + lineHeight={size === "sm" ? "18px" : "20px"} > {props.category} diff --git a/components/molecules/PlaceImage.tsx b/components/molecules/PlaceImage.tsx index e944d44de..00269b432 100644 --- a/components/molecules/PlaceImage.tsx +++ b/components/molecules/PlaceImage.tsx @@ -34,7 +34,6 @@ function PlaceImage({ imageProps, id, hasToggleHeart, selected, size }: PlaceHea const [heartType, setHeartType] = useState<"main" | "sub" | null>(); useEffect(() => { - setHeartType(myPreferType); }, [myPreferType]); @@ -47,7 +46,6 @@ function PlaceImage({ imageProps, id, hasToggleHeart, selected, size }: PlaceHea setHeartType(null); break; case "sub": - setHeartType(null); break; default: @@ -66,15 +64,15 @@ function PlaceImage({ imageProps, id, hasToggleHeart, selected, size }: PlaceHea selected === "main" ? "2px solid var(--color-mint)" : selected === "sub" - ? "2px solid var(--color-orange)" - : null + ? "2px solid var(--color-orange)" + : null } boxShadow={ selected === "main" ? " 0px 5px 10px 0px #1BB8760A" : selected === "sub" - ? "0px 5px 10px 0px #1BB8760A" - : null + ? "0px 5px 10px 0px #1BB8760A" + : null } w={sizeLength} h={sizeLength} @@ -119,7 +117,7 @@ function PlaceImage({ imageProps, id, hasToggleHeart, selected, size }: PlaceHea {heartType ? ( ) : ( - + )} )} diff --git a/components/molecules/UserComment.tsx b/components/molecules/UserComment.tsx index 74854a441..602d9eca1 100644 --- a/components/molecules/UserComment.tsx +++ b/components/molecules/UserComment.tsx @@ -2,33 +2,32 @@ import { Box, Button, Flex } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useSession } from "next-auth/react"; import { useState } from "react"; -import { useQueryClient } from "react-query"; -import { useSetRecoilState } from "recoil"; -import { GATHER_CONTENT, GROUP_STUDY } from "../../constants/keys/queryKeys"; import { useCommentLikeMutation, useCommentMutation, useSubCommentMutation, } from "../../hooks/common/mutations"; import CommentEditModal from "../../modals/common/CommentEditModal"; -import { transferGatherDataState, transferGroupDataState } from "../../recoils/transferRecoils"; +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; import { UserCommentProps as CommentProps } from "../../types/components/propTypes"; -import { DispatchBoolean, DispatchType } from "../../types/hooks/reactTypes"; +import { DispatchType } from "../../types/hooks/reactTypes"; import { getDateDiff } from "../../utils/dateTimeUtils"; import Avatar from "../atoms/Avatar"; +import { EllipsisIcon } from "../Icons/DotIcons"; interface UserCommentProps extends Omit { isSecret?: boolean; - setCommentArr: DispatchType; + setCommentArr?: DispatchType; type: "gather" | "group" | "feed" | "square"; pageId: string; commentId?: string; parentId?: string; isReComment?: boolean; - setIsReCommentInput: DispatchBoolean; + setReplyProps: DispatchType; likeList: string[]; isAuthor: boolean; + hasAuthority: boolean; } function UserComment({ @@ -36,7 +35,7 @@ function UserComment({ user, updatedAt, comment, - setIsReCommentInput, + setReplyProps, commentId, setCommentArr, isReComment, @@ -44,15 +43,10 @@ function UserComment({ type, pageId, likeList, - isAuthor, + hasAuthority, }: UserCommentProps) { - const queryClient = useQueryClient(); - const { data: session } = useSession(); - const setTransferGather = useSetRecoilState(transferGatherDataState); - const setTransferGroup = useSetRecoilState(transferGroupDataState); - const [text, setText] = useState(comment); const [isEditModal, setIsEditModal] = useState(false); const [likeArr, setLikeArr] = useState(likeList || []); @@ -113,7 +107,6 @@ function UserComment({ return null; } else { const updatedSubComments = obj.subComments.filter((item) => item._id !== commentId); - return { ...obj, subComments: updatedSubComments }; } }) @@ -122,14 +115,14 @@ function UserComment({ }; const onCompleted = () => { - if (type === "gather") { - setTransferGather(null); - queryClient.invalidateQueries([GATHER_CONTENT, pageId]); - } - if (type === "group") { - setTransferGroup(null); - queryClient.invalidateQueries([GROUP_STUDY, pageId]); - } + // if (type === "gather") { + // setTransferGather(null); + // queryClient.invalidateQueries([GATHER_CONTENT, pageId]); + // } + // if (type === "group") { + // setTransferGroup(null); + // queryClient.invalidateQueries([GROUP_STUDY, pageId]); + // } setIsEditModal(false); }; @@ -144,63 +137,79 @@ function UserComment({ }); setLikeArr((old) => [...old, session?.user.id]); }; - + console.log(54, isReComment, parentId, commentId); return ( <> - - + + - - - + + + {user.name} - + {session?.user.uid === user.uid && ( + + )} + {/* {!isSecret && user.location} {!isSecret && "."} {getDateDiff(dayjs(updatedAt))} - + */} -

+ {comment} - {user._id === session?.user.id && ( + {/* {user._id === session?.user.id && ( setIsEditModal(true)}> - )} -

{" "} - + )} */} +
{" "} + - {!isReComment && ( - + {!isReComment && hasAuthority && ( + <> + + + )} + + {getDateDiff(dayjs(updatedAt))}
diff --git a/components/molecules/UserCommentBlock.tsx b/components/molecules/UserCommentBlock.tsx index d012a4adf..8a11bc8d1 100644 --- a/components/molecules/UserCommentBlock.tsx +++ b/components/molecules/UserCommentBlock.tsx @@ -1,72 +1,53 @@ import { Box } from "@chakra-ui/react"; -import { useState } from "react"; -import { SECRET_USER_SUMMARY } from "../../constants/serviceConstants/userConstants"; -import { useUserInfoQuery } from "../../hooks/user/queries"; +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; import { UserCommentProps } from "../../types/components/propTypes"; import { DispatchType } from "../../types/hooks/reactTypes"; import UserComment from "./UserComment"; -import UserCommentInput from "./UserCommentInput"; interface UserCommentBlockProps { type: "gather" | "group" | "feed" | "square"; id: string; commentProps: UserCommentProps; - setCommentArr: DispatchType; - writeSubComment: ({ comment, commentId }: { comment: string; commentId: string }) => void; + setCommentArr?: DispatchType; + + setReplyProps: DispatchType; + hasAuthority: boolean; } function UserCommentBlock({ type, id, commentProps, + setReplyProps, + hasAuthority = true, setCommentArr, - writeSubComment, }: UserCommentBlockProps) { - const { data: userInfo } = useUserInfoQuery(); - - const [isReCommentInput, setIsReCommentInput] = useState(false); - - const onSubmitReComment = (text: string) => { - writeSubComment({ comment: text, commentId: commentProps._id }); - - setCommentArr((old) => - old.map((obj) => { - return obj._id === commentProps._id - ? { - ...obj, - subComments: Array.isArray(obj.subComments) - ? [...obj.subComments, { comment: text, user: userInfo }] - : [], - } - : obj; - }), - ); - setIsReCommentInput(false); - }; - return ( <> {commentProps?.subComments?.map((sub, idx2) => ( - + ))} - {isReCommentInput && ( - - - - )} ); } diff --git a/components/molecules/UserCommentInput.tsx b/components/molecules/UserCommentInput.tsx index eaf6e684f..8de105aff 100644 --- a/components/molecules/UserCommentInput.tsx +++ b/components/molecules/UserCommentInput.tsx @@ -1,7 +1,9 @@ -import { Flex } from "@chakra-ui/react"; +import { Button, Flex } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; import styled from "styled-components"; +import { ReplyProps } from "../../pageTemplates/square/SecretSquare/SecretSquareComments"; +import { DispatchType } from "../../types/hooks/reactTypes"; import { IUserSummary } from "../../types/models/userTypes/userInfoTypes"; import Avatar from "../atoms/Avatar"; @@ -10,6 +12,8 @@ interface UserCommentInputProps { onSubmit: (value: string) => void; user: IUserSummary; initialFocus?: boolean; + replyName: string; + setReplyProps: DispatchType; } function UserCommentInput({ @@ -17,16 +21,29 @@ function UserCommentInput({ onSubmit, initialFocus, type = "comment", + replyName, + setReplyProps, }: UserCommentInputProps) { const textareaRef = useRef(null); + const [text, setText] = useState(replyName ? "@" + replyName + " " : ""); - const [text, setText] = useState(""); + useEffect(() => { + if (replyName) { + setText("@" + replyName + " "); + textareaRef.current.focus(); + } + }, [replyName]); useEffect(() => { if (!user) return; if (initialFocus) textareaRef.current.focus(); + if (replyName && text && !text.startsWith("@" + replyName)) { + setReplyProps(null); + setText(""); + } + if (!text) { - textareaRef.current.style.height = `24px`; + textareaRef.current.style.height = `18px`; } else if (textareaRef.current) { textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; } @@ -36,56 +53,80 @@ function UserCommentInput({ onSubmit(text); setText(""); }; + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setText(value); + }; return ( {user && ( <> - + + + setText(e.target.value)} + onChange={handleChange} + replyName={replyName} /> - - {type === "comment" ? "등록" : "전송"} - + {text && ( + + )} )} ); } -const MyTextArea = styled.textarea` - margin-left: var(--gap-3); - padding: 2px 0; +const MyTextArea = styled.textarea<{ replyName: string }>` + margin-left: 8px; flex: 1; background-color: inherit; - height: 28px; - max-height: 40px; - padding-right: 20px; + height: 18px; + padding-right: 10px; + margin-right: 4px; overflow-y: auto; + font-weight: light; + font-size: 12px; + color: var(--gray-800); /* 기본 텍스트는 검정색 */ + line-height: 18px; resize: none; - &::-webkit-scrollbar { - display: none; - } - :focus { - outline: none; - } -`; + outline: none; -const SubmitBtn = styled.button<{ focus: boolean }>` - color: ${(props) => (props.focus ? "var(--color-mint)" : "var(--gray-500)")}; + &::before { + content: "${(props) => props.replyName} "; + color: var(--color-mint); /* replyName 부분 민트색 */ + } `; export default UserCommentInput; diff --git a/components/molecules/cards/GroupThumbnailCard.tsx b/components/molecules/cards/GroupThumbnailCard.tsx new file mode 100644 index 000000000..625bff3bc --- /dev/null +++ b/components/molecules/cards/GroupThumbnailCard.tsx @@ -0,0 +1,175 @@ +import { Badge, Box, Flex } from "@chakra-ui/react"; +import Image from "next/image"; +import Link from "next/link"; +import { ComponentProps } from "react"; +import styled from "styled-components"; + +import { ABOUT_USER_SUMMARY } from "../../../constants/serviceConstants/userConstants"; +import { SingleLineText } from "../../../styles/layout/components"; +import { + GroupParicipantProps, + GroupStatus, + IGroupWritingCategory +} from "../../../types/models/groupTypes/group"; +import { IUserSummary } from "../../../types/models/userTypes/userInfoTypes"; +import { UserIcon } from "../../Icons/UserIcons"; +import AvatarGroupsOverwrap from "../groups/AvatarGroupsOverwrap"; + +const VOTER_SHOW_MAX = 4; +export interface GroupThumbnailCardProps { + title: string; + text: string; + status: GroupStatus; + category: IGroupWritingCategory; + participants: (GroupParicipantProps | { user: IUserSummary })[]; + imageProps: { + image: string; + isPriority?: boolean; + }; + maxCnt: number; + func: () => void; + id: number; +} + +export function GroupThumbnailCard({ + participants, + title, + status, + text, + category, + func, + imageProps, + id, + maxCnt, +}: GroupThumbnailCardProps) { + const userAvatarArr = participants + ?.filter((par) => par) + .map((par) => + par.user + ? { + image: par.user?.profileImage, + ...(par.user.avatar?.type !== null ? { avatar: par.user.avatar } : {}), + } + : { image: ABOUT_USER_SUMMARY.profileImage }, + ); + + const statusToBadgeProps: Record = { + imminent: { text: `마감까지 ${maxCnt - participants.length}명`, colorScheme: "red" }, + full: { text: "인원마감", colorScheme: "orange" }, + waiting: { text: "오픈대기중", colorScheme: "red" }, + pending: { text: "모집중", colorScheme: "mint" }, + end: { text: "종료", colorScheme: "gray" }, + }; + + return ( + + + + + + + {category.main} + + + ・ + + + {category.sub} + + + + + {statusToBadgeProps[status].text} + + + {title} + {text} + + + + + + + = maxCnt + ? "var(--color-red)" + : "var(--color-gray)" + } + > + {participants.length} + + + / + + + {maxCnt === 0 ? : maxCnt} + + + + + + + ); +} + +type PlaceImageProps = Omit, "alt" | "sizes" | "fill">; + +function PlaceImage(props: PlaceImageProps) { + return ( + + thumbnailImage + + ); +} + +const CardLink = styled(Link)` + height: fit-content; + display: flex; + + border-radius: 12px; + background-color: white; + justify-content: space-between; + + &:hover { + background-color: var(--gray-200); + } + + &:not(:last-of-type) { + margin-bottom: 16px; + } +`; + +const Title = styled(SingleLineText)` + font-weight: 700; + font-size: 16px; + margin-bottom: 4px; + line-height: 24px; + color: var(--gray-800); +`; + +const Subtitle = styled(SingleLineText)` + color: var(--gray-500); + font-size: 12px; + font-weight: regular; + line-height: 18px; + margin-bottom: 12px; +`; diff --git a/components/molecules/cards/ProfileCommentCard.tsx b/components/molecules/cards/ProfileCommentCard.tsx index 572cf0bc8..377370ab7 100644 --- a/components/molecules/cards/ProfileCommentCard.tsx +++ b/components/molecules/cards/ProfileCommentCard.tsx @@ -49,14 +49,14 @@ export default function ProfileCommentCard({ return ( <> - + {leftComponent && {leftComponent}} {user ? ( ) : ( )} - + {user?.name || "익명"} diff --git a/components/molecules/groups/ButtonGroups.tsx b/components/molecules/groups/ButtonGroups.tsx index f687a4a5b..10428d70a 100644 --- a/components/molecules/groups/ButtonGroups.tsx +++ b/components/molecules/groups/ButtonGroups.tsx @@ -37,14 +37,21 @@ export default function ButtonGroups({ + + 모임 만들기 + + + + + {imageTileArr?.length ? ( + + ) : null} + ); } -const Layout = styled.div` - background-color: var(--gray-100); - border-bottom: 6px solid var(--gray-200); - padding: 16px; -`; - -const BlockLayout = styled.div` - height: 110px; - background-color: inherit; - padding-top: 2px; - border-radius: var(--rounded); - flex-direction: column; - display: flex; - align-items: center; - justify-content: center; - - font-size: 18px; - color: var(--gray-600); -`; - export default GroupMine; diff --git a/pageTemplates/group/detail/GroupBottomNav.tsx b/pageTemplates/group/detail/GroupBottomNav.tsx index a521443a3..cf82394c4 100644 --- a/pageTemplates/group/detail/GroupBottomNav.tsx +++ b/pageTemplates/group/detail/GroupBottomNav.tsx @@ -1,10 +1,9 @@ -import { Button } from "@chakra-ui/react"; import { useRouter } from "next/dist/client/router"; import { useParams } from "next/navigation"; import { useSession } from "next-auth/react"; import { useQueryClient } from "react-query"; -import styled from "styled-components"; +import BottomFixedButton from "../../../components/atoms/BottomFixedButton"; import { GROUP_STUDY } from "../../../constants/keys/queryKeys"; import { useCompleteToast, useErrorToast } from "../../../hooks/custom/CustomToast"; import { useGroupParticipationMutation } from "../../../hooks/groupStudy/mutations"; @@ -65,33 +64,7 @@ function GroupBottomNav({ data }: IGroupBottomNav) { const { text, handleFunction } = getButtonSettings(); - return ( - <> - - - - - ); + return ; } -const Layout = styled.nav` - position: fixed; - left: 50%; - bottom: 0; - transform: translate(-50%, 0); - width: 100%; - max-width: 390px; - padding: var(--gap-4); -`; - export default GroupBottomNav; diff --git a/pageTemplates/group/detail/GroupComment.tsx b/pageTemplates/group/detail/GroupComment.tsx index c087743b9..bdba9d1e8 100644 --- a/pageTemplates/group/detail/GroupComment.tsx +++ b/pageTemplates/group/detail/GroupComment.tsx @@ -1,47 +1,38 @@ -import { Box } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useRouter } from "next/dist/client/router"; -import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; -import { useQueryClient } from "react-query"; -import { useSetRecoilState } from "recoil"; import styled from "styled-components"; -import UserCommentBlock from "../../../components/molecules/UserCommentBlock"; -import UserCommentInput from "../../../components/molecules/UserCommentInput"; -import { GROUP_STUDY } from "../../../constants/keys/queryKeys"; +import BottomCommentInput from "../../../components/atoms/BottomCommentInput"; +import CommentSection from "../../../components/molecules/CommentSection"; import { useCommentMutation, useSubCommentMutation } from "../../../hooks/common/mutations"; import { useUserInfoQuery } from "../../../hooks/user/queries"; -import { transferGroupDataState } from "../../../recoils/transferRecoils"; +import { getCommentArr } from "../../../libs/comment/commentLib"; import { UserCommentProps } from "../../../types/components/propTypes"; import { IUserSummary } from "../../../types/models/userTypes/userInfoTypes"; import { dayjsToStr } from "../../../utils/dateTimeUtils"; +import { ReplyProps } from "../../square/SecretSquare/SecretSquareComments"; interface IGroupComments { comments: UserCommentProps[]; + hasAutority: boolean; } -function GroupComments({ comments }: IGroupComments) { - const queryClient = useQueryClient(); +function GroupComments({ comments, hasAutority }: IGroupComments) { const router = useRouter(); - const { data: session } = useSession(); - const isGuest = session?.user.name === "guest"; + const groupId = router.query.id as string; - const setTransferGroup = useSetRecoilState(transferGroupDataState); const [commentArr, setCommentArr] = useState(comments); + const [replyProps, setReplyProps] = useState(); const { data: userInfo } = useUserInfoQuery(); const { mutate: writeComment } = useCommentMutation("post", "group", groupId, { - onSuccess() { - resetCache(); - }, + onSuccess() {}, }); const { mutate: writeSubComment } = useSubCommentMutation("post", "group", groupId, { - onSuccess() { - resetCache(); - }, + onSuccess() {}, }); useEffect(() => { @@ -57,45 +48,48 @@ function GroupComments({ comments }: IGroupComments) { }; const onSubmit = async (value: string) => { + if (replyProps) { + const text = value.split(" ")?.[1]; + writeSubComment({ + comment: text, + commentId: replyProps.commentId, + subCommentId: replyProps.subCommentId, + }); + setCommentArr(getCommentArr(value, replyProps.commentId, commentArr, userInfo)); + return; + } await writeComment({ comment: value }); setCommentArr((old) => [...old, addNewComment(userInfo, value)]); }; - const resetCache = () => { - setTransferGroup(null); - queryClient.invalidateQueries([GROUP_STUDY, groupId]); - }; - return ( <> - 할 얘기가 있다면 댓글을 남겨보세요 - - {!isGuest && userInfo && ( - - - - )} -
- {commentArr?.map((item, idx) => ( - - ))} -
-
+ {commentArr && ( + + )}
+ {hasAutority && ( + + )} ); } const Layout = styled.div` - margin: var(--gap-5) var(--gap-4); display: flex; flex-direction: column; > span:first-child { @@ -103,10 +97,4 @@ const Layout = styled.div` } `; -const Comment = styled.div` - display: flex; - flex-direction: column; - font-size: 13px; -`; - export default GroupComments; diff --git a/pageTemplates/group/detail/GroupContent/ContentFeed.tsx b/pageTemplates/group/detail/GroupContent/ContentFeed.tsx index 8c6eef11c..e6b48fb90 100644 --- a/pageTemplates/group/detail/GroupContent/ContentFeed.tsx +++ b/pageTemplates/group/detail/GroupContent/ContentFeed.tsx @@ -30,7 +30,7 @@ function ContentFeed({ group }: ContentFeedProps) { }, [group]); return ( - <> + {feeds ? ( feeds?.length ? ( feeds.map((feed, idx) => { @@ -60,7 +60,7 @@ function ContentFeed({ group }: ContentFeedProps) { ) : ( )} - + ); } diff --git a/pageTemplates/group/detail/GroupContent/ContentInfo.tsx b/pageTemplates/group/detail/GroupContent/ContentInfo.tsx index 276b9b1ee..d425f35c7 100644 --- a/pageTemplates/group/detail/GroupContent/ContentInfo.tsx +++ b/pageTemplates/group/detail/GroupContent/ContentInfo.tsx @@ -31,7 +31,11 @@ function ContentInfo({ group }: IContentInfo) { {!!group?.rules?.length && ( 규칙 - {group?.rules.map((rule, idx) => {rule})} + + {group?.rules.map((rule, idx) => ( + {rule} + ))} + )} {group?.link && ( @@ -51,7 +55,7 @@ function ContentInfo({ group }: IContentInfo) { {hashTagArr?.map((tag, idx) => (tag ?
#{tag}
: null))}
- + ); } diff --git a/pageTemplates/group/detail/GroupContent/GroupStudyContent.tsx b/pageTemplates/group/detail/GroupContent/GroupStudyContent.tsx deleted file mode 100644 index 4d813a087..000000000 --- a/pageTemplates/group/detail/GroupContent/GroupStudyContent.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import styled from "styled-components"; - -import TabNav, { ITabNavOptions } from "../../../../components/molecules/navs/TabNav"; -import { GroupSectionCategory } from "../../../../pages/group/[id]"; -import { DispatchType } from "../../../../types/hooks/reactTypes"; -import { IGroup } from "../../../../types/models/groupTypes/group"; -import ContentAttend from "./ContentAttendance"; -import ContentChat from "./ContentChat"; -import ContentGather from "./ContentFeed"; -import ContentInfo from "./ContentInfo"; - -interface IGroupContent { - group: IGroup; - category: GroupSectionCategory; - setCategory: DispatchType; -} - -function GroupContent({ group, category, setCategory }: IGroupContent) { - const categoryArr: GroupSectionCategory[] = ["정보", "출석부", "피드", "채팅"]; - - const tabArr: ITabNavOptions[] = categoryArr.map((category) => ({ - text: category, - func: () => setCategory(category), - flex: 1, - })); - - return ( - - - - {category === "정보" ? ( - - ) : category === "피드" ? ( - - ) : category === "출석부" ? ( - - ) : ( - - )} - - - ); -} - -const Layout = styled.div` - display: flex; - flex-direction: column; - min-height: 140px; - border-bottom: 6px solid var(--gray-200); -`; - -const ContentContainer = styled.div` - background-color: white; - display: flex; - flex-direction: column; - - min-height: 440px; -`; - -export default GroupContent; diff --git a/pageTemplates/group/detail/GroupCover.tsx b/pageTemplates/group/detail/GroupCover.tsx index a11ef963a..0ba303d79 100644 --- a/pageTemplates/group/detail/GroupCover.tsx +++ b/pageTemplates/group/detail/GroupCover.tsx @@ -1,5 +1,5 @@ +import { Box } from "@chakra-ui/react"; import Image from "next/image"; -import styled from "styled-components"; import { SQUARE_RANDOM_IMAGE } from "../../../assets/images/imageUrl"; @@ -9,7 +9,7 @@ interface IGroupCover { function GroupCover({ image }: IGroupCover) { return ( - + study - +
); } -const Layout = styled.div` - position: relative; - width: 100%; - height: 200px; -`; - export default GroupCover; diff --git a/pageTemplates/group/detail/GroupHeader.tsx b/pageTemplates/group/detail/GroupHeader.tsx index 9be916687..3b6f85e94 100644 --- a/pageTemplates/group/detail/GroupHeader.tsx +++ b/pageTemplates/group/detail/GroupHeader.tsx @@ -1,17 +1,14 @@ import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; import { useState } from "react"; -import { useQueryClient } from "react-query"; -import { useSetRecoilState } from "recoil"; import AlertModal, { IAlertModalOptions } from "../../../components/AlertModal"; import MenuButton, { MenuProps } from "../../../components/atoms/buttons/MenuButton"; import Header from "../../../components/layouts/Header"; import { GROUP_WRITING_STORE } from "../../../constants/keys/localStorage"; -import { GROUP_STUDY } from "../../../constants/keys/queryKeys"; +import { useResetGroupQuery } from "../../../hooks/custom/CustomHooks"; import { useCompleteToast } from "../../../hooks/custom/CustomToast"; import { useGroupParticipationMutation } from "../../../hooks/groupStudy/mutations"; -import { transferGroupDataState } from "../../../recoils/transferRecoils"; import { IGroup } from "../../../types/models/groupTypes/group"; import { setLocalStorageObj } from "../../../utils/storageUtils"; @@ -21,6 +18,7 @@ interface IGroupHeader { function GroupHeader({ group }: IGroupHeader) { const { data: session } = useSession(); + const resetGroupQuery = useResetGroupQuery(); const completeToast = useCompleteToast(); const router = useRouter(); const isAdmin = group.organizer.uid === session?.user.uid; @@ -29,14 +27,10 @@ function GroupHeader({ group }: IGroupHeader) { group.participants.some((par) => par.user?.uid === session?.user.uid); const [isSettigModal, setIsSettingModal] = useState(false); - const setTransferGroup = useSetRecoilState(transferGroupDataState); - const queryClient = useQueryClient(); const movePage = async () => { completeToast("free", "탈퇴되었습니다."); - queryClient.invalidateQueries({ queryKey: [GROUP_STUDY], exact: false }); - setTransferGroup(null); - router.push("/group"); + resetGroupQuery(); }; const { mutate } = useGroupParticipationMutation("delete", group?.id, { @@ -48,7 +42,7 @@ function GroupHeader({ group }: IGroupHeader) { }; const menuArr: MenuProps[] = [ - ...(isMember + ...(isMember && !isAdmin ? [ { text: "소모임 탈퇴하기", @@ -60,6 +54,18 @@ function GroupHeader({ group }: IGroupHeader) { : []), ...(isAdmin ? [ + { + text: "관리자 페이지", + func: () => { + router.push(`/group/${group.id}/admin`); + }, + }, + { + text: "인원 관리", + func: () => { + router.push(`/group/${group.id}/member`); + }, + }, { text: "내용 수정하기", func: () => { @@ -91,7 +97,7 @@ function GroupHeader({ group }: IGroupHeader) { return ( <> -
+
{isSettigModal && ( diff --git a/pageTemplates/group/detail/GroupParticipation.tsx b/pageTemplates/group/detail/GroupParticipation.tsx index 53ad11bca..be465d3fa 100644 --- a/pageTemplates/group/detail/GroupParticipation.tsx +++ b/pageTemplates/group/detail/GroupParticipation.tsx @@ -1,3 +1,4 @@ +import { Box } from "@chakra-ui/react"; import styled from "styled-components"; import { IProfileCommentCard } from "../../../components/molecules/cards/ProfileCommentCard"; @@ -10,10 +11,8 @@ interface IGroupParticipation { } function GroupParticipation({ data }: IGroupParticipation) { - const participantsCnt = data.participants.length; - const isSecret = data?.isSecret; - console.log(data.participants); + const userCardArr: IProfileCommentCard[] = data.participants.map((par) => { if (isSecret) { return { @@ -35,57 +34,29 @@ function GroupParticipation({ data }: IGroupParticipation) { return ( -
- 참여중인 인원 - {participantsCnt} - / - {data?.memberCnt.max ? ( - {data?.memberCnt.max} - ) : ( - <> - - - - )} -
+ + 참여중인 인원 +
); } -const Header = styled.header` - font-size: 16px; - padding: var(--gap-4) 0; - font-weight: 600; - - > span:first-child { - margin-right: var(--gap-4); - } - > span:nth-child(2) { - font-weight: 700; - color: var(--color-mint); - } - > span:nth-child(3) { - margin: 0 var(--gap-1); - } -`; - const ParticipateTime = styled.div<{ isFirst: boolean }>` - font-size: 16px; + font-size: 11px; + font-weight: medium; + line-height: 12px; margin-left: auto; - margin-right: var(--gap-2); - color: ${(props) => (props.isFirst ? "var(--color-mint)" : "var(--color-orange)")}; - > span:last-child { - margin-left: 2px; - } + color: var(--color-mint); `; const Layout = styled.div` width: 100%; + padding: 0 20px; display: flex; flex-direction: column; background-color: white; - padding: 0 20px; + padding-bottom: var(--gap-4); `; diff --git a/pageTemplates/group/detail/GroupTitle.tsx b/pageTemplates/group/detail/GroupTitle.tsx index 8facb74a1..d2e7de69d 100644 --- a/pageTemplates/group/detail/GroupTitle.tsx +++ b/pageTemplates/group/detail/GroupTitle.tsx @@ -1,7 +1,7 @@ +import { Box } from "@chakra-ui/react"; import { useRouter } from "next/router"; import styled from "styled-components"; -import { Badge } from "../../../components/atoms/badges/Badges"; import { NewAlertIcon } from "../../../components/Icons/AlertIcon"; import { GatherStatus } from "../../../types/models/gatherTypes/gatherTypes"; @@ -25,23 +25,15 @@ function GroupTitle({ isWaiting, }: IGroupTitle) { const router = useRouter(); - const color = - status === "gathering" - ? "mint" - : status === "open" || status === "pending" - ? maxCnt === 0 || maxCnt > memberCnt - ? "mint" - : "red" - : "red"; const statusText = status === "gathering" ? "소그룹" : status === "open" || status === "pending" - ? maxCnt === 0 || maxCnt > memberCnt - ? "모집중" - : "마감" - : "마감"; + ? maxCnt === 0 || maxCnt > memberCnt + ? "모집중" + : "마감" + : "마감"; const onClick = () => { router.push(`${router.asPath}/admin`); @@ -49,12 +41,9 @@ function GroupTitle({ return ( - - <div> - <span>{title}</span> - <Badge text={statusText} colorScheme={color} size="lg" /> - </div> - + + {title} + 멤버 {memberCnt} · {category} · {statusText} @@ -86,8 +75,6 @@ const IconWrapper = styled.div` `; const Layout = styled.div<{ status: GatherStatus | "gathering" }>` - padding: var(--gap-4); - padding-bottom: var(--gap-2); background-color: white; border-bottom: var(--border); display: flex; @@ -113,21 +100,6 @@ const SettingBtnNav = styled.nav` } `; -const Title = styled.div` - display: flex; - align-items: center; - margin-bottom: var(--gap-1); - color: var(--gray-800); - font-size: 18px; - - font-weight: 800; - > div:first-child { - > span { - margin-right: var(--gap-2); - } - } -`; - const SubInfo = styled.div` height: 32px; font-size: 13px; diff --git a/pageTemplates/profile/profileOverview/ProfileInfo.tsx b/pageTemplates/profile/profileOverview/ProfileInfo.tsx index 08fbde0cf..3f85fc4fd 100644 --- a/pageTemplates/profile/profileOverview/ProfileInfo.tsx +++ b/pageTemplates/profile/profileOverview/ProfileInfo.tsx @@ -123,7 +123,7 @@ function ProfileInfo({ user }: IProfileInfo) { {user?.name || session?.user.name} - {!isGuest ? status : "게스트"} + {status || "게스트"} {user && user?.uid !== session?.user?.uid && ( <> diff --git a/pageTemplates/square/SecretSquare/SecretSquareComments.tsx b/pageTemplates/square/SecretSquare/SecretSquareComments.tsx index ea6d9dbeb..0b1db0f1d 100644 --- a/pageTemplates/square/SecretSquare/SecretSquareComments.tsx +++ b/pageTemplates/square/SecretSquare/SecretSquareComments.tsx @@ -1,34 +1,42 @@ -import { Box, Flex } from "@chakra-ui/react"; +import { Flex } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import BottomCommentInput from "../../../components/atoms/BottomCommentInput"; import Slide from "../../../components/layouts/PageSlide"; import UserCommentBlock from "../../../components/molecules/UserCommentBlock"; -import UserCommentInput from "../../../components/molecules/UserCommentInput"; import { SECRET_USER_SUMMARY } from "../../../constants/serviceConstants/userConstants"; -import { useCommentMutation, useSubCommentMutation } from "../../../hooks/common/mutations"; -import { useKeypadHeight } from "../../../hooks/custom/useKeypadHeight"; +import { + SubCommentParamProps, + useCommentMutation, + useSubCommentMutation, +} from "../../../hooks/common/mutations"; import { useUserInfoQuery } from "../../../hooks/user/queries"; +import { getCommentArr } from "../../../libs/comment/commentLib"; import { UserCommentProps } from "../../../types/components/propTypes"; import { IUserSummary } from "../../../types/models/userTypes/userInfoTypes"; import { dayjsToStr } from "../../../utils/dateTimeUtils"; -import { iPhoneNotchSize } from "../../../utils/validationUtils"; - interface SecretSquareCommentsProps { author: string; comments: UserCommentProps[]; refetch: () => void; } +export interface ReplyProps extends Omit { + replyName: string; + parentId?: string; +} + function SecretSquareComments({ author, comments, refetch }: SecretSquareCommentsProps) { const router = useRouter(); const { data: userInfo } = useUserInfoQuery(); - const keypadHeight = useKeypadHeight(); const squareId = router.query.id as string; const [commentArr, setCommentArr] = useState(comments || []); + const [replyProps, setReplyProps] = useState(); + console.log(24, replyProps); useEffect(() => { setCommentArr(comments); }, [comments]); @@ -54,6 +62,11 @@ function SecretSquareComments({ author, comments, refetch }: SecretSquareComment }; const onSubmit = async (value: string) => { + if (replyProps) { + writeSubComment({ comment: value, commentId: replyProps.commentId }); + setCommentArr(getCommentArr(value, replyProps.commentId, commentArr, userInfo)); + return; + } await writeComment({ comment: value }); setCommentArr((old) => [...old, addNewComment(userInfo, value)]); }; @@ -76,10 +89,10 @@ function SecretSquareComments({ author, comments, refetch }: SecretSquareComment } } }); - + console.log(commentArr); return ( <> - + {commentArr?.map((item, idx) => { const commentProps = commentArr?.find((comment) => comment._id === item._id); @@ -111,26 +124,20 @@ function SecretSquareComments({ author, comments, refetch }: SecretSquareComment })), }} setCommentArr={setCommentArr} - writeSubComment={writeSubComment} + hasAuthority={(item.user as unknown as string) !== userInfo._id} + setReplyProps={setReplyProps} /> ); })} - - - - - + ); } diff --git a/pageTemplates/square/SquareLoungeSection.tsx b/pageTemplates/square/SquareLoungeSection.tsx index b73ba4c3f..b1b40bb93 100644 --- a/pageTemplates/square/SquareLoungeSection.tsx +++ b/pageTemplates/square/SquareLoungeSection.tsx @@ -106,7 +106,7 @@ function SquareLoungeSection() { }, }; }); - console.log(2, category); + return ( diff --git a/pageTemplates/study/StudyDateBar.tsx b/pageTemplates/study/StudyDateBar.tsx index b38009d02..46089ad70 100644 --- a/pageTemplates/study/StudyDateBar.tsx +++ b/pageTemplates/study/StudyDateBar.tsx @@ -21,7 +21,7 @@ function StudyDateBar({ date, memberCnt }: IStudyDateBar) { {dayjsToFormat(dayjs(date), "M월 D일 참여 멤버")} diff --git a/pageTemplates/study/StudyHeader.tsx b/pageTemplates/study/StudyHeader.tsx index d587fc05e..974d3bb71 100644 --- a/pageTemplates/study/StudyHeader.tsx +++ b/pageTemplates/study/StudyHeader.tsx @@ -34,7 +34,7 @@ function StudyHeader({ brand, name, address, coverImage }: IStudyHeader) { <>
{/* diff --git a/pageTemplates/vote/voteDrawer/VoteDrawerPlace.tsx b/pageTemplates/vote/voteDrawer/VoteDrawerPlace.tsx index 4407ec972..d2e3877d8 100644 --- a/pageTemplates/vote/voteDrawer/VoteDrawerPlace.tsx +++ b/pageTemplates/vote/voteDrawer/VoteDrawerPlace.tsx @@ -23,11 +23,7 @@ interface PlaceDrawerProps { date: string; } -export function VoteDrawerPlace({ - setIsRightDrawer, - setIsVoteDrawer, - date, -}: PlaceDrawerProps) { +export function VoteDrawerPlace({ setIsRightDrawer, setIsVoteDrawer, date }: PlaceDrawerProps) { const router = useRouter(); const searchParams = useSearchParams(); const newSearchParams = new URLSearchParams(searchParams); @@ -58,7 +54,7 @@ export function VoteDrawerPlace({ return; } const changeLocation = getLocationByCoordinates(+placeInfo?.y, +placeInfo?.x); - + console.log(34, changeLocation); if (!changeLocation) { toast("warning", "서비스중인 지역이 아닙니다."); return; diff --git a/pages/chat/[uid].tsx b/pages/chat/[uid].tsx index 6f99ab111..f6fda2870 100644 --- a/pages/chat/[uid].tsx +++ b/pages/chat/[uid].tsx @@ -162,7 +162,14 @@ function Uid() { pb={`${keypadHeight === 0 ? iPhoneNotchSize() : 0}px`} > - + diff --git a/pages/group/[id]/admin.tsx b/pages/group/[id]/admin.tsx index f8c45a926..f1af45b18 100644 --- a/pages/group/[id]/admin.tsx +++ b/pages/group/[id]/admin.tsx @@ -8,6 +8,7 @@ import Header from "../../../components/layouts/Header"; import Slide from "../../../components/layouts/PageSlide"; import { UserItem } from "../../../components/molecules/UserItem"; import { useAdminPointSystemMutation } from "../../../hooks/admin/mutation"; +import { useResetGroupQuery } from "../../../hooks/custom/CustomHooks"; import { useCompleteToast } from "../../../hooks/custom/CustomToast"; import { useGroupWaitingStatusMutation } from "../../../hooks/groupStudy/mutations"; import { useGroupIdQuery } from "../../../hooks/groupStudy/queries"; @@ -25,6 +26,7 @@ function Admin() { const [group, setGroup] = useState(); const [isOuterModal, setIsOuterModal] = useState(false); const transferGroup = useRecoilValue(transferGroupDataState); + const resetGroup = useResetGroupQuery(); const { data: groupData } = useGroupIdQuery(id, { enabled: !!id && !transferGroup }); @@ -36,6 +38,7 @@ function Admin() { const { mutate, isLoading } = useGroupWaitingStatusMutation(+id, { onSuccess() { completeToast("free", "가입되었습니다."); + resetGroup(); }, }); @@ -63,7 +66,7 @@ function Admin() { return ( <>
- + 가입 신청 가입 질문: {group?.questionText} diff --git a/pages/group/[id]/index.tsx b/pages/group/[id]/index.tsx index a02a8884e..8a7be3dac 100644 --- a/pages/group/[id]/index.tsx +++ b/pages/group/[id]/index.tsx @@ -1,50 +1,50 @@ import "dayjs/locale/ko"; // 로케일 플러그인 로드 +import { Badge, Box, Flex, ListItem, UnorderedList } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useParams } from "next/navigation"; import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; import { useRecoilState } from "recoil"; -import styled from "styled-components"; import { GROUP_GATHERING_IMAGE } from "../../../assets/images/randomImages"; import WritingButton from "../../../components/atoms/buttons/WritingButton"; import { MainLoading } from "../../../components/atoms/loaders/MainLoading"; import Slide from "../../../components/layouts/PageSlide"; +import BlurredLink from "../../../components/molecules/BlurredLink"; +import InfoBoxCol from "../../../components/molecules/InfoBoxCol"; +import TabNav from "../../../components/molecules/navs/TabNav"; import { useGroupAttendancePatchMutation } from "../../../hooks/groupStudy/mutations"; import { useGroupIdQuery } from "../../../hooks/groupStudy/queries"; import { checkGroupGathering } from "../../../libs/group/checkGroupGathering"; import GroupBottomNav from "../../../pageTemplates/group/detail/GroupBottomNav"; -import GroupContent from "../../../pageTemplates/group/detail/GroupContent/GroupStudyContent"; +import GroupComments from "../../../pageTemplates/group/detail/GroupComment"; +import ContentFeed from "../../../pageTemplates/group/detail/GroupContent/ContentFeed"; import GroupCover from "../../../pageTemplates/group/detail/GroupCover"; import GroupHeader from "../../../pageTemplates/group/detail/GroupHeader"; -import GroupTitle from "../../../pageTemplates/group/detail/GroupTitle"; +import GroupParticipation from "../../../pageTemplates/group/detail/GroupParticipation"; import { transferGroupDataState } from "../../../recoils/transferRecoils"; -import { IGroup } from "../../../types/models/groupTypes/group"; -import { dayjsToStr } from "../../../utils/dateTimeUtils"; +import { dayjsToFormat, dayjsToStr } from "../../../utils/dateTimeUtils"; -export type GroupSectionCategory = "정보" | "피드" | "출석부" | "채팅"; +export type GroupSectionCategory = "정 보" | "피 드"; +const TAB_LIST: GroupSectionCategory[] = ["정 보", "피 드"]; function GroupDetail() { const { data: session } = useSession(); + const isGuest = session?.user.name === "guest"; const { id } = useParams<{ id: string }>() || {}; - const [group, setGroup] = useState(); - const [category, setCategory] = useState("정보"); + const [category, setCategory] = useState("정 보"); - const [transferGroup, setTransferGroup] = useRecoilState(transferGroupDataState); + const [group, setTransferGroup] = useRecoilState(transferGroupDataState); + + const { data: groupData, refetch } = useGroupIdQuery(id, { enabled: !!id && !group }); - const { data: groupData, refetch } = useGroupIdQuery(id, { enabled: !!id && !transferGroup }); - console.log(groupData); useEffect(() => { if (groupData) { - setGroup(groupData); setTransferGroup(groupData); - } else if (transferGroup) { - setGroup(transferGroup); - setTransferGroup(transferGroup); } - }, [transferGroup, groupData]); + }, [groupData]); const { mutate: patchAttendance } = useGroupAttendancePatchMutation(+id, { onSuccess() { @@ -78,25 +78,131 @@ function GroupDetail() { {group && } {group && ( - + <> - - - + + + + {group.category.main} + + + {group.category.sub} + + + {/* */} + + {group.title} + + + + + + + ({ + text: category, + func: () => setCategory(category), + flex: 1, + }))} + selected={category} + isFullSize + /> + + {category === "정 보" ? ( + + + 소개 + + + {group.content} + + + + 규칙 + + + + + {group.rules.map((rule, idx) => ( + {rule} + ))} + + + + 오픈채팅방 + + + + {group.hashTag.split("#").map((tag, idx) => + tag ? ( + + #{tag} + + ) : null, + )} + + + ) : ( + + )} + + + + + )} + {!group && } - {group && category === "정보" && !isMember ? ( + {group && category === "정 보" && !isMember && !isGuest ? ( - ) : category === "피드" && isMember ? ( + ) : category === "피 드" && isMember ? ( ("모집중"); + const [groupStudies, setGroupStudies] = useState([]); + + const [cursor, setCursor] = useState(localStorageCursorNum); const [category, setCategory] = useState({ - main: categoryIdx !== null ? GROUP_STUDY_CATEGORY_ARR[categoryIdx] : "전체", + main: GROUP_STUDY_CATEGORY_ARR[categoryIdx] || "전체", sub: null, }); const loader = useRef(null); const firstLoad = useRef(true); - const [groupStudies, setGroupStudies] = useState([]); - const [myGroups, setMyGroups] = useState(); - const [cursor, setCursor] = useState(0); - const { data: groups, isLoading } = useGroupQuery(filterType, category.main, cursor, { - enabled: !!filterType, - }); + const { data: groups, isLoading } = useGroupQuery( + status === "모집중" ? "pending" : "end", + category.main, + category.main === "전체" ? cursor : 0, + { + enabled: !!status, + }, + ); useEffect(() => { - setCursor(0); + return () => { + const localStorageCursorNumChange = !localStorageCursorNum + ? 1 + : localStorageCursorNum === 1 + ? 2 + : localStorageCursorNum === 2 + ? 0 + : localStorageCursorNum; + localStorage.setItem(GROUP_CURSOR_NUM, localStorageCursorNumChange + ""); + }; + }, []); + + useEffect(() => { + setCursor(localStorageCursorNum); setGroupStudies([]); - }, [filterType, category.main]); + }, [status, category.main]); useEffect(() => { localStorage.setItem(GROUP_WRITING_STORE, null); @@ -67,11 +90,7 @@ function GroupPage() { main: categoryIdx !== null ? GROUP_STUDY_CATEGORY_ARR[categoryIdx] : "전체", sub: null, }); - const filterToStatus: Record = { - pending: "모집중", - end: "종료", - }; - setStatus(filterType ? filterToStatus[filterType] : "모집중"); + if (!searchParams.get("filter")) { newSearchParams.append("filter", "pending"); newSearchParams.append("category", "0"); @@ -80,7 +99,14 @@ function GroupPage() { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !firstLoad.current) { - setCursor((prevCursor) => prevCursor + 1); + setCursor((prevCursor) => { + const nextCursor = prevCursor === 0 ? 1 : prevCursor === 1 ? 2 : 0; + if (nextCursor === localStorageCursorNum) { + return prevCursor; + } + + return nextCursor; + }); } }, { threshold: 1.0 }, @@ -109,31 +135,17 @@ function GroupPage() { useEffect(() => { if (!groups) return; firstLoad.current = false; - if (category.main !== "전체") return; - setGroupStudies((old) => [...shuffleArray(groups), ...old]); - }, [groups, category.main]); - - useEffect(() => { - if (!groups || category.main === "전체") return; - setGroupStudies(groups.filter((item) => !category.sub || item.category.sub === category.sub)); - }, [category.sub, groups]); - - useEffect(() => { - if (isGuest) setMyGroups([]); - else if (groupStudies.length && !myGroups) { - setMyGroups( - groupStudies.filter((item) => - item.participants.some((who) => { - if (!who?.user?.uid) { - return; - } - return who.user.uid === session?.user.uid; - }), - ), - ); + if (category.main === "전체") { + const newArray = shuffleArray(groups); + setGroupStudies((old) => [ + ...newArray.filter((item) => !old.some((existingItem) => existingItem.id === item.id)), + ...old, + ]); + } else { + setGroupStudies(groups.filter((item) => !category.sub || item.category.sub === category.sub)); } - }, [groupStudies, session?.user]); + }, [groups, category.main, category.sub]); const mainTabOptionsArr: ITabNavOptions[] = GROUP_STUDY_CATEGORY_ARR.map((category, idx) => ({ text: category, @@ -149,48 +161,87 @@ function GroupPage() { }, })); - function StatusSelector() { - return ( - + - - - - - setCategory((old) => ({ ...old, sub: value }))} - /> - - + + + + + {category.main !== "전체" && ( + + ({ + icon: ( + + ), + text: prop, + func: () => + setCategory((old) => ({ ...old, sub: old.sub === prop ? null : prop })), + }))} + currentValue={category.sub} + isEllipse + size="md" + /> + + )} + {!groupStudies.length && isLoading ? ( ) : ( - + {groupStudies ?.slice() ?.reverse() - ?.map((group) => ( - - ))} + ?.map((group, idx) => { + const status = + group.status === "end" + ? "end" + : group.memberCnt.max === 0 + ? "pending" + : group.memberCnt.max <= group.participants.length + ? "full" + : group.memberCnt.max - 2 <= group.participants.length + ? "imminent" + : group.memberCnt.min > group.participants.length + ? "waiting" + : group.status; + return ( + + + group.isSecret ? { user: ABOUT_USER_SUMMARY } : user, + )} + imageProps={{ image: group.image, isPriority: idx < 4 }} + maxCnt={group.memberCnt.max} + id={group.id} + func={() => setTransdferGroupData(group)} + /> + + ); + })} )} @@ -202,22 +253,14 @@ function GroupPage() { ) : undefined} - ); } const Layout = styled.div` min-height: 100vh; - background-color: var(--gray-100); - padding-bottom: 60px; -`; -const NavWrapper = styled.div` - padding: 0px 20px; - background: white; + padding-bottom: 60px; `; -const SubNavWrapper = styled.div``; - export default GroupPage; diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 7b27eeba1..66f492d4e 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -91,7 +91,6 @@ function Ranking() { }, ); - console.log(34, attendRecords, usersAll); useEffect(() => { if (filterOptions.category === "스터디 랭킹" && !attendRecords) return; diff --git a/pages/study/[id]/[date]/index.tsx b/pages/study/[id]/[date]/index.tsx index 6721505c5..d740960c7 100644 --- a/pages/study/[id]/[date]/index.tsx +++ b/pages/study/[id]/[date]/index.tsx @@ -30,6 +30,7 @@ import { getDistanceFromLatLonInKm } from "../../../../utils/mathUtils"; export default function Page() { const searchParams = useSearchParams(); const { data: session } = useSession(); + const isGuest = session?.user.role === "guest"; const { id, date } = useParams<{ id: string; date: string }>() || {}; const { currentLocation } = useCurrentLocation(); @@ -101,16 +102,18 @@ export default function Page() { - + {!isGuest && ( + + )} {isInviteModal && } )} diff --git a/pages/studyPage.tsx b/pages/studyPage.tsx index eab88ac7e..c81cfb3d6 100644 --- a/pages/studyPage.tsx +++ b/pages/studyPage.tsx @@ -1,7 +1,7 @@ import { Box } from "@chakra-ui/react"; import dayjs from "dayjs"; -import { useSession } from "next-auth/react"; import { useRouter, useSearchParams } from "next/navigation"; +import { useSession } from "next-auth/react"; import { useEffect, useRef, useState } from "react"; import { useRecoilState } from "recoil"; diff --git a/theme.ts b/theme.ts index bfa01964e..c96ad353f 100644 --- a/theme.ts +++ b/theme.ts @@ -233,9 +233,10 @@ const theme = extendTheme({ }), subtle: (props) => ({ ...(props.colorScheme === "gray" && { - bg: "gray.200", + bg: "gray.100", color: "gray.600", }), + fontWeight: "regular", }), }, defaultProps: { @@ -253,8 +254,9 @@ const theme = extendTheme({ px: "8px !important", py: "2px", h: "20px", - fontWeight: 700, + fontWeight: "bold", fontSize: "11px", + borderRadius: "4px", }, }, diff --git a/types/models/groupTypes/group.ts b/types/models/groupTypes/group.ts index 30fa01fb8..fc4e885b3 100644 --- a/types/models/groupTypes/group.ts +++ b/types/models/groupTypes/group.ts @@ -6,7 +6,7 @@ import { IUser, IUserSummary } from "../userTypes/userInfoTypes"; export type GroupCategory = (typeof GROUP_STUDY_CATEGORY_ARR)[number]; -export type GroupStatus = "end" | "pending"; +export type GroupStatus = "pending" | "end" | "imminent" | "full" | "waiting"; export interface IGroup extends IGroupWriting { createdAt: string; participants: GroupParicipantProps[];