CHZZK-Study / Grass-Diary-Client

์ทจ์ง€์ง 2ํŒ€ ํ”„๋กœ์ ํŠธ Grass Diary
1 stars 3 forks source link

๐Ÿ”จ fix: ๋ฆด๋ฆฌ์ฆˆ ๋ฐฐํฌ ํ›„ ๋ฒ„๊ทธ ํ•ด๊ฒฐ ๋ฐ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ #213

Closed rkdcodus closed 2 weeks ago

rkdcodus commented 4 weeks ago

โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

<ํ•ด๊ฒฐ๋œ ๋ฒ„๊ทธ>

<์ถ”๊ฐ€๋œ ๊ธฐ๋Šฅ>

๐Ÿ“ ์ž‘์—… ์ƒ์„ธ ๋‚ด์šฉ

1๏ธโƒฃ '์ด์šฉ์•ฝ๊ด€', '๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ๋ฐฉ์นจ' ๋‚ด์šฉ ํ‘œ๊ธฐ

Footer์— ์ด์šฉ์•ฝ๊ด€ ๋ฐ ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ๋ฐฉ์นจ์— ๊ด€ํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ์˜ ์„œ๋น„์Šค ์ด์šฉ์•ฝ๊ด€ ๋ฌธ๊ตฌ๋ฅผ ํ†ตํ•ด์„œ๋„ ์ด์šฉ์•ฝ๊ด€ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ƒˆ๋กœ์šด ํƒญ์ด ์—ด๋ฆฌ๋ฉด์„œ ๋…ธ์…˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

// Footer ์ปดํฌ๋„ŒํŠธ 

const Footer = () => {
  const moving = (url: string) => {
    window.open(url, '_blank', 'noopener, noreferrer');
  };

  return (
      ...
          <S.ButtonContainer>
            <S.FooterButton onClick={() => moving(FOOTER.terms_of_use_url)}>
              ์ด์šฉ์•ฝ๊ด€
            </S.FooterButton>
            <S.FooterButton onClick={() => moving(FOOTER.privacy_policy_url)}>
              ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ๋ฐฉ์นจ
            </S.FooterButton>
          </S.ButtonContainer>
      ...
  );
};
// ๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ ์ฐฝ 
  <S.TermText>
    ๋กœ๊ทธ์ธ ์‹œ,{' '}
    <S.TermAnchor
      href={FOOTER.terms_of_use_url}
      target="_blank"
      rel="noopener noreferrer"
    >
      ์„œ๋น„์Šค ์ด์šฉ์•ฝ๊ด€
    </S.TermAnchor>
    ์— ๋™์˜ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.
  </S.TermText>

target="_blank" ์†์„ฑ์„ ์ฃผ๋ฉด ์ƒˆ๋กœ์šด ํƒญ์œผ๋กœ ์—ด๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ์†์„ฑ์„ ์‚ฌ์šฉํ•  ๋• rel="noopener noreferrer"์„ ํ•จ๊ป˜ ์จ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. target="_blank"๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ณด์•ˆ ๋ฌธ์ œ์™€ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋ธ”๋กœ๊ทธ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!

2๏ธโƒฃ ์—๋ŸฌํŽ˜์ด์ง€ ๊ตฌํ˜„

ํ˜„์žฌ api ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์—๋ŸฌํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ๊ตฌํ˜„๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ „๋ถ€ ํŽ˜์ด์ง€๋กœ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์€ ์ข‹์€ ๋ฐฉํ–ฅ์ด ์•„๋‹™๋‹ˆ๋‹ค. ์—๋Ÿฌ ์ฝ”๋“œ๋ณ„๋กœ ํŽ˜์ด์ง€/ํ† ์ŠคํŠธ๋ฅผ ๊ตฌ๋ณ„ํ•˜์—ฌ ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ์ด ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ๊ตฌํ˜„ ์ค‘์— ์žˆ์Šต๋‹ˆ๋‹ค! ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์‹œ๊ฐ„์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์•„ ์ผ๋‹จ ๋ฒ„์ „ ๋ฐฐํฌ๋Š” ํŽ˜์ด์ง€๋กœ๋งŒ ๋ณด์—ฌ์ฃผ๋Š” ๊ฑธ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๋ฐฉ๋ฒ•

export const useError = () => {
  const navigate = useNavigate();

  const renderErrorPage = (error: AxiosError<ApiErrorResponse>) => {
    const message = error.response?.data.description;
    navigate('/errorpage', { state: message });
  };

  return { renderErrorPage };
};

useError ํ›…์„ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

