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

[FE] 마이페이지에서 자신의 기본 계좌 번호 수정 기능 추가 #880

Open
wants to merge 38 commits into
base: fe-dev
Choose a base branch
from

Conversation

pakxe
Copy link
Contributor

@pakxe pakxe commented Dec 23, 2024

issue

구현 목적

마이페이지에서 자신의 기본 계좌 번호를 수정할 수 있는 페이지를 추가합니다.

구현 사항

1. 기존의 EditAccountPage에서 UI만 분리

같은 모습의 페이지가 반복적으로 사용되어야 했고, UI만 분리하여도 넘겨줘야 하는 props가 4개로 많지 않았기 때문에 UI를 분리하여 EditAccountPageView라는 컴포넌트로 만들었습니다.

EditAccountPageView에 전달해야하는 props는 다음과 같습니다.

Prop Name Type Description
bankName BankName 페이지 첫 진입 시 보여줄 은행 이름.
accountNumber string 페이지 첫 진입 시 보여줄 계좌 번호.
onSubmit Function 수정 버튼을 눌렀을 때 실행될 함수.
redirectUrlOnSubmit string 완료되었을 때 이동할 URL.


2. 기존의 EditAccountPage와 새로 추가될 마이페이지의 계좌 수정 페이지인 EditUserAccountPage를 1번 컴포넌트를 사용해 구현

1번에서 만든 EditAccountPageView를 재사용해 두 페이지를 구현했습니다.

const EditAccountPage = () => {
  const {bankName, accountNumber} = useEventDataContext();
  const {patchUser} = useRequestPatchUser();

  const location = useLocation();
  const redirectUrlOnSubmit = `/${getEventBaseUrl(location.pathname)}/admin`;

  return (
    <EditAccountPageView
      bankName={bankName}
      accountNumber={accountNumber}
      onSubmit={patchUser}
      redirectUrlOnSubmit={redirectUrlOnSubmit}
    />
  );
};
const EditUserAccountPage = () => {
  const {bankName, accountNumber} = useUserInfoContext();
  const {patchUser} = useRequestPatchUser();

  return (
    <EditAccountPageView
      bankName={bankName}
      accountNumber={accountNumber}
      onSubmit={patchUser}
      redirectUrlOnSubmit={ROUTER_URLS.myPage}
    />
  );
};


3. useAccount를 계좌 수정 로직이 필요한 곳에서 모두 사용할 수 있도록 리펙터링

기존은 첫 렌더링 시 보여줄 bankName, accountNumber와 완료되었을 떄 실행할 api를 모두 useAccount에서 직접 호출해 사용하고 있었습니다.

useAccount의 위 값들의 의존성을 주입받는게 여러 페이지에서 재사용하기 유리할 것 같아 분리해주었습니다.

const useAccount = ({accountNumber: defaultAccountNumber, bankName: defaultBankName, onSubmit}: UseAccountArgs) => {

그리고 값을 업데이트하는 로직이 handleAccountOnTyping(구: handleAccount), handleAccountOnPaste에서 반복되어 handleAccount 함수로 묶어 재사용할 수 있도록 분리했습니다.

  // 값을 업데이트하는 함수
  const handleAccount = (newAccountNumber: string) => {
    const {errorMessage} = validateAccountNumber(newAccountNumber);
    setAccountNumberErrorMessage(errorMessage);

    const canEdit = canEditAccountNumber(newAccountNumber);

    if (canEdit) setAccountNumber(newAccountNumber);
  };

  // 타자로 입력할 때
  const handleAccountOnTyping = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (isPasting) return;

    const newAccountNumber = event.target.value;
    handleAccount(newAccountNumber);
  };

  // 붙여넣기 할 때
  const handleAccountOnPaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
    setIsPasting(true);

    const newAccountNumber = `${accountNumber}${event.clipboardData.getData('text')}`;
    handleAccount(newAccountNumber);

    setTimeout(() => setIsPasting(false), 0);
  };


4. 유저의 정보를 사용하는 mypage에서는 UserInfoLoader를 이용해 데이터를 받아 사용하도록 함

"/event" 경로일 때 EventLoader를 사용하듯이 같은 방식으로 UserInfoLoader로 감싸주었습니다.

