Skip to content
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

Feat: 프로필 설정 페이지 구현 및 공용 폰트 업데이트 #30

Merged
merged 34 commits into from
Feb 15, 2024

Conversation

seoyoung-min
Copy link
Contributor

@seoyoung-min seoyoung-min commented Feb 14, 2024

개요

  • 프로필 설정에 페이지를 구현했습니다.
  • 공용함수, 컴포넌트를 구현했습니다. (작업사항 참고)
  • 자잘한 오류를 해결하고 수정했습니다.

작업 사항

  • (유틸) 실시간 닉네임 중복 검사에 사용할 debounce 유틸 함수 추가.
  • (유틸) 파일을 base64로 인코딩해주는 유틸 함수 추가. (프로필 설정, 아이템 추가 등 이미지 미리보기 필요한 곳에서 모두 사용)
  • (유틸) 파일 압축을 위한 라이브러리 설치 및 유틸 함수 추가 (이미지 미리보기, 이미지 업로드 과정에서 모두 사용)
  • (공용 스타일) font.css.ts에 새로운 폰트 스타일 추가
  • (공용 컴포넌트) 헤더에 사용되는 파란색 버튼을 공용 컴포넌트로 구현했습니다. (프로필 수정, 리스트 생성 등에서 사용)
  • (프로필 수정) 닉네임 중복검사, 프로필 수정, 이미지 업로드 api 추가
  • (프로필 수정) 퍼블리싱, react-hook-form 연결
  • (프로필 수정) 기존 프로필 값 defaultValue로 설정
  • (프로필 수정) 닉네임 중복검사 실시간으로 진행 (디바운스 사용, 닉네임 수정이 이뤄지면 0.5초마다 실행)
  • (프로필 수정) 버튼 비활성화 조건 추가 (현재 정보에서 수정된 사항이 없을때, pending 중에)
  • (프로필 수정) 이미지 업로드(jpg,png,jpeg만 허용), 압축, 미리보기 구현
  • (프로필 수정) 기본 배경/프로필 이미지 중 선택 기능
  • (프로필 수정) 로딩 UI
  • (프로필 수정) 에러처리 (닉네임 중복, 닉네임 길이, 닉네임 한/영만 허용, 소개 길이, 파일 50MB 이상)
  • (기타) 카카오톡 공유하기를 위해 추가한 script에서 preload관련 에러 발생하여 수정 (lazy 로드)
  • (기타) 지금까지 미뤄둔 '공용 함수, 훅, 컴포넌트에 주석달기'를 진행했습니다.

참고 사항 (optional)

  • 사파리 input 길이관련 이슈는 아래 '관련 이슈' 참고
  • 이미지 크기가 커짐에따라 이미지 로딩속도가 너무 느려 이미지 압축 과정을 추가했습니다.
    • 이미지 압축을 위해 browser-image-compression 라이브러리를 사용했습니다. 많이 사용되고 최근까지도 업데이트 하는 것으로 확인했습니다.
    • 이미지 업로드와 미리보기가 있는 리스트 생성 > 아이템 추가 페이지에도 동일하게 적용하려고 합니다.
    • 배경 미리보기의 경우 div에 backgroundImage css로 추가하려고 했는데 Next/Image 컴포넌트보다 훨씬 느리게 로딩되고, 사이즈가 클 경우 아예 로딩되지 않는 이슈가 있었습니다. 그래서 Image 컴포넌트로 구현했습니다.
  • 아직 기본 배경이미지와 프로필 이미지가 없어, 이미지 '선택' 관련한 기능은 구현하지 못했습니다.
  • 로딩 UI는 구현하지 못했습니다

관련 이슈 (optional)


스크린샷

  • 마이페이지 > 프로필 설정 연결 / 프로필 조회하여 기본값 채우기 / router.back으로 뒤로가기
    프로필조회

  • 수정사항 있을 경우 버튼 활성화
    버튼활성화

  • 이미지 업로드 + 미리보기
    이미지업로드

  • 닉네임 에러
    에러핸들링

  • 닉네임 중복검사 디바운스
    닉네임중복검사디바운싱


