Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
199
frontend/src/app/admin/notifications/page.tsx
Normal file
199
frontend/src/app/admin/notifications/page.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
import { notificationApi } from '@/lib/api';
|
||||
|
||||
export default function AdminNotificationsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Form state
|
||||
const [title, setTitle] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [link, setLink] = useState('');
|
||||
const [sending, setSending] = useState(false);
|
||||
const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
|
||||
const handleSendToAll = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!title.trim() || !message.trim()) {
|
||||
setResult({ success: false, message: '제목과 내용을 입력해주세요.' });
|
||||
return;
|
||||
}
|
||||
|
||||
setSending(true);
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const response = await notificationApi.adminSendToAll(
|
||||
title.trim(),
|
||||
message.trim(),
|
||||
link.trim() || undefined
|
||||
);
|
||||
setResult({ success: true, message: response.message });
|
||||
setTitle('');
|
||||
setMessage('');
|
||||
setLink('');
|
||||
} catch (error) {
|
||||
console.error('Failed to send notification:', error);
|
||||
setResult({ success: false, message: '알림 발송에 실패했습니다.' });
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-800">알림 관리</h1>
|
||||
<p className="text-gray-600 mt-1">전체 사용자에게 알림을 보낼 수 있습니다.</p>
|
||||
</div>
|
||||
|
||||
{/* Send Notification Form */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">전체 알림 발송</h2>
|
||||
|
||||
<form onSubmit={handleSendToAll} className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
제목 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="알림 제목"
|
||||
maxLength={200}
|
||||
disabled={sending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
내용 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="알림 내용"
|
||||
rows={4}
|
||||
disabled={sending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Link (optional) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
링크 (선택사항)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={link}
|
||||
onChange={(e) => setLink(e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
placeholder="/page-path (클릭 시 이동할 페이지)"
|
||||
disabled={sending}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
예: /my-request, /charge, /cost
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Result Message */}
|
||||
{result && (
|
||||
<div className={`p-4 rounded-lg ${result.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'}`}>
|
||||
{result.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={sending}
|
||||
className="w-full bg-primary-600 text-white py-3 rounded-lg hover:bg-primary-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
{sending ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
발송 중...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
||||
</svg>
|
||||
전체 사용자에게 발송
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Notification Types Info */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">알림 유형 안내</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">🚗</span>
|
||||
<div>
|
||||
<p className="font-medium">차량 추천</p>
|
||||
<p className="text-xs text-gray-500">사용자 요청에 차량 추천 시</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">🚚</span>
|
||||
<div>
|
||||
<p className="font-medium">배송 업데이트</p>
|
||||
<p className="text-xs text-gray-500">배송 상태 변경 시</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">💰</span>
|
||||
<div>
|
||||
<p className="font-medium">출금 처리</p>
|
||||
<p className="text-xs text-gray-500">출금 신청 처리 시</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">🎁</span>
|
||||
<div>
|
||||
<p className="font-medium">레퍼럴 보상</p>
|
||||
<p className="text-xs text-gray-500">추천인 보상 적립 시</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">✅</span>
|
||||
<div>
|
||||
<p className="font-medium">딜러 승인</p>
|
||||
<p className="text-xs text-gray-500">딜러 신청 승인/거부 시</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<span className="text-2xl">🎉</span>
|
||||
<div>
|
||||
<p className="font-medium">공유 판매</p>
|
||||
<p className="text-xs text-gray-500">공유 차량 판매 시</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-sm text-gray-600">
|
||||
위 알림은 관련 이벤트 발생 시 자동으로 발송됩니다.
|
||||
이 페이지에서는 시스템 공지사항을 전체 사용자에게 수동으로 보낼 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user