Skip to content

Commit

Permalink
유저-정보페이지-퍼블리싱,서버연결 (#39)
Browse files Browse the repository at this point in the history
* Refactor : 서버 API 엔드포인트 상수로 관리

* Refactor : UserAvatar 컴포넌트 분리

* Minor : 스타일 변경

* New : 유저정보 페이지 구현
  • Loading branch information
jobkaeHenry authored Nov 15, 2023
1 parent f22ee31 commit 62bb4c2
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 28 deletions.
38 changes: 38 additions & 0 deletions client/src/app/user/[userId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";
import CustomAppbar from "@/components/CustomAppbar";
import { Container, Paper } from "@mui/material";
import React from "react";

type Props = {
children: React.ReactNode;
};

const UserInfoPageLayout = ({ children }: Props) => {

return (
<Paper>
<CustomAppbar
title={""}
buttonTitle={"설정"}
onClickButton={() => {
console.log("눌림");
}}
/>
<Container sx={{ p: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
<Paper
sx={{
display: "flex",
position: "relative",
flexDirection: "column",
gap: 2,
p: 2,
}}
>
{children}
</Paper>
</Container>
</Paper>
);
};

export default UserInfoPageLayout;
36 changes: 34 additions & 2 deletions client/src/app/user/[userId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
const page = ({ params }: { params: { userId: string } }) => {
return <div>{params.userId}님의 page 입니다</div>;
import UserInfoCard from "@/components/user/info/UserInfoCard";
import axios from "@/libs/axios";
import { MyInfoInterface } from "@/types/auth/myInfo";

import NoResult from "@/assets/images/noResult.png";
import { Box } from "@mui/material";
import Image from "next/image";
import PostCardList from "@/components/post/PostCardList";

const page = async ({ params }: { params: { userId: string } }) => {
try {
const { data } = await axios.get<
MyInfoInterface & { isFollowing: boolean }
>(`/user/${params.userId}/summary`);
return (
<>
<UserInfoCard data={data} />
<PostCardList/>
</>
);
} catch {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
py: 8,
}}
>
<Image priority src={NoResult} alt="no result alert" />
</Box>
);
}
};

export default page;
1 change: 1 addition & 0 deletions client/src/components/CustomAppbar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import { AppBar, Button, IconButton, Toolbar, Typography } from "@mui/material";
import GoBackIcon from "@/assets/icons/GoBackIcon.svg";
import { MouseEventHandler, memo } from "react";
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/newpost/NewPostTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const NewPostTextEditor = ({ onContentChange }: NewPostTextEditorInterface) => {
return (
<>
<ReactQuill
style={{ height: 300 }}
style={{ height: 100 }}
modules={modules}
placeholder="입력해주세요"
onChange={(content, _d, _s, editor) => {
Expand Down
25 changes: 10 additions & 15 deletions client/src/components/post/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { PostInterface } from "@/types/post/PostInterface";
import { MoreVertOutlined } from "@mui/icons-material";
import {
Avatar,
Box,
Card,
CardActions,
Expand All @@ -25,6 +24,7 @@ import useLikePostMutation from "@/queries/post/useLikePostMutation";
import useUnLikePostMutation from "@/queries/post/useUnLikePostMutation";
import "../newpost/quill.mention.css";
import { sanitize } from "isomorphic-dompurify";
import UserAvatar from "../user/info/UserAvatar";

const PostCard = ({
postAttachUrls,
Expand All @@ -49,14 +49,10 @@ const PostCard = ({

return (
<Card sx={{ display: "flex", gap: 2, p: 2 }}>
<Avatar
sx={{ bgcolor: "secondary.main" }}
sizes="40"
<UserAvatar
src={profileImgUrls[0]}
data-testid="avatar"
>
{profileImgUrls[0] || String(id)[0].toUpperCase()}
</Avatar>
fallback={String(id)[0].toUpperCase()}
/>
<Box sx={{ width: "100%" }}>
{/* Header */}
<Box
Expand Down Expand Up @@ -119,14 +115,13 @@ const PostCard = ({
)}
{/* CTA */}
<CardActions sx={{ px: 0, justifyContent: "end", gap: 2 }}>
<ButtonBase data-testid="commentBtn" aria-label="comment">
<ButtonBase
data-testid="commentBtn"
aria-label="comment"
onClick={() => openPostDetailPage(id, String(postNo))}
>
<CommentIcon />
<Typography
variant="label"
onClick={() => openPostDetailPage(id, String(postNo))}
>
{commentCount ?? 0}
</Typography>
<Typography variant="label">{commentCount ?? 0}</Typography>
</ButtonBase>
<ButtonBase
data-testid="likeBtn"
Expand Down
11 changes: 6 additions & 5 deletions client/src/components/post/PostCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import NoResult from "@/assets/images/noResult.png";
import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage";

function PostCardList(props: UseGetPostListQueryInterface) {
const { data, fetchNextPage, isFetchingNextPage, hasNextPage } =
const { data, fetchNextPage, isFetchingNextPage, hasNextPage, isSuccess,isLoading } =
useGetPostListInfiniteQuery({
...props,
headers: { Authorization: getTokenFromLocalStorage() },
Expand All @@ -31,12 +31,13 @@ function PostCardList(props: UseGetPostListQueryInterface) {

return (
<div>
{hasResult ? (
{hasResult &&
isSuccess &&
// 검색결과가 있을시
data?.pages.map((page) =>
page.list.map((post) => <PostCard {...post} key={post.postNo} />)
)
) : (
)}
{isSuccess && !hasResult && (
// 검색결과 없을 시
<Box
sx={{
Expand All @@ -50,7 +51,7 @@ function PostCardList(props: UseGetPostListQueryInterface) {
</Box>
)}
{/* 로딩창 */}
{isFetchingNextPage ? (
{isFetchingNextPage||isLoading ? (
<Box sx={{ width: "100%", textAlign: "center", py: 1 }}>
<CircularProgress />
</Box>
Expand Down
21 changes: 21 additions & 0 deletions client/src/components/user/info/UserAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Avatar, AvatarProps } from "@mui/material";
import { ReactNode } from "react";

interface UserAvatarProps extends AvatarProps {
fallback: ReactNode;
}

const UserAvatar = ({ src, fallback,sx,...others }: UserAvatarProps) => {
return (
<Avatar
sx={{...sx, bgcolor: "secondary.main" }}
src={src}
data-testid="avatar"
{...others}
>
{typeof fallback === "string" ? fallback[0].toUpperCase() : fallback}
</Avatar>
);
};

export default UserAvatar;
58 changes: 58 additions & 0 deletions client/src/components/user/info/UserInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MyInfoInterface } from "@/types/auth/myInfo";
import React from "react";
import UserAvatar from "@/components/user/info/UserAvatar";
import { Box, Button, Typography } from "@mui/material";

type Props = {
data: MyInfoInterface & { isFollowing: boolean };
};

const UserInfo = ({ data }: Props) => {

const {
id,
followerCount,
nickname,
profileImages,
isFollowing,
introduction,
} = data;

return (
<Box sx={{display:'flex',alignItems:'center',flexDirection:'column', gap:1}}>
<UserAvatar
src={profileImages[0]?.attachUrl}
fallback={id}
sx={{width:"56px",height:'56px'}}
/>
<Box sx={RowWrapperSX}>
<Typography color="primary.main" fontWeight="bold">
{nickname}
</Typography>
<Typography color="text.secondary">@{id}</Typography>
</Box>
<Box sx={{height:48}}>
<Typography color="text.secondary">
{introduction ?? "자기소개가 없습니다"}
</Typography></Box>
<Box sx={RowWrapperSX}>
<Typography fontWeight="bold">{followerCount}</Typography>
<Typography color="text.secondary">팔로워</Typography>
</Box>
{isFollowing ? (
<Button variant="outlined" fullWidth>
언팔로우
</Button>
) : (
<Button fullWidth>팔로우</Button>
)}
</Box>
);
};

const RowWrapperSX = {
display: "flex",
gap: 1,
};

export default UserInfo;
14 changes: 13 additions & 1 deletion client/src/const/serverPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,16 @@ export const ATTACH_FILE = (type: ATTACH_FILE_ResourceType, resourcePk: number)
/**
* 알콜리스트를 받아오는 URL
*/
export const GET_ALCOHOL_LIST = '/alcohols' as const
export const GET_ALCOHOL_LIST = '/alcohols' as const

/**
* 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요를 요청
* @param id 게시글의 PK
*/
export const POST_LIKE_URL = (id:string)=>`/posts/like/${id}` as const

/**
* 포스트의 PK를 입력받아 해당 PK의 게시글의 좋아요 취소를 요청
* @param id 게시글의 PK
*/
export const POST_UN_LIKE_URL = (id:string)=>`/posts/like-cancel/${id}` as const
3 changes: 1 addition & 2 deletions client/src/queries/post/useLikePostMutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ const useLikePostMutation = () => {
*/
export const useLikePostMutationFn = async (id: PostInterface["postNo"]) => {
const token = getTokenFromLocalStorage();
// FIXME 리터럴제거
const { data } = await axios.post(`/posts/like/${id}`, null, {
const { data } = await axios.post(POST_LIKE_URL(String(id)), null, {
headers: { Authorization: token },
});
return data;
Expand Down
4 changes: 2 additions & 2 deletions client/src/queries/post/useUnLikePostMutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PostInterface } from "@/types/post/PostInterface";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { getPostListInfiniteQueryKey } from "./useGetPostListInfiniteQuery";
import getTokenFromLocalStorage from "@/utils/getTokenFromLocalStorage";
import { POST_UN_LIKE_URL } from "@/const/serverPath";
/**
* 좋아요를 취소하고, 게시글을 invalidation 하는 쿼리
* @returns
Expand All @@ -26,8 +27,7 @@ const useLikePostMutation = () => {
*/
export const useLikePostMutationFn = async (id: PostInterface["postNo"]) => {
const token = getTokenFromLocalStorage();
// FIXME 리터럴제거
const { data } = await axios.post(`/posts/like-cancel/${id}`, null, {
const { data } = await axios.post(POST_UN_LIKE_URL(String(id)), null, {
headers: { Authorization: token },
});
return data;
Expand Down

0 comments on commit 62bb4c2

Please sign in to comment.