// useMutation ์ด๋ผ๋ฉด 
onError: (error: AxiosError<ApiErrorResponse>) => {
      console.error(error);
      renderErrorPage(error);
},

// useQuery๋ผ๋ฉด 
  if (isError) {
    console.error(error);
    renderErrorPage(error);
  }

์ด๋Ÿฐ ์‹์œผ๋กœ Tanstack query์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด useError์˜ renderErrorPage ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—๋Ÿฌ๋Š” ์ „์—ญ์ธ QueryClient์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ์–ด ๊ตฌํ˜„ ์ค‘์ธ ์ž‘์—…์—์„œ QueryClinet๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณ€๊ฒฝ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

3๏ธโƒฃ ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ ๋ชจ๋‹ฌ

๋กœ๊ทธ์ธ ํ›„ ๋งŒ๋ฃŒ๋˜๋Š” ์‹œ๊ฐ„์ด 10๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ์ง์ ‘ ํ…Œ์ŠคํŠธํ•ด๋ณด๋‹ˆ 30๋ถ„์ธ ๊ฒƒ์œผ๋กœ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค!

์ž‘์—… ์ „ ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ(์ž๋™ ๋กœ๊ทธ์•„์›ƒ)์™€ ์ˆ˜๋™ ๋กœ๊ทธ์•„์›ƒ์˜ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ (์ž๋™ ๋กœ๊ทธ์•„์›ƒ)

  1. ๋กœ๊ทธ์ธ ํ›„ 30๋ถ„์ด ์ง€๋‚˜๋ฉด ์—‘์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ
  2. ์ƒˆ๋กœ๊ณ ์นจ/ํŽ˜์ด์ง€ ์ด๋™์ด ์ด๋ฃจ์–ด์ง€๋ฉด memberId๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์—†์–ด 401 ์—๋Ÿฌ ๋ฐœ์ƒ
  3. API interceptor๊ฐ€ 401 ์—๋Ÿฌ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์˜ accessToken ์‚ญ์ œ
  4. ์†Œ๊ฐœํŽ˜์ด์ง€๋กœ ์ด๋™

๋กœ๊ทธ์•„์›ƒ

  1. ํ—ค๋”์˜ ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ ํด๋ฆญ
  2. ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์˜ accessToken ์‚ญ์ œ๋˜๋ฉฐ ์†Œ๊ฐœํŽ˜์ด์ง€๋กœ ์ด๋™
  3. ํ—ค๋”์˜ ํ”„๋กœํ•„์ด memberId๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์—†์–ด 401์—๋Ÿฌ ๋ฐœ์ƒ -> ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์œผ๋กœ ์ „ํ™˜
  4. API interceptor๊ฐ€ 401 ๊ฐ์ง€

๊ตฌํ˜„ํ•˜๊ธฐ (๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ์ด์šฉ)

ํ—ค๋”์—์„œ๋Š” ํ•ญ์ƒ useUser ํ›…์„ ํ†ตํ•œ memberId๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ—ค๋”์˜ memberId๊ฐ€ null์ด๋ฉด ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ๋กœ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. 30๋ถ„์ด ์ง€๋‚˜ ์—‘์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด ์ƒˆ๋กœ๊ณ ์นจ์ด๋‚˜ ํŽ˜์ด์ง€ ์ด๋™์ด ์—†์ด๋„ memberId๊ฐ€ null์ž„์„ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด useUser์˜ memberId๋ฅผ 5์ดˆ๋งˆ๋‹ค fetchํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

const { data, isSuccess, isError, error } = useQuery<
    number,
    AxiosError<ApiErrorResponse>,
    number,
    string[]
  >({
    queryKey: ['memberId'],
    queryFn: fetchAxios,
    enabled: !!isAuthenticated,
    retry: 1,
    refetchInterval: 5000,
    refetchIntervalInBackground: true,
  });

ํ•ด๋‹น API๋Š” isAuthenticated๊ฐ€ true์ผ ๊ฒฝ์šฐ์—๋งŒ, ์ฆ‰ ๋กœ๊ทธ์ธ๋˜์–ด์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ 5์ดˆ๋งˆ๋‹ค fetch๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ๋ฃŒ๋˜์—ˆ์„ ๊ฒฝ์šฐ ๋ฐ”๋กœ ๋ชจ๋‹ฌ์„ ๋„์›Œ์ฃผ๊ธฐ ์œ„ํ•ด ์žฌ์‹œ๋„๋Š” 1๋ฒˆ๋งŒ ํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’์€ 3) refetchIntervalInBackground๋ฅผ true๋กœ ์ฃผ์–ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํฌ์ปค์Šค๋˜์–ด์žˆ์ง€ ์•Š์•„๋„ fetch๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

