142 lines
5.1 KiB
TypeScript
142 lines
5.1 KiB
TypeScript
import React, { useEffect, useRef } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { UserMeResponse } from '../types/api';
|
|
import LanguageSwitch from './LanguageSwitch';
|
|
import { NAV } from './navItems';
|
|
|
|
interface SettingsPanelProps {
|
|
variant: 'sidebar' | 'bottom';
|
|
onClose: () => void;
|
|
onNavigate: (id: string) => void;
|
|
activeItem: string;
|
|
userInfo?: UserMeResponse | null;
|
|
credits?: number | null;
|
|
isGuest: boolean;
|
|
isLoggingOut?: boolean;
|
|
onLogout?: () => void;
|
|
onLoginClick: () => void;
|
|
/** 팝업을 여는 설정 버튼 영역. 외부 클릭 닫기에서 제외해 재클릭 시 토글이 정상 동작하게 한다 */
|
|
anchorRef?: React.RefObject<HTMLElement | null>;
|
|
}
|
|
|
|
const SettingsPanel: React.FC<SettingsPanelProps> = ({
|
|
variant,
|
|
onClose,
|
|
onNavigate,
|
|
activeItem,
|
|
userInfo,
|
|
credits,
|
|
isGuest,
|
|
isLoggingOut,
|
|
onLogout,
|
|
onLoginClick,
|
|
anchorRef,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
const target = e.target as Node;
|
|
if (anchorRef?.current?.contains(target)) return;
|
|
if (panelRef.current && !panelRef.current.contains(target)) {
|
|
onClose();
|
|
}
|
|
};
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
document.addEventListener('keydown', handleEscape);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
document.removeEventListener('keydown', handleEscape);
|
|
};
|
|
}, [onClose, anchorRef]);
|
|
|
|
return (
|
|
<div ref={panelRef} className={`settings-panel settings-panel--${variant}`}>
|
|
{/* 1) 프로필 — 패널 헤더 역할 */}
|
|
{!isGuest && (
|
|
<>
|
|
<div className="profile-section settings-panel-profile">
|
|
{userInfo?.profile_image_url || userInfo?.thumbnail_image_url ? (
|
|
<img
|
|
src={userInfo.thumbnail_image_url || userInfo.profile_image_url || ''}
|
|
alt="Profile"
|
|
className="profile-avatar"
|
|
/>
|
|
) : (
|
|
<div className="profile-avatar profile-avatar-default">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
<circle cx="12" cy="7" r="4" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<p className="profile-name">{userInfo?.nickname || t('sidebar.defaultUser')}</p>
|
|
{credits !== null && credits !== undefined && (
|
|
<p className="profile-credits">{t('sidebar.credits', { count: credits })}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* 2) 메뉴 그룹 — 대시보드 */}
|
|
<button
|
|
className={`settings-panel-item ${activeItem === NAV.DASHBOARD ? 'active' : ''}`}
|
|
onClick={() => {
|
|
onNavigate(NAV.DASHBOARD);
|
|
onClose();
|
|
}}
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
<rect x="3" y="3" width="7" height="9" /><rect x="14" y="3" width="7" height="5" />
|
|
<rect x="14" y="12" width="7" height="9" /><rect x="3" y="16" width="7" height="5" />
|
|
</svg>
|
|
<span>{t('sidebar.dashboard')}</span>
|
|
</button>
|
|
|
|
<div className="settings-panel-divider" />
|
|
|
|
{/* 3) 하단 그룹 — 언어 전환 / 로그아웃(로그인) / 고객의견 */}
|
|
<div className="settings-panel-language">
|
|
<LanguageSwitch isCollapsed={false} />
|
|
</div>
|
|
|
|
{isGuest ? (
|
|
<button className="settings-panel-item" onClick={onLoginClick}>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" /><polyline points="10 17 15 12 10 7" /><line x1="15" y1="12" x2="3" y2="12" />
|
|
</svg>
|
|
<span>{t('sidebar.login')}</span>
|
|
</button>
|
|
) : (
|
|
<button className="settings-panel-item" onClick={onLogout} disabled={isLoggingOut}>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" y1="12" x2="9" y2="12" />
|
|
</svg>
|
|
<span>{isLoggingOut ? t('sidebar.loggingOut') : t('sidebar.logout')}</span>
|
|
</button>
|
|
)}
|
|
|
|
<a
|
|
href="https://forms.gle/4a8mGebBYtdesvby9"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="settings-panel-item"
|
|
title={t('sidebar.inquiry')}
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
</svg>
|
|
<span>{t('sidebar.inquiry')}</span>
|
|
</a>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SettingsPanel;
|