diff --git a/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx b/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx index a8bbe9a..21c258c 100644 --- a/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx +++ b/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx @@ -1,25 +1,26 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import useAuthStore from '../../../store/authStore' import { useUserStatus } from '../../../hooks/useUserStatus' import { API_BASE_URL } from '../../../utils/api' -import { useToast } from '../../../components/toast/toastComponent' // NEW +import { useToast } from '../../../components/toast/toastComponent' export default function CompanySignContractPage() { const router = useRouter() const { accessToken } = useAuthStore() const { refreshStatus } = useUserStatus() - const { showToast } = useToast() // NEW + const { showToast } = useToast() const [companyName, setCompanyName] = useState('') const [repName, setRepName] = useState('') const [repTitle, setRepTitle] = useState('') const [location, setLocation] = useState('') - const [date, setDate] = useState('') const [note, setNote] = useState('') + const [date, setDate] = useState('') + const [signatureDataUrl, setSignatureDataUrl] = useState('') const [agreeContract, setAgreeContract] = useState(false) const [agreeData, setAgreeData] = useState(false) const [confirmSignature, setConfirmSignature] = useState(false) @@ -31,9 +32,91 @@ export default function CompanySignContractPage() { const [previewError, setPreviewError] = useState(null) useEffect(() => { - setDate(new Date().toISOString().slice(0,10)) + setDate(new Date().toISOString().slice(0, 10)) }, []) + const canvasRef = useRef(null) + const isDrawing = useRef(false) + const scaleRef = useRef(1) + + const getPos = (e: React.MouseEvent | React.TouchEvent) => { + const canvas = canvasRef.current + if (!canvas) return { x: 0, y: 0 } + const rect = canvas.getBoundingClientRect() + const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX + const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY + const x = (clientX - rect.left) * scaleRef.current + const y = (clientY - rect.top) * scaleRef.current + return { x, y } + } + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + const resize = () => { + const rect = canvas.getBoundingClientRect() + const dpr = window.devicePixelRatio || 1 + canvas.width = rect.width * dpr + canvas.height = rect.height * dpr + const ctx = canvas.getContext('2d') + if (ctx) { + ctx.setTransform(dpr, 0, 0, dpr, 0, 0) + ctx.lineWidth = 2 + ctx.lineCap = 'round' + ctx.strokeStyle = '#0f172a' + } + scaleRef.current = dpr + } + resize() + window.addEventListener('resize', resize) + return () => window.removeEventListener('resize', resize) + }, []) + + const startDrawing = (e: React.MouseEvent | React.TouchEvent) => { + e.preventDefault() + const canvas = canvasRef.current + if (!canvas) return + const ctx = canvas.getContext('2d') + if (!ctx) return + ctx.lineWidth = 2 + ctx.lineCap = 'round' + ctx.strokeStyle = '#0f172a' + const { x, y } = getPos(e) + ctx.beginPath() + ctx.moveTo(x, y) + isDrawing.current = true + } + + const draw = (e: React.MouseEvent | React.TouchEvent) => { + e.preventDefault() + if (!isDrawing.current) return + const canvas = canvasRef.current + if (!canvas) return + const ctx = canvas.getContext('2d') + if (!ctx) return + const { x, y } = getPos(e) + ctx.lineTo(x, y) + ctx.stroke() + } + + const endDrawing = () => { + if (!isDrawing.current) return + isDrawing.current = false + const canvas = canvasRef.current + if (!canvas) return + const dataUrl = canvas.toDataURL('image/png') + setSignatureDataUrl(dataUrl) + } + + const clearSignature = () => { + const canvas = canvasRef.current + if (!canvas) return + const ctx = canvas.getContext('2d') + if (!ctx) return + ctx.clearRect(0, 0, canvas.width, canvas.height) + setSignatureDataUrl('') + } + // Load latest contract preview for company user useEffect(() => { const loadPreview = async () => { @@ -41,10 +124,10 @@ export default function CompanySignContractPage() { setPreviewLoading(true) setPreviewError(null) try { - const res = await fetch(`${API_BASE_URL}/api/contracts/preview/latest`, { + const res = await fetch(`${API_BASE_URL}/api/contracts/preview/latest?contract_type=company`, { method: 'GET', - headers: { Authorization: `Bearer ${accessToken}` }, - credentials: 'include' + headers: { Authorization: `Bearer ${accessToken}` }, + credentials: 'include' }) if (!res.ok) { const text = await res.text().catch(() => '') @@ -64,15 +147,16 @@ export default function CompanySignContractPage() { }, [accessToken]) const valid = () => { - const companyValid = companyName.trim().length >= 3 // Min 3 characters for company name - const repNameValid = repName.trim().length >= 3 // Min 3 characters for representative name - const repTitleValid = repTitle.trim().length >= 2 // Min 2 characters for title - const locationValid = location.trim().length >= 2 // Min 2 characters for location + const companyValid = companyName.trim().length >= 3 + const repNameValid = repName.trim().length >= 3 + const repTitleValid = repTitle.trim().length >= 2 + const locationValid = location.trim().length >= 2 const contractChecked = agreeContract const dataChecked = agreeData const signatureChecked = confirmSignature + const signatureDrawn = !!signatureDataUrl - return companyValid && repNameValid && repTitleValid && locationValid && contractChecked && dataChecked && signatureChecked + return companyValid && repNameValid && repTitleValid && locationValid && contractChecked && dataChecked && signatureChecked && signatureDrawn } const handleSubmit = async (e: React.FormEvent) => { @@ -87,6 +171,7 @@ export default function CompanySignContractPage() { if (!agreeContract) issues.push('Contract read and understood') if (!agreeData) issues.push('Privacy policy accepted') if (!confirmSignature) issues.push('Electronic signature confirmed') + if (!signatureDataUrl) issues.push('Signature captured on pad') const msg = `Please complete: ${issues.join(', ')}` setError(msg) @@ -117,9 +202,9 @@ export default function CompanySignContractPage() { companyName: companyName.trim(), representativeName: repName.trim(), representativeTitle: repTitle.trim(), - location: location.trim(), date, - note: note.trim() || null, + location: location.trim(), + note: note.trim(), contractType: 'company', confirmations: { agreeContract, @@ -131,12 +216,11 @@ export default function CompanySignContractPage() { // Create FormData for the existing backend endpoint const formData = new FormData() formData.append('contractData', JSON.stringify(contractData)) - // Create a dummy PDF file since the backend expects one (electronic signature) - const dummyPdfContent = '%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n2 0 obj\n<<\n/Type /Pages\n/Kids [3 0 R]\n/Count 1\n>>\nendobj\n3 0 obj\n<<\n/Type /Page\n/Parent 2 0 R\n/MediaBox [0 0 612 792]\n/Contents 4 0 R\n>>\nendobj\n4 0 obj\n<<\n/Length 44\n>>\nstream\nBT\n/F1 12 Tf\n100 700 Td\n(Electronic Signature) Tj\nET\nendstream\nendobj\nxref\n0 5\n0000000000 65535 f \n0000000010 00000 n \n0000000079 00000 n \n0000000136 00000 n \n0000000225 00000 n \ntrailer\n<<\n/Size 5\n/Root 1 0 R\n>>\nstartxref\n319\n%%EOF' - const dummyFile = new Blob([dummyPdfContent], { type: 'application/pdf' }) - formData.append('contract', dummyFile, 'electronic_signature.pdf') + if (signatureDataUrl) { + formData.append('signatureImage', signatureDataUrl) + } - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/upload/contract/company`, { + const response = await fetch(`${API_BASE_URL}/api/upload/contract/company`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}` @@ -190,13 +274,10 @@ export default function CompanySignContractPage() { return (
- {/* Animated background (same as dashboard) */}
- {/* Soft gradient blobs */}
- {/* Subtle radial highlight */}
@@ -213,7 +294,6 @@ export default function CompanySignContractPage() { Please review the contract details and sign on behalf of the company.

- {/* Meta + Preview */}
@@ -257,7 +337,7 @@ export default function CompanySignContractPage() { setPreviewLoading(true) setPreviewError(null) try { - const res = await fetch(`${API_BASE_URL}/api/contracts/preview/latest`, { + const res = await fetch(`${API_BASE_URL}/api/contracts/preview/latest?contract_type=company`, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` }, credentials: 'include' @@ -296,14 +376,11 @@ export default function CompanySignContractPage() {
- {/* Company Signature Fields */}

Company & Representative

- + { setCompanyName(e.target.value); setError('') }} @@ -313,9 +390,7 @@ export default function CompanySignContractPage() { />
- +
- + { setLocation(e.target.value); setError('') }} @@ -337,9 +410,7 @@ export default function CompanySignContractPage() { />
- + { setRepName(e.target.value); setError('') }} @@ -349,9 +420,7 @@ export default function CompanySignContractPage() { />
- + { setRepTitle(e.target.value); setError('') }} @@ -361,9 +430,7 @@ export default function CompanySignContractPage() { />
- + setNote(e.target.value)} @@ -372,11 +439,40 @@ export default function CompanySignContractPage() { />
+ +
+ +
+ +
+ + Use mouse or touch to sign. A signature is required. + {signatureDataUrl && Captured} +
+
+

- {/* Confirmations */}

Confirmations