userInfo를 불러오고 그 데이터를 Provider로 뿌려주는 역할을 수행합니다.

image



5. 자잘한 리펙터링

가능한 bankname에 대한 타입이 없어(string으로만 제한됨) BANKS를 as const로 만든 후 유효한 은행 이름만 추출한 BankName 타입을 선언했습니다.

// service.type
export type BankName = (typeof BANKS)[number]['name'] | '';
// '우리은행' | '토스뱅크' ...

pakxe added 30 commits December 22, 2024 21:03
@pakxe pakxe added 🖥️ FE Frontend ⚙️ feat feature 🚧 refactor refactoring labels Dec 23, 2024
@pakxe pakxe added this to the v3.1.0 milestone Dec 23, 2024
@pakxe pakxe self-assigned this Dec 23, 2024
Copy link

Test Results

135 tests   135 ✅  7s ⏱️
 25 suites    0 💤
 25 files      0 ❌

Results for commit aff607f.

Copy link

@pakxe pakxe changed the base branch from main to fe-dev December 23, 2024 07:44
Copy link
Contributor

@jinhokim98 jinhokim98 left a comment

Choose a reason for hiding this comment

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

꽤 많은 수정 사항이 있었네요... 고생했어요
mutate onSuccess 관련한 고민을 공유해줘서 고마워요~ 덕분에 언제 onSuccess가 실행되는지 탐구해볼 수 있었습니다. 몇 가지 코멘트 남겨 놨으니 확인 부탁 드려요!

Comment on lines +44 to +46
await enrollAccount();

navigate(redirectUrlOnSubmit);
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분에서 await을 해줘서 이전 데이터가 보이는 문제를 해결했다는 거죠? 좋습니다.
저도 이 부분 궁금해서 mutate 함수와 mutate async를 사용했을 때 onSuccess 콜백은 언제 실행되는지 테스트해봤어요!

image

사진을 보면 mutate async, await과 onSuccess의 경우 함수가 실행되어 요청을 기다린 후 onSuccess가 호출된 후 navigate하는 모습을 보여줬어요.

하지만 mutate와 onSuccess의 경우 함수가 실행되어 navigate하게 되어 함수가 종료된 후 onSuccess가 실행되는 모습을 확인할 수 있었어요.

이 부분에서 일어난 오류가 아니었을까.. 예상합니다

Copy link
Contributor Author

@pakxe pakxe Dec 24, 2024

Choose a reason for hiding this comment

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

오..! 신기한 내용을 알아봐주셔서 감사합니다.
그렇지만 제가 사용한 mutate는 처음부터 mutateAsync였습니다. 흐흑.. ㅜㅜ 아마 프로미스에 관련된 문제 같아요.

Copy link
Contributor

Choose a reason for hiding this comment

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

해결되었으니 다행이에요~

};

