-
Notifications
You must be signed in to change notification settings - Fork 0
Feature(#77): 마이 페이지 API 연동 관련 기본 로직 작업 #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
281fdd2
44e5e1e
b912de5
eeba8f2
32c67dd
02dbb5d
b61c349
d8fab4b
0ce28ac
fcec906
33c9766
de30d45
650e07f
ff2c70b
69c1488
37fcbbf
509a152
2ccec62
e3a6860
b3b2c0a
a7c2fc6
7326dd3
39be6d2
81fed72
2bd061a
dbe6d37
b2b4ac9
e4427f6
5046423
ee5c8f3
f58361e
f4c006a
7f03e2a
937e3b1
f5cbb08
02cbb2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,12 +22,11 @@ const useAuth = () => { | |
Authorization: accessToken | ||
} | ||
}) | ||
.then((response) => { | ||
localStorage.setItem("username", response.data.username); | ||
localStorage.setItem("email", response.data.email); | ||
.then((result) => { | ||
localStorage.setItem("username", result.data.username); | ||
localStorage.setItem("email", result.data.email); | ||
}) | ||
.catch((error) => { | ||
console.log(error.response?.status); | ||
if (error.response?.status === 401) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 401 아닌 다른 상태코드에서는 어떻게 처리되나요? |
||
showToast({ message: "로그인이 만료되었어요.", type: "alert" }); | ||
navigate("/userauth"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,188 +1,156 @@ | ||
import { useState } from "react"; | ||
import { useState, useEffect } from "react"; | ||
import axios from "axios"; | ||
import Button from "@/components/Button/Button"; | ||
import ProfileBig from "@/assets/imgs/profileBig.png"; | ||
import SubLogoOriginal from "@/assets/imgs/subLogoOriginal.png"; | ||
import EnterIcon from "@/assets/svgs/enter.svg?react"; | ||
import { useToast } from "@/components/Toast/useToast"; | ||
import ReplayLectureCard from "./ReplayLectureCard"; | ||
import useAuth from "@/hooks/useAuth"; | ||
import { useNavigate } from "react-router-dom"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import 되는 순서를 라이브러리끼리, 컴포넌트끼리 .. 이런식으로 묶어두면 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 구현만 진행하느라 정말 기초적인 부분인데 신경쓰지 못했던 것 같습니다. 감사합니다 :) |
||
|
||
interface MyPageSectionProps { | ||
profileImage: string; | ||
} | ||
const NICKNAME_REGEXP = /^[a-zA-Z0-9가-힣]{3,15}$/; | ||
|
||
const NICKNAME_REGEXP = /^(?![0-9-_.]+$)[가-힣A-Za-z0-9-_.]{1,10}$/; | ||
|
||
const DUMMY_DATA = [ | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.11", | ||
title: "프로그래밍 기초", | ||
description: "컴퓨터 프로그래밍의 기본 원리를 배우는 입문 강좌. 언어 선택부터 기본 구문까지." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.12", | ||
title: "요리의 기초", | ||
description: "기본적인 요리 기술과 요리법을 배우는 강좌. 초보자를 위한 쉬운 레시피와 요리 팁 제공." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.13", | ||
title: "디지털 마케팅", | ||
description: "디지털 마케팅 전략과 소셜 미디어 활용 방법을 배우는 실용적인 강좌." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.14", | ||
title: "수학의 이해", | ||
description: "기본적인 수학 개념과 문제 해결 기술을 배우는 강좌. 수학에 대한 두려움을 극복할 수 있도록 도움." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.15", | ||
title: "역사 탐험", | ||
description: "세계 역사의 주요 사건과 인물을 탐구하는 강좌. 역사를 통해 현재를 이해하는 데 도움." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.16", | ||
title: "창의적 글쓰기", | ||
description: "창의적인 글쓰기 기술과 아이디어 발상법을 배우는 강좌. 글쓰기를 통한 자기 표현 방법 탐구." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.17", | ||
title: "영화 분석", | ||
description: "영화의 기술적 요소와 예술적 가치를 분석하는 강좌. 영화를 깊이 있게 이해하는 방법 제공." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.18", | ||
title: "피트니스 가이드", | ||
description: "건강하고 효율적인 운동 방법을 배우는 강좌. 개인별 맞춤 운동 계획 설정 방법 제공." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.19", | ||
title: "사진술 입문", | ||
description: "기초 사진 기술과 구도, 조명 활용법을 배우는 강좌. 사진을 통해 예술적 감각 향상." | ||
}, | ||
{ | ||
profileImage: "", | ||
duration: "12:34", | ||
user: "볼록이", | ||
date: "2023.11.20", | ||
title: "환경 보호", | ||
description: "환경 보호와 지속 가능한 생활 방식에 대해 배우는 강좌. 일상에서 실천할 수 있는 환경 보호 활동 소개." | ||
} | ||
]; | ||
type Lecture = { | ||
date: string; | ||
duration: string; | ||
user: string; | ||
title: string; | ||
description: string; | ||
}; | ||
|
||
const MyPageSection = ({ profileImage }: MyPageSectionProps) => { | ||
const MyPageSection = () => { | ||
const showToast = useToast(); | ||
const [nickname, setNickname] = useState("볼록이"); | ||
const [isNicknameEdit, setIsNicknameEdit] = useState(false); | ||
const navigate = useNavigate(); | ||
const { checkAuth } = useAuth(); | ||
const [username, setUsername] = useState(localStorage.getItem("username") || ""); | ||
const [isUsernameEdit, setIsUsernameEdit] = useState(false); | ||
const [isValid, setIsValid] = useState(true); | ||
const [lectureList, setLectureList] = useState<Lecture[]>([]); | ||
|
||
useEffect(() => { | ||
checkAuth(); | ||
axios | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 질문에 답변을 해본다면..? fetch API로 안되는건 어떤 것이에요? |
||
.get(`${import.meta.env.VITE_API_SERVER_URL}/lecture/list`, { | ||
headers: { Authorization: localStorage.getItem("token") } | ||
}) | ||
.then((result) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 특별한 경우가 아니면 await 표현법이 더나을거에요. |
||
setLectureList(result.data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. result 라는 이름, data 라는 속성 모두..구체적이지 않은 이름이네요. |
||
}) | ||
.catch((error) => { | ||
if (error.response.status === 401) { | ||
showToast({ message: "로그인이 만료되었어요.", type: "alert" }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 401은 꼭 만료되는 것만을 의미할까요? |
||
navigate("/userauth"); | ||
} else showToast({ message: "정보를 불러오는데 오류가 발생했어요.", type: "alert" }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alert ? 은 참고로 그렇게 좋은 ux는 아닙니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확인했습니다! 다만, 해당 코드의 경우, 브라우저의 alert 창을 띄우는 방식이 아닌 따로 개발한 Toast를 띄우는 형태인데 이 또한 좋지 않은 방향일까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러메시지를 소스코드에 직접 하드코딩하진 않고, 별도로 묶어서 관리하긴하죠. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 개선 사항으로 추가하겠습니다! 감사합니다 :) |
||
}); | ||
}, []); | ||
|
||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const newNickname = event.target.value; | ||
setNickname(newNickname); | ||
setIsValid(NICKNAME_REGEXP.test(newNickname)); | ||
const newUsername = event.target.value; | ||
setUsername(newUsername); | ||
setIsValid(NICKNAME_REGEXP.test(newUsername)); | ||
}; | ||
|
||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (e.key === "Enter") handleEditButtonClicked(); | ||
}; | ||
|
||
const handleEditButtonClicked = () => { | ||
if (isNicknameEdit) { | ||
if (isUsernameEdit) { | ||
if (isValid) { | ||
axios | ||
.post( | ||
`${import.meta.env.VITE_API_SERVER_URL}/profile`, | ||
{ username }, | ||
{ | ||
headers: { Authorization: localStorage.getItem("token") } | ||
} | ||
) | ||
.then((result) => { | ||
localStorage.setItem("username", result.data.username); | ||
localStorage.setItem("email", result.data.email); | ||
setUsername(result.data.username); | ||
}) | ||
.catch((error) => { | ||
if (error.response.status === 401) { | ||
showToast({ message: "로그인이 만료되었어요.", type: "alert" }); | ||
navigate("/userauth"); | ||
} else showToast({ message: "정보를 불러오는데 오류가 발생했어요.", type: "alert" }); | ||
}); | ||
showToast({ message: "닉네임 변경을 완료했습니다.", type: "success" }); | ||
setIsNicknameEdit(false); | ||
setIsUsernameEdit(false); | ||
} else { | ||
showToast({ message: "올바르지 않은 닉네임입니다.", type: "alert" }); | ||
} | ||
} else { | ||
showToast({ message: "닉네임을 변경합니다.", type: "default" }); | ||
setIsNicknameEdit(true); | ||
setIsUsernameEdit(true); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col items-center my-32 sm:mt-36"> | ||
<section className="flex relative w-11/12 max-w-3xl p-6 flex-col items-center gap-6 rounded-2xl border-default shadow-xl"> | ||
<img | ||
src={profileImage ? profileImage : ProfileBig} | ||
alt="프로필 이미지" | ||
className="absolute -top-20 w-40 h-40 sm:w-56 sm:h-56 sm:-top-28" | ||
/> | ||
<img src={ProfileBig} alt="프로필 이미지" className="absolute -top-20 w-40 h-40 sm:w-56 sm:h-56 sm:-top-28" /> | ||
|
||
<input | ||
type="text" | ||
value={nickname} | ||
value={username} | ||
onChange={handleChange} | ||
onKeyDown={handleKeyDown} | ||
size={nickname.length + 1 || 3} | ||
size={username.length + 1 || 3} | ||
placeholder="닉네임" | ||
maxLength={10} | ||
disabled={!isNicknameEdit} | ||
disabled={!isUsernameEdit} | ||
className="mt-20 sm:mt-28 semibold-32 text-center border-b-2 border-grayscale-gray focus:border-grayscale-black outline-none transition duration-200 rounded-none disabled:border-none disabled:text-grayscale-black disabled:bg-grayscale-white" | ||
/> | ||
|
||
<div className="flex flex-col gap-1 justify-center items-center"> | ||
{isNicknameEdit ? ( | ||
{isUsernameEdit ? ( | ||
<> | ||
{" "} | ||
<p className="semibold-18 text-grayscale-darkgray">사용할 닉네임을 입력해 주세요.</p> | ||
<p className={`medium-12 ${isValid ? "text-boarlog-100" : "text-alert-100"}`}> | ||
한글, 영문, 숫자, -, _, ., 총 10자 이내 | ||
한글, 영문, 숫자 총 10자 이내 | ||
</p> | ||
</> | ||
) : ( | ||
<p className="semibold-16 text-grayscale-darkgray">[email protected]</p> | ||
<p className="semibold-16 text-grayscale-darkgray">{localStorage.getItem("email")}</p> | ||
)} | ||
</div> | ||
|
||
<div className="w-full max-w-sm"> | ||
<Button | ||
type="full" | ||
buttonStyle={isNicknameEdit && isValid ? "blue" : "black"} | ||
buttonStyle={isUsernameEdit && isValid ? "blue" : "black"} | ||
onClick={handleEditButtonClicked} | ||
> | ||
<EnterIcon className="fill-grayscale-white" /> | ||
{isNicknameEdit ? "닉네임 변경 완료하기" : "닉네임 변경하기"} | ||
{isUsernameEdit ? "닉네임 변경 완료하기" : "닉네임 변경하기"} | ||
</Button> | ||
</div> | ||
|
||
<h3 className="mt-12 semibold-32">강의 다시보기</h3> | ||
<div className="flex flex-col w-full gap-6"> | ||
{DUMMY_DATA.map((value, index) => ( | ||
<ReplayLectureCard | ||
key={index} | ||
profileImage={value.profileImage} | ||
date={value.date} | ||
duration={value.duration} | ||
user={value.user} | ||
title={value.title} | ||
description={value.description} | ||
/> | ||
))} | ||
<div className="flex flex-col w-full gap-6 items-center"> | ||
{lectureList.length ? ( | ||
lectureList.map((value, index) => ( | ||
<ReplayLectureCard | ||
key={index} | ||
date={value.date} | ||
duration={value.duration} | ||
user={value.user} | ||
title={value.title} | ||
description={value.description} | ||
onClick={() => navigate("/")} | ||
/> | ||
)) | ||
) : ( | ||
<> | ||
<img className="max-w-sm w-full" src={SubLogoOriginal} /> | ||
<p className="semibold-16 text-grayscale-darkgray -mb-4">아직 수강한 강의가 존재하지 않아요.</p> | ||
<p className="semibold-16 text-grayscale-darkgray mb-6">새로운 강의를 수강하러 가볼까요?</p> | ||
</> | ||
)} | ||
</div> | ||
</section> | ||
</div> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,7 +136,7 @@ const UserAuthSection = ({ isSignIn, setIsSignIn }: UserAuthSectionProps) => { | |
type="text" | ||
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent" | ||
placeholder="이메일을 입력해주세요" | ||
maxLength={50} | ||
maxLength={30} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EMAIL_MAX_LENGTH 처럼 상수로 따로 관리해줘도 좋을 것 같아요! |
||
value={email} | ||
onChange={handleEmailChange} | ||
/> | ||
|
@@ -151,7 +151,7 @@ const UserAuthSection = ({ isSignIn, setIsSignIn }: UserAuthSectionProps) => { | |
type="text" | ||
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent" | ||
placeholder="닉네임을 입력해주세요" | ||
maxLength={15} | ||
maxLength={10} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 NICKNAME_MAX_LENGTH로 바꿔 볼 수 있겠네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이후 상수로 관리하는 과정에서 포함해서 개선해보겠습니다! |
||
value={username} | ||
onChange={handleUsernameChange} | ||
/> | ||
|
@@ -168,7 +168,7 @@ const UserAuthSection = ({ isSignIn, setIsSignIn }: UserAuthSectionProps) => { | |
type="password" | ||
className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent" | ||
placeholder="비밀번호을 입력해주세요" | ||
maxLength={50} | ||
maxLength={30} | ||
value={password} | ||
onChange={handlePasswordChange} | ||
/> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
많이 반복되는건 아니지만, result.data 를 반복적으로 접근하는건 객체탐색비용만 들어가서.. 해체할당문법으로 미리 변수로 담으면 더 좋죠.
data 라는 이름은 의미가 없어서 별로입니다.