File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { useFeedback } from '@/app/_hooks/use-feedback';
interface LogoutNoticeProps {
/** 로그아웃 Server Action이 백엔드 호출 실패를 알리려고 실어 보낸 쿼리 파라미터가 있었는가. */
showError: boolean;
/** 다른 곳에서 로그인되어 세션이 밀렸는가. */
showSessionRevoked: boolean;
}
const LOGOUT_ERROR_MESSAGE =
'로그아웃 처리 중 일부 오류가 있었지만 로그아웃되었습니다.';
const SESSION_REVOKED_MESSAGE =
'다른 곳에서 로그인되어 로그아웃되었습니다. 다시 로그인해 주세요.';
/**
* 화면에 그릴 UI가 없는 알림 트리거 컴포넌트 — `app/(protected)/_actions.ts`의 `logout()`이
* 백엔드 로그아웃 호출 실패를 알리려고 `/login`에 실어 보낸 쿼리 파라미터를 Page(Server
* Component)가 읽어 `showError`로 넘겨주면, 마운트 시 1회 토스트로 보여준다.
*
* Server Action은 redirect() 이후 클라이언트에 값을 돌려줄 방법이 없다(redirect가 즉시
* 던져짐) — 그래서 이 화면에 도착해 마운트된 뒤(=redirect가 끝난 뒤)에만 알릴 수 있다. 이
* 컴포넌트는 오직 이 시점에만 반응하므로 redirect와 토스트 표시가 시점상 겹치지 않는다.
*
* 표시 후에는 쿼리 파라미터를 제거한다(router.replace) — 새로고침·북마크 재방문 시 지난 실패가
* 다시 뜨는 것을 막는다.
*
* `hasNotifiedRef`로 실행을 1회로 못박는다 — App Router는 기본 Strict Mode(next.config.ts에
* 별도 설정이 없으면 true, Next.js 13.5.1부터 기본값)라 개발 모드에서 effect가 mount→cleanup→
* remount로 2회 호출된다. 이 effect엔 cleanup이 없어 가드가 없으면 토스트가 두 번 뜬다 —
* router.replace가 아직 반영되기 전(같은 커밋 내)에 두 번째 호출이 일어나 showError prop이
* 그대로 true이기 때문이다. 프로덕션은 원래 1회지만, 사용자에게 실제로 보이는 부수효과(알림)는
* Strict Mode 유무와 무관하게 항상 1회여야 한다.
*/
export function LogoutNotice({
showError,
showSessionRevoked,
}: LogoutNoticeProps) {
const router = useRouter();
const { showToast } = useFeedback();
const hasNotifiedRef = useRef(false);
useEffect(() => {
if ((!showError && !showSessionRevoked) || hasNotifiedRef.current) {
return;
}
hasNotifiedRef.current = true;
// 내부 정보(백엔드 message 원문 등)는 노출하지 않는다 — 항상 이 고정 문구만 보여준다.
showToast({
variant: 'warning',
message: showSessionRevoked
? SESSION_REVOKED_MESSAGE
: LOGOUT_ERROR_MESSAGE,
});
router.replace('/login');
}, [showError, showSessionRevoked, showToast, router]);
return null;
}