From 7c08c70ad1038710c02cbea986eededa0de9f6b8 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 3 Dec 2023 01:07:31 +0900 Subject: [PATCH 01/26] =?UTF-8?q?breakpoint=20=EC=88=98=EC=A0=95=20(#601)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/_app.tsx | 6 ++---- .../meetingDetail/Feed/Skeleton/FeedItemSkeleton.tsx | 9 +++------ stitches.config.ts | 3 ++- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index fb949d4e..ab11465c 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -138,10 +138,8 @@ const Layout = styled('div', { color: theme.colors.white, mx: '$auto', marginTop: '100px', - '@desktop': { - maxWidth: '1260px', - px: '$30', - }, + maxWidth: '1260px', + px: '$30', '@tablet': { px: '$20', }, diff --git a/src/components/page/meetingDetail/Feed/Skeleton/FeedItemSkeleton.tsx b/src/components/page/meetingDetail/Feed/Skeleton/FeedItemSkeleton.tsx index 8c23f53b..a0d112d7 100644 --- a/src/components/page/meetingDetail/Feed/Skeleton/FeedItemSkeleton.tsx +++ b/src/components/page/meetingDetail/Feed/Skeleton/FeedItemSkeleton.tsx @@ -26,12 +26,9 @@ const FeedItemSkeleton = () => { export default FeedItemSkeleton; const SFeedItemSkeleton = styled('div', { - '@desktop': { - padding: '$24 $20 $28 $20', - background: '#171818', - borderRadius: '12px', - }, - + padding: '$24 $20 $28 $20', + background: '#171818', + borderRadius: '12px', '@tablet': { padding: '$20', }, diff --git a/stitches.config.ts b/stitches.config.ts index 681cb312..65cec4ab 100644 --- a/stitches.config.ts +++ b/stitches.config.ts @@ -296,7 +296,8 @@ const stitches = createStitches({ small_mobile: '(max-width: 375px)', mobile: '(max-width: 414px)', tablet: '(max-width: 768px)', - desktop: '(min-width: 768px)', + laptop: '(max-width: 1259px)', + // default is desktop }, utils: { size: (value: number) => ({ From 10b165146397800e750d4b58f1aba1dfca6baafe Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 3 Dec 2023 22:48:49 +0900 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=20=ED=83=AD=20=EC=8B=A0=EC=84=A4=20(#584)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: sopt-makers/colors 업데이트 * feat: 모임피드 탭 신설 * feat: 내가 신청한 모임과 내가 만든 모임 순서 변경 * feat: 모임 피드 탭 신설 * feat: 주석 추가 * feat: 불필요한 ! * feat: card hover 시 10px 위로 * chore: 헤더 버전 변경으로 인한 margin-top 수정 * refactor: 코드 리뷰 반영 --- package.json | 2 +- pages/_app.tsx | 2 +- pages/index.tsx | 175 +++++++----------- pages/list/index.tsx | 140 ++++++++++++++ pages/mine/index.tsx | 46 +++-- .../page/meetingDetail/Feed/FeedItem.tsx | 22 ++- yarn.lock | 5 - 7 files changed, 249 insertions(+), 143 deletions(-) create mode 100644 pages/list/index.tsx diff --git a/package.json b/package.json index db788cd8..88ea9688 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@hookform/resolvers": "^2.9.10", "@nanostores/react": "^0.7.1", "@sentry/nextjs": "^7.51.0", - "@sopt-makers/colors": "^2.2.0", + "@sopt-makers/colors": "^3.0.0", "@sopt-makers/playground-common": "^1.4.2", "@stitches/react": "^1.2.8", "@tanstack/react-query": "^4.10.3", diff --git a/pages/_app.tsx b/pages/_app.tsx index ab11465c..ae4d8fd5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -137,7 +137,7 @@ const Layout = styled('div', { minHeight: '100vh', color: theme.colors.white, mx: '$auto', - marginTop: '100px', + marginTop: '128px', maxWidth: '1260px', px: '$30', '@tablet': { diff --git a/pages/index.tsx b/pages/index.tsx index e448e644..180cad51 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,143 +1,102 @@ +import { ampli } from '@/ampli'; +import { useInfinitePosts } from '@api/post/hooks'; +import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; +import MobileFeedListSkeleton from '@components/page/meetingDetail/Feed/Skeleton/MobileFeedListSkeleton'; +import { TabList } from '@components/tabList/TabList'; +import { Flex } from '@components/util/layout/Flex'; +import { TAKE_COUNT } from '@constants/feed'; +import { MasonryInfiniteGrid } from '@egjs/react-infinitegrid'; +import { useDisplay } from '@hooks/useDisplay'; +import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; import type { NextPage } from 'next'; import Link from 'next/link'; -import { useRouter } from 'next/router'; import { styled } from 'stitches.config'; -import useModal from '@hooks/useModal'; -import { playgroundLink } from '@sopt-makers/playground-common'; -import ConfirmModal from '@components/modal/ConfirmModal'; -import { TabList } from '@components/tabList/TabList'; -import { Flex } from '@components/util/layout/Flex'; -import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense'; -import { MeetingListOfAll } from '@components/page/meetingList/Grid/List'; -import Filter from '@components/page/meetingList/Filter'; -import Search from '@components/page/meetingList/Filter/Search'; -import GridLayout from '@components/page/meetingList/Grid/Layout'; -import CardSkeleton from '@components/page/meetingList/Card/Skeleton'; -import PlusIcon from '@assets/svg/plus.svg'; -import WriteIcon from '@assets/svg/write.svg'; -import { useQueryMyProfile } from '@api/user/hooks'; -import NoticeSlider from '@components/page/meetingList/Slider/NoticeSlider/NoticeSlider'; -import useNotices from '@api/notice/hooks'; -import { ampli } from '@/ampli'; const Home: NextPage = () => { - const router = useRouter(); - const { data: me } = useQueryMyProfile(); - const { isModalOpened, handleModalOpen, handleModalClose } = useModal(); - const { data: notices } = useNotices(); + const { isTablet } = useDisplay(); - const handleMakeMeeting = () => { - if (!me?.hasActivities) { - handleModalOpen(); - return; + // TODO: 전체 모임 피드 api 나오면 교체 예정 + const { data: postsData, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts(TAKE_COUNT, 89); + + const onIntersect: IntersectionObserverCallback = ([{ isIntersecting }]) => { + if (isIntersecting && hasNextPage) { + fetchNextPage(); } - ampli.clickMakeGroup(); - router.push('/make'); }; + const { setTarget } = useIntersectionObserver({ onIntersect }); + + const renderedPosts = postsData?.pages.map(post => ( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + + + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + + + + )); return ( <>
- + - { - ampli.clickNavbarGroup({ menu: '전체 모임' }); - }} - > - 전체 모임 + ampli.clickNavbarGroup({ menu: '피드' })}> + 모임 피드 + + + + ampli.clickNavbarGroup({ menu: '전체 모임' })}> + 전체 모임 - { - ampli.clickNavbarGroup({ menu: '내 모임' }); - }} - > + ampli.clickNavbarGroup({ menu: '내 모임' })}> 내 모임 - - - - - - - 모임 개설하기 - - - - - - - - - {new Array(6).fill(null).map((_, index) => ( - - ))} - - } - > - - + + {isTablet ? ( + {renderedPosts} + ) : ( + + {renderedPosts} + + )} +
+ + {isFetchingNextPage && isTablet && }
- (window.location.href = `${playgroundLink.memberUpload()}`)} - /> ); }; export default Home; -const SMakeMeetingButton = styled('button', { - flexType: 'verticalCenter', - padding: '$16 $24 $16 $20', - background: '$gray10', - borderRadius: '16px', - '& > span': { - ml: '$12', - fontAg: '18_bold_100', - color: '$gray950', - }, - '@tablet': { - display: 'none', - }, -}); - -const SMobileButtonContainer = styled('div', { - display: 'none', - '@tablet': { - flexType: 'verticalCenter', - gap: '16px', - }, - svg: { - cursor: 'pointer', +const SDesktopContainer = styled(MasonryInfiniteGrid, { + marginTop: '$40', + a: { + width: 'calc(calc(100% - 60px) / 3)', }, }); -const SFilterWrapper = styled('div', { - mt: '$40', - mb: '$64', - '@tablet': { - mt: '$32', - mb: '$24', - }, -}); +const SMobileContainer = styled('div', { + display: 'flex', + flexDirection: 'column', + marginTop: 0, + '& a:not(:first-child)::before': { + content: '', + display: 'none', -const SNoticeWrapper = styled('div', { - mt: '$64', - '@tablet': { - mt: '$28', + '@tablet': { + display: 'block', + width: '100vw', + height: '8px', + marginLeft: 'calc(50% - 50vw)', + background: '$gray800', + }, }, }); diff --git a/pages/list/index.tsx b/pages/list/index.tsx new file mode 100644 index 00000000..dafeea56 --- /dev/null +++ b/pages/list/index.tsx @@ -0,0 +1,140 @@ +import { ampli } from '@/ampli'; +import useNotices from '@api/notice/hooks'; +import { useQueryMyProfile } from '@api/user/hooks'; +import PlusIcon from '@assets/svg/plus.svg'; +import WriteIcon from '@assets/svg/write.svg'; +import ConfirmModal from '@components/modal/ConfirmModal'; +import CardSkeleton from '@components/page/meetingList/Card/Skeleton'; +import Filter from '@components/page/meetingList/Filter'; +import Search from '@components/page/meetingList/Filter/Search'; +import GridLayout from '@components/page/meetingList/Grid/Layout'; +import { MeetingListOfAll } from '@components/page/meetingList/Grid/List'; +import NoticeSlider from '@components/page/meetingList/Slider/NoticeSlider/NoticeSlider'; +import { TabList } from '@components/tabList/TabList'; +import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense'; +import { Flex } from '@components/util/layout/Flex'; +import useModal from '@hooks/useModal'; +import { playgroundLink } from '@sopt-makers/playground-common'; +import type { NextPage } from 'next'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { styled } from 'stitches.config'; + +const Home: NextPage = () => { + const router = useRouter(); + const { data: me } = useQueryMyProfile(); + const { isModalOpened, handleModalOpen, handleModalClose } = useModal(); + const { data: notices } = useNotices(); + + const handleMakeMeeting = () => { + if (!me?.hasActivities) { + handleModalOpen(); + return; + } + ampli.clickMakeGroup(); + router.push('/make'); + }; + + return ( + <> +
+ + + + ampli.clickNavbarGroup({ menu: '피드' })}> + 모임 피드 + + + + ampli.clickNavbarGroup({ menu: '전체 모임' })}> + 전체 모임 + + + + ampli.clickNavbarGroup({ menu: '내 모임' })}> + 내 모임 + + + + + + + + + + 모임 개설하기 + + + + + + + + + + {new Array(6).fill(null).map((_, index) => ( + + ))} + + } + > + + +
+ (window.location.href = `${playgroundLink.memberUpload()}`)} + /> + + ); +}; + +export default Home; + +const SMakeMeetingButton = styled('button', { + flexType: 'verticalCenter', + padding: '$16 $24 $16 $20', + background: '$gray10', + borderRadius: '16px', + '& > span': { + ml: '$12', + fontAg: '18_bold_100', + color: '$gray950', + }, + '@tablet': { + display: 'none', + }, +}); + +const SMobileButtonContainer = styled('div', { + display: 'none', + '@tablet': { + flexType: 'verticalCenter', + gap: '16px', + }, + svg: { + cursor: 'pointer', + }, +}); + +const SFilterWrapper = styled('div', { + mt: '$40', + mb: '$64', + '@tablet': { + mt: '$32', + mb: '$24', + }, +}); + +const SNoticeWrapper = styled('div', { + mt: '$64', + '@tablet': { + mt: '$28', + }, +}); diff --git a/pages/mine/index.tsx b/pages/mine/index.tsx index 60372673..bad4c1d2 100644 --- a/pages/mine/index.tsx +++ b/pages/mine/index.tsx @@ -2,17 +2,17 @@ import type { NextPage } from 'next'; import { TabList } from '@components/tabList/TabList'; import { Flex } from '@components/util/layout/Flex'; -import Link from 'next/link'; import { Tab } from '@headlessui/react'; -import { styled } from 'stitches.config'; -import { Fragment } from 'react'; import useSessionStorage from '@hooks/useSessionStorage'; +import Link from 'next/link'; +import { Fragment } from 'react'; +import { styled } from 'stitches.config'; -import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense'; -import { MeetingListOfApplied, MeetingListOfMine } from '@components/page/meetingList/Grid/List'; -import GridLayout from '@components/page/meetingList/Grid/Layout'; -import CardSkeleton from '@components/page/meetingList/Card/Skeleton'; import { ampli } from '@/ampli'; +import CardSkeleton from '@components/page/meetingList/Card/Skeleton'; +import GridLayout from '@components/page/meetingList/Grid/Layout'; +import { MeetingListOfApplied, MeetingListOfMine } from '@components/page/meetingList/Grid/List'; +import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense'; const enum MeetingType { MADE, @@ -20,15 +20,23 @@ const enum MeetingType { } const MinePage: NextPage = () => { - const [selectedMeetingType, setSelectedMeetingType] = useSessionStorage('meetingType', MeetingType.MADE); + const [selectedMeetingType, setSelectedMeetingType] = useSessionStorage( + 'meetingType', + MeetingType.APPLIED + ); return (
+ ampli.clickNavbarGroup({ menu: '피드' })}> + 모임 피드 + + + ampli.clickNavbarGroup({ menu: '전체 모임' })}> - 전체 모임 + 전체 모임 @@ -42,18 +50,18 @@ const MinePage: NextPage = () => { ampli.clickMakebymeGroup()} + isSelected={Number(selectedMeetingType) === MeetingType.APPLIED} + onClick={() => ampli.clickRegisteredGroup()} > - 내가 만든 모임 + 내가 신청한 모임 ampli.clickRegisteredGroup()} + isSelected={Number(selectedMeetingType) === MeetingType.MADE} + onClick={() => ampli.clickMakebymeGroup()} > - 내가 신청한 모임 + 내가 만든 모임 @@ -63,13 +71,13 @@ const MinePage: NextPage = () => { - {new Array(6).fill(null).map((_, index) => ( + {new Array(4).fill(null).map((_, index) => ( ))} } > - + @@ -77,13 +85,13 @@ const MinePage: NextPage = () => { - {new Array(4).fill(null).map((_, index) => ( + {new Array(6).fill(null).map((_, index) => ( ))} } > - + diff --git a/src/components/page/meetingDetail/Feed/FeedItem.tsx b/src/components/page/meetingDetail/Feed/FeedItem.tsx index ddc7d15c..88004693 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem.tsx @@ -2,12 +2,14 @@ import AvatarGroup from '@components/avatar/AvatarGroup'; import { Flex } from '@components/util/layout/Flex'; import { styled } from 'stitches.config'; // import MoreIcon from '@assets/svg/more.svg'; -import LikeDefaultIcon from '@assets/svg/like_default.svg'; +import { ampli } from '@/ampli'; +import { useQueryGetMeeting } from '@api/meeting/hooks'; +import { useMutationUpdateLike } from '@api/post/hooks'; +import { UserResponse } from '@api/user'; import LikeActiveIcon from '@assets/svg/like_active.svg'; +import LikeDefaultIcon from '@assets/svg/like_default.svg'; import ProfileDefaultIcon from '@assets/svg/profile_default.svg?rect'; import Avatar from '@components/avatar/Avatar'; -import truncateText from '@utils/truncateText'; -import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; import { AVATAR_MAX_LENGTH, CARD_CONTENT_MAX_LENGTH, @@ -15,14 +17,12 @@ import { LIKE_MAX_COUNT, TAKE_COUNT, } from '@constants/feed'; -import { UserResponse } from '@api/user'; +import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; +import { useDisplay } from '@hooks/useDisplay'; +import { playgroundLink } from '@sopt-makers/playground-common'; import { fromNow } from '@utils/dayjs'; -import { useMutationUpdateLike } from '@api/post/hooks'; +import truncateText from '@utils/truncateText'; import { useRouter } from 'next/router'; -import { ampli } from '@/ampli'; -import { useQueryGetMeeting } from '@api/meeting/hooks'; -import { playgroundLink } from '@sopt-makers/playground-common'; -import { useDisplay } from '@hooks/useDisplay'; interface FeedItemProps { id: number; @@ -139,6 +139,10 @@ const SFeedItem = styled('div', { borderRadius: '12px', color: '$gray10', width: '100%', + transition: 'transform 0.3s ease', + '&:hover': { + transform: 'translateY(-10px)', + }, '@tablet': { padding: '$24 0 $28 0', background: 'transparent', diff --git a/yarn.lock b/yarn.lock index 6f6fdde2..e7d37547 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3569,11 +3569,6 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sopt-makers/colors@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@sopt-makers/colors/-/colors-2.2.0.tgz#32be1c92c806d72f2e6893c094d446217ba3d7c8" - integrity sha512-L91wbWPxuLc5qTR+UJ1N69WzYqv35Z+jR5Yo3DhZHhzKFUwUrp9xOMVQd1ezS0RJKBXS60MsouMf3s2jA4ukug== - "@sopt-makers/colors@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sopt-makers/colors/-/colors-3.0.0.tgz#a3db487bf645255cd25edcba49ba59e2cd54db05" From cd0dd522747b08dd85d100d569db48e0af2e5138 Mon Sep 17 00:00:00 2001 From: NaReum Date: Mon, 4 Dec 2023 22:13:56 +0900 Subject: [PATCH 03/26] =?UTF-8?q?ScrollRestoration=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=20(#574)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/_app.tsx | 29 +++++++----- src/api/post/hooks.ts | 12 ++--- .../page/meetingDetail/Feed/FeedPanel.tsx | 27 ++++++----- src/components/page/meetingList/Grid/List.tsx | 13 +++-- src/hooks/useScrollRestoration.ts | 47 +++++++++++++++++++ 5 files changed, 91 insertions(+), 37 deletions(-) create mode 100644 src/hooks/useScrollRestoration.ts diff --git a/pages/_app.tsx b/pages/_app.tsx index ae4d8fd5..782fae5c 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,21 +1,22 @@ -import type { AppProps } from 'next/app'; -import Script from 'next/script'; -import { useRouter } from 'next/router'; -import React, { useEffect } from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { styled, theme } from 'stitches.config'; -import '../styles/globals.css'; +import { crewToken, playgroundToken } from '@/stores/tokenStore'; +import { fetchMyProfile } from '@api/user'; import Header from '@components/header/Header'; -import { GTM_ID, pageview } from '@utils/gtm'; -import { setAccessTokens } from '@components/util/auth'; import Loader from '@components/loader/Loader'; -import ChannelService from '@utils/ChannelService'; -import { fetchMyProfile } from '@api/user'; -import { OverlayProvider } from '@hooks/useOverlay/OverlayProvider'; import SEO from '@components/seo/SEO'; -import { crewToken, playgroundToken } from '@/stores/tokenStore'; +import { setAccessTokens } from '@components/util/auth'; +import { OverlayProvider } from '@hooks/useOverlay/OverlayProvider'; +import useScrollRestoration from '@hooks/useScrollRestoration'; import { useStore } from '@nanostores/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import ChannelService from '@utils/ChannelService'; +import { GTM_ID, pageview } from '@utils/gtm'; +import type { AppProps } from 'next/app'; +import { useRouter } from 'next/router'; +import Script from 'next/script'; +import React, { useEffect } from 'react'; +import { styled, theme } from 'stitches.config'; import { ampli } from '../src/ampli'; +import '../styles/globals.css'; setAccessTokens(); @@ -35,6 +36,8 @@ function MyApp({ Component, pageProps }: AppProps) { const _playgroundToken = useStore(playgroundToken); const isServiceReady = _crewToken && _playgroundToken; + useScrollRestoration(); + useEffect(() => { router.events.on('routeChangeComplete', pageview); return () => { diff --git a/src/api/post/hooks.ts b/src/api/post/hooks.ts index 875ac08b..3b127076 100644 --- a/src/api/post/hooks.ts +++ b/src/api/post/hooks.ts @@ -1,12 +1,10 @@ -import { InfiniteData, useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { getPost, deleteComment, getPosts } from '.'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { postLike } from '.'; -import { produce } from 'immer'; import { paths } from '@/__generated__/schema'; +import { InfiniteData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { produce } from 'immer'; +import { deleteComment, getPost, getPosts, postLike } from '.'; export const useInfinitePosts = (take: number, meetingId: number) => { - const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useInfiniteQuery({ + const { data, hasNextPage, fetchNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery({ queryKey: ['getPosts', take, meetingId], queryFn: ({ pageParam = 1 }) => getPosts(pageParam, take, meetingId), getNextPageParam: (lastPage, allPages) => { @@ -24,7 +22,7 @@ export const useInfinitePosts = (take: number, meetingId: number) => { }), }); - return { data, hasNextPage, fetchNextPage, isFetchingNextPage }; + return { data, hasNextPage, fetchNextPage, isFetchingNextPage, isLoading }; }; export const useMutationUpdateLike = (take: number, meetingId: number, postId: number) => { diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index f505c687..7d256db9 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -1,19 +1,19 @@ -import React from 'react'; -import { styled } from 'stitches.config'; -import EmptyView from './EmptyView'; -import { useRouter } from 'next/router'; +import { ampli } from '@/ampli'; import { useInfinitePosts } from '@api/post/hooks'; -import FeedItem from './FeedItem'; -import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; +import { useQueryMyProfile } from '@api/user/hooks'; +import FeedCreateModal from '@components/feed/Modal/FeedCreateModal'; import { POST_MAX_COUNT, TAKE_COUNT } from '@constants/feed'; -import { useDisplay } from '@hooks/useDisplay'; -import MobileFeedListSkeleton from './Skeleton/MobileFeedListSkeleton'; -import Link from 'next/link'; import { MasonryInfiniteGrid } from '@egjs/react-infinitegrid'; -import FeedCreateModal from '@components/feed/Modal/FeedCreateModal'; +import { useDisplay } from '@hooks/useDisplay'; +import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; import { useOverlay } from '@hooks/useOverlay/Index'; -import { ampli } from '@/ampli'; -import { useQueryMyProfile } from '@api/user/hooks'; +import { useScrollRestorationAfterLoading } from '@hooks/useScrollRestoration'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { styled } from 'stitches.config'; +import EmptyView from './EmptyView'; +import FeedItem from './FeedItem'; +import MobileFeedListSkeleton from './Skeleton/MobileFeedListSkeleton'; interface FeedPanelProps { isMember: boolean; @@ -31,7 +31,10 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { fetchNextPage, hasNextPage, isFetchingNextPage, + isLoading, } = useInfinitePosts(TAKE_COUNT, Number(meetingId)); + useScrollRestorationAfterLoading(isLoading); + const isEmpty = !postsData?.pages[0]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/src/components/page/meetingList/Grid/List.tsx b/src/components/page/meetingList/Grid/List.tsx index 022349d2..8fc73f42 100644 --- a/src/components/page/meetingList/Grid/List.tsx +++ b/src/components/page/meetingList/Grid/List.tsx @@ -1,6 +1,7 @@ -import { usePageParams } from '@hooks/queryString/custom'; import { useQueryMeetingListOfAll } from '@api/meeting/hooks'; import { useQueryMeetingListOfApplied, useQueryMeetingListOfMine } from '@api/user/hooks'; +import { usePageParams } from '@hooks/queryString/custom'; +import { useScrollRestorationAfterLoading } from '@hooks/useScrollRestoration'; import { styled } from 'stitches.config'; import Card from '../Card'; import ManagementButton from '../Card/ManagementButton'; @@ -11,7 +12,8 @@ import GridLayout from './Layout'; export function MeetingListOfAll() { const { value: page, setValue: setPage } = usePageParams(); - const { data: meetingListData } = useQueryMeetingListOfAll(); + const { data: meetingListData, isLoading } = useQueryMeetingListOfAll(); + useScrollRestorationAfterLoading(isLoading); return (
@@ -43,8 +45,8 @@ const PaginationWrapper = styled('div', { }); export function MeetingListOfMine() { - const { data: mineData } = useQueryMeetingListOfMine(); - + const { data: mineData, isLoading } = useQueryMeetingListOfMine(); + useScrollRestorationAfterLoading(isLoading); return (
{mineData?.meetings.length}개의 모임 @@ -67,7 +69,8 @@ export function MeetingListOfMine() { } export function MeetingListOfApplied() { - const { data: applyData } = useQueryMeetingListOfApplied(); + const { data: applyData, isLoading } = useQueryMeetingListOfApplied(); + useScrollRestorationAfterLoading(isLoading); return (
diff --git a/src/hooks/useScrollRestoration.ts b/src/hooks/useScrollRestoration.ts new file mode 100644 index 00000000..a9cc3493 --- /dev/null +++ b/src/hooks/useScrollRestoration.ts @@ -0,0 +1,47 @@ +import { NextRouter, useRouter } from 'next/router'; +import { useEffect } from 'react'; + +export default function useScrollRestoration() { + const router = useRouter(); + + useEffect(() => { + if (!('scrollRestoration' in window.history)) return; + + window.history.scrollRestoration = 'manual'; + + const onRouteChangeStart = () => { + const scrollPos = { + x: window.scrollX, + y: Math.min(window.scrollY, document.documentElement.scrollHeight - window.innerHeight), + }; + sessionStorage.setItem(router.asPath, JSON.stringify(scrollPos)); + }; + + const onRouteChangeComplete = () => { + restoreScrollPosition(router); + }; + + router.events.on('routeChangeStart', onRouteChangeStart); + router.events.on('routeChangeComplete', onRouteChangeComplete); + + return () => { + router.events.off('routeChangeStart', onRouteChangeStart); + router.events.off('routeChangeComplete', onRouteChangeComplete); + }; + }, [router]); +} + +export const restoreScrollPosition = (router: NextRouter) => { + const scrollPos = JSON.parse(sessionStorage.getItem(router.asPath) || '{"x": 0, "y": 0}'); + window.scroll(scrollPos.x, scrollPos.y); +}; + +export const useScrollRestorationAfterLoading = (isLoading: boolean) => { + const router = useRouter(); + + useEffect(() => { + if (!isLoading) { + restoreScrollPosition(router); + } + }, [router, isLoading]); +}; From 0588a3e97ecdcc1088f2fa7419ed5f0955290fc1 Mon Sep 17 00:00:00 2001 From: Hyeonsu Kim <86764406+borimong@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:54:08 +0900 Subject: [PATCH 04/26] =?UTF-8?q?Feed=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95:=20=EB=AA=A8=EC=9E=84=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ui 추가 * feat: groupInfo 스타일 완성 * feat: group Info action 과 Link action 이랑 분리 * feat: FeedItem에 HeaderSection 제거 * feat: GroupInfo => MeetingInfo * feat: MeetingInfo 적용 및 mockData 입력해놓음 * feat: meetingInfo mockData 적용 --------- Co-authored-by: na-reum --- pages/index.tsx | 17 +++-- src/components/button/Arrow.tsx | 2 +- .../Feed/FeedItem/MeetingInfo.tsx | 65 +++++++++++++++++++ .../Feed/{FeedItem.tsx => FeedItem/index.tsx} | 13 +++- .../page/meetingDetail/Feed/FeedPanel.tsx | 2 +- 5 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx rename src/components/page/meetingDetail/Feed/{FeedItem.tsx => FeedItem/index.tsx} (97%) diff --git a/pages/index.tsx b/pages/index.tsx index 180cad51..2591aa6e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,7 @@ import { ampli } from '@/ampli'; import { useInfinitePosts } from '@api/post/hooks'; import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; +import MeetingInfo from '@components/page/meetingDetail/Feed/FeedItem/MeetingInfo'; import MobileFeedListSkeleton from '@components/page/meetingDetail/Feed/Skeleton/MobileFeedListSkeleton'; import { TabList } from '@components/tabList/TabList'; import { Flex } from '@components/util/layout/Flex'; @@ -24,14 +25,22 @@ const Home: NextPage = () => { } }; const { setTarget } = useIntersectionObserver({ onIntersect }); - const renderedPosts = postsData?.pages.map(post => ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - + + } + /> )); diff --git a/src/components/button/Arrow.tsx b/src/components/button/Arrow.tsx index 16463e50..9d4e0a12 100644 --- a/src/components/button/Arrow.tsx +++ b/src/components/button/Arrow.tsx @@ -57,7 +57,7 @@ export const Arrow = ({ return ( { + e.preventDefault(); + e.stopPropagation(); + // TODO: id값을 어떻게 넘겨줄지 고민해보기 + router.push(`/detail?id=${meetingInfo.id}`); + }} + > + + {meetingInfo.category} + {meetingInfo.title} + + + + ); +} + +export default MeetingInfo; + +const Container = styled('div', { + display: 'flex', + background: '$gray800', + borderRadius: '8px', + height: '46px', + alignItems: 'center', + justifyContent: 'space-between', + fontStyle: 'T5', + padding: '$0 $12', + mb: '$20', +}); + +const MeetingInfoWrapper = styled('div', { + width: '90%', + display: 'flex', +}); + +const MeetingType = styled('p', { + color: '$secondary', + mr: '$6', + whiteSpace: 'nowrap', +}); + +const MeetingName = styled('p', { + width: '80%', + color: '$gray30', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + wordBreak: 'break-all', +}); diff --git a/src/components/page/meetingDetail/Feed/FeedItem.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx similarity index 97% rename from src/components/page/meetingDetail/Feed/FeedItem.tsx rename to src/components/page/meetingDetail/Feed/FeedItem/index.tsx index 88004693..0a494d19 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -24,7 +24,7 @@ import { fromNow } from '@utils/dayjs'; import truncateText from '@utils/truncateText'; import { useRouter } from 'next/router'; -interface FeedItemProps { +interface PostProps { id: number; user: UserResponse; title: string; @@ -37,7 +37,12 @@ interface FeedItemProps { isLiked: boolean; } -const FeedItem = (post: FeedItemProps) => { +interface FeedItemProps { + post: PostProps; + HeaderSection?: React.ReactNode; +} + +const FeedItem = ({ post, HeaderSection }: FeedItemProps) => { const { id, user, title, contents, images, updatedDate, commenterThumbnails, commentCount, likeCount, isLiked } = post; const formattedLikeCount = likeCount > LIKE_MAX_COUNT ? `${LIKE_MAX_COUNT}+` : likeCount; @@ -68,6 +73,7 @@ const FeedItem = (post: FeedItemProps) => { }) } > + {HeaderSection} { export default FeedItem; const SFeedItem = styled('div', { - padding: '$24 $20 $28 $20', + padding: '$20 $20 $28 $20', background: '#171818', borderRadius: '12px', color: '$gray10', @@ -155,6 +161,7 @@ const STop = styled('div', { display: 'flex', justifyContent: 'space-between', mb: '$12', + mt: '$4', }); const SProfileButton = styled('button', { diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index 7d256db9..2283ec64 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -63,7 +63,7 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} {/* @ts-ignore */} - + )); From 9ad4fe56752d4ec3a550529f8cabafd43f1f0b92 Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Thu, 7 Dec 2023 00:04:28 +0900 Subject: [PATCH 05/26] =?UTF-8?q?feat:=2012/08=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EA=B1=B4=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=95=B0=ED=94=8C?= =?UTF-8?q?=EB=A6=AC=ED=8A=9C=EB=93=9C=20=EC=8B=A0=EA=B7=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EB=8C=80=EC=9D=91=20(#603)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ampli pull 이후 location property 추가 * feat: 새로운 이벤트 추가 --- pages/list/index.tsx | 2 +- src/ampli/index.ts | 93 ++++++++++++++++++- src/components/feed/Modal/FeedCreateModal.tsx | 15 ++- .../meetingDetail/Feed/FeedItem/index.tsx | 5 +- .../page/meetingDetail/Feed/FeedPanel.tsx | 2 +- 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/pages/list/index.tsx b/pages/list/index.tsx index dafeea56..b527768d 100644 --- a/pages/list/index.tsx +++ b/pages/list/index.tsx @@ -31,7 +31,7 @@ const Home: NextPage = () => { handleModalOpen(); return; } - ampli.clickMakeGroup(); + ampli.clickMakeGroup({ location: router.pathname }); router.push('/make'); }; diff --git a/src/ampli/index.ts b/src/ampli/index.ts index 265425d1..6370cbed 100644 --- a/src/ampli/index.ts +++ b/src/ampli/index.ts @@ -206,12 +206,31 @@ export interface ClickFeedCardProperties { * | Type | integer | */ group_id?: number; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; /** * 어떤 플랫폼으로 접속했는지를 의미합니다. */ platform_type?: string; } +export interface ClickFeedCardGroupLabelProperties { + /** + * 모임의 id값을 의미합니다. + * + * | Rule | Value | + * |---|---| + * | Type | integer | + */ + group_id?: number; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; +} + export interface ClickFeeddatailImageProperties { /** * 모임의 소속 구성원 여부를 의미합니다. @@ -238,6 +257,10 @@ export interface ClickFeedlistLikeProperties { * 모임의 소속 구성원 여부를 의미합니다. */ crew_status?: boolean; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; } export interface ClickFeedPostingProperties { @@ -249,6 +272,10 @@ export interface ClickFeedPostingProperties { * | Type | integer | */ group_id?: number; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; /** * 플레이그라운드 DB 기반 유저의 고유한 ID를 의미합니다. * @@ -264,6 +291,10 @@ export interface ClickFeedProfileProperties { * 모임의 소속 구성원 여부를 의미합니다. */ crew_status?: boolean; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; } export interface ClickFilterCategoryProperties { @@ -356,6 +387,10 @@ export interface ClickMakebymeGroupProperties { } export interface ClickMakeGroupProperties { + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; /** * | Rule | Value | * |---|---| @@ -537,6 +572,10 @@ export interface CompletedFeedPostingProperties { * 모임 피드를 업로드 완료한 시간을 의미합니다. */ feed_upload?: any; + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; /** * 어떤 플랫폼으로 접속했는지를 의미합니다. */ @@ -552,6 +591,10 @@ export interface CompletedFeedPostingProperties { } export interface CompletedFeedPostingCanceledProperties { + /** + * 모임 서비스 내 액션들의 /path location을 의미합니다. + */ + location?: string; /** * 어떤 플랫폼으로 접속했는지를 의미합니다. */ @@ -648,6 +691,10 @@ export class ClickCommentLike implements BaseEvent { } } +export class ClickFeedAction implements BaseEvent { + event_type = 'Click-feedAction'; +} + export class ClickFeedCard implements BaseEvent { event_type = 'Click-feedCard'; @@ -658,6 +705,16 @@ export class ClickFeedCard implements BaseEvent { } } +export class ClickFeedCardGroupLabel implements BaseEvent { + event_type = 'Click-feedCardGroupLabel'; + + constructor( + public event_properties?: ClickFeedCardGroupLabelProperties, + ) { + this.event_properties = event_properties; + } +} + export class ClickFeeddatailImage implements BaseEvent { event_type = 'Click-feeddatailImage'; @@ -1170,6 +1227,21 @@ export class Ampli { return this.track(new ClickCommentLike(properties), options); } + /** + * Click-feedAction + * + * [View in Tracking Plan](https://data.amplitude.com/sopt-makers/sopt-makers-crew/events/main/latest/Click-feedAction) + * + * 피드 뷰에서 플로팅 CTA를 클릭한 경우 + * + * @param options Amplitude event options. + */ + clickFeedAction( + options?: EventOptions, + ) { + return this.track(new ClickFeedAction(), options); + } + /** * Click-feedCard * @@ -1187,6 +1259,23 @@ export class Ampli { return this.track(new ClickFeedCard(properties), options); } + /** + * Click-feedCardGroupLabel + * + * [View in Tracking Plan](https://data.amplitude.com/sopt-makers/sopt-makers-crew/events/main/latest/Click-feedCardGroupLabel) + * + * Event has no description in tracking plan. + * + * @param properties The event's properties (e.g. group_id) + * @param options Amplitude event options. + */ + clickFeedCardGroupLabel( + properties?: ClickFeedCardGroupLabelProperties, + options?: EventOptions, + ) { + return this.track(new ClickFeedCardGroupLabel(properties), options); + } + /** * Click-feeddatailImage * @@ -1415,7 +1504,7 @@ export class Ampli { * * \[+모임 개설하기\] 버튼 클릭 * - * @param properties The event's properties (e.g. url) + * @param properties The event's properties (e.g. location) * @param options Amplitude event options. */ clickMakeGroup( @@ -1702,7 +1791,7 @@ export class Ampli { * * 피드 작성 중 이탈한 경우 * - * @param properties The event's properties (e.g. platform_type) + * @param properties The event's properties (e.g. location) * @param options Amplitude event options. */ completedFeedPostingCanceled( diff --git a/src/components/feed/Modal/FeedCreateModal.tsx b/src/components/feed/Modal/FeedCreateModal.tsx index 3c91c881..81cfae3e 100644 --- a/src/components/feed/Modal/FeedCreateModal.tsx +++ b/src/components/feed/Modal/FeedCreateModal.tsx @@ -15,6 +15,7 @@ import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; import { ampli } from '@/ampli'; import { useQueryMyProfile } from '@api/user/hooks'; import { formatDate } from '@utils/dayjs'; +import { useRouter } from 'next/router'; const DevTool = dynamic(() => import('@hookform/devtools').then(module => module.DevTool), { ssr: false, @@ -26,6 +27,7 @@ interface CreateModalProps extends ModalContainerProps { function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateModalProps) { const queryClient = useQueryClient(); + const router = useRouter(); const { data: detailData } = useQueryGetMeeting({ params: { id: meetingId } }); const { data: me } = useQueryMyProfile(); const exitModal = useModal(); @@ -63,7 +65,12 @@ function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateM const onSubmit = async () => { const createFeedParameter = { ...formMethods.getValues(), meetingId: Number(meetingId) }; await mutateCreateFeed(createFeedParameter); - ampli.completedFeedPosting({ user_id: Number(me?.orgId), platform_type: platform, feed_upload: formatDate() }); + ampli.completedFeedPosting({ + user_id: Number(me?.orgId), + platform_type: platform, + feed_upload: formatDate(), + location: router.pathname, + }); }; useEffect(() => { @@ -72,7 +79,11 @@ function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateM useEffect(() => { return () => { - ampli.completedFeedPostingCanceled({ user_id: Number(me?.orgId), platform_type: platform }); + ampli.completedFeedPostingCanceled({ + user_id: Number(me?.orgId), + platform_type: platform, + location: router.pathname, + }); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx index 0a494d19..920e0d39 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -55,7 +55,7 @@ const FeedItem = ({ post, HeaderSection }: FeedItemProps) => { const handleLikeClick = (e: React.MouseEvent) => { e.preventDefault(); mutate(); - ampli.clickFeedlistLike({ crew_status: meeting?.approved }); + ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); }; return ( { group_id: Number(meetingId), crew_status: meeting?.approved, platform_type: isMobile ? 'MO' : 'PC', + location: router.pathname, }) } > @@ -79,7 +80,7 @@ const FeedItem = ({ post, HeaderSection }: FeedItemProps) => { { e.preventDefault(); - ampli.clickFeedProfile({ crew_status: meeting?.approved }); + ampli.clickFeedProfile({ crew_status: meeting?.approved, location: router.pathname }); window.location.href = `${playgroundLink.memberDetail(user.orgId)}`; }} > diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index 2283ec64..840b7867 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -49,7 +49,7 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { const handleModalOpen = () => { if (me?.orgId) { - ampli.clickFeedPosting({ user_id: Number(me?.orgId), group_id: Number(meetingId) }); + ampli.clickFeedPosting({ user_id: Number(me?.orgId), group_id: Number(meetingId), location: router.pathname }); } feedCreateOverlay.open(({ isOpen, close }) => { return ; From 0ec4f1dc6c57ac10a67018fccc7a3fa1376482f2 Mon Sep 17 00:00:00 2001 From: Hyeonsu Kim <86764406+borimong@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:25:12 +0900 Subject: [PATCH 06/26] =?UTF-8?q?=EB=AA=A8=EC=9E=84=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=20(=ED=99=88=ED=99=94=EB=A9=B4)=20=ED=94=8C=EB=A1=9C=ED=8C=85?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20UI=20=EA=B5=AC=ED=98=84=20(#608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ui 추가 * feat: groupInfo 스타일 완성 * feat: group Info action 과 Link action 이랑 분리 * feat: FeedItem에 HeaderSection 제거 * feat: GroupInfo => MeetingInfo * feat: MeetingInfo 적용 및 mockData 입력해놓음 * feat: meetingInfo mockData 적용 * feat: floatingButton ui 제작 * feat: background 추가 * feat: 인터렉션 방향 조정 * feat: 버튼 모바일 대응 --------- Co-authored-by: na-reum --- pages/index.tsx | 2 + .../assets/svg/floating_button_feed_icon.svg | 4 + .../assets/svg/floating_button_group_icon.svg | 6 ++ src/components/modal/FloatingButtonModal.tsx | 91 ++++++++++++++++++ src/components/modal/ModalBackground.tsx | 2 +- .../page/postList/FloatingButton/index.tsx | 94 +++++++++++++++++++ 6 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 public/assets/svg/floating_button_feed_icon.svg create mode 100644 public/assets/svg/floating_button_group_icon.svg create mode 100644 src/components/modal/FloatingButtonModal.tsx create mode 100644 src/components/page/postList/FloatingButton/index.tsx diff --git a/pages/index.tsx b/pages/index.tsx index 2591aa6e..9d846564 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -3,6 +3,7 @@ import { useInfinitePosts } from '@api/post/hooks'; import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; import MeetingInfo from '@components/page/meetingDetail/Feed/FeedItem/MeetingInfo'; import MobileFeedListSkeleton from '@components/page/meetingDetail/Feed/Skeleton/MobileFeedListSkeleton'; +import FloatingButton from '@components/page/postList/FloatingButton'; import { TabList } from '@components/tabList/TabList'; import { Flex } from '@components/util/layout/Flex'; import { TAKE_COUNT } from '@constants/feed'; @@ -78,6 +79,7 @@ const Home: NextPage = () => {
{isFetchingNextPage && isTablet && } +
); diff --git a/public/assets/svg/floating_button_feed_icon.svg b/public/assets/svg/floating_button_feed_icon.svg new file mode 100644 index 00000000..723e3ebd --- /dev/null +++ b/public/assets/svg/floating_button_feed_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/svg/floating_button_group_icon.svg b/public/assets/svg/floating_button_group_icon.svg new file mode 100644 index 00000000..2fd34cac --- /dev/null +++ b/public/assets/svg/floating_button_group_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/modal/FloatingButtonModal.tsx b/src/components/modal/FloatingButtonModal.tsx new file mode 100644 index 00000000..2999e3bc --- /dev/null +++ b/src/components/modal/FloatingButtonModal.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import GroupIcon from '../../../public/assets/svg/floating_button_group_icon.svg'; +import FeedIcon from '../../../public/assets/svg/floating_button_feed_icon.svg'; +import { styled } from 'stitches.config'; +import { keyframes } from 'stitches.config'; + +const FloatingButtonModal = (props: { isActive: boolean }) => { + const { isActive } = props; + return ( + + + + + ); +}; + +export default FloatingButtonModal; + +const fadeIn = keyframes({ + from: { opacity: '0', transform: 'translateY(7px)' }, + to: { opacity: '1', transform: 'translateY(0px)' }, +}); + +const fadeOut = keyframes({ + from: { opacity: '1', transform: 'translateY(0px)' }, + to: { opacity: '0', transform: 'translateY(7px)' }, +}); + +const Container = styled('div', { + width: '160px', + height: '112px', + zIndex: '$3', + position: 'absolute', + bottom: '76px', + right: '5%', + backgroundColor: '$gray10', + borderRadius: '20px', + color: '$gray600', + flexType: 'center', + flexWrap: 'wrap', + transition: 'all 0.3s ease', + padding: '$8 $6 $8 $6', + variants: { + isActive: { + true: { + animation: `${fadeIn} 200ms ease-out`, + }, + false: { + animation: `${fadeOut} 200ms ease-out`, + opacity: '0', + }, + }, + }, + '@tablet': { + width: '140px', + height: '90px', + borderRadius: '18px', + bottom: '72px', + padding: '$6 $0 $6 $4', + }, +}); + +const Button = styled('button', { + display: 'flex', + alignItems: 'center', + width: '100%', + height: '46px', + paddingLeft: '12px', + fontSize: '16px', + fontWeight: '600', + lineHeight: '22px', + '&:hover': { + borderRadius: '16px', + backgroundColor: '$gray30', + }, + '@tablet': { + '&:hover': { + backgroundColor: '$gray10', + }, + height: '38px', + fontSize: '14px', + fontWeight: '600', + lineHeight: '18px', + }, +}); diff --git a/src/components/modal/ModalBackground.tsx b/src/components/modal/ModalBackground.tsx index 8751946c..09b9a827 100644 --- a/src/components/modal/ModalBackground.tsx +++ b/src/components/modal/ModalBackground.tsx @@ -17,5 +17,5 @@ const SModalBackground = styled('div', { zIndex: '$2', width: '100%', height: '100%', - backgroundColor: '$black80_trans', + backgroundColor: '$grayAlpha800', }); diff --git a/src/components/page/postList/FloatingButton/index.tsx b/src/components/page/postList/FloatingButton/index.tsx new file mode 100644 index 00000000..d2acd2ee --- /dev/null +++ b/src/components/page/postList/FloatingButton/index.tsx @@ -0,0 +1,94 @@ +import Plus from '@assets/svg/plus.svg?rect'; +import ModalBackground from '@components/modal/ModalBackground'; +import { useState } from 'react'; +import { styled } from 'stitches.config'; +import FloatingButtonModal from '@components/modal/FloatingButtonModal'; + +function FloatingButton() { + const [isActive, setIsActive] = useState(false); + return ( + <> + + + setIsActive(isActive => !isActive)}> + + + + + + ); +} + +export default FloatingButton; + +const Container = styled('div', { + position: 'fixed', + bottom: '5%', + right: '5%', + width: '56px', + height: '56px', + borderRadius: '20px', + flexType: 'center', + background: '$white', + zIndex: '$3', + transition: 'all 0.3s ease', + variants: { + isActive: { + true: { + background: '$gray500', + }, + }, + }, + + '@tablet': { + width: '48px', + height: '48px', + borderRadius: '18px', + }, +}); + +const OptionOpenButton = styled('button', { + width: '100%', + height: '100%', + flexType: 'center', + background: 'none', + border: 'none', + outline: 'none', + cursor: 'pointer', + transition: 'all 0.3s ease', + variants: { + isActive: { + true: { + transform: 'rotate(-45deg)', + }, + }, + }, +}); + +const Icon = styled(Plus, { + width: '30px', + height: '30px', + '& path': { + strokeWidth: '1.3px', + }, + transition: 'all 0.3s ease', + variants: { + isActive: { + true: { + '& path': { + stroke: '$white', + }, + }, + }, + }, + '@tablet': { + width: '24px', + height: '24px', + }, +}); From eabe81c8effaeec12f0aaa8e5ef3c27cb5c2c6c1 Mon Sep 17 00:00:00 2001 From: NaReum Date: Thu, 7 Dec 2023 23:25:36 +0900 Subject: [PATCH 07/26] =?UTF-8?q?=ED=94=8C=EB=A1=9C=ED=8C=85=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=97=90=20=EB=93=A4=EC=96=B4=EA=B0=88=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1=20=EB=B7=B0=20=EC=A0=9C=EC=9E=91?= =?UTF-8?q?=20(#607)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: edit과 create의 schema를 분리하여 create때 meetingId schema를 받을 수 있게 함 * feat: 불필요한 console.log 삭제 * feat: feedFormPresentation props로 분기 * feat: SelectMeeting 컴포넌트 제작 * feat: 아 컴포넌트 이름 추천 좀 * feat: alert 메세지 수정 * chore: 불필요한 console.log 삭제 --- src/api/post/index.ts | 8 +- src/components/feed/Modal/FeedCreateModal.tsx | 42 ++-- .../FeedCreateWithSelectMeetingModal.tsx | 189 ++++++++++++++++++ src/components/feed/Modal/FeedEditModal.tsx | 32 +-- .../feed/Modal/FeedFormPresentation.tsx | 64 ++++-- .../SelectMeeting/SelectMeetingOptionItem.tsx | 105 ++++++++++ .../feed/Modal/SelectMeeting/index.tsx | 147 ++++++++++++++ src/components/feed/Modal/feedSchema.ts | 14 +- 8 files changed, 539 insertions(+), 62 deletions(-) create mode 100644 src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx create mode 100644 src/components/feed/Modal/SelectMeeting/SelectMeetingOptionItem.tsx create mode 100644 src/components/feed/Modal/SelectMeeting/index.tsx diff --git a/src/api/post/index.ts b/src/api/post/index.ts index bcc4b23b..a682579e 100644 --- a/src/api/post/index.ts +++ b/src/api/post/index.ts @@ -1,6 +1,6 @@ -import { FormType } from '@components/feed/Modal/feedSchema'; -import { UserResponse } from '@api/user'; import { MeetingResponse } from '@api/meeting'; +import { UserResponse } from '@api/user'; +import { FormCreateType, FormEditType } from '@components/feed/Modal/feedSchema'; import { Data, api, apiV2 } from '..'; export interface PostResponse { @@ -16,12 +16,12 @@ export interface PostResponse { meeting: Pick; } -export const createPost = async (formData: FormType) => { +export const createPost = async (formData: FormCreateType) => { const { data } = await api.post>('/post/v1', formData); return data; }; -export const editPost = async (postId: string, formData: FormType) => { +export const editPost = async (postId: string, formData: FormEditType) => { const { data } = await api.put>>( `/post/v1/${postId}`, formData diff --git a/src/components/feed/Modal/FeedCreateModal.tsx b/src/components/feed/Modal/FeedCreateModal.tsx index 81cfae3e..abe26fab 100644 --- a/src/components/feed/Modal/FeedCreateModal.tsx +++ b/src/components/feed/Modal/FeedCreateModal.tsx @@ -1,21 +1,21 @@ -import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; -import { styled } from 'stitches.config'; -import { zodResolver } from '@hookform/resolvers/zod'; -import dynamic from 'next/dynamic'; -import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; -import FeedFormPresentation from './FeedFormPresentation'; -import { FormType, feedSchema } from './feedSchema'; -import ConfirmModal from '@components/modal/ConfirmModal'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { createPost } from '@api/post'; -import { useQueryGetMeeting } from '@api/meeting/hooks'; -import useModal from '@hooks/useModal'; -import { useEffect } from 'react'; -import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; import { ampli } from '@/ampli'; +import { useQueryGetMeeting } from '@api/meeting/hooks'; +import { createPost } from '@api/post'; import { useQueryMyProfile } from '@api/user/hooks'; +import ConfirmModal from '@components/modal/ConfirmModal'; +import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; +import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; +import { zodResolver } from '@hookform/resolvers/zod'; +import useModal from '@hooks/useModal'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { formatDate } from '@utils/dayjs'; +import dynamic from 'next/dynamic'; import { useRouter } from 'next/router'; +import { useEffect } from 'react'; +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; +import { styled } from 'stitches.config'; +import FeedFormPresentation from './FeedFormPresentation'; +import { FormCreateType, feedCreateSchema } from './feedSchema'; const DevTool = dynamic(() => import('@hookform/devtools').then(module => module.DevTool), { ssr: false, @@ -34,22 +34,22 @@ function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateM const submitModal = useModal(); const platform = window.innerWidth > 768 ? 'PC' : 'MO'; - const formMethods = useForm({ + const formMethods = useForm({ mode: 'onChange', - resolver: zodResolver(feedSchema), + resolver: zodResolver(feedCreateSchema), }); const { isValid } = formMethods.formState; const { mutateAsync: mutateCreateFeed, isLoading: isSubmitting } = useMutation({ - mutationFn: (formData: FormType) => createPost(formData), + mutationFn: (formData: FormCreateType) => createPost(formData), onSuccess: () => { queryClient.invalidateQueries(['getPosts']); alert('피드를 작성했습니다.'); submitModal.handleModalClose(); handleModalClose(); }, - onError: () => alert('피드를 개설하지 못했습니다.'), + onError: () => alert('피드 작성에 실패했습니다.'), }); const handleDeleteImage = (index: number) => { @@ -58,12 +58,12 @@ function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateM formMethods.setValue('images', images); }; - const handleSubmitClick: SubmitHandler = () => { + const handleSubmitClick: SubmitHandler = () => { submitModal.handleModalOpen(); }; const onSubmit = async () => { - const createFeedParameter = { ...formMethods.getValues(), meetingId: Number(meetingId) }; + const createFeedParameter = { ...formMethods.getValues() }; await mutateCreateFeed(createFeedParameter); ampli.completedFeedPosting({ user_id: Number(me?.orgId), @@ -74,7 +74,7 @@ function FeedCreateModal({ isModalOpened, meetingId, handleModalClose }: CreateM }; useEffect(() => { - formMethods.reset(); + formMethods.reset({ meetingId: Number(meetingId) }); }, [formMethods, isModalOpened]); useEffect(() => { diff --git a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx new file mode 100644 index 00000000..a2cd112a --- /dev/null +++ b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx @@ -0,0 +1,189 @@ +import { ampli } from '@/ampli'; +import { createPost } from '@api/post'; +import { useQueryMyProfile } from '@api/user/hooks'; +import ConfirmModal from '@components/modal/ConfirmModal'; +import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; +import { zodResolver } from '@hookform/resolvers/zod'; +import useModal from '@hooks/useModal'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { formatDate } from '@utils/dayjs'; +import dynamic from 'next/dynamic'; +import { useEffect } from 'react'; +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; +import { styled } from 'stitches.config'; +import FeedFormPresentation, { GroupInfo } from './FeedFormPresentation'; +import { FormCreateType, feedCreateSchema } from './feedSchema'; + +const DevTool = dynamic(() => import('@hookform/devtools').then(module => module.DevTool), { + ssr: false, +}); + +interface CreateModalProps extends ModalContainerProps { + meetingId: string; +} +const mockAttendGroupsInfo: GroupInfo[] = [ + { + id: 63, + title: '모임 이름', + imageUrl: + 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', + category: '카테고리', + contents: '모임 소개', + }, + { + id: 2, + title: '모임 이름2', + imageUrl: + 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', + category: '카테고리', + contents: '모임 소개', + }, + { + id: 3, + title: '모임 이름3', + imageUrl: + 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', + category: '카테고리', + contents: '모임 소개', + }, + { + id: 4, + title: '모임 이름4', + imageUrl: + 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', + category: '카테고리', + contents: '모임 소개', + }, + { + id: 5, + title: '모임 이름5', + imageUrl: + 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', + category: '카테고리', + contents: '모임 소개', + }, +]; + +function FeedCreateWithSelectMeetingModal({ isModalOpened, handleModalClose }: CreateModalProps) { + const queryClient = useQueryClient(); + const { data: me } = useQueryMyProfile(); + const exitModal = useModal(); + const submitModal = useModal(); + const platform = window.innerWidth > 768 ? 'PC' : 'MO'; + + const formMethods = useForm({ + mode: 'onChange', + resolver: zodResolver(feedCreateSchema), + }); + + const { isValid } = formMethods.formState; + + const { mutateAsync: mutateCreateFeed, isLoading: isSubmitting } = useMutation({ + mutationFn: (formData: FormCreateType) => createPost(formData), + onSuccess: () => { + queryClient.invalidateQueries(['getPosts']); + alert('피드를 작성했습니다.'); + submitModal.handleModalClose(); + handleModalClose(); + }, + onError: () => alert('피드 작성에 실패했습니다.'), + }); + + const handleDeleteImage = (index: number) => { + const images = formMethods.getValues().images.slice(); + images.splice(index, 1); + formMethods.setValue('images', images); + }; + + const handleSubmitClick: SubmitHandler = () => { + submitModal.handleModalOpen(); + }; + + const onSubmit = async () => { + const createFeedParameter = { ...formMethods.getValues() }; + await mutateCreateFeed(createFeedParameter); + ampli.completedFeedPosting({ user_id: Number(me?.orgId), platform_type: platform, feed_upload: formatDate() }); + }; + + useEffect(() => {}, [formMethods, isModalOpened]); + + useEffect(() => { + return () => { + ampli.completedFeedPostingCanceled({ user_id: Number(me?.orgId), platform_type: platform }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + formMethods.setValue('meetingId', meetingInfo?.id as unknown as number, { + shouldValidate: true, + shouldDirty: true, + shouldTouch: true, + }) + } + onSubmit={formMethods.handleSubmit(handleSubmitClick)} + disabled={isSubmitting || !isValid} + /> + + + { + exitModal.handleModalClose(); + handleModalClose(); + }} + /> + + {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + + + ); +} + +export default FeedCreateWithSelectMeetingModal; + +const SDialogWrapper = styled('div', { + position: 'fixed', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + zIndex: '$2', + borderRadius: '20px', + backgroundColor: '$gray700', + width: '100%', + maxWidth: '$768', + boxShadow: '0px 4px 4px rgba(0,0,0,0.25)', + maxHeight: '100vh', + overflow: 'auto scroll', + '&::-webkit-scrollbar': { + display: 'none', + }, + '@tablet': { + width: '100%', + height: '100%', + boxShadow: 'none', + borderRadius: '0', + }, +}); diff --git a/src/components/feed/Modal/FeedEditModal.tsx b/src/components/feed/Modal/FeedEditModal.tsx index 7c53ba93..27efc358 100644 --- a/src/components/feed/Modal/FeedEditModal.tsx +++ b/src/components/feed/Modal/FeedEditModal.tsx @@ -1,18 +1,18 @@ -import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; -import { styled } from 'stitches.config'; -import { zodResolver } from '@hookform/resolvers/zod'; -import dynamic from 'next/dynamic'; -import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; -import FeedFormPresentation from './FeedFormPresentation'; -import { FormType, feedSchema } from './feedSchema'; +import { editPost } from '@api/post'; +import { useQueryGetPost } from '@api/post/hooks'; +import { useQueryMyProfile } from '@api/user/hooks'; import ConfirmModal from '@components/modal/ConfirmModal'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; +import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; +import { zodResolver } from '@hookform/resolvers/zod'; import useModal from '@hooks/useModal'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import dynamic from 'next/dynamic'; import { useEffect } from 'react'; -import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; -import { useQueryGetPost } from '@api/post/hooks'; -import { editPost } from '@api/post'; -import { useQueryMyProfile } from '@api/user/hooks'; +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; +import { styled } from 'stitches.config'; +import FeedFormPresentation from './FeedFormPresentation'; +import { FormEditType, feedEditSchema } from './feedSchema'; const DevTool = dynamic(() => import('@hookform/devtools').then(module => module.DevTool), { ssr: false, @@ -29,15 +29,15 @@ function FeedEditModal({ isModalOpened, postId, handleModalClose }: EditModal) { const submitModal = useModal(); const { data: me } = useQueryMyProfile(); - const formMethods = useForm({ + const formMethods = useForm({ mode: 'onChange', - resolver: zodResolver(feedSchema), + resolver: zodResolver(feedEditSchema), }); const { isValid } = formMethods.formState; const { mutateAsync: mutateEditFeed, isLoading: isSubmitting } = useMutation({ - mutationFn: (formData: FormType) => editPost(postId, formData), + mutationFn: (formData: FormEditType) => editPost(postId, formData), onSuccess: () => { queryClient.invalidateQueries(['getPost', postId]); queryClient.invalidateQueries(['getPosts']); @@ -54,7 +54,7 @@ function FeedEditModal({ isModalOpened, postId, handleModalClose }: EditModal) { formMethods.setValue('images', images); }; - const handleSubmitClick: SubmitHandler = () => { + const handleSubmitClick: SubmitHandler = () => { submitModal.handleModalOpen(); }; diff --git a/src/components/feed/Modal/FeedFormPresentation.tsx b/src/components/feed/Modal/FeedFormPresentation.tsx index 38f83715..17df2be2 100644 --- a/src/components/feed/Modal/FeedFormPresentation.tsx +++ b/src/components/feed/Modal/FeedFormPresentation.tsx @@ -1,32 +1,37 @@ import { ACCEPTED_IMAGE_TYPES, MAX_FILE_SIZE } from '@type/form'; import { styled } from 'stitches.config'; +import { ampli } from '@/ampli'; +import { getPresignedUrl, uploadImage } from '@api/meeting'; +import CameraIcon from '@assets/svg/camera.svg'; import CancelIcon from '@assets/svg/x_big_gray.svg'; -import { getResizedImage } from '@utils/image'; +import FormController from '@components/form/FormController'; import { Divider } from '@components/util/Divider'; -import ImagePreview from './ImagePreview'; -import CameraIcon from '@assets/svg/camera.svg'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { FORM_TITLE_MAX_LENGTH } from '@constants/feed'; import { imageS3Bucket } from '@constants/url'; -import { getPresignedUrl, uploadImage } from '@api/meeting'; -import FormController from '@components/form/FormController'; -import { ERROR_MESSAGE } from './feedSchema'; import useToast from '@hooks/useToast'; -import { FORM_TITLE_MAX_LENGTH } from '@constants/feed'; -import { ampli } from '@/ampli'; +import { getResizedImage } from '@utils/image'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import ImagePreview from './ImagePreview'; +import SelectMeeting from './SelectMeeting'; +import { ERROR_MESSAGE } from './feedSchema'; -interface GroupInfo { +export interface GroupInfo { + id?: number; title: string; imageUrl: string; category: string; + contents?: string; } interface PresentationProps { userId?: number; - groupInfo: GroupInfo; + groupInfo?: GroupInfo; + attendGroupsInfo?: GroupInfo[]; title: string; handleModalClose: () => void; handleDeleteImage: (index: number) => void; + setMeetingInfo?: (meeting: GroupInfo) => void; onSubmit: React.FormEventHandler; disabled: boolean; } @@ -38,9 +43,11 @@ interface FileChangeHandler { function FeedFormPresentation({ userId, groupInfo, + attendGroupsInfo = [], title, handleModalClose, handleDeleteImage, + setMeetingInfo, onSubmit, disabled = true, }: PresentationProps) { @@ -50,6 +57,7 @@ function FeedFormPresentation({ const [textareaHeightChangeFlag, setTextareaHeightChangeFlag] = useState(false); const textAreaRef = useRef(null); const [remainingHeight, setRemainingHeight] = useState(100); + const [selectedMeeting, setSelectedMeeting] = useState(undefined); const handleWindowResize = () => { setTextareaHeightChangeFlag(flag => !flag); }; @@ -117,6 +125,11 @@ function FeedFormPresentation({ return imageUrls; }; + const handleSelectMeeting = (meeting: GroupInfo) => { + setSelectedMeeting(meeting); + setMeetingInfo?.(meeting); + }; + return (
@@ -127,15 +140,23 @@ function FeedFormPresentation({ 완료 - - + + {groupInfo.category} + {groupInfo.title}{' '} + + ) : ( + - {groupInfo.category} - {groupInfo.title} - + )} void; +} + +function SelectMeetingOptionItem({ meetingInfo, isSelected, onClick }: SelectMeetingOptionItemProps) { + return ( + <> + onClick(meetingInfo)} isSelected={isSelected}> + + + + {meetingInfo.category} + {meetingInfo.title} + + {meetingInfo.contents} + + + + ); +} +export default SelectMeetingOptionItem; +const SelectItemWrapper = styled('div', { + display: 'flex', + padding: '10px 12px', + borderRadius: '6px', + backgroundColor: '$gray600', + mb: '$4', + cursor: 'pointer', + variants: { + isSelected: { + true: { + backgroundColor: '$gray600', + }, + false: { + backgroundColor: 'transparent', + }, + }, + }, +}); + +const STitle = styled('p', { + maxWidth: '70%', + color: '$white', + fontStyle: 'T3', + ml: '$8', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + wordBreak: 'break-all', + '@tablet': { + fontStyle: 'T4', + }, +}); +const SelectItemThumbnailImage = styled('div', { + width: '48px', + height: '48px', + borderRadius: '$6', + overflow: 'hidden', + backgroundColor: '$gray800', + backgroundSize: 'cover', + backgroundPosition: 'center center', + backgroundRepeat: 'no-repeat', + '@tablet': { + width: '40px', + height: '40px', + }, +}); + +const SelectItemInfoWrapper = styled('div', { + width: '80%', + ml: '$14', +}); + +const SelectItemTitleSection = styled('div', { + flexType: 'verticalCenter', +}); + +const SelectItemCategory = styled('p', { + fontStyle: 'T4', + color: '$secondary', +}); + +const SelectItemContent = styled('p', { + fontStyle: 'B3', + fontWeight: 400, + lineHeight: '20px', + color: '$gray300', + width: '100%', + maxWidth: '100%', + + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + wordBreak: 'break-all', +}); diff --git a/src/components/feed/Modal/SelectMeeting/index.tsx b/src/components/feed/Modal/SelectMeeting/index.tsx new file mode 100644 index 00000000..bf2e965f --- /dev/null +++ b/src/components/feed/Modal/SelectMeeting/index.tsx @@ -0,0 +1,147 @@ +import { Arrow } from '@components/button/Arrow'; +import ModalBackground from '@components/modal/ModalBackground'; +import { getResizedImage } from '@utils/image'; +import { useState } from 'react'; +import { styled } from 'stitches.config'; +import { GroupInfo } from '../FeedFormPresentation'; +import SelectMeetingOptionItem from './SelectMeetingOptionItem'; + +interface SelectMeetingProps { + selectMeetingInfo?: GroupInfo; + meetingList: GroupInfo[]; + onClick: (meeting: GroupInfo) => void; +} + +function SelectMeeting({ selectMeetingInfo, meetingList, onClick }: SelectMeetingProps) { + const [isSelectOpen, setIsSelectOpen] = useState(false); + + const handleSelectClick = () => { + setIsSelectOpen(prev => !prev); + }; + + const handleSelectItemClick = (meetingInfo: GroupInfo) => { + onClick(meetingInfo); + setIsSelectOpen(false); + }; + + return ( + + + {selectMeetingInfo ? ( + <> + + {selectMeetingInfo.category} + {selectMeetingInfo.title}{' '} + + ) : ( + 어떤 모임의 피드를 작성할까요? + )} + + + + {}} + css={{ + background: 'rgba(0, 0, 0, 0)', + transition: 'all 0.3s ease', + pointerEvents: isSelectOpen ? 'auto' : 'none', + }} + /> + + {meetingList.map(meetingInfo => ( + + ))} + + + ); +} + +export default SelectMeeting; + +const Container = styled('div', { + position: 'relative', +}); + +const InfoWrapper = styled('div', { + mt: '$40', + flexType: 'verticalCenter', + cursor: 'pointer', + '@tablet': { + px: '$20', + }, +}); + +const SThumbnailImage = styled('div', { + width: '56px', + height: '56px', + borderRadius: '$6', + overflow: 'hidden', + backgroundColor: '$gray800', + backgroundSize: 'cover', + backgroundPosition: 'center center', + backgroundRepeat: 'no-repeat', + '@tablet': { + width: '40px', + height: '40px', + }, +}); + +const SCategory = styled('p', { + color: '$gray400', + fontStyle: 'T3', + ml: '$20', + '@tablet': { + fontStyle: 'T4', + ml: '$12', + }, +}); + +const STitle = styled('p', { + maxWidth: '70%', + color: '$white', + fontStyle: 'T3', + ml: '$8', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + wordBreak: 'break-all', + '@tablet': { + fontStyle: 'T4', + }, +}); + +const SelectWrapper = styled('div', { + position: 'absolute', + top: '120%', + left: 0, + p: '$8', + maxWidth: '500px', + height: '344px', + width: '100%', + + backgroundColor: '$gray700', + borderRadius: '$6', + zIndex: '$3', + overflowX: 'hidden', + overflowY: 'auto', + transition: 'all 0.3s ease-in-out', + variants: { + isSelectOpen: { + true: { + height: '344px', + }, + false: { + height: '0px', + p: '$0', + }, + }, + }, +}); diff --git a/src/components/feed/Modal/feedSchema.ts b/src/components/feed/Modal/feedSchema.ts index 56d51d3c..d54f9cf4 100644 --- a/src/components/feed/Modal/feedSchema.ts +++ b/src/components/feed/Modal/feedSchema.ts @@ -1,6 +1,9 @@ import { z } from 'zod'; export const ERROR_MESSAGE = { + MEETING_ID: { + REQUIRED: '모임을 선택해주세요.', + }, TITLE: { MIN: '피드 제목을 입력해주세요.', MAX: '최대 100자까지만 입력할 수 있어요', @@ -10,11 +13,18 @@ export const ERROR_MESSAGE = { }, }; -export const feedSchema = z.object({ +export const feedCreateSchema = z.object({ + meetingId: z.number({ required_error: ERROR_MESSAGE.MEETING_ID.REQUIRED }), title: z.string().max(100, { message: ERROR_MESSAGE.TITLE.MAX }).min(1, { message: ERROR_MESSAGE.TITLE.MIN }), + contents: z.string().min(1, { message: ERROR_MESSAGE.CONTENTS.MIN }), + images: z.array(z.string()), +}); +export const feedEditSchema = z.object({ + title: z.string().max(100, { message: ERROR_MESSAGE.TITLE.MAX }).min(1, { message: ERROR_MESSAGE.TITLE.MIN }), contents: z.string().min(1, { message: ERROR_MESSAGE.CONTENTS.MIN }), images: z.array(z.string()), }); -export type FormType = z.infer; +export type FormCreateType = z.infer; +export type FormEditType = z.infer; From 888a66b276907e3dd4a3b5ab1817e31af9ca428c Mon Sep 17 00:00:00 2001 From: NaReum Date: Sat, 9 Dec 2023 15:01:47 +0900 Subject: [PATCH 08/26] =?UTF-8?q?selectMeeting=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91=20(#610)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: MeetingInfo 호버 애니메이션 * feat: 배경 누르면 플로팅이 꺼진다 * feat: 모바일 대응 * chore: 원래 모달로 되돌리기 * Update src/components/feed/Modal/FeedFormPresentation.tsx Co-authored-by: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> --------- Co-authored-by: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> --- .../feed/Modal/FeedFormPresentation.tsx | 37 ++--- .../feed/Modal/SelectMeeting/index.tsx | 135 ++++++++++++++---- .../Feed/FeedItem/MeetingInfo.tsx | 4 + .../page/postList/FloatingButton/index.tsx | 9 +- 4 files changed, 135 insertions(+), 50 deletions(-) diff --git a/src/components/feed/Modal/FeedFormPresentation.tsx b/src/components/feed/Modal/FeedFormPresentation.tsx index 17df2be2..2665072c 100644 --- a/src/components/feed/Modal/FeedFormPresentation.tsx +++ b/src/components/feed/Modal/FeedFormPresentation.tsx @@ -140,23 +140,26 @@ function FeedFormPresentation({ 완료 - {attendGroupsInfo?.length === 0 && groupInfo ? ( - - + {attendGroupsInfo?.length === 0 && groupInfo ? ( + + + {groupInfo.category} + {groupInfo.title} + + ) : ( + - {groupInfo.category} - {groupInfo.title}{' '} - - ) : ( - - )} + )} +
+ )} /> - + - - {selectMeetingInfo ? ( - <> - - {selectMeetingInfo.category} - {selectMeetingInfo.title}{' '} - - ) : ( - 어떤 모임의 피드를 작성할까요? - )} - - - + <> {}} + onClick={handleSelectClick} css={{ - background: 'rgba(0, 0, 0, 0)', + background: isSelectOpen ? '$grayAlpha800' : 'rgba(0, 0, 0, 0)', transition: 'all 0.3s ease', pointerEvents: isSelectOpen ? 'auto' : 'none', }} /> - - {meetingList.map(meetingInfo => ( - - ))} - - + + 어떤 모임의 피드를 작성할까요? + + {meetingList.map(meetingInfo => ( + + ))} + + + + + {selectMeetingInfo ? ( + <> + + {selectMeetingInfo.category} + {selectMeetingInfo.title}{' '} + + ) : ( + 어떤 모임의 피드를 작성할까요? + )} + + + + {}} + css={{ + background: 'rgba(0, 0, 0, 0)', + transition: 'all 0.3s ease', + pointerEvents: isSelectOpen ? 'auto' : 'none', + }} + /> + + {meetingList.map(meetingInfo => ( + + ))} + + + ); } @@ -118,7 +140,7 @@ const STitle = styled('p', { }, }); -const SelectWrapper = styled('div', { +const SelectDesktopLayout = styled('div', { position: 'absolute', top: '120%', left: 0, @@ -144,4 +166,55 @@ const SelectWrapper = styled('div', { }, }, }, + '@tablet': { + display: 'none', + }, +}); + +const SelectMobileTitle = styled('p', { + color: '$gray10', + fontStyle: 'T4', + ml: '$16', + mr: '$16', + mb: '$12', +}); + +const SelectMobileLayout = styled('div', { + position: 'absolute', + display: 'none', + + bottom: '20px', + left: '20px', + right: '20px', + pt: '$24', + pb: '$16', + height: '400px', + overflow: 'hidden', + + backgroundColor: '$gray700', + borderRadius: '16px', + zIndex: '$3', + transition: 'all 0.3s ease-in-out', + variants: { + isSelectOpen: { + true: { + height: '400px', + }, + false: { + height: '0px', + p: '$0', + }, + }, + }, + '@tablet': { + display: 'block', + position: 'absolute', + }, +}); + +const SelectMobileListWrapper = styled('div', { + height: '324px', + overflowX: 'hidden', + overflowY: 'auto', + p: '$8', }); diff --git a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx index 329d7aba..218e2a72 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx @@ -42,6 +42,10 @@ const Container = styled('div', { fontStyle: 'T5', padding: '$0 $12', mb: '$20', + border: '1px solid $gray800', + '&:hover': { + border: '1px solid $gray500', + }, }); const MeetingInfoWrapper = styled('div', { diff --git a/src/components/page/postList/FloatingButton/index.tsx b/src/components/page/postList/FloatingButton/index.tsx index d2acd2ee..84f22633 100644 --- a/src/components/page/postList/FloatingButton/index.tsx +++ b/src/components/page/postList/FloatingButton/index.tsx @@ -1,14 +1,19 @@ import Plus from '@assets/svg/plus.svg?rect'; +import FloatingButtonModal from '@components/modal/FloatingButtonModal'; import ModalBackground from '@components/modal/ModalBackground'; import { useState } from 'react'; import { styled } from 'stitches.config'; -import FloatingButtonModal from '@components/modal/FloatingButtonModal'; function FloatingButton() { const [isActive, setIsActive] = useState(false); + + const handleButtonClick = () => setIsActive(isActive => !isActive); + const handleOptionClose = () => setIsActive(false); + return ( <> - setIsActive(isActive => !isActive)}> + From 0df28e74b4c97d4fe2d7f622c954ad00f468b5e9 Mon Sep 17 00:00:00 2001 From: Hyeonsu Kim <86764406+borimong@users.noreply.github.com> Date: Sat, 9 Dec 2023 22:50:16 +0900 Subject: [PATCH 09/26] =?UTF-8?q?=EB=82=B4=EA=B0=80=20=EC=86=8D=ED=95=9C?= =?UTF-8?q?=20=EB=AA=A8=EC=9E=84=20=EC=97=86=EC=9D=8C=20=EB=AA=A8=EB=8B=AC?= =?UTF-8?q?=20UI=20=EC=9E=91=EC=84=B1=20(#614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 플로팅 버튼 옵션 창 첫 렌더링 시 뜨는 오류 fix * feat: 내가 속한 모임 없음 모달 ui 작성 * feat: 리뷰반영 * feat: Modal에 background 추가 --------- Co-authored-by: na-reum --- src/components/modal/FloatingButtonModal.tsx | 37 ++++++---- src/components/modal/NoJoinedGroupModal.tsx | 77 ++++++++++++++++++++ 2 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 src/components/modal/NoJoinedGroupModal.tsx diff --git a/src/components/modal/FloatingButtonModal.tsx b/src/components/modal/FloatingButtonModal.tsx index 2999e3bc..736a3597 100644 --- a/src/components/modal/FloatingButtonModal.tsx +++ b/src/components/modal/FloatingButtonModal.tsx @@ -1,22 +1,29 @@ -import React from 'react'; -import GroupIcon from '../../../public/assets/svg/floating_button_group_icon.svg'; +import { useOverlay } from '@hooks/useOverlay/Index'; +import { keyframes, styled } from 'stitches.config'; import FeedIcon from '../../../public/assets/svg/floating_button_feed_icon.svg'; -import { styled } from 'stitches.config'; -import { keyframes } from 'stitches.config'; +import GroupIcon from '../../../public/assets/svg/floating_button_group_icon.svg'; +import NoJoinedGroupModal from './NoJoinedGroupModal'; const FloatingButtonModal = (props: { isActive: boolean }) => { const { isActive } = props; + + const overlay = useOverlay(); + const handleNoJoinedModalClose = () => + overlay.open(({ isOpen, close }) => ); + return ( - - - - + <> + + + + + ); }; @@ -28,7 +35,7 @@ const fadeIn = keyframes({ }); const fadeOut = keyframes({ - from: { opacity: '1', transform: 'translateY(0px)' }, + from: { transform: 'translateY(0px)' }, to: { opacity: '0', transform: 'translateY(7px)' }, }); diff --git a/src/components/modal/NoJoinedGroupModal.tsx b/src/components/modal/NoJoinedGroupModal.tsx new file mode 100644 index 00000000..2b787fc9 --- /dev/null +++ b/src/components/modal/NoJoinedGroupModal.tsx @@ -0,0 +1,77 @@ +import { Dialog } from '@headlessui/react'; +import Link from 'next/link'; +import { styled } from 'stitches.config'; +import ModalBackground from './ModalBackground'; +import { ModalContainerProps } from './ModalContainer'; + +const NoJoinedGroupModal = ({ isModalOpened, handleModalClose }: ModalContainerProps) => { + return ( + + {' '} + + +
+ 내가 가입한 모임이 없어요 + 가입한 모임이 있어야만 피드를 작성할 수 있어요. 모임을 찾아볼까요? +
+
+ + 모임 찾기 + +
+
+
+
+ ); +}; + +export default NoJoinedGroupModal; + +const Container = styled('div', { + width: '80%', + maxWidth: '500px', + minHeight: '222px', + borderRadius: '14px', + backgroundColor: '$gray800', + position: 'fixed', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + padding: '24px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + zIndex: '$4', +}); + +const Title = styled('div', { + fontStyle: 'T2', + mb: '$12', + color: '$gray10', +}); + +const Content = styled('div', { + fontSize: '16px', + fontWeight: '400', + lineHeight: '26px', + color: '$gray100', +}); + +const FindGroupButton = styled('p', { + width: '92px', + height: '44px', + borderRadius: '10px', + backgroundColor: '$white', + color: '$black', + flexType: 'center', + fontSize: '14px', + fontWeight: '600', + lineHeight: '18px', + float: 'right ', + cursor: 'pointer', +}); From 7787b9fc251cf5d779f5de1b4deb254000aa7220 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 00:00:03 +0900 Subject: [PATCH 10/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5(=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=95=A9=EC=84=B1,=20onClick=20props=20=EC=B6=94=EA=B0=80)=20(?= =?UTF-8?q?#616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit also add LikeButton component and compose on FeedPanel --- src/api/post/hooks.ts | 6 +- src/components/button/LikeButton.tsx | 42 +++++++++++ .../meetingDetail/Feed/FeedItem/index.tsx | 75 +++---------------- .../page/meetingDetail/Feed/FeedPanel.tsx | 56 ++++++++++---- 4 files changed, 99 insertions(+), 80 deletions(-) create mode 100644 src/components/button/LikeButton.tsx diff --git a/src/api/post/hooks.ts b/src/api/post/hooks.ts index 3b127076..399aa96d 100644 --- a/src/api/post/hooks.ts +++ b/src/api/post/hooks.ts @@ -25,12 +25,12 @@ export const useInfinitePosts = (take: number, meetingId: number) => { return { data, hasNextPage, fetchNextPage, isFetchingNextPage, isLoading }; }; -export const useMutationUpdateLike = (take: number, meetingId: number, postId: number) => { +export const useMutationUpdateLike = (take: number, meetingId: number) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: () => postLike(String(postId)), - onMutate: async () => { + mutationFn: (postId: number) => postLike(String(postId)), + onMutate: async postId => { await queryClient.cancelQueries(['getPosts', take, meetingId]); type Post = paths['/post/v1']['get']['responses']['200']['content']['application/json']['data']; diff --git a/src/components/button/LikeButton.tsx b/src/components/button/LikeButton.tsx new file mode 100644 index 00000000..56b06395 --- /dev/null +++ b/src/components/button/LikeButton.tsx @@ -0,0 +1,42 @@ +import LikeActiveIcon from '@assets/svg/like_active.svg'; +import LikeDefaultIcon from '@assets/svg/like_default.svg'; +import { LIKE_MAX_COUNT } from '@constants/feed'; +import { styled } from 'stitches.config'; + +interface LikeButtonProps { + isLiked: boolean; + likeCount: number; + onClickLike: (e: React.MouseEvent) => void; +} + +export default function LikeButton({ isLiked, likeCount, onClickLike }: LikeButtonProps) { + const formattedLikeCount = likeCount > LIKE_MAX_COUNT ? `${LIKE_MAX_COUNT}+` : likeCount; + + return ( + + {isLiked ? : } + {formattedLikeCount} + + ); +} + +const SLikeButton = styled('button', { + display: 'flex', + alignItems: 'center', + fontStyle: 'H5', + + variants: { + like: { + true: { + color: '$red', + }, + false: { + color: '$gray10', + }, + }, + }, + + '& > svg': { + mr: '$6', + }, +}); diff --git a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx index 920e0d39..f5e7e24b 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -4,19 +4,10 @@ import { styled } from 'stitches.config'; // import MoreIcon from '@assets/svg/more.svg'; import { ampli } from '@/ampli'; import { useQueryGetMeeting } from '@api/meeting/hooks'; -import { useMutationUpdateLike } from '@api/post/hooks'; import { UserResponse } from '@api/user'; -import LikeActiveIcon from '@assets/svg/like_active.svg'; -import LikeDefaultIcon from '@assets/svg/like_default.svg'; import ProfileDefaultIcon from '@assets/svg/profile_default.svg?rect'; import Avatar from '@components/avatar/Avatar'; -import { - AVATAR_MAX_LENGTH, - CARD_CONTENT_MAX_LENGTH, - CARD_TITLE_MAX_LENGTH, - LIKE_MAX_COUNT, - TAKE_COUNT, -} from '@constants/feed'; +import { AVATAR_MAX_LENGTH, CARD_CONTENT_MAX_LENGTH, CARD_TITLE_MAX_LENGTH } from '@constants/feed'; import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; import { useDisplay } from '@hooks/useDisplay'; import { playgroundLink } from '@sopt-makers/playground-common'; @@ -40,40 +31,20 @@ interface PostProps { interface FeedItemProps { post: PostProps; HeaderSection?: React.ReactNode; + LikeButton?: React.ReactNode; + meetingId?: number; + onClick?: (e: React.MouseEvent) => void; } -const FeedItem = ({ post, HeaderSection }: FeedItemProps) => { - const { id, user, title, contents, images, updatedDate, commenterThumbnails, commentCount, likeCount, isLiked } = - post; - const formattedLikeCount = likeCount > LIKE_MAX_COUNT ? `${LIKE_MAX_COUNT}+` : likeCount; +const FeedItem = ({ post, HeaderSection, LikeButton, meetingId: _meetingId, onClick }: FeedItemProps) => { + const { user, title, contents, images, updatedDate, commenterThumbnails, commentCount } = post; const router = useRouter(); - const meetingId = router.query.id as string; - const { data: meeting } = useQueryGetMeeting({ params: { id: meetingId } }); - const { mutate } = useMutationUpdateLike(TAKE_COUNT, Number(meetingId), id); - const { isMobile } = useDisplay(); + // NOTE: 게시글 상세페이지에선 router.query.id 가 post의 id 이기 때문에 meetingId를 주입받아야 한다. + const meetingId = _meetingId ?? Number(router.query.id as string); + const { data: meeting } = useQueryGetMeeting({ params: { id: String(meetingId) } }); - const handleLikeClick = (e: React.MouseEvent) => { - e.preventDefault(); - mutate(); - ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); - }; return ( - - ampli.clickFeedCard({ - feed_id: id, - feed_upload: updatedDate, - feed_title: title, - feed_image_total: images ? images.length : 0, - feed_comment_total: commentCount, - feed_like_total: likeCount, - group_id: Number(meetingId), - crew_status: meeting?.approved, - platform_type: isMobile ? 'MO' : 'PC', - location: router.pathname, - }) - } - > + {HeaderSection} @@ -129,10 +100,7 @@ const FeedItem = ({ post, HeaderSection }: FeedItemProps) => { - - {isLiked ? : } - {formattedLikeCount} - + {LikeButton} ); @@ -281,27 +249,6 @@ const SCommentCount = styled('span', { fontStyle: 'H5', }); -const SLikeButton = styled('button', { - display: 'flex', - alignItems: 'center', - fontStyle: 'H5', - - variants: { - like: { - true: { - color: '$red', - }, - false: { - color: '$gray10', - }, - }, - }, - - '& > svg': { - mr: '$6', - }, -}); - const SOverlay = styled('div', { position: 'absolute', background: '$gray950', diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index 840b7867..754ef219 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -1,5 +1,5 @@ import { ampli } from '@/ampli'; -import { useInfinitePosts } from '@api/post/hooks'; +import { useInfinitePosts, useMutationUpdateLike } from '@api/post/hooks'; import { useQueryMyProfile } from '@api/user/hooks'; import FeedCreateModal from '@components/feed/Modal/FeedCreateModal'; import { POST_MAX_COUNT, TAKE_COUNT } from '@constants/feed'; @@ -14,6 +14,8 @@ import { styled } from 'stitches.config'; import EmptyView from './EmptyView'; import FeedItem from './FeedItem'; import MobileFeedListSkeleton from './Skeleton/MobileFeedListSkeleton'; +import LikeButton from '@components/button/LikeButton'; +import { useQueryGetMeeting } from '@api/meeting/hooks'; interface FeedPanelProps { isMember: boolean; @@ -24,7 +26,7 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { const meetingId = router.query.id as string; const feedCreateOverlay = useOverlay(); - const { isTablet } = useDisplay(); + const { isMobile, isTablet } = useDisplay(); const { data: me } = useQueryMyProfile(); const { data: postsData, @@ -34,6 +36,8 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { isLoading, } = useInfinitePosts(TAKE_COUNT, Number(meetingId)); useScrollRestorationAfterLoading(isLoading); + const { data: meeting } = useQueryGetMeeting({ params: { id: meetingId } }); + const { mutate: mutateLike } = useMutationUpdateLike(TAKE_COUNT, Number(meetingId)); const isEmpty = !postsData?.pages[0]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -56,17 +60,43 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { }); }; - const renderedPosts = postsData?.pages.map(post => ( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - - - - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - - - - )); + const handleLikeClick = (postId: number) => (e: React.MouseEvent) => { + e.preventDefault(); + mutateLike(postId); + ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); + }; + + const renderedPosts = postsData?.pages.map(post => { + if (!post) return; + return ( + + + + } + onClick={() => + ampli.clickFeedCard({ + feed_id: post.id, + feed_upload: post.updatedDate, + feed_title: post.title, + feed_image_total: post.images ? post.images.length : 0, + feed_comment_total: post.commentCount, + feed_like_total: post.likeCount, + group_id: Number(meetingId), + crew_status: meeting?.approved, + platform_type: isMobile ? 'MO' : 'PC', + location: router.pathname, + }) + } + /> + + + ); + }); return ( <> From a6eb4a0de8efe86488c9eabee3f214c67860b692 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 00:01:16 +0900 Subject: [PATCH 11/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EB=B7=B0=EC=97=90=20=ED=95=B4=EB=8B=B9=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=20=ED=94=BC=EB=93=9C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#602)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: integrate get posts API associated with the meeting * fix: fix bug call API with undefined * chore: 텍스트 수정 * feat: 모임 피드 상세 뷰 UI 수정 (#606) * feat: 댓글 입력 placeholder 변경 * feat: 좋아요 영역 hover 처리 * feat: 댓글 수 표시 영역을 버튼으로 spec 변경, 댓글 버튼 클릭 시 댓글 textarea 포커스 * feat: 더보기 버튼 왼쪽에 공유 버튼 추가 (클릭 시 토스트 메시지 등장) * chore: color 및 크기 수정 * feat: hover 시 버튼 처리 * chore: textarea focus 시 처리 * feat: 모임 정보 안내 버튼 추가 * feat: 모임 정보 안내 버튼 안에 아이콘 추가 * feat: 모임 소개 말줄임 처리 * refactor: 코드 리뷰 반영 * feat: 댓글 입력 부분 디자인 수정사항 반영 * refactor: use useInfinitePosts to fetch posts on post detail page * feat: add meetingId props on FeedItem * feat: compose like button and fix bug optimistip update not work * feat: 앰플리튜드 로깅 추가 * chore: 줄바꿈 없애기 --------- Co-authored-by: 100Gyeon Co-authored-by: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> --- pages/post/index.tsx | 100 ++++++++++++- public/assets/svg/arrow_card.svg | 3 + public/assets/svg/comment_hover.svg | 10 ++ public/assets/svg/like_hover.svg | 3 + public/assets/svg/send.svg | 15 +- public/assets/svg/send_fill.svg | 3 + public/assets/svg/share.svg | 4 + .../FeedCommentInput/FeedCommentInput.tsx | 97 ++++++++----- .../FeedCommentLikeSection.tsx | 57 +++++++- .../feed/FeedPostViewer/FeedPostViewer.tsx | 134 +++++++++++++++--- .../meetingDetail/Feed/FeedItem/index.tsx | 1 - 11 files changed, 349 insertions(+), 78 deletions(-) create mode 100644 public/assets/svg/arrow_card.svg create mode 100644 public/assets/svg/comment_hover.svg create mode 100644 public/assets/svg/like_hover.svg create mode 100644 public/assets/svg/send_fill.svg create mode 100644 public/assets/svg/share.svg diff --git a/pages/post/index.tsx b/pages/post/index.tsx index 9a03dd8c..77dca654 100644 --- a/pages/post/index.tsx +++ b/pages/post/index.tsx @@ -6,7 +6,7 @@ import { useMutation } from '@tanstack/react-query'; import { apiV2 } from '@api/index'; import FeedCommentInput from '@components/feed/FeedCommentInput/FeedCommentInput'; import { useQueryMyProfile } from '@api/user/hooks'; -import { useMutationPostLike, useQueryGetPost } from '@api/post/hooks'; +import { useInfinitePosts, useMutationPostLike, useMutationUpdateLike, useQueryGetPost } from '@api/post/hooks'; import FeedCommentLikeSection from '@components/feed/FeedCommentLikeSection/FeedCommentLikeSection'; import useComment from '@hooks/useComment/useComment'; import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; @@ -20,10 +20,15 @@ import { styled } from 'stitches.config'; import FeedEditModal from '@components/feed/Modal/FeedEditModal'; import { ampli } from '@/ampli'; import { useQueryGetMeeting } from '@api/meeting/hooks'; -import React from 'react'; +import React, { useRef } from 'react'; import { useDisplay } from '@hooks/useDisplay'; +import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; +import Link from 'next/link'; +import LikeButton from '@components/button/LikeButton'; +import { TAKE_COUNT } from '@constants/feed'; export default function PostPage() { + const commentRef = useRef(null); const overlay = useOverlay(); const showToast = useToast(); const router = useRouter(); @@ -83,7 +88,7 @@ export default function PostPage() { }; const post = postQuery.data; - const { data: meeting } = useQueryGetMeeting({ params: { id: String(post?.meeting.id) } }); + const { data: meeting } = useQueryGetMeeting({ params: { id: post?.meeting.id ? String(post.meeting.id) : '' } }); const comments = commentQuery.data?.pages .flatMap(page => page.data?.data?.comments) @@ -94,11 +99,29 @@ export default function PostPage() { !!comment ); + const handleClickComment = () => { + const refCurrent = commentRef.current; + if (refCurrent) { + refCurrent.focus(); + } + }; + const handleClickPostLike = () => { ampli.clickFeeddetailLike({ crew_status: meeting?.approved }); togglePostLike(); }; + const meetingId = meeting?.id; + const { data: posts } = useInfinitePosts(TAKE_COUNT, meetingId as number); // meetingId가 undefined 일 때는 enabled되지 않음 + const postsInMeeting = posts?.pages.filter(_post => _post?.id !== post?.id).slice(0, 3); + + const { mutate: mutateLike } = useMutationUpdateLike(TAKE_COUNT, Number(meetingId)); + const handleLikeClick = (postId: number) => (e: React.MouseEvent) => { + e.preventDefault(); + mutateLike(postId); + ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); + }; + // TODO: loading 스켈레톤 UI가 있으면 좋을 듯 if (!post) return ; @@ -156,6 +179,7 @@ export default function PostPage() { isLiked={post.isLiked} commentCount={commentQuery.data?.pages[0].data?.data?.meta.itemCount || 0} likeCount={post.likeCount} + onClickComment={handleClickComment} onClickLike={handleClickPostLike} /> } @@ -173,7 +197,14 @@ export default function PostPage() { {commentQuery.hasNextPage &&
} } - CommentInput={} + CommentInput={ + + } onClickImage={() => { ampli.clickFeeddetailLike({ crew_status: meeting?.approved }); }} @@ -185,10 +216,71 @@ export default function PostPage() { window.location.assign(href); }} /> + + + 이 모임의 다른 피드 + + {postsInMeeting?.map(post => { + if (!post) return; + return ( + + + } + /> + + + ); + })} + + + + SOPT 모임들의 최신 피드 + {/* TODO: 전체 모임 피드 */} + + ); } const Container = styled('div', { flexType: 'horizontalCenter', + gap: '40px', + '@laptop': { + flexDirection: 'column', + alignItems: 'center', + gap: 0, + }, +}); +const FeedListContainer = styled('div', { + width: '380px', + display: 'flex', + flexDirection: 'column', + gap: '80px', + '@laptop': { + width: '800px', + flexDirection: 'row', + alignItems: 'flex-start', + gap: '20px', + }, + '@tablet': { + display: 'none', + }, +}); +const FeedListWrapper = styled('div', {}); +const FeedListTitle = styled('h3', { + marginBottom: '24px', + color: '$white', + fontStyle: 'T4', +}); +const FeedList = styled('ul', { + display: 'flex', + flexDirection: 'column', + gap: '24px', }); diff --git a/public/assets/svg/arrow_card.svg b/public/assets/svg/arrow_card.svg new file mode 100644 index 00000000..d6557c94 --- /dev/null +++ b/public/assets/svg/arrow_card.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/svg/comment_hover.svg b/public/assets/svg/comment_hover.svg new file mode 100644 index 00000000..46341907 --- /dev/null +++ b/public/assets/svg/comment_hover.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/svg/like_hover.svg b/public/assets/svg/like_hover.svg new file mode 100644 index 00000000..81906a76 --- /dev/null +++ b/public/assets/svg/like_hover.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/svg/send.svg b/public/assets/svg/send.svg index 44d7a6e5..fa78e8b2 100644 --- a/public/assets/svg/send.svg +++ b/public/assets/svg/send.svg @@ -1,12 +1,3 @@ - - - - - - - - - - \ No newline at end of file + + + diff --git a/public/assets/svg/send_fill.svg b/public/assets/svg/send_fill.svg new file mode 100644 index 00000000..60bfeb7f --- /dev/null +++ b/public/assets/svg/send_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/svg/share.svg b/public/assets/svg/share.svg new file mode 100644 index 00000000..69113bfd --- /dev/null +++ b/public/assets/svg/share.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/feed/FeedCommentInput/FeedCommentInput.tsx b/src/components/feed/FeedCommentInput/FeedCommentInput.tsx index d3ad84ec..ef99e7f3 100644 --- a/src/components/feed/FeedCommentInput/FeedCommentInput.tsx +++ b/src/components/feed/FeedCommentInput/FeedCommentInput.tsx @@ -1,53 +1,79 @@ import { styled } from 'stitches.config'; import SendIcon from 'public/assets/svg/send.svg'; -import React, { useState } from 'react'; +import SendFillIcon from 'public/assets/svg/send_fill.svg'; +import React, { forwardRef, useState } from 'react'; interface FeedCommentInputProps { + writerName: string; onSubmit: (comment: string) => Promise; disabled?: boolean; } -export default function FeedCommentInput({ onSubmit, disabled }: FeedCommentInputProps) { - const [comment, setComment] = useState(''); +const FeedCommentInput = forwardRef( + ({ writerName, onSubmit, disabled }, ref) => { + const [comment, setComment] = useState(''); + const [isFocused, setIsFocused] = useState(false); - const handleChange = (e: React.ChangeEvent) => { - if (e.target.value.length === 0) { - e.target.style.height = 'auto'; - } else { - e.target.style.height = `${e.target.scrollHeight}px`; - } - setComment(e.target.value); - }; + const handleChange = (e: React.ChangeEvent) => { + if (e.target.value.length === 0) { + e.target.style.height = 'auto'; + } else { + e.target.style.height = `${e.target.scrollHeight}px`; + } + setComment(e.target.value); + }; - const handleSubmit = (event: React.MouseEvent) => { - event.preventDefault(); + const handleSubmit = (event: React.MouseEvent) => { + event.preventDefault(); - if (!comment.trim()) return; - onSubmit(comment).then(() => setComment('')); - }; + if (!comment.trim()) return; + onSubmit(comment).then(() => setComment('')); + }; - return ( - - - - - - - ); -} + return ( + + setIsFocused(true)} + onBlur={() => setIsFocused(false)} + placeholder={`${writerName}님의 피드에 댓글을 남겨보세요!`} + rows={1} + /> + + {isFocused ? : } + + + ); + } +); + +export default FeedCommentInput; const Container = styled('form', { + background: '$gray800', flexType: 'verticalCenter', - gap: '16px', + borderRadius: '10px', + variants: { + isFocused: { + true: { + outline: '1px solid $gray200', + }, + false: { + outline: 'none', + }, + }, + }, }); const CommentInput = styled('textarea', { minWidth: 0, - width: '692px', - height: '54px', + width: '100%', + height: '48px', maxHeight: '120px', - padding: '14px 24px', - borderRadius: '25px', - background: '$gray700', + padding: '11px 16px', + borderRadius: '10px', + background: '$gray800', color: '$gray10', fontStyle: 'B2', border: 'none', @@ -57,12 +83,13 @@ const CommentInput = styled('textarea', { color: '$gray300', }, '@tablet': { - height: '48px', - padding: '12px 24px', fontStyle: 'B3', }, }); const SendButton = styled('button', { - width: '32px', - height: '32px', + width: '48px', + height: '48px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }); diff --git a/src/components/feed/FeedCommentLikeSection/FeedCommentLikeSection.tsx b/src/components/feed/FeedCommentLikeSection/FeedCommentLikeSection.tsx index 2aacdc38..c290587a 100644 --- a/src/components/feed/FeedCommentLikeSection/FeedCommentLikeSection.tsx +++ b/src/components/feed/FeedCommentLikeSection/FeedCommentLikeSection.tsx @@ -1,12 +1,15 @@ import { styled } from 'stitches.config'; import CommentIcon from 'public/assets/svg/comment.svg'; +import CommentHoverIcon from 'public/assets/svg/comment_hover.svg'; import LikeIcon from 'public/assets/svg/like.svg'; +import LikeHoverIcon from 'public/assets/svg/like_hover.svg'; import LikeFillIcon from 'public/assets/svg/like_fill.svg'; interface FeedCommentLikeSectionProps { isLiked: boolean; commentCount: number; likeCount: number; + onClickComment: () => void; onClickLike: () => void; } @@ -14,17 +17,26 @@ export default function FeedCommentLikeSection({ isLiked, commentCount, likeCount, + onClickComment, onClickLike, }: FeedCommentLikeSectionProps) { return ( <> - - + + + 댓글 {commentCount} - - {isLiked ? : } + + {isLiked ? ( + + ) : ( + <> + + + + )} 좋아요 {likeCount} @@ -36,20 +48,53 @@ const Divider = styled('div', { width: '1px', height: '24px', }); -const CommentWrapper = styled('div', { +const CommentWrapper = styled('button', { width: '400px', display: 'flex', flexType: 'center', - color: '$gray400', + color: '$gray300', fontStyle: 'T5', '@tablet': { width: '50%', fontStyle: 'T6', }, + '&:hover svg:first-of-type': { + display: 'none', + }, + '&:hover svg:last-of-type': { + display: 'block', + }, +}); +const StyledCommentIcon = styled(CommentIcon, { + display: 'block', +}); +const StyledCommentHoverIcon = styled(CommentHoverIcon, { + display: 'none', }); const LikeWrapper = styled(CommentWrapper, { cursor: 'pointer', userSelect: 'none', + variants: { + isLiked: { + true: { + '&:hover svg:first-of-type': { + display: 'block', + }, + }, + }, + }, + '&:hover svg:first-of-type': { + display: 'none', + }, + '&:hover svg:nth-of-type(2)': { + display: 'block', + }, +}); +const StyledLikeIcon = styled(LikeIcon, { + display: 'block', +}); +const StyledLikeHoverIcon = styled(LikeHoverIcon, { + display: 'none', }); const Like = styled('span', { marginLeft: '4px', diff --git a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx index 7271d1b9..364d60d5 100644 --- a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx +++ b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx @@ -1,7 +1,9 @@ import { paths } from '@/__generated__/schema'; import { Menu } from '@headlessui/react'; import Avatar from '@components/avatar/Avatar'; +import ShareIcon from 'public/assets/svg/share.svg'; import MenuIcon from 'public/assets/svg/ic_menu.svg'; +import ArrowIcon from '@assets/svg/arrow_card.svg'; import { styled } from 'stitches.config'; import { useOverlay } from '@hooks/useOverlay/Index'; import ImageCarouselModal from '@components/modal/ImageCarouselModal'; @@ -12,6 +14,9 @@ import 'dayjs/locale/ko'; import { playgroundURL } from '@constants/url'; import { playgroundLink } from '@sopt-makers/playground-common'; import { parseTextToLink } from '@components/util/parseTextToLink'; +import useToast from '@hooks/useToast'; +import Link from 'next/link'; + dayjs.extend(relativeTime); dayjs.locale('ko'); @@ -35,6 +40,17 @@ export default function FeedPostViewer({ onClickAuthor, }: FeedPostViewerProps) { const overlay = useOverlay(); + const showToast = useToast(); + + const handleClickShare = async () => { + const link = window.location.href; + try { + await navigator.clipboard.writeText(link); + showToast({ type: 'info', message: '링크를 복사했어요.' }); + } catch (e) { + showToast({ type: 'error', message: '링크 복사에 실패했어요. 다시 시도해 주세요.' }); + } + }; const handleClickImage = (images: string[], startIndex: number) => () => { onClickImage?.(); @@ -57,16 +73,21 @@ export default function FeedPostViewer({ {fromNow(post.updatedDate)} - - - - - - {Actions.map((Action, index) => ( - {Action} - ))} - - + + + + + + + + {Actions.map((Action, index) => ( + {Action} + ))} + + + {post.title} @@ -87,6 +108,20 @@ export default function FeedPostViewer({ )} 조회 {post.viewCount}회 + + + + +
+ {post.meeting.category} + {post.meeting.title} +
+ {/* TODO: API 배포 후 모임 소개 수정 예정 */} + 모임 소개 +
+ +
+ {CommentLikeSection} @@ -113,11 +148,12 @@ const Container = styled('div', { }, }); const ContentWrapper = styled('div', { - padding: '36px 24px 28px 40px', + padding: '32px', display: 'flex', flexDirection: 'column', - gap: '20px', + gap: '24px', '@tablet': { + gap: '16px', padding: '0 0 20px 0', }, }); @@ -126,6 +162,10 @@ const ContentHeader = styled('div', { justifyContent: 'space-between', alignItems: 'center', }); +const ButtonContainer = styled('div', { + display: 'flex', + gap: '12px', +}); const AuthorWrapper = styled('a', { display: 'flex', alignItems: 'center', @@ -149,6 +189,65 @@ const ContentBody = styled('div', { display: 'flex', flexDirection: 'column', }); +const GroupButton = styled('a', { + display: 'flex', + gap: '$16', + alignItems: 'center', + justifyContent: 'space-between', + background: '$gray800', + width: '100%', + height: '102px', + padding: '$18 $20', + borderRadius: '12px', + '&:hover': { + outline: '1px solid $gray500', + }, + '@tablet': { + height: 'fit-content', + padding: '$14 $12', + }, +}); +const GroupThumbnail = styled('img', { + width: '88px', + height: '100%', + objectFit: 'cover', + borderRadius: '8px', + '@tablet': { + display: 'none', + }, +}); +const GroupInformation = styled('div', { + display: 'flex', + flexDirection: 'column', + flex: '1', + gap: '8px', + color: '$gray100', + 'span + span': { + marginLeft: '$8', + '@tablet': { + marginLeft: '$6', + }, + }, + 'span:first-child': { + color: '$secondary', + }, + 'span:last-child': { + color: '$gray30', + }, +}); +const GroupDescription = styled('p', { + height: '$40', + overflow: 'hidden', + whiteSpace: 'normal', + textOverflow: 'ellipsis', + wordBreak: 'break-all', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + '@tablet': { + display: 'none', + }, +}); const Title = styled('h2', { color: 'white', fontStyle: 'H2', @@ -167,11 +266,9 @@ const Contents = styled('p', { }, }); const ImageSection = styled('section', { - paddingRight: '16px', - marginTop: '20px', - marginBottom: '12px', + margin: '24px 0', '@tablet': { - paddingRight: 0, + margin: '16px 0', }, }); const BigImage = styled('img', { @@ -210,13 +307,10 @@ const ImageListItem = styled('img', { }, }); const ViewCount = styled('span', { - mt: '$16', - mr: '$16', // TODO: design 체크 필요 > 체크 완료 alignSelf: 'flex-end', - color: '$gray500', + color: '$gray200', fontStyle: 'B4', '@tablet': { - mr: '$0', fontStyle: 'C1', }, }); diff --git a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx index f5e7e24b..969de97f 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -9,7 +9,6 @@ import ProfileDefaultIcon from '@assets/svg/profile_default.svg?rect'; import Avatar from '@components/avatar/Avatar'; import { AVATAR_MAX_LENGTH, CARD_CONTENT_MAX_LENGTH, CARD_TITLE_MAX_LENGTH } from '@constants/feed'; import { THUMBNAIL_IMAGE_INDEX } from '@constants/index'; -import { useDisplay } from '@hooks/useDisplay'; import { playgroundLink } from '@sopt-makers/playground-common'; import { fromNow } from '@utils/dayjs'; import truncateText from '@utils/truncateText'; From db8dbbb7aa59e1dc4ba35462806e64fcbcd77795 Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 00:45:27 +0900 Subject: [PATCH 12/26] =?UTF-8?q?=ED=94=8C=EB=A1=9C=ED=8C=85=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20action=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20(#613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: getPosts에서 meetingId 가 필요 없음 * feat: 내가 속한 모임 리스트 api * feat: 내기 속한 모임 api 연결 * feat: 사용하던 타입으로 교체 * feat: TODO 작성 * feat: 앰플리튜드 이벤트 추가 * 내가 속한 모임 없음 모달 UI 작성 (#614) * fix: 플로팅 버튼 옵션 창 첫 렌더링 시 뜨는 오류 fix * feat: 내가 속한 모임 없음 모달 ui 작성 * feat: 리뷰반영 * feat: Modal에 background 추가 --------- Co-authored-by: na-reum * feat: getPosts에서 meetingId 가 필요 없음 * feat: 내가 속한 모임 리스트 api * feat: 내기 속한 모임 api 연결 * feat: 사용하던 타입으로 교체 * feat: TODO 작성 * feat: enabled를 인자로 받게 수정 * feat: panel 에서는 enabled 사용 * feat: 주석 제거 * feat: mutate 결과로 분기처리 * feat: users => user * feat: 컬러 변경 * feat: codegen and remove ts-ignore * feat: 리뷰 반영 --------- Co-authored-by: 100Gyeon Co-authored-by: Hyeonsu Kim <86764406+borimong@users.noreply.github.com> --- pages/index.tsx | 3 +- src/__generated__/schema.d.ts | 13 ++++- src/api/post/hooks.ts | 4 +- src/api/post/index.ts | 2 +- src/api/user/index.ts | 5 ++ .../FeedCreateWithSelectMeetingModal.tsx | 11 ++-- src/components/modal/FloatingButtonModal.tsx | 58 ++++++++++++++----- src/components/modal/NoJoinedGroupModal.tsx | 2 +- .../Feed/FeedItem/MeetingInfo.tsx | 3 +- .../meetingDetail/Feed/FeedItem/index.tsx | 2 +- .../page/meetingDetail/Feed/FeedPanel.tsx | 2 +- .../page/postList/FloatingButton/index.tsx | 8 ++- 12 files changed, 82 insertions(+), 31 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index 9d846564..7e1ecc89 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -17,8 +17,7 @@ import { styled } from 'stitches.config'; const Home: NextPage = () => { const { isTablet } = useDisplay(); - // TODO: 전체 모임 피드 api 나오면 교체 예정 - const { data: postsData, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts(TAKE_COUNT, 89); + const { data: postsData, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts(TAKE_COUNT); const onIntersect: IntersectionObserverCallback = ([{ isIntersecting }]) => { if (isIntersecting && hasNextPage) { diff --git a/src/__generated__/schema.d.ts b/src/__generated__/schema.d.ts index 5ed072c9..74724e8c 100644 --- a/src/__generated__/schema.d.ts +++ b/src/__generated__/schema.d.ts @@ -1137,6 +1137,12 @@ export interface components { /** @description 작성자 프로필 */ profileImage: string | null; }; + PostV1GetPostsResponsePostMeetingDto: { + id: number; + title: string; + /** @enum {string} */ + category: "스터디" | "강연" | "번개" | "행사"; + }; PostV1GetPostsResponsePostDto: { /** @description 게시글 고유 ID */ id: number; @@ -1161,6 +1167,7 @@ export interface components { commentCount: number; /** @description 댓글 작성자 썸네일 리스트 */ commenterThumbnails: string[]; + meeting: components["schemas"]["PostV1GetPostsResponsePostMeetingDto"]; }; PostV1GetPostsResponseDto: { /** @description 게시물 목록 */ @@ -1191,6 +1198,8 @@ export interface components { imageURL: components["schemas"]["PostV1GetPostResponseImageUrlDto"][]; /** @description 모임 카테고리 */ category: string; + /** @description 모임 소개 */ + desc: string; }; PostV1GetPostResponseDto: { /** @description 게시글 고유 ID */ @@ -1993,7 +2002,7 @@ export interface operations { /** 모임 게시글 목록 조회 */ PostV1Controller_getPosts: { parameters: { - query: { + query?: { /** * @description 각 페이지 * @example 1 @@ -2002,7 +2011,7 @@ export interface operations { /** @example 12 */ take?: number; /** @description 모임 id */ - meetingId: number; + meetingId?: number | null; }; }; responses: { diff --git a/src/api/post/hooks.ts b/src/api/post/hooks.ts index 399aa96d..48fa93ad 100644 --- a/src/api/post/hooks.ts +++ b/src/api/post/hooks.ts @@ -3,7 +3,7 @@ import { InfiniteData, useInfiniteQuery, useMutation, useQuery, useQueryClient } import { produce } from 'immer'; import { deleteComment, getPost, getPosts, postLike } from '.'; -export const useInfinitePosts = (take: number, meetingId: number) => { +export const useInfinitePosts = (take: number, meetingId?: number, enabled?: boolean) => { const { data, hasNextPage, fetchNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery({ queryKey: ['getPosts', take, meetingId], queryFn: ({ pageParam = 1 }) => getPosts(pageParam, take, meetingId), @@ -14,7 +14,7 @@ export const useInfinitePosts = (take: number, meetingId: number) => { } return allPages.length + 1; }, - enabled: !!meetingId, + enabled: enabled, select: data => ({ pages: data.pages.flatMap(page => page?.data?.posts), pageParams: data.pageParams, diff --git a/src/api/post/index.ts b/src/api/post/index.ts index a682579e..12c88be7 100644 --- a/src/api/post/index.ts +++ b/src/api/post/index.ts @@ -29,7 +29,7 @@ export const editPost = async (postId: string, formData: FormEditType) => { return data; }; -export const getPosts = async (page: number, take: number, meetingId: number) => { +export const getPosts = async (page: number, take: number, meetingId?: number) => { const { GET } = apiV2.get(); const { data } = await GET('/post/v1', { params: { query: { page, take, meetingId } } }); return data; diff --git a/src/api/user/index.ts b/src/api/user/index.ts index 7fbb5598..7a772a87 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -1,3 +1,4 @@ +import { GroupInfo } from '@components/feed/Modal/FeedFormPresentation'; import { api, PromiseResponse } from '..'; import { MeetingResponse } from '../meeting'; @@ -51,3 +52,7 @@ export const fetchMeetingListOfMine = async () => { export const fetchMyProfile = async () => { return api.get>('/users/v1/profile/me'); }; + +export const fetchMeetingListOfUserAttend = async () => { + return api.get>('/user/v2/meeting/all'); +}; diff --git a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx index a2cd112a..35373929 100644 --- a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx +++ b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx @@ -1,11 +1,12 @@ import { ampli } from '@/ampli'; import { createPost } from '@api/post'; +import { fetchMeetingListOfUserAttend } from '@api/user'; import { useQueryMyProfile } from '@api/user/hooks'; import ConfirmModal from '@components/modal/ConfirmModal'; import ModalContainer, { ModalContainerProps } from '@components/modal/ModalContainer'; import { zodResolver } from '@hookform/resolvers/zod'; import useModal from '@hooks/useModal'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { formatDate } from '@utils/dayjs'; import dynamic from 'next/dynamic'; import { useEffect } from 'react'; @@ -18,9 +19,9 @@ const DevTool = dynamic(() => import('@hookform/devtools').then(module => module ssr: false, }); -interface CreateModalProps extends ModalContainerProps { - meetingId: string; -} +type CreateModalProps = ModalContainerProps; + +// TODO: 실제 api 나오면 삭제할것 const mockAttendGroupsInfo: GroupInfo[] = [ { id: 63, @@ -66,6 +67,8 @@ const mockAttendGroupsInfo: GroupInfo[] = [ function FeedCreateWithSelectMeetingModal({ isModalOpened, handleModalClose }: CreateModalProps) { const queryClient = useQueryClient(); + const { data: attendMeetingList } = useQuery(['fetchMeetingList', 'all'], fetchMeetingListOfUserAttend); + console.log(attendMeetingList); const { data: me } = useQueryMyProfile(); const exitModal = useModal(); const submitModal = useModal(); diff --git a/src/components/modal/FloatingButtonModal.tsx b/src/components/modal/FloatingButtonModal.tsx index 736a3597..29663bbe 100644 --- a/src/components/modal/FloatingButtonModal.tsx +++ b/src/components/modal/FloatingButtonModal.tsx @@ -1,4 +1,10 @@ +import { ampli } from '@/ampli'; +import { fetchMeetingListOfUserAttend } from '@api/user'; +import { useQueryMyProfile } from '@api/user/hooks'; +import FeedCreateWithSelectMeetingModal from '@components/feed/Modal/FeedCreateWithSelectMeetingModal'; import { useOverlay } from '@hooks/useOverlay/Index'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; import { keyframes, styled } from 'stitches.config'; import FeedIcon from '../../../public/assets/svg/floating_button_feed_icon.svg'; import GroupIcon from '../../../public/assets/svg/floating_button_group_icon.svg'; @@ -6,24 +12,46 @@ import NoJoinedGroupModal from './NoJoinedGroupModal'; const FloatingButtonModal = (props: { isActive: boolean }) => { const { isActive } = props; - + const router = useRouter(); const overlay = useOverlay(); - const handleNoJoinedModalClose = () => - overlay.open(({ isOpen, close }) => ); + const { data: me } = useQueryMyProfile(); + const queryClient = useQueryClient(); + const { mutate: fetchUserAttendMeetingListMutate } = useMutation(fetchMeetingListOfUserAttend, { + onSuccess: data => { + queryClient.setQueryData(['fetchMeetingList', 'all'], data); + if (data.data.data.length === 0) { + overlay.open(({ isOpen, close }) => ); + } else { + overlay.open(({ isOpen, close }) => ( + + )); + } + }, + }); + + const handleGroupCreateButtonClick = () => { + ampli.clickMakeGroup({ location: router.pathname }); + router.push('/make'); + }; + + const handleFeedCreateButtonClick = () => { + if (me?.orgId) { + ampli.clickFeedPosting({ user_id: Number(me?.orgId), location: router.pathname }); + } + fetchUserAttendMeetingListMutate(); + }; return ( - <> - - - - - + + + + ); }; diff --git a/src/components/modal/NoJoinedGroupModal.tsx b/src/components/modal/NoJoinedGroupModal.tsx index 2b787fc9..74f1a2db 100644 --- a/src/components/modal/NoJoinedGroupModal.tsx +++ b/src/components/modal/NoJoinedGroupModal.tsx @@ -12,7 +12,7 @@ const NoJoinedGroupModal = ({ isModalOpened, handleModalClose }: ModalContainerP css={{ zIndex: '$3', }} - />{' '} + />
diff --git a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx index 218e2a72..343b7205 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx @@ -1,3 +1,4 @@ +import { ampli } from '@/ampli'; import { Arrow } from '@components/button/Arrow'; import { useRouter } from 'next/router'; import { styled } from 'stitches.config'; @@ -17,7 +18,7 @@ function MeetingInfo({ meetingInfo }: MeetingInfoProps) { onClick={e => { e.preventDefault(); e.stopPropagation(); - // TODO: id값을 어떻게 넘겨줄지 고민해보기 + ampli.clickFeedCardGroupLabel({ group_id: meetingInfo.id, location: router.pathname }); router.push(`/detail?id=${meetingInfo.id}`); }} > diff --git a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx index 969de97f..497e250f 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -109,7 +109,7 @@ export default FeedItem; const SFeedItem = styled('div', { padding: '$20 $20 $28 $20', - background: '#171818', + background: '$gray900', borderRadius: '12px', color: '$gray10', width: '100%', diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index 754ef219..fb13057d 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -34,7 +34,7 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { hasNextPage, isFetchingNextPage, isLoading, - } = useInfinitePosts(TAKE_COUNT, Number(meetingId)); + } = useInfinitePosts(TAKE_COUNT, Number(meetingId), !!meetingId); useScrollRestorationAfterLoading(isLoading); const { data: meeting } = useQueryGetMeeting({ params: { id: meetingId } }); const { mutate: mutateLike } = useMutationUpdateLike(TAKE_COUNT, Number(meetingId)); diff --git a/src/components/page/postList/FloatingButton/index.tsx b/src/components/page/postList/FloatingButton/index.tsx index 84f22633..3adc6c09 100644 --- a/src/components/page/postList/FloatingButton/index.tsx +++ b/src/components/page/postList/FloatingButton/index.tsx @@ -1,3 +1,4 @@ +import { ampli } from '@/ampli'; import Plus from '@assets/svg/plus.svg?rect'; import FloatingButtonModal from '@components/modal/FloatingButtonModal'; import ModalBackground from '@components/modal/ModalBackground'; @@ -7,7 +8,12 @@ import { styled } from 'stitches.config'; function FloatingButton() { const [isActive, setIsActive] = useState(false); - const handleButtonClick = () => setIsActive(isActive => !isActive); + const handleButtonClick = () => { + if (!isActive) { + ampli.clickFeedAction(); + } + setIsActive(isActive => !isActive); + }; const handleOptionClose = () => setIsActive(false); return ( From b90f42417469c630211f27b0f2c4cc6376e6b7b2 Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Sun, 10 Dec 2023 00:47:45 +0900 Subject: [PATCH 13/26] =?UTF-8?q?fix:=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(#618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 댓글 이슈 해결 * fix: 영역 벗어나지 않도록 수정 --- src/components/feed/FeedCommentInput/FeedCommentInput.tsx | 6 ++++-- src/components/page/meetingDetail/Feed/FeedItem/index.tsx | 2 +- src/components/util/auth.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/feed/FeedCommentInput/FeedCommentInput.tsx b/src/components/feed/FeedCommentInput/FeedCommentInput.tsx index ef99e7f3..03768304 100644 --- a/src/components/feed/FeedCommentInput/FeedCommentInput.tsx +++ b/src/components/feed/FeedCommentInput/FeedCommentInput.tsx @@ -27,7 +27,10 @@ const FeedCommentInput = forwardRef( event.preventDefault(); if (!comment.trim()) return; - onSubmit(comment).then(() => setComment('')); + onSubmit(comment).then(() => { + setComment(''); + setIsFocused(false); + }); }; return ( @@ -37,7 +40,6 @@ const FeedCommentInput = forwardRef( value={comment} onChange={handleChange} onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} placeholder={`${writerName}님의 피드에 댓글을 남겨보세요!`} rows={1} /> diff --git a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx index 497e250f..91bb348f 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/index.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/index.tsx @@ -166,7 +166,7 @@ const STime = styled('span', { const STitle = styled('div', { mb: '$8', fontStyle: 'H3', - + wordBreak: 'break-all', '@tablet': { fontStyle: 'H4', }, diff --git a/src/components/util/auth.ts b/src/components/util/auth.ts index 142d1357..88524978 100644 --- a/src/components/util/auth.ts +++ b/src/components/util/auth.ts @@ -32,7 +32,7 @@ export const setAccessTokens = async () => { // NOTE: development 환경에서는 테스트 토큰을 사용한다. if (process.env.NODE_ENV === 'development') { crewToken.set( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7J207J6s7ZuIIiwiaWQiOjI1NywiaWF0IjoxNjgxODE5NTcxLCJleHAiOjE3MTc4MTk1NzF9.JVG-xzOVikIbX7vj_cZig_TTHxM-EzMgjO-_VGRbLTs' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi6rCV7JiB7JqwIiwiaWQiOjI1MywiaWF0IjoxNzAyMTMxMzIyLCJleHAiOjE3MzgxMzEzMjJ9.2y5MApbYgcUnrjaPyfZN_bqpl6LR4RNGA1jvSYivVIk' ); playgroundToken.set( 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyMyIsImV4cCI6MTY4MjI0NzIzNn0.jPK_OTNXVNvnVFkbdme6tfABsdryUFgXEYOYGCAxdPc' From 908e5cb65eec70264b4dc2e5b5a82ea0cf1cc606 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 01:29:59 +0900 Subject: [PATCH 14/26] =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=9D=B8=EC=A6=9D=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#620)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/util/auth.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/util/auth.ts b/src/components/util/auth.ts index 88524978..416f5222 100644 --- a/src/components/util/auth.ts +++ b/src/components/util/auth.ts @@ -31,12 +31,8 @@ export const getCrewServiceToken = async (playgroundToken: string) => { export const setAccessTokens = async () => { // NOTE: development 환경에서는 테스트 토큰을 사용한다. if (process.env.NODE_ENV === 'development') { - crewToken.set( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi6rCV7JiB7JqwIiwiaWQiOjI1MywiaWF0IjoxNzAyMTMxMzIyLCJleHAiOjE3MzgxMzEzMjJ9.2y5MApbYgcUnrjaPyfZN_bqpl6LR4RNGA1jvSYivVIk' - ); - playgroundToken.set( - 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyMyIsImV4cCI6MTY4MjI0NzIzNn0.jPK_OTNXVNvnVFkbdme6tfABsdryUFgXEYOYGCAxdPc' - ); + crewToken.set(process.env.NEXT_PUBLIC_CREW_TOKEN); + playgroundToken.set(process.env.NEXT_PUBLIC_PLAYGROUND_TOKEN); return; } From 8879719b95b3ce8ab449a71ab72c6f0c8bcee678 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 01:31:42 +0900 Subject: [PATCH 15/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95=20(#625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/FeedPostViewer/FeedPostViewer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx index 364d60d5..c11ae153 100644 --- a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx +++ b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx @@ -136,6 +136,7 @@ export default function FeedPostViewer({ const Container = styled('div', { width: '800px', + height: '100%', flexShrink: 0, borderRadius: '20px', border: '1px solid $gray700', From 08b2ff53af63f4b01e5e13aed61e21543c6908c3 Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 01:40:29 +0900 Subject: [PATCH 16/26] =?UTF-8?q?mockData=20=EB=A5=BC=20=EC=95=88=EB=BA=8F?= =?UTF-8?q?=EC=A7=80=EB=AD=90=EC=97=90=EC=9A=94=3F=3F=3F=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ampli.clickFeedProfile 에서 crew_status 제거 * feat: mock data 삭제 * feat: 리뷰 반영 --- .../FeedCreateWithSelectMeetingModal.tsx | 87 +++++-------------- .../meetingDetail/Feed/FeedItem/index.tsx | 9 +- 2 files changed, 26 insertions(+), 70 deletions(-) diff --git a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx index 35373929..2dcb3f9e 100644 --- a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx +++ b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx @@ -12,7 +12,7 @@ import dynamic from 'next/dynamic'; import { useEffect } from 'react'; import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { styled } from 'stitches.config'; -import FeedFormPresentation, { GroupInfo } from './FeedFormPresentation'; +import FeedFormPresentation from './FeedFormPresentation'; import { FormCreateType, feedCreateSchema } from './feedSchema'; const DevTool = dynamic(() => import('@hookform/devtools').then(module => module.DevTool), { @@ -21,54 +21,13 @@ const DevTool = dynamic(() => import('@hookform/devtools').then(module => module type CreateModalProps = ModalContainerProps; -// TODO: 실제 api 나오면 삭제할것 -const mockAttendGroupsInfo: GroupInfo[] = [ - { - id: 63, - title: '모임 이름', - imageUrl: - 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', - category: '카테고리', - contents: '모임 소개', - }, - { - id: 2, - title: '모임 이름2', - imageUrl: - 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', - category: '카테고리', - contents: '모임 소개', - }, - { - id: 3, - title: '모임 이름3', - imageUrl: - 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', - category: '카테고리', - contents: '모임 소개', - }, - { - id: 4, - title: '모임 이름4', - imageUrl: - 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', - category: '카테고리', - contents: '모임 소개', - }, - { - id: 5, - title: '모임 이름5', - imageUrl: - 'https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/18/94cf107b-4ba4-4a4d-962a-b4351c95ab93.png', - category: '카테고리', - contents: '모임 소개', - }, -]; - function FeedCreateWithSelectMeetingModal({ isModalOpened, handleModalClose }: CreateModalProps) { const queryClient = useQueryClient(); - const { data: attendMeetingList } = useQuery(['fetchMeetingList', 'all'], fetchMeetingListOfUserAttend); - console.log(attendMeetingList); + const { data: attendMeetingList, isLoading: isFetchAttendMeetingLoading } = useQuery( + ['fetchMeetingList', 'all'], + fetchMeetingListOfUserAttend + ); + const { data: me } = useQueryMyProfile(); const exitModal = useModal(); const submitModal = useModal(); @@ -121,22 +80,24 @@ function FeedCreateWithSelectMeetingModal({ isModalOpened, handleModalClose }: C - - formMethods.setValue('meetingId', meetingInfo?.id as unknown as number, { - shouldValidate: true, - shouldDirty: true, - shouldTouch: true, - }) - } - onSubmit={formMethods.handleSubmit(handleSubmitClick)} - disabled={isSubmitting || !isValid} - /> + {!isFetchAttendMeetingLoading && ( + + formMethods.setValue('meetingId', meetingInfo?.id as number, { + shouldValidate: true, + shouldDirty: true, + shouldTouch: true, + }) + } + onSubmit={formMethods.handleSubmit(handleSubmitClick)} + disabled={isSubmitting || !isValid} + /> + )} ) => void; } -const FeedItem = ({ post, HeaderSection, LikeButton, meetingId: _meetingId, onClick }: FeedItemProps) => { +const FeedItem = ({ post, HeaderSection, LikeButton, onClick }: FeedItemProps) => { const { user, title, contents, images, updatedDate, commenterThumbnails, commentCount } = post; const router = useRouter(); - // NOTE: 게시글 상세페이지에선 router.query.id 가 post의 id 이기 때문에 meetingId를 주입받아야 한다. - const meetingId = _meetingId ?? Number(router.query.id as string); - const { data: meeting } = useQueryGetMeeting({ params: { id: String(meetingId) } }); return ( @@ -50,7 +45,7 @@ const FeedItem = ({ post, HeaderSection, LikeButton, meetingId: _meetingId, onCl { e.preventDefault(); - ampli.clickFeedProfile({ crew_status: meeting?.approved, location: router.pathname }); + ampli.clickFeedProfile({ location: router.pathname }); window.location.href = `${playgroundLink.memberDetail(user.orgId)}`; }} > From 866801e545c5024a2bb8008f8dc46154aab46a20 Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 01:45:27 +0900 Subject: [PATCH 17/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EC=A0=84=EC=B2=B4=20=ED=94=BC=EB=93=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=AA=A9=EB=A1=9D=20UI=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#623)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: edit meetingId param as optional on useMutationUpdateLike * feat: integrate getting all meeting post API on PostPage * feat: edit fetching posts --- pages/post/index.tsx | 51 +++++++++++++++++++++++++++++++++++-------- src/api/post/hooks.ts | 2 +- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/pages/post/index.tsx b/pages/post/index.tsx index 77dca654..a5ec2ecc 100644 --- a/pages/post/index.tsx +++ b/pages/post/index.tsx @@ -20,12 +20,13 @@ import { styled } from 'stitches.config'; import FeedEditModal from '@components/feed/Modal/FeedEditModal'; import { ampli } from '@/ampli'; import { useQueryGetMeeting } from '@api/meeting/hooks'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { useDisplay } from '@hooks/useDisplay'; import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; import Link from 'next/link'; import LikeButton from '@components/button/LikeButton'; import { TAKE_COUNT } from '@constants/feed'; +import MeetingInfo from '@components/page/meetingDetail/Feed/FeedItem/MeetingInfo'; export default function PostPage() { const commentRef = useRef(null); @@ -114,13 +115,25 @@ export default function PostPage() { const meetingId = meeting?.id; const { data: posts } = useInfinitePosts(TAKE_COUNT, meetingId as number); // meetingId가 undefined 일 때는 enabled되지 않음 const postsInMeeting = posts?.pages.filter(_post => _post?.id !== post?.id).slice(0, 3); - const { mutate: mutateLike } = useMutationUpdateLike(TAKE_COUNT, Number(meetingId)); - const handleLikeClick = (postId: number) => (e: React.MouseEvent) => { - e.preventDefault(); - mutateLike(postId); - ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); - }; + + const handleClickLike = + (postId: number) => (mutateCb: (postId: number) => void) => (e: React.MouseEvent) => { + e.preventDefault(); + ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); + mutateCb(postId); + }; + + // NOTE: 전체 피드 게시글 조회 & 좋아요의 경우 meetingId가 없고, 캐시 키로 meetingId를 사용하지 않기 때문에 optimistic update가 정상 동작하도록 별도 mutation을 사용한다. + const { mutate: mutateLikeInAllPost } = useMutationUpdateLike(TAKE_COUNT); + const { data: allPosts, hasNextPage, fetchNextPage } = useInfinitePosts(TAKE_COUNT); + const allMeetingPosts = allPosts?.pages.filter(_post => _post?.meeting.id !== meetingId).slice(0, 5); // 현재 조회하는 게시글이 속한 모임의 게시글은 제외한다 + // 현재 모임의 게시글을 제외했는데 모임 게시글이 없다면 다음 페이지를 불러온다. + useEffect(() => { + if (!hasNextPage) return; + // 정책) 전체 모임 게시글 5개 불러올 때 까지 페이지네이션 한다. + if (allMeetingPosts?.length !== 5) fetchNextPage(); + }, [hasNextPage, allMeetingPosts, fetchNextPage]); // TODO: loading 스켈레톤 UI가 있으면 좋을 듯 if (!post) return ; @@ -232,7 +245,7 @@ export default function PostPage() { post={post} meetingId={meetingId} // eslint-disable-next-line prettier/prettier - LikeButton={} + LikeButton={} /> @@ -242,7 +255,27 @@ export default function PostPage() { SOPT 모임들의 최신 피드 - {/* TODO: 전체 모임 피드 */} + + {allMeetingPosts?.map(post => { + if (!post) return; + return ( + + + } + // eslint-disable-next-line prettier/prettier + LikeButton={} + /> + + + ); + })} + diff --git a/src/api/post/hooks.ts b/src/api/post/hooks.ts index 48fa93ad..da4edfce 100644 --- a/src/api/post/hooks.ts +++ b/src/api/post/hooks.ts @@ -25,7 +25,7 @@ export const useInfinitePosts = (take: number, meetingId?: number, enabled?: boo return { data, hasNextPage, fetchNextPage, isFetchingNextPage, isLoading }; }; -export const useMutationUpdateLike = (take: number, meetingId: number) => { +export const useMutationUpdateLike = (take: number, meetingId?: number) => { const queryClient = useQueryClient(); return useMutation({ From e3a12295dac0fd54aa7c723dc463ebd27bf2d6c1 Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 02:09:23 +0900 Subject: [PATCH 18/26] =?UTF-8?q?=EB=82=B4=EB=AA=A8=EC=9E=84=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=9D=B4=20=EC=9D=B4=EC=83=81=ED=95=98=EC=9A=94=20(#6?= =?UTF-8?q?28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/mine/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/mine/index.tsx b/pages/mine/index.tsx index bad4c1d2..0c2bab4e 100644 --- a/pages/mine/index.tsx +++ b/pages/mine/index.tsx @@ -15,8 +15,8 @@ import { MeetingListOfApplied, MeetingListOfMine } from '@components/page/meetin import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense'; const enum MeetingType { - MADE, APPLIED, + MADE, } const MinePage: NextPage = () => { From 47ed89c48300818716d9b5cc008e8f306711ebbe Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 02:32:33 +0900 Subject: [PATCH 19/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=9D=B4=EC=8A=88=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#631)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: edit title style * style: fade out feed list --- pages/post/index.tsx | 49 ++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/pages/post/index.tsx b/pages/post/index.tsx index a5ec2ecc..d87905fa 100644 --- a/pages/post/index.tsx +++ b/pages/post/index.tsx @@ -230,29 +230,31 @@ export default function PostPage() { }} /> - - 이 모임의 다른 피드 - - {postsInMeeting?.map(post => { - if (!post) return; - return ( - - - 0 && ( + + 이 모임의 다른 피드 + + {postsInMeeting?.map(post => { + if (!post) return; + return ( + + + } - /> - - - ); - })} - - + /> + + + ); + })} + + + )} SOPT 모임들의 최신 피드 @@ -311,6 +313,9 @@ const FeedListTitle = styled('h3', { marginBottom: '24px', color: '$white', fontStyle: 'T4', + fontSize: '20px', + fontWeight: 600, + lineHeight: '30px', }); const FeedList = styled('ul', { display: 'flex', From 2b24c369ca9c2a584f15392ed709d9ec31071d7d Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 02:49:17 +0900 Subject: [PATCH 20/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=ED=95=A9=EC=84=B1=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 좋아요 합성 * feat: crew_status 삭제 * feat: 리뷰반영 --- pages/index.tsx | 56 ++++++++++++------- .../Feed/FeedItem/MeetingInfo.tsx | 3 + .../page/meetingDetail/Feed/FeedPanel.tsx | 6 +- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index 7e1ecc89..f350fe5c 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,6 @@ import { ampli } from '@/ampli'; -import { useInfinitePosts } from '@api/post/hooks'; +import { useInfinitePosts, useMutationUpdateLike } from '@api/post/hooks'; +import LikeButton from '@components/button/LikeButton'; import FeedItem from '@components/page/meetingDetail/Feed/FeedItem'; import MeetingInfo from '@components/page/meetingDetail/Feed/FeedItem/MeetingInfo'; import MobileFeedListSkeleton from '@components/page/meetingDetail/Feed/Skeleton/MobileFeedListSkeleton'; @@ -12,10 +13,12 @@ import { useDisplay } from '@hooks/useDisplay'; import { useIntersectionObserver } from '@hooks/useIntersectionObserver'; import type { NextPage } from 'next'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import { styled } from 'stitches.config'; const Home: NextPage = () => { const { isTablet } = useDisplay(); + const router = useRouter(); const { data: postsData, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts(TAKE_COUNT); @@ -25,25 +28,38 @@ const Home: NextPage = () => { } }; const { setTarget } = useIntersectionObserver({ onIntersect }); - const renderedPosts = postsData?.pages.map(post => ( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - - - - } - /> - - - )); + + const { mutate: mutateLikeInAllPost } = useMutationUpdateLike(TAKE_COUNT); + + const handleClickLike = + (postId: number) => (mutateCb: (postId: number) => void) => (e: React.MouseEvent) => { + e.preventDefault(); + ampli.clickFeedlistLike({ location: router.pathname }); + mutateCb(postId); + }; + + const renderedPosts = postsData?.pages.map(post => { + if (!post) return; + return ( + + + + } + HeaderSection={} + /> + + + ); + }); return ( <> diff --git a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx index 343b7205..cf19b38f 100644 --- a/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx +++ b/src/components/page/meetingDetail/Feed/FeedItem/MeetingInfo.tsx @@ -47,6 +47,9 @@ const Container = styled('div', { '&:hover': { border: '1px solid $gray500', }, + '@tablet': { + background: '$gray900', + }, }); const MeetingInfoWrapper = styled('div', { diff --git a/src/components/page/meetingDetail/Feed/FeedPanel.tsx b/src/components/page/meetingDetail/Feed/FeedPanel.tsx index fb13057d..424bdb35 100644 --- a/src/components/page/meetingDetail/Feed/FeedPanel.tsx +++ b/src/components/page/meetingDetail/Feed/FeedPanel.tsx @@ -1,6 +1,8 @@ import { ampli } from '@/ampli'; +import { useQueryGetMeeting } from '@api/meeting/hooks'; import { useInfinitePosts, useMutationUpdateLike } from '@api/post/hooks'; import { useQueryMyProfile } from '@api/user/hooks'; +import LikeButton from '@components/button/LikeButton'; import FeedCreateModal from '@components/feed/Modal/FeedCreateModal'; import { POST_MAX_COUNT, TAKE_COUNT } from '@constants/feed'; import { MasonryInfiniteGrid } from '@egjs/react-infinitegrid'; @@ -14,8 +16,6 @@ import { styled } from 'stitches.config'; import EmptyView from './EmptyView'; import FeedItem from './FeedItem'; import MobileFeedListSkeleton from './Skeleton/MobileFeedListSkeleton'; -import LikeButton from '@components/button/LikeButton'; -import { useQueryGetMeeting } from '@api/meeting/hooks'; interface FeedPanelProps { isMember: boolean; @@ -63,7 +63,7 @@ const FeedPanel = ({ isMember }: FeedPanelProps) => { const handleLikeClick = (postId: number) => (e: React.MouseEvent) => { e.preventDefault(); mutateLike(postId); - ampli.clickFeedlistLike({ crew_status: meeting?.approved, location: router.pathname }); + ampli.clickFeedlistLike({ location: router.pathname }); }; const renderedPosts = postsData?.pages.map(post => { From c6ce9b6d8365f0c3a7abab31fdfddeec48e8e4ee Mon Sep 17 00:00:00 2001 From: "Eunsu(Evan) Kim" Date: Sun, 10 Dec 2023 02:52:19 +0900 Subject: [PATCH 21/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=8A=94=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=20=EC=95=88=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20(#635)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useScrollRestoration.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hooks/useScrollRestoration.ts b/src/hooks/useScrollRestoration.ts index a9cc3493..d1c4fa37 100644 --- a/src/hooks/useScrollRestoration.ts +++ b/src/hooks/useScrollRestoration.ts @@ -1,12 +1,16 @@ import { NextRouter, useRouter } from 'next/router'; import { useEffect } from 'react'; +const blacklist = ['/post']; + export default function useScrollRestoration() { const router = useRouter(); useEffect(() => { if (!('scrollRestoration' in window.history)) return; + if (blacklist.includes(router.pathname)) return; + window.history.scrollRestoration = 'manual'; const onRouteChangeStart = () => { From 08bbe9e5a3d34312610ea3216bd8328f5b7a39e9 Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Sun, 10 Dec 2023 03:14:39 +0900 Subject: [PATCH 22/26] =?UTF-8?q?chore:=20=ED=94=BC=EB=93=9C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=88=98=EC=A0=95=20(#634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 폰트 수정사항 반영 * chore: 하단 margin 추가 * chore: author 영역 스타일 수정사항 반영 * chore: 모임 버튼 디자인 변경 * chore: 이 모임의 다른 피드, SOPT 모임들의 최신 피드 폰트 스타일 반영 * chore: 기존 스타일 제거 * chore: 폰트 스타일 수정 --- pages/post/index.tsx | 13 ++++--- .../feed/FeedPostViewer/FeedPostViewer.tsx | 36 +++++++++++++++---- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pages/post/index.tsx b/pages/post/index.tsx index d87905fa..e073ecff 100644 --- a/pages/post/index.tsx +++ b/pages/post/index.tsx @@ -308,14 +308,17 @@ const FeedListContainer = styled('div', { display: 'none', }, }); -const FeedListWrapper = styled('div', {}); +const FeedListWrapper = styled('div', { + '&:last-child': { + marginBottom: '140px', + }, +}); const FeedListTitle = styled('h3', { - marginBottom: '24px', - color: '$white', - fontStyle: 'T4', fontSize: '20px', - fontWeight: 600, + fontWeight: '600', lineHeight: '30px', + marginBottom: '24px', + color: '$white', }); const FeedList = styled('ul', { display: 'flex', diff --git a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx index c11ae153..1a0b7245 100644 --- a/src/components/feed/FeedPostViewer/FeedPostViewer.tsx +++ b/src/components/feed/FeedPostViewer/FeedPostViewer.tsx @@ -116,8 +116,7 @@ export default function FeedPostViewer({ {post.meeting.category} {post.meeting.title}
- {/* TODO: API 배포 후 모임 소개 수정 예정 */} - 모임 소개 + {post.meeting.desc} @@ -170,7 +169,7 @@ const ButtonContainer = styled('div', { const AuthorWrapper = styled('a', { display: 'flex', alignItems: 'center', - gap: '12px', + gap: '16px', }); const AuthorInfo = styled('div', { display: 'flex', @@ -180,11 +179,23 @@ const AuthorInfo = styled('div', { }); const AuthorName = styled('span', { color: '$gray10', - fontStyle: 'H5', + fontWeight: '600', + fontSize: '16px', + lineHeight: '22px', + '@tablet': { + fontSize: '14px', + lineHeight: '18px', + }, }); const UpdatedDate = styled('span', { color: '$gray300', - fontStyle: 'B4', + fontWeight: '600', + fontSize: '14px', + lineHeight: '18px', + '@tablet': { + fontSize: '12px', + lineHeight: '16px', + }, }); const ContentBody = styled('div', { display: 'flex', @@ -205,7 +216,7 @@ const GroupButton = styled('a', { }, '@tablet': { height: 'fit-content', - padding: '$14 $12', + padding: '$14 $12 $14 $14', }, }); const GroupThumbnail = styled('img', { @@ -223,6 +234,17 @@ const GroupInformation = styled('div', { flex: '1', gap: '8px', color: '$gray100', + fontSize: '13px', + lineHeight: '20px', + span: { + fontWeight: '600', + fontSize: '16px', + lineHeight: '22px', + '@tablet': { + fontSize: '14px', + lineHeight: '18px', + }, + }, 'span + span': { marginLeft: '$8', '@tablet': { @@ -343,6 +365,8 @@ const CommentListWrapper = styled('div', { }, }); const SAvatar = styled(Avatar, { + width: '44px', + height: '44px', '@tablet': { width: '40px', height: '40px', From 18d38d096c126cfe2a9aff71c6a77e93d2eecec6 Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 03:24:18 +0900 Subject: [PATCH 23/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=EC=9E=91=EC=84=B1?= =?UTF-8?q?=EC=9D=B4=20=EC=95=88=EB=96=A0=EC=9A=94=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/user/index.ts | 2 +- .../FeedCreateWithSelectMeetingModal.tsx | 2 +- src/components/modal/FloatingButtonModal.tsx | 22 ++++++++++++++----- .../page/postList/FloatingButton/index.tsx | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/api/user/index.ts b/src/api/user/index.ts index 7a772a87..8b841998 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -54,5 +54,5 @@ export const fetchMyProfile = async () => { }; export const fetchMeetingListOfUserAttend = async () => { - return api.get>('/user/v2/meeting/all'); + return api.get('/user/v2/meeting/all'); }; diff --git a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx index 2dcb3f9e..b9158a78 100644 --- a/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx +++ b/src/components/feed/Modal/FeedCreateWithSelectMeetingModal.tsx @@ -83,7 +83,7 @@ function FeedCreateWithSelectMeetingModal({ isModalOpened, handleModalClose }: C {!isFetchAttendMeetingLoading && ( { - const { isActive } = props; +const FloatingButtonModal = (props: { isActive: boolean; handleOptionClose: () => void }) => { + const { isActive, handleOptionClose } = props; const router = useRouter(); const overlay = useOverlay(); const { data: me } = useQueryMyProfile(); @@ -19,11 +19,23 @@ const FloatingButtonModal = (props: { isActive: boolean }) => { const { mutate: fetchUserAttendMeetingListMutate } = useMutation(fetchMeetingListOfUserAttend, { onSuccess: data => { queryClient.setQueryData(['fetchMeetingList', 'all'], data); - if (data.data.data.length === 0) { - overlay.open(({ isOpen, close }) => ); + if (data.data.length === 0) { + overlay.open(({ isOpen, close }) => ( + { + close(), handleOptionClose(); + }} + /> + )); } else { overlay.open(({ isOpen, close }) => ( - + { + close(), handleOptionClose(); + }} + /> )); } }, diff --git a/src/components/page/postList/FloatingButton/index.tsx b/src/components/page/postList/FloatingButton/index.tsx index 3adc6c09..775fa199 100644 --- a/src/components/page/postList/FloatingButton/index.tsx +++ b/src/components/page/postList/FloatingButton/index.tsx @@ -30,7 +30,7 @@ function FloatingButton() { - +
); From 5d3ad0fb16b182f5ce265d7e96741b91a007f319 Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Sun, 10 Dec 2023 03:43:03 +0900 Subject: [PATCH 24/26] =?UTF-8?q?fix:=20=EB=AA=A8=EC=9E=84=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A4=EC=97=90=EC=84=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=9E=98=20=EB=9C=A8=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useScrollRestoration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useScrollRestoration.ts b/src/hooks/useScrollRestoration.ts index d1c4fa37..0409af26 100644 --- a/src/hooks/useScrollRestoration.ts +++ b/src/hooks/useScrollRestoration.ts @@ -1,7 +1,7 @@ import { NextRouter, useRouter } from 'next/router'; import { useEffect } from 'react'; -const blacklist = ['/post']; +const blacklist = ['/post', '/make']; export default function useScrollRestoration() { const router = useRouter(); From 8ff91f9a33438cb3584a81f2febb1659f0e054d5 Mon Sep 17 00:00:00 2001 From: NaReum Date: Sun, 10 Dec 2023 03:54:28 +0900 Subject: [PATCH 25/26] =?UTF-8?q?=ED=94=BC=EB=93=9C=EA=B0=9C=EC=84=A4=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(#642)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 플로팅 버튼 위치 이슈 * feat: 스터 디 * feat: hover color --- .../SelectMeeting/SelectMeetingOptionItem.tsx | 7 +++---- .../feed/Modal/SelectMeeting/index.tsx | 1 + src/components/modal/FloatingButtonModal.tsx | 18 ++++-------------- .../page/postList/FloatingButton/index.tsx | 2 +- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/components/feed/Modal/SelectMeeting/SelectMeetingOptionItem.tsx b/src/components/feed/Modal/SelectMeeting/SelectMeetingOptionItem.tsx index 9db3ce67..a4bd06ec 100644 --- a/src/components/feed/Modal/SelectMeeting/SelectMeetingOptionItem.tsx +++ b/src/components/feed/Modal/SelectMeeting/SelectMeetingOptionItem.tsx @@ -33,17 +33,16 @@ const SelectItemWrapper = styled('div', { display: 'flex', padding: '10px 12px', borderRadius: '6px', - backgroundColor: '$gray600', mb: '$4', cursor: 'pointer', + '&:hover': { + backgroundColor: '$gray600', + }, variants: { isSelected: { true: { backgroundColor: '$gray600', }, - false: { - backgroundColor: 'transparent', - }, }, }, }); diff --git a/src/components/feed/Modal/SelectMeeting/index.tsx b/src/components/feed/Modal/SelectMeeting/index.tsx index 7da71bef..ff8c75f6 100644 --- a/src/components/feed/Modal/SelectMeeting/index.tsx +++ b/src/components/feed/Modal/SelectMeeting/index.tsx @@ -119,6 +119,7 @@ const SThumbnailImage = styled('div', { const SCategory = styled('p', { color: '$gray400', fontStyle: 'T3', + whiteSpace: 'nowrap', ml: '$20', '@tablet': { fontStyle: 'T4', diff --git a/src/components/modal/FloatingButtonModal.tsx b/src/components/modal/FloatingButtonModal.tsx index eef20dba..7d71a27b 100644 --- a/src/components/modal/FloatingButtonModal.tsx +++ b/src/components/modal/FloatingButtonModal.tsx @@ -18,24 +18,13 @@ const FloatingButtonModal = (props: { isActive: boolean; handleOptionClose: () = const queryClient = useQueryClient(); const { mutate: fetchUserAttendMeetingListMutate } = useMutation(fetchMeetingListOfUserAttend, { onSuccess: data => { + handleOptionClose(); queryClient.setQueryData(['fetchMeetingList', 'all'], data); if (data.data.length === 0) { - overlay.open(({ isOpen, close }) => ( - { - close(), handleOptionClose(); - }} - /> - )); + overlay.open(({ isOpen, close }) => ); } else { overlay.open(({ isOpen, close }) => ( - { - close(), handleOptionClose(); - }} - /> + )); } }, @@ -101,6 +90,7 @@ const Container = styled('div', { false: { animation: `${fadeOut} 200ms ease-out`, opacity: '0', + display: 'none', }, }, }, diff --git a/src/components/page/postList/FloatingButton/index.tsx b/src/components/page/postList/FloatingButton/index.tsx index 775fa199..dcfd2a52 100644 --- a/src/components/page/postList/FloatingButton/index.tsx +++ b/src/components/page/postList/FloatingButton/index.tsx @@ -47,7 +47,7 @@ const Container = styled('div', { borderRadius: '20px', flexType: 'center', background: '$white', - zIndex: '$3', + zIndex: '$2', transition: 'all 0.3s ease', variants: { isActive: { From 9ca903822ae67fbe9af76ad3df0e76d60b910d9b Mon Sep 17 00:00:00 2001 From: Jiyeon Baek <58380158+100Gyeon@users.noreply.github.com> Date: Sun, 10 Dec 2023 04:10:54 +0900 Subject: [PATCH 26/26] =?UTF-8?q?chore:=20margin-left=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(#644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/Modal/FeedFormPresentation.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/feed/Modal/FeedFormPresentation.tsx b/src/components/feed/Modal/FeedFormPresentation.tsx index 2665072c..73561b0c 100644 --- a/src/components/feed/Modal/FeedFormPresentation.tsx +++ b/src/components/feed/Modal/FeedFormPresentation.tsx @@ -355,6 +355,7 @@ const STitleInput = styled('input', { width: '100%', color: '$white', fontStyle: 'H3', + ml: '$8', '@tablet': { px: '$20', @@ -371,6 +372,7 @@ const SFeedContentTextArea = styled('textarea', { fontStyle: 'B2', color: '$white', backgroundColor: 'inherit', + ml: '$8', '@tablet': { px: '$20',