2026-02-06 10:43:16 +09:00
|
|
|
/**
|
|
|
|
|
* @file features/auth/components/session-timer.tsx
|
|
|
|
|
* @description 헤더에 표시되는 세션 만료 카운트다운 컴포넌트
|
|
|
|
|
* @remarks
|
|
|
|
|
* - [레이어] Components/UI
|
|
|
|
|
* - [사용자 행동] 남은 시간 확인 -> 만료 임박 시 붉은색 경고
|
|
|
|
|
* - [데이터 흐름] Zustand Store -> Calculation -> UI
|
|
|
|
|
* - [연관 파일] stores/session-store.ts, features/layout/header.tsx
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { useSessionStore } from "@/stores/session-store";
|
|
|
|
|
import { SESSION_TIMEOUT_MS } from "@/features/auth/constants";
|
2026-02-10 11:16:39 +09:00
|
|
|
import { cn } from "@/lib/utils";
|
2026-02-06 10:43:16 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 세션 만료 타이머 컴포넌트
|
|
|
|
|
* 남은 시간을 mm:ss 형태로 표시 (10분 미만 시 경고 스타일)
|
|
|
|
|
* @returns 시간 표시 배지 (모바일 숨김)
|
|
|
|
|
* @remarks 1초마다 리렌더링 발생
|
|
|
|
|
* @see header.tsx - 로그인 상태일 때 헤더에 표시
|
|
|
|
|
*/
|
2026-02-10 11:16:39 +09:00
|
|
|
interface SessionTimerProps {
|
|
|
|
|
/** 셰이더 배경 위에서 가독성을 높이는 투명 모드 */
|
|
|
|
|
blendWithBackground?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function SessionTimer({ blendWithBackground = false }: SessionTimerProps) {
|
2026-02-06 10:43:16 +09:00
|
|
|
const lastActive = useSessionStore((state) => state.lastActive);
|
|
|
|
|
|
|
|
|
|
// [State] 남은 시간 (밀리초)
|
|
|
|
|
const [timeLeft, setTimeLeft] = useState<number>(SESSION_TIMEOUT_MS);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const calculateTimeLeft = () => {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
const passed = now - lastActive;
|
|
|
|
|
|
|
|
|
|
// [Step 1] 남은 시간 계산 (음수 방지)
|
|
|
|
|
const remaining = Math.max(0, SESSION_TIMEOUT_MS - passed);
|
|
|
|
|
setTimeLeft(remaining);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
calculateTimeLeft(); // 초기 실행
|
|
|
|
|
|
|
|
|
|
// [Step 2] 1초마다 남은 시간 갱신
|
|
|
|
|
const interval = setInterval(calculateTimeLeft, 1000);
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, [lastActive]);
|
|
|
|
|
|
|
|
|
|
// [Step 3] 시간 포맷팅 (mm:ss)
|
|
|
|
|
const minutes = Math.floor(timeLeft / 60000);
|
|
|
|
|
const seconds = Math.floor((timeLeft % 60000) / 1000);
|
|
|
|
|
|
|
|
|
|
// [Step 4] 10분 미만일 때 긴급 스타일 적용
|
|
|
|
|
const isUrgent = timeLeft < 10 * 60 * 1000;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
2026-02-10 11:16:39 +09:00
|
|
|
className={cn(
|
|
|
|
|
"hidden rounded-full border px-3 py-1.5 text-sm font-medium tabular-nums backdrop-blur-md transition-colors md:block",
|
2026-02-06 10:43:16 +09:00
|
|
|
isUrgent
|
2026-02-10 11:16:39 +09:00
|
|
|
? "border-red-200 bg-red-50/50 text-red-500 dark:border-red-800 dark:bg-red-900/20"
|
|
|
|
|
: blendWithBackground
|
|
|
|
|
? "border-white/30 bg-black/45 text-white shadow-sm shadow-black/40"
|
|
|
|
|
: "border-border/40 bg-background/50 text-muted-foreground",
|
|
|
|
|
)}
|
2026-02-06 10:43:16 +09:00
|
|
|
>
|
|
|
|
|
{/* ========== 라벨 ========== */}
|
|
|
|
|
<span className="mr-2">세션 만료</span>
|
|
|
|
|
{/* ========== 시간 표시 ========== */}
|
|
|
|
|
{minutes.toString().padStart(2, "0")}:
|
|
|
|
|
{seconds.toString().padStart(2, "0")}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|