'use client' import { useState, useEffect, useRef, useCallback } 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' import { useTranslation } from '../../../i18n/useTranslation' export default function CompanySignContractPage() { const { t } = useTranslation(); const router = useRouter() const user = useAuthStore(s => s.user) // NEW const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() const { showToast } = useToast() const [date, setDate] = useState('') const [signatureDataUrl, setSignatureDataUrl] = useState('') const [agreeContract, setAgreeContract] = useState(false) const [agreeData, setAgreeData] = useState(false) const [confirmSignature, setConfirmSignature] = useState(false) const [submitting, setSubmitting] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState('') const [previewLoading, setPreviewLoading] = useState(false) const [activeTab, setActiveTab] = useState<'contract' | 'gdpr'>('contract') const [previewState, setPreviewState] = useState({ contract: { loading: false, html: null as string | null, error: null as string | null }, gdpr: { loading: false, html: null as string | null, error: null as string | null } }) useEffect(() => { setDate(new Date().toISOString().slice(0, 10)) }, []) const canvasRef = useRef(null) const isDrawing = useRef(false) 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 // Coordinates should be in CSS pixels. The canvas context is already DPR-scaled via setTransform. const x = clientX - rect.left const y = clientY - rect.top 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' } } 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('') } const loadPreview = async (contractType: 'contract' | 'gdpr') => { if (!accessToken) return setPreviewState((prev) => ({ ...prev, [contractType]: { ...prev[contractType], loading: true, error: null } })) try { const res = await fetch(`${API_BASE_URL}/api/contracts/preview/latest?contract_type=${contractType}`, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` }, credentials: 'include' }) if (!res.ok) { await res.text().catch(() => null) throw new Error('No contract available at this moment, please contact us.') } const html = await res.text() setPreviewState((prev) => ({ ...prev, [contractType]: { loading: false, html, error: null } })) } catch (e: unknown) { console.error('CompanySignContractPage.loadPreview error:', e) setPreviewState((prev) => ({ ...prev, [contractType]: { loading: false, html: null, error: 'No contract available at this moment, please contact us.' } })) } } // Load latest contract + GDPR previews for company user useEffect(() => { if (!accessToken) return setPreviewLoading(true) Promise.all([ loadPreview('contract'), loadPreview('gdpr') ]).finally(() => setPreviewLoading(false)) }, [accessToken]) useEffect(() => { const doneLoading = !previewState.contract.loading && !previewState.gdpr.loading && !previewLoading const anyAvailable = !!previewState.contract.html || !!previewState.gdpr.html const blockingMsg = 'Temporarily unable to sign contracts. No active documents are available at this moment.' if (doneLoading) { // If one preview is missing, default to showing the available one if (!previewState.contract.html && previewState.gdpr.html) { setActiveTab('gdpr') } else if (previewState.contract.html && !previewState.gdpr.html) { setActiveTab('contract') } } // Only show a blocking error if BOTH are missing; clear it as soon as one exists. if (doneLoading) { if (anyAvailable) { setError((prev) => (prev === blockingMsg ? '' : prev)) } else { setError(blockingMsg) } } }, [previewState, previewLoading]) const valid = () => { const contractAvailable = !!previewState.contract.html const gdprAvailable = !!previewState.gdpr.html // Only require acknowledgements for documents that actually exist const contractChecked = contractAvailable ? agreeContract : true const dataChecked = gdprAvailable ? agreeData : true const signatureChecked = confirmSignature const signatureDrawn = !!signatureDataUrl const anyPreview = contractAvailable || gdprAvailable return contractChecked && dataChecked && signatureChecked && signatureDrawn && anyPreview } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!valid()) { // Detailed error message to help debug const issues: string[] = [] const contractAvailable = !!previewState.contract.html const gdprAvailable = !!previewState.gdpr.html if (!contractAvailable && !gdprAvailable) { const msg = t('quickactionDashboard.contractSigning.noDocumentsAvailableMessage') setError(msg) showToast({ variant: 'error', title: t('quickactionDashboard.contractSigning.noDocumentsAvailableTitle'), message: msg, }) return } if (contractAvailable && !agreeContract) issues.push(t('quickactionDashboard.contractSigning.contractReadUnderstood')) if (gdprAvailable && !agreeData) issues.push(t('quickactionDashboard.contractSigning.privacyAccepted')) if (!confirmSignature) issues.push(t('quickactionDashboard.contractSigning.electronicSignatureConfirmed')) if (!signatureDataUrl) issues.push(t('quickactionDashboard.contractSigning.signatureCaptured')) const msg = `${t('quickactionDashboard.contractSigning.completePrefix')} ${issues.join(', ')}` setError(msg) showToast({ variant: 'error', title: t('quickactionDashboard.contractSigning.missingInformationTitle'), message: msg, }) return } if (!accessToken) { const msg = t('quickactionDashboard.contractSigning.authErrorMessage') setError(msg) showToast({ variant: 'error', title: t('quickactionDashboard.contractSigning.authErrorTitle'), message: msg, }) return } setError('') setSubmitting(true) try { const contractData = { date, contractType: 'company', } // Create FormData for the existing backend endpoint const formData = new FormData() formData.append('contractData', JSON.stringify(contractData)) if (signatureDataUrl) { formData.append('signatureImage', signatureDataUrl) } const response = await fetch(`${API_BASE_URL}/api/upload/contract/company`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}` // Don't set Content-Type, let browser set it for FormData }, body: formData }) if (!response.ok) { const errorData = await response.json().catch(() => ({ message: t('autofix.keac0c9e7') })) throw new Error(errorData.message || 'Contract signing failed') } setSuccess(true) showToast({ variant: 'success', title: t('quickactionDashboard.contractSigning.contractSignedTitle'), message: t('quickactionDashboard.contractSigning.companyContractSignedMessage'), }) // Refresh user status to update contract signed state await refreshStatus() // Redirect back to tutorial modal (avoid intermediate dashboard redirect) suppressAutoRedirectRef.current = true smoothReplace('/quickaction-dashboard?tutorial=true') } catch (error: unknown) { console.error('Contract signing error:', error) const msg = error instanceof Error ? (error.message || t('quickactionDashboard.contractSigning.signingFailedMessage')) : t('quickactionDashboard.contractSigning.signingFailedMessage') setError(msg) showToast({ variant: 'error', title: t('quickactionDashboard.contractSigning.signingFailedTitle'), message: msg, }) } finally { setSubmitting(false) } } // NEW: hard block if step already done OR all steps done const [redirectTo, setRedirectTo] = useState(null) const redirectOnceRef = useRef(false) const suppressAutoRedirectRef = useRef(false) const smoothReplace = useCallback((to: string) => { if (redirectOnceRef.current) return redirectOnceRef.current = true setRedirectTo(to) window.setTimeout(() => router.replace(to), 200) }, [router]) useEffect(() => { if (statusLoading || !userStatus) return if (suppressAutoRedirectRef.current) return const allDone = !!userStatus.email_verified && !!userStatus.documents_uploaded && !!userStatus.profile_completed && !!userStatus.contract_signed if (allDone) { smoothReplace('/dashboard') // CHANGED } else if (userStatus.contract_signed) { smoothReplace('/quickaction-dashboard') // CHANGED } }, [statusLoading, userStatus, smoothReplace]) // NEW: must be logged in useEffect(() => { if (!isAuthReady) return if (!user || !accessToken) smoothReplace('/login') }, [isAuthReady, user, accessToken, smoothReplace]) return ( {/* NEW: smooth redirect overlay */} {redirectTo && (
{t('quickactionDashboard.redirecting')}
{t('quickactionDashboard.pleaseWait')}
)}
{/* CHANGED: tighter padding on mobile */}
{/* CHANGED: tighter padding on mobile */}

{t('quickactionDashboard.contractSigning.companyTitle')}

{t('quickactionDashboard.contractSigning.companySubtitle')}

{/* CHANGED: smaller gaps on mobile */}
{/* CHANGED: tighter padding */}
{/* CHANGED: stack header + tabs on mobile */}

{t('quickactionDashboard.contractSigning.documentInformation')}

{(['contract','gdpr'] as const).map((tab) => ( ))}
{(() => { const meta = activeTab === 'contract' ? { id: 'COMP-2025-001', title: 'VERTRIEBSPARTNER / BUSINESSPARTNER / AFFILIATE - VERTRAG', version: 'idF 21.05.2025', jurisdiction: 'EU / Austria (Graz)', language: 'DE (binding)', issuer: 'Profit Planet GmbH', address: 'Liebenauer Hauptstraße 82c, A-8041 Graz' } : { id: 'COMP-GDPR-2025-001', title: 'SUB-AUFTRAGSVERARBEITUNGS-VERTRAG', version: 'Art. 28 Abs. 3 DSGVO', jurisdiction: 'EU / Austria (Graz)', language: 'DE (binding)', issuer: 'Profit Planet GmbH', address: 'Liebenauer Hauptstraße 82c, A-8041 Graz' } return (
  • {t('quickactionDashboard.contractSigning.documentLabel')} {meta.title}
  • {t('quickactionDashboard.contractSigning.idLabel')} {meta.id}
  • {t('quickactionDashboard.contractSigning.versionLabel')} {meta.version}
  • {t('quickactionDashboard.contractSigning.jurisdictionLabel')} {meta.jurisdiction}
  • {t('quickactionDashboard.contractSigning.languageLabel')} {meta.language}
  • {t('quickactionDashboard.contractSigning.issuerLabel')} {meta.issuer}
  • {t('quickactionDashboard.contractSigning.addressLabel')} {meta.address}
) })()}
{/* CHANGED: tighter padding */}

{t('quickactionDashboard.contractSigning.attentionTitle')}

{t('quickactionDashboard.contractSigning.attentionBody')}

{/* CHANGED: stack toolbar on mobile */}
{t('quickactionDashboard.contractSigning.documentPreview')}
{(['contract','gdpr'] as const).map((tab) => ( ))}
{/* CHANGED: buttons wrap + full width on mobile */}
{/* CHANGED: shorter on mobile */} {previewLoading || previewState[activeTab].loading ? (
{t('quickactionDashboard.contractSigning.loadingPreview')}
) : previewState[activeTab].error ? (
{previewState[activeTab].error}
) : previewState[activeTab].html ? (