리뷰어에게

  • 에러처리를 해본다고 했는데, 꼼꼼하게 다 된 건지 모르겠어요ㅠㅠ 에러 처리 할 경우들을 찾는게 어렵습니다!!
  • 유진님 헤더 컴포넌트 넘 잘썼고, 너무 잘 됩니다!!👍🙇‍♀️
  • 공용 폰트는 곧 작업하겠습니다 죄송해요 🥲🥲 (완료)
    • 아직 피그마에 많은 화면들이 이전 폰트랑 섞여 사용되고 있습니다. 아직 이전 폰트 스타일을 그대로 놔두었는데, 피그마 수정이 완료된 후에 기존 폰트들을 삭제하겠습니다!
    • 아래 이미지 참고. (참고로 Body Regular와 Medium 이름이 서로 바뀔 수 있음 주의...)
Screenshot 2024-02-15 at 02 39 51

Copy link

vercel bot commented Feb 14, 2024

@seoyoung-min is attempting to deploy a commit to the Eujin Ahn's projects Team on Vercel.

A member of the Team first needs to authorize it.

@seoyoung-min seoyoung-min added Feat 구현 Fix 에러/버그 해결 Refactor 리팩토링 추후 확인 필요 나중에 다시 살펴봐야할 PR (미해결 문제 존재, 급한 머지 등) labels Feb 14, 2024
Copy link
Contributor

@ParkSohyunee ParkSohyunee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님, 프로필 수정 기능 구현하시느라 정말 고생많으셨습니다!!!
보다보니 어느새 2시간이 흘러있네요 ㅋㅋㅋㅋ 그래도 하나하나 보면서 서영님께서 많이 생각하시고 작성한 코드라는 생각이 내내 들었습니다. 덕분에 시간이 금방갈 정도로 몰입해서 본 것 같습니다.
어깨너머 리액트 훅 폼에 대해 많이 배운것 같습니다. 감사합니다.🩷

Comment on lines 12 to 15

