236 lines
7.9 KiB
TypeScript
236 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Mail, Plus, Loader2, RefreshCw, LayoutDashboard } from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
import {
|
|
MailAccount,
|
|
getMailAccounts,
|
|
createMailAccount,
|
|
updateMailAccount,
|
|
deleteMailAccount,
|
|
testMailAccountConnection,
|
|
CreateMailAccountDto,
|
|
UpdateMailAccountDto,
|
|
} from "@/lib/api/mail";
|
|
import MailAccountModal from "@/components/mail/MailAccountModal";
|
|
import MailAccountTable from "@/components/mail/MailAccountTable";
|
|
import ConfirmDeleteModal from "@/components/mail/ConfirmDeleteModal";
|
|
|
|
export default function MailAccountsPage() {
|
|
const router = useRouter();
|
|
const [accounts, setAccounts] = useState<MailAccount[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
const [selectedAccount, setSelectedAccount] = useState<MailAccount | null>(null);
|
|
const [modalMode, setModalMode] = useState<'create' | 'edit'>('create');
|
|
|
|
const loadAccounts = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await getMailAccounts();
|
|
// 배열인지 확인하고 설정
|
|
if (Array.isArray(data)) {
|
|
setAccounts(data);
|
|
} else {
|
|
console.error('API 응답이 배열이 아닙니다:', data);
|
|
setAccounts([]);
|
|
}
|
|
} catch (error) {
|
|
console.error('계정 로드 실패:', error);
|
|
setAccounts([]); // 에러 시 빈 배열로 설정
|
|
// alert('계정 목록을 불러오는데 실패했습니다.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadAccounts();
|
|
}, []);
|
|
|
|
const handleOpenCreateModal = () => {
|
|
setModalMode('create');
|
|
setSelectedAccount(null);
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleOpenEditModal = (account: MailAccount) => {
|
|
setModalMode('edit');
|
|
setSelectedAccount(account);
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleOpenDeleteModal = (account: MailAccount) => {
|
|
setSelectedAccount(account);
|
|
setIsDeleteModalOpen(true);
|
|
};
|
|
|
|
const handleSaveAccount = async (data: CreateMailAccountDto | UpdateMailAccountDto) => {
|
|
try {
|
|
if (modalMode === 'create') {
|
|
await createMailAccount(data as CreateMailAccountDto);
|
|
} else if (modalMode === 'edit' && selectedAccount) {
|
|
await updateMailAccount(selectedAccount.id, data as UpdateMailAccountDto);
|
|
}
|
|
await loadAccounts();
|
|
setIsModalOpen(false);
|
|
} catch (error) {
|
|
throw error; // 모달에서 에러 처리
|
|
}
|
|
};
|
|
|
|
const handleDeleteAccount = async () => {
|
|
if (!selectedAccount) return;
|
|
|
|
try {
|
|
await deleteMailAccount(selectedAccount.id);
|
|
await loadAccounts();
|
|
alert('계정이 삭제되었습니다.');
|
|
} catch (error) {
|
|
console.error('계정 삭제 실패:', error);
|
|
alert('계정 삭제에 실패했습니다.');
|
|
}
|
|
};
|
|
|
|
const handleToggleStatus = async (account: MailAccount) => {
|
|
try {
|
|
const newStatus = account.status === 'active' ? 'inactive' : 'active';
|
|
await updateMailAccount(account.id, { status: newStatus });
|
|
await loadAccounts();
|
|
} catch (error) {
|
|
console.error('상태 변경 실패:', error);
|
|
alert('상태 변경에 실패했습니다.');
|
|
}
|
|
};
|
|
|
|
const handleTestConnection = async (account: MailAccount) => {
|
|
try {
|
|
setLoading(true);
|
|
const result = await testMailAccountConnection(account.id);
|
|
|
|
if (result.success) {
|
|
alert(`✅ SMTP 연결 성공!\n\n${result.message || '정상적으로 연결되었습니다.'}`);
|
|
} else {
|
|
alert(`❌ SMTP 연결 실패\n\n${result.message || '연결에 실패했습니다.'}`);
|
|
}
|
|
} catch (error: any) {
|
|
console.error('연결 테스트 실패:', error);
|
|
alert(`❌ SMTP 연결 테스트 실패\n\n${error.message || '알 수 없는 오류가 발생했습니다.'}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
<div className="w-full max-w-none px-4 py-8 space-y-8">
|
|
{/* 페이지 제목 */}
|
|
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">메일 계정 관리</h1>
|
|
<p className="mt-2 text-gray-600">SMTP 메일 계정을 관리하고 발송 통계를 확인합니다</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => router.push('/admin/mail/dashboard')}
|
|
>
|
|
<LayoutDashboard className="w-4 h-4 mr-2" />
|
|
대시보드
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={loadAccounts}
|
|
disabled={loading}
|
|
>
|
|
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
|
새로고침
|
|
</Button>
|
|
<Button
|
|
className="bg-orange-500 hover:bg-orange-600"
|
|
onClick={handleOpenCreateModal}
|
|
>
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
새 계정 추가
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 메인 컨텐츠 */}
|
|
{loading ? (
|
|
<Card className="shadow-sm">
|
|
<CardContent className="flex justify-center items-center py-16">
|
|
<Loader2 className="w-8 h-8 animate-spin text-orange-500" />
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<Card className="shadow-sm">
|
|
<CardContent className="p-6">
|
|
<MailAccountTable
|
|
accounts={accounts}
|
|
onEdit={handleOpenEditModal}
|
|
onDelete={handleOpenDeleteModal}
|
|
onToggleStatus={handleToggleStatus}
|
|
onTestConnection={handleTestConnection}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* 안내 정보 */}
|
|
<Card className="bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200 shadow-sm">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center">
|
|
<Mail className="w-5 h-5 mr-2 text-orange-500" />
|
|
메일 계정 관리
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-gray-700 mb-4">
|
|
💡 SMTP 계정을 등록하여 시스템에서 메일을 발송할 수 있어요!
|
|
</p>
|
|
<ul className="space-y-2 text-sm text-gray-600">
|
|
<li className="flex items-start">
|
|
<span className="text-orange-500 mr-2">✓</span>
|
|
<span>Gmail, Naver, 자체 SMTP 서버 지원</span>
|
|
</li>
|
|
<li className="flex items-start">
|
|
<span className="text-orange-500 mr-2">✓</span>
|
|
<span>비밀번호는 암호화되어 안전하게 저장됩니다</span>
|
|
</li>
|
|
<li className="flex items-start">
|
|
<span className="text-orange-500 mr-2">✓</span>
|
|
<span>일일 발송 제한 설정 가능</span>
|
|
</li>
|
|
</ul>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* 모달들 */}
|
|
<MailAccountModal
|
|
isOpen={isModalOpen}
|
|
onClose={() => setIsModalOpen(false)}
|
|
onSave={handleSaveAccount}
|
|
account={selectedAccount}
|
|
mode={modalMode}
|
|
/>
|
|
|
|
<ConfirmDeleteModal
|
|
isOpen={isDeleteModalOpen}
|
|
onClose={() => setIsDeleteModalOpen(false)}
|
|
onConfirm={handleDeleteAccount}
|
|
title="메일 계정 삭제"
|
|
message="이 메일 계정을 삭제하시겠습니까?"
|
|
itemName={selectedAccount?.name}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|