- 컨트롤러 2개 (EsignController, EsignPublicController) - 뷰 8개 (dashboard, create, detail, fields, send, sign/auth, sign/sign, sign/done) - React 하이브리드 방식 (기존 Finance 패턴) - 라우트 추가 (인증 esign/* + 공개 esign/sign/*) - PDF.js 기반 서명 위치 설정 - signature_pad 기반 전자서명 입력 - OTP 본인인증 플로우 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
6.2 KiB
PHP
128 lines
6.2 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>서명 완료 - SAM E-Sign</title>
|
|
@vite(['resources/css/app.css'])
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen">
|
|
<div id="esign-done-root" data-token="{{ $token }}"></div>
|
|
|
|
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
<script type="text/babel">
|
|
const { useState, useEffect } = React;
|
|
|
|
const TOKEN = document.getElementById('esign-done-root')?.dataset.token;
|
|
const API = window.SAM_CONFIG?.apiBaseUrl || '{{ config("services.api.base_url", "") }}';
|
|
const API_KEY = '{{ config("services.api.key", "") }}';
|
|
|
|
const App = () => {
|
|
const [contract, setContract] = useState(null);
|
|
const [signer, setSigner] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
try {
|
|
const res = await fetch(`${API}/api/v1/esign/sign/${TOKEN}`, {
|
|
headers: { 'Accept': 'application/json', 'X-API-Key': API_KEY },
|
|
});
|
|
const json = await res.json();
|
|
if (json.success) {
|
|
setContract(json.data.contract);
|
|
setSigner(json.data.signer);
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
setLoading(false);
|
|
})();
|
|
}, []);
|
|
|
|
if (loading) return <div className="flex items-center justify-center min-h-screen"><p className="text-gray-400">로딩 중...</p></div>;
|
|
|
|
const isSigned = signer?.status === 'signed';
|
|
const isRejected = signer?.status === 'rejected';
|
|
const isCompleted = contract?.status === 'completed';
|
|
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen p-4">
|
|
<div className="w-full max-w-md text-center">
|
|
<div className="mb-8">
|
|
<h1 className="text-2xl font-bold text-gray-900">SAM E-Sign</h1>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-xl shadow-sm border p-8">
|
|
{isSigned && (
|
|
<>
|
|
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<svg className="w-10 h-10 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<h2 className="text-xl font-bold text-gray-900 mb-2">서명이 완료되었습니다</h2>
|
|
<p className="text-gray-500 mb-6">
|
|
{isCompleted
|
|
? '모든 서명자의 서명이 완료되어 계약이 체결되었습니다.'
|
|
: '서명이 정상적으로 접수되었습니다. 다른 서명자의 서명이 완료되면 알려드리겠습니다.'
|
|
}
|
|
</p>
|
|
</>
|
|
)}
|
|
|
|
{isRejected && (
|
|
<>
|
|
<div className="w-20 h-20 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<svg className="w-10 h-10 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</div>
|
|
<h2 className="text-xl font-bold text-gray-900 mb-2">서명이 거절되었습니다</h2>
|
|
<p className="text-gray-500 mb-6">서명 거절이 접수되었습니다. 계약 담당자에게 알림이 발송됩니다.</p>
|
|
</>
|
|
)}
|
|
|
|
{!isSigned && !isRejected && (
|
|
<>
|
|
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<svg className="w-10 h-10 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<h2 className="text-xl font-bold text-gray-900 mb-2">처리 완료</h2>
|
|
<p className="text-gray-500 mb-6">요청이 처리되었습니다.</p>
|
|
</>
|
|
)}
|
|
|
|
<div className="bg-gray-50 rounded-lg p-4 text-left text-sm">
|
|
<div className="flex justify-between mb-2">
|
|
<span className="text-gray-500">계약</span>
|
|
<span className="font-medium">{contract?.title || '-'}</span>
|
|
</div>
|
|
<div className="flex justify-between mb-2">
|
|
<span className="text-gray-500">서명자</span>
|
|
<span className="font-medium">{signer?.name || '-'}</span>
|
|
</div>
|
|
{signer?.signed_at && (
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-500">서명일시</span>
|
|
<span className="font-medium">{signer.signed_at?.slice(0,16).replace('T', ' ')}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-xs text-gray-400 mt-6">
|
|
SAM E-Sign 전자계약 서명 시스템
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
ReactDOM.render(<App />, document.getElementById('esign-done-root'));
|
|
</script>
|
|
</body>
|
|
</html>
|