const Header = () => {
  const { memberId, isError } = useUser();
  const { modal, loginModal } = useModal();
 ...

  useEffect(() => {
    const manualLogout = localStorage.getItem('manualLogout');
    if (isError && manualLogout === null) {
      const setting = {
        title: MODAL.login_expiration.title,
        content: MODAL.login_expiration.content,
      };

      const button1 = {
        active: true,
        text: MODAL.confirm,
        color: semantic.light.accent.solid.hero,
        interaction: INTERACTION.accent.subtle(),
      };

      modal(setting, button1);
    }
  }, [isError]);
...

๋˜ํ•œ, ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ(์ž๋™ ๋กœ๊ทธ์•„์›ƒ)๊ณผ ์ˆ˜๋™ ๋กœ๊ทธ์•„์›ƒ์ด ๋˜๋ฉด ๋ชจ๋‘ 401์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์–ด API interceptor๊ฐ€ ๊ด€์—ฌํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ์™€ ์ˆ˜๋™ ๋กœ๊ทธ์•„์›ƒ์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜๋™ ๋กœ๊ทธ์•„์›ƒ์„ ์‹œํ‚ฌ ์‹œ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— manualLogout: true ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์—์„œ manualLogout์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ 401 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ manualLogout์ด null์ด๋ผ๋ฉด ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ๋กœ ํŒ๋‹จํ•˜์—ฌ ๋ชจ๋‹ฌ์„ ๋„์›Œ์ฃผ๊ณ  401 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ manualLogout์ด null์ด ์•„๋‹ˆ๋ผ๋ฉด ์ˆ˜๋™ ๋กœ๊ทธ์•„์›ƒ์œผ๋กœ ํŒ๋‹จํ•˜์—ฌ ๋ชจ๋‹ฌ์„ ๋„์šฐ์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

4๏ธโƒฃ ์ˆ˜์ •ํŽ˜์ด์ง€ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ด๋ฆ„ ๋ฐ ์šฉ๋Ÿ‰ ํ‘œ๊ธฐ

๋ณ€๊ฒฝ๋œ ์ด๋ฏธ์ง€ state์ž…๋‹ˆ๋‹ค.

  // ์ด๋ฏธ์ง€ state
  const [image, setImage] = useState<ImageInfo>({
    imageId: 0,
    imageURL: '',
    imageName: '',
    imageSize: 0,
    imageType: '',
  });

๋ฐฑ์—์„œ ์ƒ์„ธํŽ˜์ด์ง€ ๋ฐ ์ˆ˜์ •ํŽ˜์ด์ง€์—์„œ ์ €์žฅ๋œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ iamgeId, imageURL, imageName, imageSize๊ฐ€ ์˜ค๋Š” ๊ฑธ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. (image api post ํ•  ๋•Œ๋Š” ๊ทธ๋Œ€๋กœ ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฐ์ฒด๋งŒ ๋ณด๋‚ด์คŒ) base64 ํ˜•์‹์˜ ์ด๋ฏธ์ง€๋ฅผ file ๊ฐ์ฒด๋กœ ๋ฐ”๊พธ๋Š” ๊ณผ์ •์—์„œ ์ž„์‹œ๋กœ ์ง€์ •ํ–ˆ๋˜ ํŒŒ์ผ ์ด๋ฆ„(image.jpg)๊ณผ ํŒŒ์ผ ํƒ€์ž…(image/jpeg)์„ ์›๋ณธ ํŒŒ์ผ ์ด๋ฆ„๊ณผ ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿšจ ๋ฒ„๊ทธ ๋ฐœ์ƒ ์ด์œ  (์„ ํƒ ์‚ฌํ•ญ)

๐Ÿ”Ž ํ›„์† ์ž‘์—… (์„ ํƒ ์‚ฌํ•ญ)

๐Ÿค” ์งˆ๋ฌธ ์‚ฌํ•ญ (์„ ํƒ ์‚ฌํ•ญ)

๐Ÿ“š ์ฐธ๊ณ  ์ž๋ฃŒ (์„ ํƒ ์‚ฌํ•ญ)

์ถ”๊ฐ€์ ์ธ ์ฐธ๊ณ  ์‚ฌํ•ญ์ด๋‚˜ ๊ด€๋ จ๋œ ๋ฌธ์„œ, ๋งํฌ ๋“ฑ์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”.

๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท (์„ ํƒ ์‚ฌํ•ญ)

โœ… ์…€ํ”„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์ด์Šˆ ๋ฒˆํ˜ธ: