diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 6660bbb1..6fe8ac34 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -41,9 +41,11 @@ function RootLayout({
-
-
-
+ +
+
+
+ {children} @@ -62,8 +64,12 @@ const Applayout = styled.div` overflow: hidden; height: calc(100svh - 55px); ${media.medium} { - padding-top: 37px; + padding-top: 34px; } `; +const Stars = styled.div` + height: 6px; +`; + export default RootLayout; diff --git a/apps/web/components/my/MbtiSelect.tsx b/apps/web/app/my/components/MbtiSelect.tsx similarity index 100% rename from apps/web/components/my/MbtiSelect.tsx rename to apps/web/app/my/components/MbtiSelect.tsx diff --git a/apps/web/components/my/TabContainer.tsx b/apps/web/app/my/components/TabContainer.tsx similarity index 100% rename from apps/web/components/my/TabContainer.tsx rename to apps/web/app/my/components/TabContainer.tsx diff --git a/apps/web/app/my/components/UserInfoContainer.tsx b/apps/web/app/my/components/UserInfoContainer.tsx new file mode 100644 index 00000000..1dbddaf0 --- /dev/null +++ b/apps/web/app/my/components/UserInfoContainer.tsx @@ -0,0 +1,87 @@ +import ImageUploadButton from "components/common/ImageUploadButton"; +import { useGetUserInfo } from "hooks/useGetUserInfo"; +import Path from "lib/Path"; +import Link from "next/link"; +import { Gender } from "types/user"; +import styled, { css } from "styled-components"; +import { media } from "@chooz/ui/styles/media"; + +function UserInfoContainer() { + const { data: userInfo } = useGetUserInfo(); + + if (!userInfo) return <>; + + const { gender, username, age, mbti } = userInfo; + + return ( + <> + + + + + + <> + {gender === Gender.MALE ? "남" : "여"} + + {age} + + {mbti} + + + {username} + + 프로필 수정 + + + + ); +} + +const AddImageButtonWrapper = styled.div` + float: left; +`; + +const Profile = styled.div` + display: flex; + flex-direction: column; + padding-left: 17px; + + ${media.medium} { + padding-left: 28px; + } +`; + +const UserInfo = styled.span` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 86px; + background-color: ${({ theme }) => theme.palette.background.black}; + border-radius: 4px; +`; + +const Divider = styled.div` + width: 1px; + height: 8px; + margin: 0 4px; + background-color: ${({ theme }) => theme.palette.ink.base}; +`; + +const Nickname = styled.span` + margin-top: 8px; + ${({ theme }) => css` + ${theme.textStyle.Title_Small}; + color: ${theme.palette.ink.lighter}; + `}; +`; + +const ProfileModifyButton = styled.button` + width: 71px; + height: 30px; + border: 1px solid ${({ theme }) => theme.palette.border.light}; + border-radius: 4px; + margin-top: 25px; +`; + +export default UserInfoContainer; diff --git a/apps/web/app/my/components/VoteCountContainer.tsx b/apps/web/app/my/components/VoteCountContainer.tsx new file mode 100644 index 00000000..b50e4df4 --- /dev/null +++ b/apps/web/app/my/components/VoteCountContainer.tsx @@ -0,0 +1,44 @@ +import styled from "styled-components"; +import useGetVoteCount from "../services/useGetVoteCount"; + +function VoteCountContainer() { + const voteCountList = useGetVoteCount(); + + return ( + + {voteCountList.map(({ voteTypeText, count }) => { + return ( + + {count} + {voteTypeText} + + ); + })} + + ); +} + +const Container = styled.section` + display: flex; + flex-direction: row; + justify-content: space-evenly; + width: 100%; + margin-top: 24px; +`; + +const VoteType = styled.div` + display: flex; + flex-direction: column; +`; + +const VoteCount = styled.span` + ${({ theme }) => theme.textStyle.Title_Large}; + font-family: NeoDunggeunmo, Pretendard Variable, -apple-system, BlinkMacSystemFont, system-ui, + Roboto, "Helvetica Neue"; +`; + +const VoteTypeText = styled.span` + color: ${({ theme }) => theme.palette.ink.light}; +`; + +export default VoteCountContainer; diff --git a/apps/web/components/my/VoteItemDesktop.tsx b/apps/web/app/my/components/VoteItemDesktop.tsx similarity index 98% rename from apps/web/components/my/VoteItemDesktop.tsx rename to apps/web/app/my/components/VoteItemDesktop.tsx index 40531fbe..3cf48ff4 100644 --- a/apps/web/components/my/VoteItemDesktop.tsx +++ b/apps/web/app/my/components/VoteItemDesktop.tsx @@ -5,10 +5,10 @@ import { timeDataProcessing } from "lib/utils/timeDataProcessing"; import Image from "next/image"; import { AIcon, BIcon } from "public/icons"; import styled, { css } from "styled-components"; -import { MyPageVote } from "types/vote"; +import { MyVote } from "types/vote"; interface Props { - vote: MyPageVote; + vote: MyVote; } function VoteItemDesktop({ vote }: Props) { diff --git a/apps/web/components/my/VoteItemMobile.tsx b/apps/web/app/my/components/VoteItemMobile.tsx similarity index 98% rename from apps/web/components/my/VoteItemMobile.tsx rename to apps/web/app/my/components/VoteItemMobile.tsx index 766e540f..b3960e3e 100644 --- a/apps/web/components/my/VoteItemMobile.tsx +++ b/apps/web/app/my/components/VoteItemMobile.tsx @@ -7,10 +7,10 @@ import { timeDataProcessing } from "lib/utils/timeDataProcessing"; import Image from "next/image"; import { AIcon, BIcon, BookmarkIcon } from "public/icons"; import styled, { css } from "styled-components"; -import { MyPageVote } from "types/vote"; +import { MyVote } from "types/my"; interface Props { - vote: MyPageVote; + vote: MyVote; } function VoteItemMobile({ vote }: Props) { diff --git a/apps/web/components/my/VoteList.tsx b/apps/web/app/my/components/VoteList.tsx similarity index 80% rename from apps/web/components/my/VoteList.tsx rename to apps/web/app/my/components/VoteList.tsx index afbdb4f0..07336189 100644 --- a/apps/web/components/my/VoteList.tsx +++ b/apps/web/app/my/components/VoteList.tsx @@ -1,11 +1,11 @@ import { media } from "@chooz/ui/styles/media"; import styled from "styled-components"; -import { MyPageVote } from "types/vote"; +import { MyVote } from "types/vote"; import VoteItemDesktop from "./VoteItemDesktop"; import VoteItemMobile from "./VoteItemMobile"; interface Props { - voteList: MyPageVote[]; + voteList: MyVote[]; } function VoteList({ voteList }: Props) { @@ -14,12 +14,12 @@ function VoteList({ voteList }: Props) { return ( <> - {voteList.map((vote: MyPageVote, index: number) => ( + {voteList.map((vote: MyVote, index: number) => ( ))} - {voteList.map((vote: MyPageVote, index: number) => ( + {voteList.map((vote: MyVote, index: number) => ( ))} diff --git a/apps/web/app/my/edit/page.tsx b/apps/web/app/my/edit/page.tsx index 3f149242..56765915 100644 --- a/apps/web/app/my/edit/page.tsx +++ b/apps/web/app/my/edit/page.tsx @@ -2,8 +2,7 @@ import { Button, transitions } from "@chooz/ui"; import ImageUploadButton from "components/common/ImageUploadButton"; -import MbtiSelect from "components/my/MbtiSelect"; -import TabContainer from "components/my/TabContainer"; +import MbtiSelect from "app/my/components/MbtiSelect"; import { useGetUserInfo } from "hooks/useGetUserInfo"; import { uploadProfileImageAPI } from "lib/apis/upload"; import { updateUserInfo } from "lib/apis/user"; @@ -18,6 +17,7 @@ import styled, { css } from "styled-components"; import { media } from "styles/media"; import { Gender } from "types/user"; import { IMAGE_CATEGORY_LIST } from "types/vote"; +import TabContainer from "../components/TabContainer"; function ProfileEditPage() { const router = useRouter(); diff --git a/apps/web/app/my/page.tsx b/apps/web/app/my/page.tsx index 70f3878c..af741b50 100644 --- a/apps/web/app/my/page.tsx +++ b/apps/web/app/my/page.tsx @@ -1,83 +1,33 @@ "use client"; import { media } from "@chooz/ui/styles/media"; -import { useQuery } from "@tanstack/react-query"; -import ImageUploadButton from "components/common/ImageUploadButton"; -import TabContainer from "components/my/TabContainer"; -import VoteList from "components/my/VoteList"; -import { useGetUserInfo } from "hooks/useGetUserInfo"; -import { getVoteCount, VoteListType } from "lib/apis/user"; import { MY_PAGE_VOTE_TYPE } from "lib/constants"; -import Path from "lib/Path"; -import { reactQueryKeys } from "lib/queryKeys"; -import Link from "next/link"; import { useState } from "react"; -import useInfiniteMyPageVoteListService from "services/useInfiniteMyPageVoteListService"; import styled, { css } from "styled-components"; -import { Gender } from "types/user"; +import TabContainer from "./components/TabContainer"; +import VoteList from "./components/VoteList"; +import CountVoteContainer from "./components/VoteCountContainer"; +import { MyVoteListType } from "types/my"; +import useInfiniteMyVoteListService from "./services/useInfiniteMyPageVoteListService"; +import UserInfoContainer from "./components/UserInfoContainer"; function MyPage() { - const [selectedTab, setSelectedTab] = useState("created"); + const [selectedTab, setSelectedTab] = useState("created"); const onClickSelectedTab = (e: React.MouseEvent) => { - setSelectedTab(e.currentTarget.value as VoteListType); + setSelectedTab(e.currentTarget.value as MyVoteListType); }; - const loadVoteCount = () => { - const { data } = useQuery(reactQueryKeys.myPageVoteCount(), getVoteCount); - return { data }; - }; - - const { voteList, subscribe } = useInfiniteMyPageVoteListService({ + const { voteList, subscribe } = useInfiniteMyVoteListService({ size: 7, voteType: selectedTab, }); - const { data: userInfo } = useGetUserInfo(); - const { data: voteCount } = loadVoteCount(); - - if (!userInfo) return
데이터 없음
; - if (!voteCount) return
데이터 없음
; - - const { gender, username, age, mbti } = userInfo; - const { countCreatedVote, countParticipatedVote, countBookmarkedVote } = voteCount; - return ( - - - - - - <> - {gender === Gender.MALE ? "남" : "여"} - - {age} - - {mbti} - - - {username} - - 프로필 수정 - - - {/* @TODO api 연결하면 map 사용 */} - - - {countCreatedVote} - 작성한 투표 - - - {countParticipatedVote} - 참여한 투표 - - - {countBookmarkedVote} - 북마크 투표 - - + + theme.palette.background.black}; - border-radius: 4px; -`; - -const Divider = styled.div` - width: 1px; - height: 8px; - margin: 0 4px; - background-color: ${({ theme }) => theme.palette.ink.base}; -`; - -const Nickname = styled.span` - margin-top: 8px; - ${({ theme }) => css` - ${theme.textStyle.Title_Small}; - color: ${theme.palette.ink.lighter}; - `}; -`; - -const ProfileModifyButton = styled.button` - width: 71px; - height: 30px; - border: 1px solid ${({ theme }) => theme.palette.border.light}; - border-radius: 4px; - margin-top: 25px; -`; - -const NumberOfVoteSection = styled.section` - display: flex; - flex-direction: row; - justify-content: space-evenly; - width: 100%; - margin-top: 24px; -`; - -const NumberOfVoteContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const NumberOfVote = styled.span` - ${({ theme }) => theme.textStyle.Title_Large}; - font-family: NeoDunggeunmo, Pretendard Variable, -apple-system, BlinkMacSystemFont, system-ui, - Roboto, "Helvetica Neue"; -`; - -const NumberOfVoteText = styled.span` - color: ${({ theme }) => theme.palette.ink.light}; -`; - const TabContainerWrapper = styled.div` position: relative; top: -8px; diff --git a/apps/web/app/my/services/useGetVoteCount.ts b/apps/web/app/my/services/useGetVoteCount.ts new file mode 100644 index 00000000..0585b5ee --- /dev/null +++ b/apps/web/app/my/services/useGetVoteCount.ts @@ -0,0 +1,28 @@ +import { useQuery } from "@tanstack/react-query"; +import { getVoteCount } from "lib/apis/my"; +import { reactQueryKeys } from "lib/queryKeys"; + +function useGetVoteCount() { + const { data } = useQuery(reactQueryKeys.myVoteCount(), getVoteCount); + + const { countCreatedVote, countParticipatedVote, countBookmarkedVote } = data || {}; + + const voteCountList = [ + { + voteTypeText: "작성한 투표", + count: countCreatedVote, + }, + { + voteTypeText: "참여한 투표", + count: countParticipatedVote, + }, + { + voteTypeText: "북마크 투표", + count: countBookmarkedVote, + }, + ]; + + return voteCountList; +} + +export default useGetVoteCount; diff --git a/apps/web/services/useInfiniteMyPageVoteListService.ts b/apps/web/app/my/services/useInfiniteMyPageVoteListService.ts similarity index 70% rename from apps/web/services/useInfiniteMyPageVoteListService.ts rename to apps/web/app/my/services/useInfiniteMyPageVoteListService.ts index df3da206..fef61a18 100644 --- a/apps/web/services/useInfiniteMyPageVoteListService.ts +++ b/apps/web/app/my/services/useInfiniteMyPageVoteListService.ts @@ -1,16 +1,16 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteScroll } from "hooks/useInfiniteScroll"; -import { getMyPageVoteList, GetMyPageVoteListRequest } from "lib/apis/user"; +import { getMyVoteList, GetMyVoteListRequest } from "lib/apis/my"; import { reactQueryKeys } from "lib/queryKeys"; -type Params = Omit; +type Params = Omit; // @Todo infinite service 합칠 수 있는 방법 고민하기 -export default function useInfiniteMyPageVoteListService(params: Params) { +export default function useInfiniteMyVoteListService(params: Params) { const { data, fetchNextPage } = useInfiniteQuery( reactQueryKeys.voteList([params.voteType]), - ({ pageParam }) => getMyPageVoteList({ ...params, page: pageParam?.page || 0 }), + ({ pageParam }) => getMyVoteList({ ...params, page: pageParam?.page || 0 }), { getNextPageParam: ({ last, number }) => { if (last) return undefined; diff --git a/apps/web/lib/apis/my.ts b/apps/web/lib/apis/my.ts new file mode 100644 index 00000000..22ee9461 --- /dev/null +++ b/apps/web/lib/apis/my.ts @@ -0,0 +1,52 @@ +import apiClient from "lib/apis/apiClient"; +import { MyVote, MyVoteListType } from "types/my"; + +export interface GetMyVoteListRequest { + page: number; + size: number; + voteType: MyVoteListType; +} + +interface GetMyVoteListResponse { + content: MyVote[]; + empty: boolean; + first: boolean; + last: boolean; + number: number; + numberOfElements: number; + size: number; +} + +export const getMyVoteList = async ({ page, size, voteType }: GetMyVoteListRequest) => { + const response = await apiClient.get("api/user/mypage", { + params: { + page, + size, + voteType, + }, + }); + return response.data; +}; + +interface GetVoteCountResponse { + countCreatedVote: number; + countParticipatedVote: number; + countBookmarkedVote: number; +} + +export const getVoteCount = async () => { + const response = await apiClient.get("api/user/mypage/count"); + return response.data; +}; + +interface UpdateUserInfoRequest { + nickname: string; + // image: string; + mbti: string; + categoryList: string[]; +} + +export const updateUserInfo = async (updateUserInfoRequest: UpdateUserInfoRequest) => { + const response = await apiClient.patch("api/user/mypage/edit", updateUserInfoRequest); + return response.data; +}; diff --git a/apps/web/lib/apis/user.ts b/apps/web/lib/apis/user.ts index d19925d9..5feacf7d 100644 --- a/apps/web/lib/apis/user.ts +++ b/apps/web/lib/apis/user.ts @@ -1,5 +1,5 @@ import { GetUserInfoResponse } from "types/user"; -import { CategoryNameType, MyPageVote } from "types/vote"; +import { CategoryNameType } from "types/vote"; import apiClient from "./apiClient"; interface AddInfoRequest { @@ -30,55 +30,3 @@ export const getUserInfo = async () => { const response = await apiClient.get("api/oauth/login"); return response.data; }; - -export type VoteListType = "created" | "participated" | "bookmarked"; - -export interface GetMyPageVoteListRequest { - page: number; - size: number; - voteType: VoteListType; -} -interface GetMyPageVoteListResponse { - content: MyPageVote[]; - empty: boolean; - first: boolean; - last: boolean; - number: number; - numberOfElements: number; - size: number; -} - -// @Note api 경로에 따라 분리하는 게 맞나, 도메인에 따라 분리하는 게 맞나 -export const getMyPageVoteList = async ({ page, size, voteType }: GetMyPageVoteListRequest) => { - const response = await apiClient.get("api/user/mypage", { - params: { - page, - size, - voteType, - }, - }); - return response.data; -}; - -interface GetVoteCountResponse { - countCreatedVote: number; - countParticipatedVote: number; - countBookmarkedVote: number; -} - -export const getVoteCount = async () => { - const response = await apiClient.get("api/user/mypage/count"); - return response.data; -}; - -interface UpdateUserInfoRequest { - nickname: string; - // image: string; - mbti: string; - categoryList: string[]; -} - -export const updateUserInfo = async (updateUserInfoRequest: UpdateUserInfoRequest) => { - const response = await apiClient.patch("api/user/mypage/edit", updateUserInfoRequest); - return response.data; -}; diff --git a/apps/web/lib/queryKeys.ts b/apps/web/lib/queryKeys.ts index ad0f070c..86327b7c 100644 --- a/apps/web/lib/queryKeys.ts +++ b/apps/web/lib/queryKeys.ts @@ -1,6 +1,6 @@ +import { MyVoteListType } from "types/my"; import { Agetype } from "types/user"; import { GenderType } from "types/vote"; -import { VoteListType } from "./apis/user"; export const queryKeys = { USER_INFO: "userInfo" as const, @@ -12,8 +12,8 @@ export const queryKeys = { DETAIL_ANALYSIS: "analysisByVoteId" as const, DETAIL_FILTERED_ANALYSIS: "filteredAnalysisByVoteId" as const, VOTING_CHECK: "votingCheck" as const, - MY_PAGE_VOTE_LIST: "myPageVoteList" as const, - MY_PAGE_VOTE_COUNT: "myPageVoteCount" as const, + MY_VOTE_LIST: "myVoteList" as const, + MY_VOTE_COUNT: "myVoteCount" as const, }; export const reactQueryKeys = { @@ -34,6 +34,6 @@ export const reactQueryKeys = { detailFilterdAnalysis: (id: number, mbti: string, gender: string, age: string) => [queryKeys.DETAIL_FILTERED_ANALYSIS, id, mbti, gender, age] as const, votingCheck: (id: number) => [queryKeys.VOTING_CHECK, id] as const, - myPageVoteList: (params: VoteListType) => [queryKeys.MY_PAGE_VOTE_LIST, ...params] as const, - myPageVoteCount: () => [queryKeys.MY_PAGE_VOTE_COUNT] as const, + myVoteList: (params: MyVoteListType) => [queryKeys.MY_VOTE_LIST, ...params] as const, + myVoteCount: () => [queryKeys.MY_VOTE_COUNT] as const, }; diff --git a/apps/web/types/my.ts b/apps/web/types/my.ts new file mode 100644 index 00000000..ebc3b8fc --- /dev/null +++ b/apps/web/types/my.ts @@ -0,0 +1,7 @@ +import { Vote } from "./vote"; + +export interface MyVote extends Vote { + countComment: number; +} + +export type MyVoteListType = "created" | "participated" | "bookmarked"; diff --git a/apps/web/types/vote.ts b/apps/web/types/vote.ts index 2de101ed..546540ab 100644 --- a/apps/web/types/vote.ts +++ b/apps/web/types/vote.ts @@ -58,10 +58,6 @@ export interface Vote { imageB: string | null; } -export interface MyPageVote extends Vote { - countComment: number; -} - export type AorB = "A" | "B"; export type Voting = PostVotingRequest;