diff --git a/src/app/ClientWrapper.tsx b/src/app/ClientWrapper.tsx
index 76d850b..b7e849f 100644
--- a/src/app/ClientWrapper.tsx
+++ b/src/app/ClientWrapper.tsx
@@ -1,7 +1,14 @@
'use client';
import { I18nProvider } from './i18n/useTranslation';
+import AuthInitializer from './components/AuthInitializer';
export default function ClientWrapper({ children }: { children: React.ReactNode }) {
- return
{error}
++ We'll send a verification code to your email address. +
+ + ++ Please review and upload your signed service agreement. +
+ + + setContractFile(e.target.files?.[0] || null)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" + /> ++ Complete your verification process to unlock all features.{' '} + +
+
+ {JSON.stringify(debugInfo, null, 2)}
+
+ Loading: {loading ? 'Yes' : 'No'}
+Error: {error || 'None'}
+Status:
+
+ {JSON.stringify(userStatus, null, 2)}
+
+ API Base URL: {process.env.NEXT_PUBLIC_API_BASE_URL}
+Node Env: {process.env.NODE_ENV}
+Current URL: {typeof window !== 'undefined' ? window.location.href : 'SSR'}
+- Personal Account + {isClient && user?.userType === 'company' ? 'Company Account' : 'Personal Account'}
+ {loading && ( +Loading status...
+ )} + {error && ( +{error}
+- Gib den 6-stelligen Code ein, den wir an - {' '} - - {user?.email || 'deine E-Mail'} - {' '} - gesendet haben. + {initialEmailSent ? ( + <> + Wir haben einen 6-stelligen Code an{' '} + + {user?.email || 'deine E-Mail'} + {' '} + gesendet. Gib ihn unten ein. + > + ) : ( + <> + E-Mail wird gesendet an{' '} + + {user?.email || 'deine E-Mail'} + + ... + > + )}
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 169b319..2af6be0 100644 --- a/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx +++ b/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx @@ -1,9 +1,16 @@ 'use client' import { useState, useEffect } from 'react' +import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' +import useAuthStore from '../../../store/authStore' +import { useUserStatus } from '../../../hooks/useUserStatus' export default function CompanySignContractPage() { + const router = useRouter() + const { accessToken } = useAuthStore() + const { refreshStatus } = useUserStatus() + const [companyName, setCompanyName] = useState('') const [repName, setRepName] = useState('') const [repTitle, setRepTitle] = useState('') @@ -21,29 +28,94 @@ export default function CompanySignContractPage() { setDate(new Date().toISOString().slice(0,10)) }, []) - const valid = () => - companyName.trim().length > 2 && - repName.trim().length > 4 && - repTitle.trim().length > 1 && - location.trim().length > 1 && - agreeContract && - agreeData && - confirmSignature + 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 contractChecked = agreeContract + const dataChecked = agreeData + const signatureChecked = confirmSignature + + return companyValid && repNameValid && repTitleValid && locationValid && contractChecked && dataChecked && signatureChecked + } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!valid()) { - setError('Bitte alle Pflichtfelder & Bestätigungen ausfüllen.') + // Detailed error message to help debug + const issues = [] + if (companyName.trim().length < 3) issues.push('Firmenname (mindestens 3 Zeichen)') + if (repName.trim().length < 3) issues.push('Vertreter Name (mindestens 3 Zeichen)') + if (repTitle.trim().length < 2) issues.push('Vertretertitel (mindestens 2 Zeichen)') + if (location.trim().length < 2) issues.push('Ort (mindestens 2 Zeichen)') + if (!agreeContract) issues.push('Vertrag gelesen und verstanden') + if (!agreeData) issues.push('Datenschutzerklärung zugestimmt') + if (!confirmSignature) issues.push('Elektronische Signatur bestätigt') + + setError(`Bitte vervollständigen: ${issues.join(', ')}`) return } + + if (!accessToken) { + setError('Not authenticated. Please log in again.') + return + } + setError('') setSubmitting(true) + try { - // TODO: POST /contracts/company/sign { companyName, repName, repTitle, location, date, note } - await new Promise(r => setTimeout(r, 1400)) + const contractData = { + companyName: companyName.trim(), + representativeName: repName.trim(), + representativeTitle: repTitle.trim(), + location: location.trim(), + date, + note: note.trim() || null, + contractType: 'company', + confirmations: { + agreeContract, + agreeData, + confirmSignature + } + } + + // 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') + + const response = await fetch(`${process.env.NEXT_PUBLIC_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: 'Contract signing failed' })) + throw new Error(errorData.message || 'Contract signing failed') + } + setSuccess(true) - } catch { - setError('Signatur fehlgeschlagen. Bitte erneut versuchen.') + + // Refresh user status to update contract signed state + await refreshStatus() + + // Redirect to main dashboard after short delay + setTimeout(() => { + router.push('/quickaction-dashboard') + }, 2000) + + } catch (error: any) { + console.error('Contract signing error:', error) + setError(error.message || 'Signatur fehlgeschlagen. Bitte erneut versuchen.') } finally { setSubmitting(false) } diff --git a/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx b/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx index bdd6e58..40200ce 100644 --- a/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx +++ b/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx @@ -1,9 +1,16 @@ 'use client' import { useState, useEffect } from 'react' +import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' +import useAuthStore from '../../../store/authStore' +import { useUserStatus } from '../../../hooks/useUserStatus' export default function PersonalSignContractPage() { + const router = useRouter() + const { accessToken } = useAuthStore() + const { refreshStatus } = useUserStatus() + const [fullName, setFullName] = useState('') const [location, setLocation] = useState('') const [date, setDate] = useState('') @@ -19,27 +26,88 @@ export default function PersonalSignContractPage() { setDate(new Date().toISOString().slice(0, 10)) }, []) - const valid = () => - fullName.trim().length > 4 && - location.trim().length > 1 && - agreeContract && - agreeData && - confirmSignature + const valid = () => { + const nameValid = fullName.trim().length >= 3 // Min 3 characters for name + const locationValid = location.trim().length >= 2 // Min 2 characters for location + const contractChecked = agreeContract + const dataChecked = agreeData + const signatureChecked = confirmSignature + + return nameValid && locationValid && contractChecked && dataChecked && signatureChecked + } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!valid()) { - setError('Bitte alle Pflichtfelder & Bestätigungen ausfüllen.') + // Detailed error message to help debug + const issues = [] + if (fullName.trim().length < 3) issues.push('Vollständiger Name (mindestens 3 Zeichen)') + if (location.trim().length < 2) issues.push('Ort (mindestens 2 Zeichen)') + if (!agreeContract) issues.push('Vertrag gelesen und verstanden') + if (!agreeData) issues.push('Datenschutzerklärung zugestimmt') + if (!confirmSignature) issues.push('Elektronische Signatur bestätigt') + + setError(`Bitte vervollständigen: ${issues.join(', ')}`) return } + + if (!accessToken) { + setError('Not authenticated. Please log in again.') + return + } + setError('') setSubmitting(true) + try { - // TODO: POST /contracts/personal/sign { fullName, location, date, note } - await new Promise(r => setTimeout(r, 1200)) + const contractData = { + fullName: fullName.trim(), + location: location.trim(), + date, + note: note.trim() || null, + contractType: 'personal', + confirmations: { + agreeContract, + agreeData, + confirmSignature + } + } + + // 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') + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/upload/contract/personal`, { + 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: 'Contract signing failed' })) + throw new Error(errorData.message || 'Contract signing failed') + } + setSuccess(true) - } catch { - setError('Signatur fehlgeschlagen. Bitte erneut versuchen.') + + // Refresh user status to update contract signed state + await refreshStatus() + + // Redirect to main dashboard after short delay + setTimeout(() => { + router.push('/quickaction-dashboard') + }, 2000) + + } catch (error: any) { + console.error('Contract signing error:', error) + setError(error.message || 'Signatur fehlgeschlagen. Bitte erneut versuchen.') } finally { setSubmitting(false) } diff --git a/src/app/quickaction-dashboard/register-upload-id/company/page.tsx b/src/app/quickaction-dashboard/register-upload-id/company/page.tsx index 6984bb7..270e7da 100644 --- a/src/app/quickaction-dashboard/register-upload-id/company/page.tsx +++ b/src/app/quickaction-dashboard/register-upload-id/company/page.tsx @@ -1,12 +1,19 @@ 'use client' import { useState, useRef } from 'react' +import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import { DocumentArrowUpIcon, XMarkIcon } from '@heroicons/react/24/outline' +import useAuthStore from '../../../store/authStore' +import { useUserStatus } from '../../../hooks/useUserStatus' const DOC_TYPES = ['Handelsregisterauszug', 'Gewerbeanmeldung', 'Steuerbescheid', 'Sonstiges'] export default function CompanyIdUploadPage() { + const router = useRouter() + const { accessToken } = useAuthStore() + const { refreshStatus } = useUserStatus() + const [docNumber, setDocNumber] = useState('') const [docType, setDocType] = useState('') const [issueDate, setIssueDate] = useState('') @@ -39,14 +46,51 @@ export default function CompanyIdUploadPage() { setError('Bitte alle Pflichtfelder (mit *) ausfüllen.') return } + + if (!accessToken) { + setError('Not authenticated. Please log in again.') + return + } + setError('') setSubmitting(true) + try { - // TODO: API upload - await new Promise(r => setTimeout(r, 1200)) + const formData = new FormData() + formData.append('frontFile', frontFile) + if (extraFile) { + formData.append('backFile', extraFile) + } + formData.append('docType', docType) + formData.append('docNumber', docNumber.trim()) + formData.append('issueDate', issueDate) + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/upload/company-id`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${accessToken}` + }, + body: formData + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ message: 'Upload failed' })) + throw new Error(errorData.message || 'Upload failed') + } + setSuccess(true) - } catch { - setError('Upload fehlgeschlagen.') + + // Refresh user status to update verification state + await refreshStatus() + + // Redirect to next step after short delay + setTimeout(() => { + router.push('/quickaction-dashboard/register-additional-information') + }, 1500) + + } catch (error: any) { + console.error('Company ID upload error:', error) + setError(error.message || 'Upload fehlgeschlagen.') } finally { setSubmitting(false) } diff --git a/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx b/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx index 59a8439..907c091 100644 --- a/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx +++ b/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx @@ -2,14 +2,23 @@ import { useState, useRef, useCallback } from 'react' import PageLayout from '../../../components/PageLayout' +import useAuthStore from '../../../store/authStore' +import { useUserStatus } from '../../../hooks/useUserStatus' import { DocumentArrowUpIcon, XMarkIcon } from '@heroicons/react/24/outline' -const ID_TYPES = ['Personalausweis', 'Reisepass', 'Führerschein', 'Aufenthaltstitel'] +const ID_TYPES = [ + { value: 'national_id', label: 'Personalausweis' }, + { value: 'passport', label: 'Reisepass' }, + { value: 'driver_license', label: 'Führerschein' }, + { value: 'other', label: 'Aufenthaltstitel' } +] export default function PersonalIdUploadPage() { + const token = useAuthStore(s => s.accessToken) + const { refreshStatus } = useUserStatus() const [idNumber, setIdNumber] = useState('') const [idType, setIdType] = useState('') const [expiry, setExpiry] = useState('') @@ -58,13 +67,47 @@ export default function PersonalIdUploadPage() { const submit = async (e: React.FormEvent) => { e.preventDefault() if (!validate()) return + if (!token) { + setError('Nicht authentifiziert. Bitte erneut einloggen.') + return + } + setSubmitting(true) + setError('') + try { - // TODO: Upload logic (multipart/form-data) - await new Promise(r => setTimeout(r, 1200)) - setSuccess(true) - } catch { - setError('Upload fehlgeschlagen. Bitte erneut versuchen.') + const formData = new FormData() + formData.append('front', frontFile!) + if (hasBack && backFile) { + formData.append('back', backFile) + } + formData.append('idType', idType) + formData.append('idNumber', idNumber) + formData.append('expiryDate', expiry) + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/upload/personal-id`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + }, + body: formData + }) + + const data = await response.json() + + if (response.ok && data.success) { + setSuccess(true) + await refreshStatus() // Refresh user status + // Redirect after 2 seconds + setTimeout(() => { + window.location.href = '/quickaction-dashboard' + }, 2000) + } else { + setError(data.message || 'Upload fehlgeschlagen. Bitte erneut versuchen.') + } + } catch (error) { + console.error('Upload error:', error) + setError('Netzwerkfehler. Bitte erneut versuchen.') } finally { setSubmitting(false) } @@ -146,7 +189,7 @@ export default function PersonalIdUploadPage() { required > - {ID_TYPES.map(t => )} + {ID_TYPES.map(t => )} diff --git a/src/app/store/authStore.ts b/src/app/store/authStore.ts index 2d43c9f..67f5ab2 100644 --- a/src/app/store/authStore.ts +++ b/src/app/store/authStore.ts @@ -27,6 +27,28 @@ const getStoredUser = () => { } }; +const getStoredToken = () => { + if (typeof window === 'undefined') return null; // SSR check + try { + const token = sessionStorage.getItem('accessToken'); + if (token) { + const expiry = getTokenExpiry(token); + if (expiry && expiry.getTime() > Date.now()) { + log("🔑 Retrieved valid token from sessionStorage"); + return token; + } else { + log("⏰ Stored token expired, removing"); + sessionStorage.removeItem('accessToken'); + return null; + } + } + return null; + } catch (error) { + log("❌ Error retrieving token from sessionStorage:", error); + return null; + } +}; + interface User { email?: string; companyName?: string; @@ -50,7 +72,7 @@ interface AuthStore { const useAuthStore = create