const BANKS: Bank[] = [
const BANKS = [
Copy link
Contributor

Choose a reason for hiding this comment

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

오... 이거 생각도 못 했는데.. 저는 은행 이름은 그냥 string으로 관리해도 되겠다고 생각했어요.
확실히 제한을 주면 더 안전할 것 같아요

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 +62 to +63
accountNumberFormat: '계좌번호는 숫자, 연속되지 않는 하이픈(-)만 입력 가능해요',
accountNumberLength: `계좌번호는 ${RULE.minAccountNumberLength}자에서 ${RULE.maxAccountNumberLength}자 사이로 입력 가능해요`,
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 +27 to +28
initialData: enableInitialData ? initialData : undefined,
initialDataUpdatedAt: enableInitialData ? 0 : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

initialDataUpdatedAt은 어떠한 이유로 사용한 것인지 알려주실 수 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

initialDataUpdatedAt을 써야 initialData가 처음에 세팅되자마자 queryFn으로 요청해옵니다. 로딩없는 서스펜스 느낌이라고 생각하시면 좋을 것 같아요. initialData를 박아놓음과 동시에 get요청을 날리는거죠. get 리스폰스를 받아오는 그 시간동안은 intitalData를 보여주는 겁니다.

initialData 자체는 staleTime동안 fresh하게 남습니다.
만약 유저가 기본 staleTime인 1분 이내로 로그인을 마치고 다시 MainPage로 돌아올 경우 올바르게 유저 데이터를 불러와야 합니다.
하지만 initialData가 fresh한 1분 동안은 다시 요청하지 않기 때문의 1분 안으로 로그인하고 다시 돌아왔을 때 isGuest=true인 initialData를 그대로 사용합니다. 그래서 유저는 로그인을 했는데도 게스트로 평가되어 로그인 화면이 뜨게되죠

이걸 막기 위해 initialData가 즉시 fresh한 데이터로 덮어씌워져야 한단 의미로 initialDataUpdatedAt을 0으로 사용한 것입니다. Data.now와 비교해서 0은 당연히 작기 때문에 바로 queryFn이 호출됩니다.

staleTime을 써도 되긴 하지만 용도가 일치하지 않아서 initialDataUpdatedAt이라는 프로퍼티가 등장한 것이라고 알고있어요.

Copy link
Contributor

Choose a reason for hiding this comment

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

아하! useQuery에 init data를 설정해서 초기 값을 설정해서 데이터가 불러와 지는 동안에는 초기 값을 보여주고 싶었지만 fresh한 시간 내에 다시 접속했을 때 문제가 일어나서 설정해주신 거군요!

그러면 또 궁금한 점이 있어요. 여기서 초기 값을 설정한 이유가 있을까요? isGuest를 true로 주기 위해 설정해주신 것일까요?

  const initialData: UserInfo = {
    isGuest: true,
    nickname: '',
    profileImage: '',
    accountNumber: '',
    bankName: '',
  };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

MainPage에서 이 쿼리를 호출할 때 연출되는 상황이 3종류입니다.

  1. (토큰존재) isGuest = false인 카카오 로그인 후
    2.(토큰존재) isGuest = true인 게스트로 어떤 행사를 만든 후
  2. (토큰없음) 401에러 뜨는 진짜 첫 접속 또는 쿠키 소멸 상황
    현재 로그인 페이지가 뜨는 조건은 isGuest = true인 상황입니다. 다만 3번의 경우 401로 어떤 데이터도 세팅되지 못하기 떄문에 이를 위해 initialData를 주었어요.

Copy link
Contributor

Choose a reason for hiding this comment

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

메인페이지에서 행사 생성으로 넘어갈 때 isGuest에 따라서 다른 결과를 보여줘야하는데 여기서 초기값이 없으면 안 되기 때문이라고 이해했는데 맞을까요?

bankName: '',
};

const {data, ...rest} = useSuspenseQuery({
Copy link
Contributor

Choose a reason for hiding this comment

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

여기를 SuspenseQuery로 바꾸면서 /event/eventId/admin/login/guest or user url에서 문제 없었을까요?
저도 여기를 SuspenseQuery로 하다가 이 부분이 에러가 나서 일반 query로 돌려놨던 문제가 있어서요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

구체적으로 어떤 에러가 나는지 잘 모르겠습니다..! 더 자세한 상황을 설명해주실 수 있나요?!?
일단 제가 개발하면서는 크게 에러를 겪은 적은 없는데 혹시 모르겠네요

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 +19 to +24
<EditAccountPageView
bankName={bankName}
accountNumber={accountNumber}
onSubmit={patchUser}
redirectUrlOnSubmit={redirectUrlOnSubmit}
/>
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.

페이지가 받아야하는 props가 많았더라면 이렇게 분리하진 못했을 것 같습니다.. ㅜㅎㅎ

@@ -59,8 +59,9 @@ export interface EventCreationData {
password: Password;
}

export type BankName = (typeof BANKS)[number]['name'] | '';
Copy link
Contributor

Choose a reason for hiding this comment

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

typescript의 신 웨디~~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저런 방법이 있다는걸 저도 이번에 처음 알게 되었네요..!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🖥️ FE Frontend ⚙️ feat feature 🚧 refactor refactoring
Projects
Status: 🤼 In Review
Development

Successfully merging this pull request may close these issues.

[FE] 마이페이지에서 자신의 기본 계좌 번호 수정 기능 추가
2 participants