From 496f7a6a4c1f42a0ed525ced8da7f18175320a33 Mon Sep 17 00:00:00 2001 From: "SK\\ssssk" Date: Fri, 16 Aug 2024 22:25:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index a3c7bee8..d0640d0d 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -139,7 +139,7 @@ const Header = ({ ) : userId > 0 ? ( <> Date: Fri, 16 Aug 2024 22:26:48 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mypage/page.tsx | 29 +++++++++++++++++-- src/components/mypage/MyPageHeader.tsx | 19 +++++++----- .../mypage/MyPageHeaderContainer.tsx | 15 ++++++---- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 9336a653..7d54b7cf 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -1,20 +1,45 @@ import MyPageHeaderContainer from "@/containers/mypage/MyPageHeaderContainer"; import MyPageMainContainer from "@/containers/mypage/MyPageMainContainer"; +import { userResponseDto } from "@/types/UserDto"; +import { fetchWithAuth } from "@/utils/fetchWithAuth"; import { Metadata } from "next"; +import { cookies } from "next/headers"; export const metadata: Metadata = { title: "마이페이지", description: "Solitour 사용자 마이페이지", }; -export default function page() { +async function getUserInfo() { + const cookie = cookies().get("access_token"); + const response = await fetchWithAuth( + `${process.env.BACKEND_URL}/api/users/info`, + { + method: "GET", + headers: { + Cookie: `${cookie?.name}=${cookie?.value}`, + }, + }, + ); + + if (!response.ok) { + // This will activate the closest 'error.tsx' Error Boundary. + throw new Error(response.statusText); + } + + return response.json() as Promise; +} + + +export default async function page() { + const userInfo = await getUserInfo(); return (
- +
); diff --git a/src/components/mypage/MyPageHeader.tsx b/src/components/mypage/MyPageHeader.tsx index 1f24d29d..55634352 100644 --- a/src/components/mypage/MyPageHeader.tsx +++ b/src/components/mypage/MyPageHeader.tsx @@ -1,3 +1,4 @@ +import { userResponseDto } from "@/types/UserDto"; import Image from "next/image"; import Link from "next/link"; @@ -26,7 +27,11 @@ const dummyData: IDummyData = { user_image: null, }; -const MyPageHeader = () => { +interface IMyPageHeader { + userInfo: userResponseDto; +} + +const MyPageHeader = ({userInfo}:IMyPageHeader) => { return (

마이페이지

@@ -34,19 +39,19 @@ const MyPageHeader = () => {
{/* ? 유저의 썸네일 이미지가 있는지? */} {/* ? 썸네일 이미지가 없다면 남자인지 여자인지? => 만약에 성별을 선택안하게 되면 어떻게 해야할지? */} - {dummyData.user_image ? ( + {userInfo.userImage?.address ? ( {"user_image"} - ) : dummyData.user_sex == "man" ? ( + ) : userInfo.sex == "MALE" ? ( {"user_image"} {
- {dummyData.user_nickname} + {userInfo.nickname}
-
{dummyData.user_email}
+
{userInfo.email}
diff --git a/src/containers/mypage/MyPageHeaderContainer.tsx b/src/containers/mypage/MyPageHeaderContainer.tsx index edc562d2..c36d33a3 100644 --- a/src/containers/mypage/MyPageHeaderContainer.tsx +++ b/src/containers/mypage/MyPageHeaderContainer.tsx @@ -1,9 +1,12 @@ -"use client"; - import MyPageHeader from "@/components/mypage/MyPageHeader"; +import { userResponseDto } from "@/types/UserDto"; -const MyPageHeaderContainer = () => { - return ; +interface IMyPageHeaderContainer { + userInfo: userResponseDto; +} +const MyPageHeaderContainer = (props: IMyPageHeaderContainer) => { + return <> + + ; }; - -export default MyPageHeaderContainer; +export default MyPageHeaderContainer \ No newline at end of file From d155140a96f5e5f0351de8bfcbbe001434d6924b Mon Sep 17 00:00:00 2001 From: "SK\\ssssk" Date: Sun, 18 Aug 2024 00:26:44 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/mypage/change-nickname/route.ts | 34 ++++++ src/app/mypage/layout.tsx | 12 -- src/app/mypage/profile/page.tsx | 39 ++++++- src/components/mypage/MyProfile.tsx | 109 ++++++++++++------- src/containers/mypage/MyProfileContainer.tsx | 42 ++++++- src/middleware.ts | 4 +- 6 files changed, 177 insertions(+), 63 deletions(-) create mode 100644 src/app/api/mypage/change-nickname/route.ts delete mode 100644 src/app/mypage/layout.tsx diff --git a/src/app/api/mypage/change-nickname/route.ts b/src/app/api/mypage/change-nickname/route.ts new file mode 100644 index 00000000..80bb9ec5 --- /dev/null +++ b/src/app/api/mypage/change-nickname/route.ts @@ -0,0 +1,34 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function PUT(request: NextRequest) { + const access_cookie = request.cookies.get("access_token"); + if (!access_cookie) { + const refresh_cookie = request.cookies.get("refresh_token"); + if (!refresh_cookie) { + // 리프레시 토큰이 없으므로 요청 중단 + return new NextResponse("Refresh token not found", { status: 403 }); + } + // 리프레시 토큰으로 재발급 받아 재요청 보내기 위한 응답 + return new NextResponse("Refresh token not found", { status: 401 }); + } + + const data = await request.json(); + try { + const response = await fetch(`${process.env.BACKEND_URL}/api/users/nickname`, { + method: "PUT", + headers: { + Cookie: `${access_cookie?.name}=${access_cookie?.value}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(response.statusText); + } + + return NextResponse.json({ status: 200, message: "닉네임 변경 성공" }); + } catch (err) { + return new Response("Internal Server Error", { status: 500 }); + } +} diff --git a/src/app/mypage/layout.tsx b/src/app/mypage/layout.tsx deleted file mode 100644 index b1be0647..00000000 --- a/src/app/mypage/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( -
- {children} -
- ); -} diff --git a/src/app/mypage/profile/page.tsx b/src/app/mypage/profile/page.tsx index 8804dae4..5334d141 100644 --- a/src/app/mypage/profile/page.tsx +++ b/src/app/mypage/profile/page.tsx @@ -1,20 +1,47 @@ import MyProfileContainer from "@/containers/mypage/MyProfileContainer"; +import { userResponseDto } from "@/types/UserDto"; +import { fetchWithAuth } from "@/utils/fetchWithAuth"; import { Metadata } from "next"; +import { cookies } from "next/headers"; export const metadata: Metadata = { title: "마이페이지-프로필 설정", description: "Solitour 사용자 마이페이지-프로필 설정", }; -export default function page() { - return ( -
; +} + + + export default async function page() { + const userInfo = await getUserInfo(); + + return ( +
- -
+ + ); } \ No newline at end of file diff --git a/src/components/mypage/MyProfile.tsx b/src/components/mypage/MyProfile.tsx index 5f0bc61b..bdd7c52c 100644 --- a/src/components/mypage/MyProfile.tsx +++ b/src/components/mypage/MyProfile.tsx @@ -1,3 +1,4 @@ +import { userResponseDto } from "@/types/UserDto"; import Image from "next/image"; import Link from "next/link"; import { RefObject } from "react"; @@ -10,35 +11,19 @@ interface IMyProfileProps { onDrop: (e: React.DragEvent) => void; imageUrl: string; onChangeImageUploadInputHandler: (e: any) => void; -} -interface IDummyData { - user_id?: number; - user_status_id?: string; - user_oauth_id?: string; - user_nickname?: string; - user_age?: number | null; - user_sex?: string | null; - user_email?: string | null; - user_phone_number?: string | null; - user_image?: string; - // is_admin: boolean, + userInfo: userResponseDto; + submitChangeNicknameHandler: () => void; + nickname: string; + changeNickname: (value: string) => void; + defaultNickname: string; + message: string; } -const dummyData: IDummyData = { - user_id: 1, - user_status_id: "1", - user_oauth_id: "1", - user_nickname: "하몽님", - user_age: 20, - user_sex: "woman", - user_email: "sola240@gmail.com", - user_phone_number: "010-1234-5678", - // user_image: null, -}; +const NICKNAME_LENGTH = 20; const MyProfile = (props: IMyProfileProps) => { return ( -
+
@@ -72,16 +57,14 @@ const MyProfile = (props: IMyProfileProps) => { onDragOver={props.onDragOver} onDrop={props.onDrop} > - {/* ? 유저의 썸네일 이미지가 있는지? */} - {/* ? 썸네일 이미지가 없다면 남자인지 여자인지? => 만약에 성별을 선택안하게 되면 어떻게 해야할지? */} - {props.imageUrl != "/" ? ( + {props.userInfo.userImage?.address ? ( {"user_image"} - ) : dummyData.user_sex == "man" ? ( + ) : props.userInfo.sex == "MALE" ? ( {"user_image"} {
-
- 닉네임 +
+ 닉네임 *
+ + +
+ +
+ { + props.message != "" && + + {`변경이 ${props.message} 했습니다.`} + + } + + {props.nickname.length}/{NICKNAME_LENGTH} + +
+
+
+
+
+ 이메일 +
-
- 0/50 -
-
- 이메일 - * +
+ 성별
@@ -164,7 +191,7 @@ const MyProfile = (props: IMyProfileProps) => {
카카오톡
- 2024.06.01 + {props.userInfo.userImage.createdDate}
{ +interface IMyProfileContainer { + userInfo: userResponseDto; +} + +const MyProfileContainer = ({userInfo}: IMyProfileContainer) => { const imageUploadRef = useRef(null); const [, setIsDragging] = useState(false); const [imageUrl, setImageUrl] = useState("/"); - + const [nickname, setNickname] = useState(userInfo.nickname); + const [defaultNickname, setDefaultNickname] = useState(userInfo.nickname); + const [message, setMessage] = useState(""); const onDragEnter = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); @@ -48,8 +56,30 @@ const MyProfileContainer = () => { const result = URL.createObjectURL(file); setImageUrl(result); }; + const submitChangeNicknameHandler = async () => { + if (nickname == "" && nickname == defaultNickname) return; + const res = await fetchWithAuth("/api/mypage/change-nickname", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ nickname: nickname }), + }) + + const data = await res.json(); + if (data.status == 200) { + setDefaultNickname(nickname); + setMessage("성공"); + } else { + setMessage("실패"); + } + } + + const changeNickname = (value: string) => { + setNickname(value); + setMessage(""); + } - // TODO : 디폴트 이미지로 돌아가는 방법에 대해서도 코드 작성이 필요하다. 나중에 서버에 이미지 업로드 될 때 같이 변경해서 변경할 예정 return ( { imageUploadRef={imageUploadRef} imageUrl={imageUrl} onChangeImageUploadInputHandler={onChangeImageUploadInputHandler} + userInfo={userInfo} + submitChangeNicknameHandler={submitChangeNicknameHandler} + nickname={nickname} + changeNickname={changeNickname} + defaultNickname={defaultNickname} + message={message} /> ); }; diff --git a/src/middleware.ts b/src/middleware.ts index 8bdae95a..51f4d821 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -7,7 +7,8 @@ export async function middleware(request: NextRequest) { pathname.startsWith("/informations/write") || pathname.startsWith("/informations/edit") || pathname.startsWith("/gathering/write") || - pathname.startsWith("/diary") + pathname.startsWith("/diary") || + pathname.startsWith("/mypage") ) { const token = request.cookies.get("access_token"); const refresh_token = request.cookies.get("refresh_token"); @@ -62,5 +63,6 @@ export const config = { "/gathering/write", "/diary/:path*", "/auth/:path*", + "/mypage/:path*", ], };