'use client'; import { useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { User, Upload, X, } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import type { AccountInfo, TermsAgreement, MarketingConsent } from './types'; import { ACCOUNT_STATUS_LABELS } from './types'; import { withdrawAccount, suspendTenant, updateAgreements, uploadProfileImage } from './actions'; // ===== Props 인터페이스 ===== interface AccountInfoClientProps { initialAccountInfo: AccountInfo; initialTermsAgreements: TermsAgreement[]; initialMarketingConsent: MarketingConsent; error?: string; } export function AccountInfoClient({ initialAccountInfo, initialTermsAgreements, initialMarketingConsent, error, }: AccountInfoClientProps) { const router = useRouter(); const fileInputRef = useRef(null); // ===== 상태 관리 ===== const [accountInfo] = useState(initialAccountInfo); const [termsAgreements] = useState(initialTermsAgreements); const [marketingConsent, setMarketingConsent] = useState(initialMarketingConsent); const [profileImage, setProfileImage] = useState(initialAccountInfo.profileImage); const [isSavingMarketing, setIsSavingMarketing] = useState(false); const [isUploadingImage, setIsUploadingImage] = useState(false); // 다이얼로그 상태 const [showWithdrawDialog, setShowWithdrawDialog] = useState(false); const [showSuspendDialog, setShowSuspendDialog] = useState(false); const [isWithdrawing, setIsWithdrawing] = useState(false); const [isSuspending, setIsSuspending] = useState(false); const [withdrawPassword, setWithdrawPassword] = useState(''); // 에러 표시 useEffect(() => { if (error) { toast.error(error); } }, [error]); // ===== 버튼 활성화 조건 ===== const canWithdraw = !accountInfo.isTenantMaster; // 테넌트 마스터가 아닌 경우만 const canSuspend = accountInfo.isTenantMaster; // 테넌트 마스터인 경우만 // ===== 핸들러 ===== const handleImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // 파일 크기 체크 (10MB) if (file.size > 10 * 1024 * 1024) { toast.error('파일 크기는 10MB 이하여야 합니다.'); return; } // 파일 타입 체크 const validTypes = ['image/png', 'image/jpeg', 'image/gif']; if (!validTypes.includes(file.type)) { toast.error('PNG, JPEG, GIF 파일만 업로드 가능합니다.'); return; } // 미리보기 생성 (낙관적 업데이트) const previousImage = profileImage; const reader = new FileReader(); reader.onload = (event) => { setProfileImage(event.target?.result as string); }; reader.readAsDataURL(file); // API 호출 setIsUploadingImage(true); try { const formData = new FormData(); formData.append('file', file); const result = await uploadProfileImage(formData); if (result.success) { toast.success('프로필 이미지가 업로드되었습니다.'); } else { // 실패 시 롤백 setProfileImage(previousImage); toast.error(result.error || '이미지 업로드에 실패했습니다.'); } } catch { // 에러 시 롤백 setProfileImage(previousImage); toast.error('서버 오류가 발생했습니다.'); } finally { setIsUploadingImage(false); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleRemoveImage = () => { setProfileImage(undefined); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handlePasswordChange = () => { // 비밀번호 설정 화면으로 이동 router.push('/ko/settings/account-info/change-password'); }; const handleWithdraw = () => { setShowWithdrawDialog(true); }; const handleConfirmWithdraw = async () => { if (!withdrawPassword) { toast.error('비밀번호를 입력해주세요.'); return; } setIsWithdrawing(true); try { const result = await withdrawAccount(withdrawPassword); if (result.success) { toast.success('계정이 탈퇴되었습니다.'); setShowWithdrawDialog(false); setWithdrawPassword(''); // 로그아웃 및 로그인 페이지로 이동 router.push('/ko/login'); } else { toast.error(result.error || '계정 탈퇴에 실패했습니다.'); } } catch { toast.error('서버 오류가 발생했습니다.'); } finally { setIsWithdrawing(false); } }; const handleSuspend = () => { setShowSuspendDialog(true); }; const handleConfirmSuspend = async () => { setIsSuspending(true); try { const result = await suspendTenant(); if (result.success) { toast.success('테넌트 사용이 중지되었습니다.'); setShowSuspendDialog(false); } else { toast.error(result.error || '사용 중지에 실패했습니다.'); } } catch { toast.error('서버 오류가 발생했습니다.'); } finally { setIsSuspending(false); } }; const handleEdit = () => { // 수정 모드로 전환 또는 별도 수정 페이지로 이동 router.push('/ko/settings/account-info?mode=edit'); }; const handleMarketingChange = async (type: 'email' | 'sms', checked: boolean) => { const now = new Date().toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }).replace(/\. /g, '-').replace('.', ''); // 낙관적 업데이트 (UI 먼저 변경) setMarketingConsent(prev => ({ ...prev, [type]: { agreed: checked, ...(checked ? { agreedAt: now } : { withdrawnAt: now }), }, })); // API 호출 setIsSavingMarketing(true); try { const result = await updateAgreements([{ type, agreed: checked }]); if (result.success) { toast.success(checked ? '수신 동의되었습니다.' : '수신 동의가 철회되었습니다.'); } else { // 실패 시 롤백 setMarketingConsent(prev => ({ ...prev, [type]: { agreed: !checked, ...(checked ? { withdrawnAt: now } : { agreedAt: now }), }, })); toast.error(result.error || '변경에 실패했습니다.'); } } catch { // 에러 시 롤백 setMarketingConsent(prev => ({ ...prev, [type]: { agreed: !checked, ...(checked ? { withdrawnAt: now } : { agreedAt: now }), }, })); toast.error('서버 오류가 발생했습니다.'); } finally { setIsSavingMarketing(false); } }; // ===== 헤더 액션 버튼 ===== const headerActions = (
); return ( <>
{/* 계정 정보 섹션 */} 계정 정보 {/* 프로필 사진 */}
{profileImage ? ( <> 프로필 ) : (
IMG
)}

1250 X 250px, 10MB 이하의 PNG, JPEG, GIF

{/* 아이디 & 비밀번호 */}
{/* 권한 & 상태 */}
{/* 약관 동의 정보 섹션 */} 약관 동의 정보 {/* 필수 약관 */}
{termsAgreements.map((term, index) => (
동의일시 {term.agreedAt}
))}
{/* 선택 약관 - 마케팅 정보 수신 동의 */}
{/* 이메일 수신 동의 */}
handleMarketingChange('email', checked as boolean)} disabled={isSavingMarketing} />
동의일시 {marketingConsent.email.agreedAt || '-'}
{/* SMS 수신 동의 */}
handleMarketingChange('sms', checked as boolean)} disabled={isSavingMarketing} />
{marketingConsent.sms.agreed ? `동의일시 ${marketingConsent.sms.withdrawnAt || '-'}` : `동의철회일시 ${marketingConsent.sms.withdrawnAt || '-'}` }
{/* 탈퇴 확인 다이얼로그 */} { setShowWithdrawDialog(open); if (!open) setWithdrawPassword(''); }}> 계정 탈퇴

정말 탈퇴하시겠습니까?
모든 테넌트에서 탈퇴 처리되며, SAM 서비스에서 완전히 탈퇴됩니다.

setWithdrawPassword(e.target.value)} disabled={isWithdrawing} />
취소 {isWithdrawing ? '처리 중...' : '확인'}
{/* 사용중지 확인 다이얼로그 */} 계정 사용중지 정말 사용중지하시겠습니까?
해당 테넌트의 사용이 중지됩니다.
취소 {isSuspending ? '처리 중...' : '확인'}
); }