interface updateProfileParams {
userId: Number;
data: UserProfileEditType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님 타입 대문자로 수정해야할 것 같습니다..!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

소현님 매번 이 부분을 놓치네요ㅠㅠ 앞으로 더욱 꼼꼼하게 체크하겠습니다!
감사합니다 🙇‍♀️👏

Comment on lines 21 to 30

//프로필 수정
const profileData: UserProfileInfoType = {
nickname,
description,
backgroundImageUrl,
profileImageUrl,
};
const result = await axiosInstance.patch(`/users/${userId}`, profileData);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님 혹시 profileData 부분은 정의하지 않고 바로 사용하는 방법은 어떨까요..? 그렇게 된다면 타입을 선언하지 않아도 될 것 같고, UserProfileInfoType이 많이 사용되지 않는것 같아서 공용type에서도 제외하면 좋을 것 같아서요..! 🤔

const result = await axiosInstance.patch(`/users/${userId}`, {
    nickname,
    description,
    backgroundImageUrl,
    profileImageUrl,
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 너무 좋은 생각인 것 같습니다!

UserProfileInfoType을 2군데에서 사용하려고 공용으로 빼두었는데, 한 군데에서 쓸모 없게 된 상황이었어요! 공용 타입에서 빼는 것을 생각못하고 있었는데, 덕분에 공용 타입 삭제도 하고 코드도 훨씬 깔끔해졌네요😻👍

Comment on lines 31 to 32
if (result.status === 204 && (newBackgroundFileList !== null || newProfileFileList !== null)) {
//1. presignedUrl 생성요청
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님 이부분은 배경이나 프로필 이미지가 바뀔때만 실행하는 로직(즉, new url이 있는 경우)이 맞나요?
그렇다면 배경이나 프로필 이미지가 동일하다면 먼저 return을 시키는 방향으로 가면 어떨까요..?🤔 return문이 맨 마지막에 있는 것 보다 먼저 나오면 코드 읽기가 더 쉬울것 같아서요..!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜,, 최고입니다!! 코드 읽기 훨씬 수월하겠어요!
정말 감사합니다!!🥹

Comment on lines +6 to +10
const { onClickMoveToPage } = useMoveToPage();
return (
<>
<div>마이페이지</div>
<button onClick={onClickMoveToPage('account/profile')}>프로필설정</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뿌듯하네요😊 ㅋㅋㅋㅋ

Comment on lines +9 to +15
alignItems: 'center',

position: 'relative',

borderRadius: '30px',

overflow: 'hidden',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님 혹시 이렇게 각기 다른 속성이 한개씩만 있는 경우에는 공백없이 작성하는 것도 좋을 것 같은데 서영님 생각은 어떠신가요?!
css 컨벤션을 정했지만 그 이유가 가독성 측면이어서 지금처럼 4~5줄은 붙여도 될 것 같아서 의견드려봅니다..!
+. 하지만, 가독성은 개인차도 있을 것 같아서 참고로만 봐주시면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오! 저도 순서만 지켜진다면, 각기 다른 속성 한개씩만 있는 경우 공백없이 작성하는 것 좋아보입니다!! 내일 안건으로 이야기 나눠보면 좋을 것 같아요 :)👍

Comment on lines 5 to 10

export const editProfileToastMessage = {
editProfileSuccess: '프로필을 수정했습니다.🥰',
editProfileError: '프로필 수정에 실패했어요. 다시 시도해주세요.🥲',
imageSizeError: '사진이 너무 커요. 5MB 이하 사진을 넣어주세요.🥹',
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이모티콘 넘 기엽네요 서영님😊
그리고 토스트에러메세지 넣으면서 생각해본건데 다국어기능 지원을 위해 처음부터 아래처럼 ko와 en을 나누어 상수화해도 좋을 것 같다는 생각이 들었습니다, 그렇게되면 같은 Key를 사용할 수 있고, 언어부분(ko/en)만 객체의 키로 접근하거나, 파라미터로 보내주면 더 간결하게 다국어를 지원할 수 있을 것 같습니다.

export const toastMessage = {
  ko: {
    requiredLogin: '로그인이 필요해요.',
    limitFollow: `최대 ${MAX_FOLLOWING.toLocaleString('ko-KR')}명까지 팔로우할 수 있어요.`,
    editProfileSuccess: '프로필을 수정했습니다.🥰',
    editProfileError: '프로필 수정에 실패했어요. 다시 시도해주세요.🥲',
    imageSizeError: '사진이 너무 커요. 5MB 이하 사진을 넣어주세요.🥹',
  },
  // 언어변경 시, en 추가
  en: {
    editProfileSuccess: 'abcd',
    editProfileError: '...',
    imageSizeError: '...',
  },
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 소현님😻 이모지 넣는 것이 괜찮을지 논의가 필요한데, 우선은 넣어두었어요!ㅎㅎ

정말 좋은 생각인 것 같습니다! 우선 아래와 같이 수정해두었습니다! (gpt야 번역 고마워~)

Suggested change
export const editProfileToastMessage = {
editProfileSuccess: '프로필을 수정했습니다.🥰',
editProfileError: '프로필 수정에 실패했어요. 다시 시도해주세요.🥲',
imageSizeError: '사진이 너무 커요. 5MB 이하 사진을 넣어주세요.🥹',
};
const toastMessage = {
ko: {
uploadImageError: '이미지를 업로드에 실패했어요. 다시 업로드해주세요.🥲',
createListError: '리스트 생성에 실패했어요. 다시 시도해주세요.🥲',
updateProfileSuccess: '프로필을 수정했습니다.🥰',
updateProfileError: '프로필 수정에 실패했어요. 다시 시도해주세요.🥲',
imageSizeError: '사진이 너무 커요. 50MB 이하 사진을 넣어주세요.🥹',
},
en: {
uploadImageError: 'Failed to upload the image. Please try again.🥲',
createListError: 'Failed to create the list. Please try again.🥲',
updateProfileSuccess: 'Profile updated successfully.🥰',
updateProfileError: 'Failed to update the profile. Please try again.🥲',
imageSizeError: 'The image is too large. Please insert an image smaller than 50MB.🥹',
},
};
export default toastMessage;

Comment on lines -17 to 18
profileImageUrl?: string;
profileImageUrl?: string /**TODO: 옵셔널 없애기 */;
backgroundImageUrl?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 온보딩 완성후에 타입 같이 살펴보아요 서영님~~!

Comment on lines 6 to 19

export default function debounce<T extends (...args: any[]) => any>(callback: T, delay: number) {
let timeout: ReturnType<typeof setTimeout>;

return (...args: Parameters<T>): ReturnType<T> => {
let result: any;

if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
result = callback(args);
}, delay);

return result;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님, 이 디바운싱은 닉네임을 입력하면 리액트훅폼에서 onChange가 발생하는 것을 delay 시켜주는 함수가 맞나요?!
위의 코드에서 볼때는 value를 전달하고 있는 것 같아서 궁금해서 여쭤봅니다!!

+. 만약 value를 전달한다면 onChange는 그대로 발생하게하고, 에러검증하는 value를 디바운싱으로 감지하도록 하는 방법도 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

특정 함수(callback)을 실행이 delay되는 형태로 바꾸어 return 해주는 함수입니다!

  • 디바운스 함수에 닉네임중복검사 함수(=callback)를 전달
  • 닉네임중복검사 실행이 delay되는 형태의 새로운 함수를 return
  • 새로운 함수인 debouncedOnNicknameChange(e.target.value)로 닉네임중복검사 함수를 실행
  • => onChange가 아닌 닉네임중복검사 함수 실행이 delay되도록 함

즉 debounce 함수의 return은 새로운 디바운싱함수(?)이고, 디바운싱함수의 return은 callback함수의 실행값(여기서는 닉네임중복검사 실행결과 boolean) 입니다!

혹시 현재 실행되는 방식이 소현님께서 말씀하신 방법이 맞을까요??
그리고 소현님 말씀을 듣고 나니 debounce라는 함수명이 안어울리는 것 같기도 하네요ㅠㅠ!!

Copy link
Contributor

@ParkSohyunee ParkSohyunee Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님! 상세히 설명 해 주셔서 감사합니다.

  1. 우선 jsdoc에 return에 대한 설명도 추가하면 좋을 것 같습니다.
    (ex) @returns {function} 함수(callback)을 실행이 delay되는 형태로 바꾸어 return 해주는 함수

  2. 사실 제가 생각한 방식은 서영님께서 구현하신 방식이랑은 조금 다르게 생각했습니다!

  • onChange 이벤트는 그대로 발생한다.
  • 다만, api 호출날릴때 들어가는 value를 onChange value가 아닌 debounced value로 넣는다.
  • debounced value는 value를 받아서 delay 후에 value를 return 한다. (useDebounce hook으로 구현)
  • 예를 들어, ㄱ,ㅏ,ㄴ,ㅏ,ㄷ,ㅏ에 대한 중복검사를 했다면, debounced value를 받아서 가,나,다에 대한 중복검사를 실행하는 형태
  1. 사실 위 방법도 단점인 부분도 있어서 서영님께서 구현하신 방법으로 api 요청을 delay시키는 방법으로 가는것도 좋아보입니다. (+. 서영님 말씀대로 파일명만 조금 더 알맞게 수정하면 더욱 좋을 것 같습니다! )

Comment on lines +6 to +15

const fileToBase64 = async (file: File, setter: (arg: string) => void) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setter(reader.result as string);
};
};

export default fileToBase64;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

Comment on lines 76 to 87
//위에 지우기

export const headlineLarge = style({
fontSize: '3.2rem',
fontWeight: '600',
lineHeight: '4.0rem',
});

export const headlineMedium = style({
fontSize: '2.8rem',
fontWeight: '600',
lineHeight: '3.6rem',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다, 서영님❤️‍🔥

@seoyoung-min seoyoung-min changed the title Feat: 프로필 설정 페이지 구현 Feat: 프로필 설정 페이지 구현 및 공용 폰트 업데이트 Feb 14, 2024
Copy link
Contributor

@ParkSohyunee ParkSohyunee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님 리뷰확인하여 다시 리뷰 남겼습니다~ 감사합니다. 🥰

Comment on lines +70 to +71
} else {
newBackgroundImageRegister.onChange(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호! 완전 이해했습니다 서영님~! 보통 쓰던 방식과는 달라서 여쭤봤는데 명확히 해결되었습니다! ㅎㅎ

Comment on lines 6 to 19

export default function debounce<T extends (...args: any[]) => any>(callback: T, delay: number) {
let timeout: ReturnType<typeof setTimeout>;

return (...args: Parameters<T>): ReturnType<T> => {
let result: any;

if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
result = callback(args);
}, delay);

return result;
};
Copy link
Contributor

@ParkSohyunee ParkSohyunee Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서영님! 상세히 설명 해 주셔서 감사합니다.

  1. 우선 jsdoc에 return에 대한 설명도 추가하면 좋을 것 같습니다.
    (ex) @returns {function} 함수(callback)을 실행이 delay되는 형태로 바꾸어 return 해주는 함수

  2. 사실 제가 생각한 방식은 서영님께서 구현하신 방식이랑은 조금 다르게 생각했습니다!

  • onChange 이벤트는 그대로 발생한다.
  • 다만, api 호출날릴때 들어가는 value를 onChange value가 아닌 debounced value로 넣는다.
  • debounced value는 value를 받아서 delay 후에 value를 return 한다. (useDebounce hook으로 구현)
  • 예를 들어, ㄱ,ㅏ,ㄴ,ㅏ,ㄷ,ㅏ에 대한 중복검사를 했다면, debounced value를 받아서 가,나,다에 대한 중복검사를 실행하는 형태
  1. 사실 위 방법도 단점인 부분도 있어서 서영님께서 구현하신 방법으로 api 요청을 delay시키는 방법으로 가는것도 좋아보입니다. (+. 서영님 말씀대로 파일명만 조금 더 알맞게 수정하면 더욱 좋을 것 같습니다! )

Comment on lines 8 to 16
},
en: {
uploadImageError: 'Failed to upload the image. Please try again.🥲',
createListError: 'Failed to create the list. Please try again.🥲',
updateProfileSuccess: 'Profile updated successfully.🥰',
updateProfileError: 'Failed to update the profile. Please try again.🥲',
imageSizeError: 'The image is too large. Please insert an image smaller than 50MB.🥹',
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋ 서영님 en은 그냥 주석으로 달아놓자는 의미였는데 이렇게 잘 작성을 해주셨네요 ㅎㅎ👍🍫

Copy link
Contributor

@Nahyun-Kang Nahyun-Kang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 얼른 프로필 바꾸고 싶어요!! 🤩 서영님 이제 presigned 달인이시네용!!👍코드도 깔끔하게 잘 쓰셔서 부럽습니다👀 LGTM!

@seoyoung-min seoyoung-min merged commit dd199f9 into 8-Sprinters:dev Feb 15, 2024
0 of 2 checks passed
@seoyoung-min seoyoung-min deleted the feature/account-profile branch June 3, 2024 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feat 구현 Fix 에러/버그 해결 Refactor 리팩토링 추후 확인 필요 나중에 다시 살펴봐야할 PR (미해결 문제 존재, 급한 머지 등)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

사파리 input 가로길이 고정 불가 에러
3 participants