feat: update success messages for document uploads and contract signing to include redirect notice + remove cow

This commit is contained in:
seaznCode 2025-11-29 15:23:18 +01:00
parent 1f91f09777
commit 6bf1ca006e
6 changed files with 113 additions and 69 deletions

View File

@ -211,11 +211,11 @@ export default function TutorialModal({
{/* Visual Section - Right Half */}
<div className="relative lg:flex-1 mt-4 lg:mt-0 h-32 lg:h-full lg:min-h-[150px] flex items-end justify-end">
<img
{/* <img
src="/images/misc/cow.png"
alt="Profit Planet Mascot"
className="max-h-full max-w-full object-contain opacity-90 pl-30"
/>
/> */}
</div>
</div>
</Dialog.Panel>
@ -324,7 +324,7 @@ export const createTutorialSteps = (
{
id: 6,
title: "You're all set! 🎉 Welcome to the family",
description: "Congratulations! You've completed all the steps perfectly. Our friendly team will now review your information - we'll have you approved and ready to go very soon!",
description: "Congratulations! Our team will now review your information and have you approved very soon!",
details: [
"Our team will carefully review everything you've submitted",
"This usually takes just 1-2 business days",

View File

@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import PageLayout from '../../../components/PageLayout'
import useAuthStore from '../../../store/authStore'
@ -64,6 +64,50 @@ export default function PersonalAdditionalInformationPage() {
const [success, setSuccess] = useState(false)
const [error, setError] = useState('')
// Prefill form if profile already exists
useEffect(() => {
let abort = false
async function loadProfile() {
if (!accessToken) return
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/me`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` }
})
if (!res.ok) return
const data = await res.json().catch(() => null)
const profile = data?.profile
const user = data?.user
if (!profile || abort) return
const toDateInput = (d?: string) => {
if (!d) return ''
const dt = new Date(d)
if (Number.isNaN(dt.getTime())) return ''
return dt.toISOString().split('T')[0]
}
setForm(prev => ({
...prev,
dob: toDateInput(profile.date_of_birth || profile.dateOfBirth),
nationality: profile.nationality || prev.nationality,
street: profile.address || prev.street,
postalCode: profile.zip_code || profile.zipCode || prev.postalCode,
city: profile.city || prev.city,
country: profile.country || prev.country,
accountHolder: profile.account_holder_name || profile.accountHolderName || prev.accountHolder,
// Prefer IBAN from users table (data.user.iban), fallback to profile if any
iban: (user?.iban ?? profile.iban ?? prev.iban) as string,
secondPhone: profile.phone_secondary || profile.phoneSecondary || prev.secondPhone,
emergencyName: profile.emergency_contact_name || profile.emergencyContactName || prev.emergencyName,
emergencyPhone: profile.emergency_contact_phone || profile.emergencyContactPhone || prev.emergencyPhone,
}))
} catch (_) {
// ignore prefill errors; user can still fill manually
}
}
loadProfile()
return () => { abort = true }
}, [accessToken])
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.target
setForm(p => ({ ...p, [name]: value }))
@ -101,20 +145,20 @@ export default function PersonalAdditionalInformationPage() {
]
for (const k of requiredKeys) {
if (!form[k].trim()) {
setError('Bitte alle Pflichtfelder ausfüllen.')
setError('Please fill in all required fields.')
return false
}
}
// Date of birth validation
if (!validateDateOfBirth(form.dob)) {
setError('Ungültiges Geburtsdatum. Sie müssen mindestens 18 Jahre alt sein.')
setError('Invalid date of birth. You must be at least 18 years old.')
return false
}
// very loose IBAN check
if (!/^([A-Z]{2}\d{2}[A-Z0-9]{10,30})$/i.test(form.iban.replace(/\s+/g,''))) {
setError('Ungültige IBAN.')
setError('Invalid IBAN.')
return false
}
setError('')
@ -183,7 +227,7 @@ export default function PersonalAdditionalInformationPage() {
} catch (error: any) {
console.error('Personal profile save error:', error)
setError(error.message || 'Speichern fehlgeschlagen. Bitte erneut versuchen.')
setError(error.message || 'Save failed. Please try again.')
} finally {
setLoading(false)
}
@ -413,7 +457,7 @@ export default function PersonalAdditionalInformationPage() {
)}
{success && (
<div className="mt-6 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-xs text-green-700">
Daten gespeichert.
Data saved. Redirecting shortly
</div>
)}
@ -423,7 +467,7 @@ export default function PersonalAdditionalInformationPage() {
disabled={loading || success}
className="inline-flex items-center rounded-md bg-indigo-600 px-6 py-2.5 text-sm font-semibold text-white shadow hover:bg-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{loading ? 'Speichern…' : success ? 'Gespeichert' : 'Save & Continue'}
{loading ? 'Saving…' : success ? 'Saved' : 'Save & Continue'}
</button>
</div>
</div>

View File

@ -78,15 +78,15 @@ export default function CompanySignContractPage() {
if (!valid()) {
// 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')
if (companyName.trim().length < 3) issues.push('Company name (min 3 characters)')
if (repName.trim().length < 3) issues.push('Representative name (min 3 characters)')
if (repTitle.trim().length < 2) issues.push('Representative title (min 2 characters)')
if (location.trim().length < 2) issues.push('Location (min 2 characters)')
if (!agreeContract) issues.push('Contract read and understood')
if (!agreeData) issues.push('Privacy policy accepted')
if (!confirmSignature) issues.push('Electronic signature confirmed')
setError(`Bitte vervollständigen: ${issues.join(', ')}`)
setError(`Please complete: ${issues.join(', ')}`)
return
}
@ -156,7 +156,7 @@ export default function CompanySignContractPage() {
} catch (error: any) {
console.error('Contract signing error:', error)
setError(error.message || 'Signatur fehlgeschlagen. Bitte erneut versuchen.')
setError(error.message || 'Signature failed. Please try again.')
} finally {
setSubmitting(false)
}
@ -193,7 +193,7 @@ export default function CompanySignContractPage() {
Sign Company Partnership Contract
</h1>
<p className="text-center text-sm text-gray-600 mb-8">
Prüfe die Vertragsdetails und unterschreibe im Namen des Unternehmens.
Please review the contract details and sign on behalf of the company.
</p>
{/* Meta + Preview */}
@ -203,22 +203,22 @@ export default function CompanySignContractPage() {
<h2 className="text-sm font-semibold text-gray-800 mb-3">Contract Information</h2>
<ul className="space-y-2 text-xs sm:text-sm text-gray-600">
<li><span className="font-medium text-gray-700">Contract ID:</span> COMP-2024-017</li>
<li><span className="font-medium text-gray-700">Version:</span> 2.4 (gültig ab 01.11.2024)</li>
<li><span className="font-medium text-gray-700">Version:</span> 2.4 (valid from 01.11.2024)</li>
<li><span className="font-medium text-gray-700">Jurisdiction:</span> EU / Germany</li>
<li><span className="font-medium text-gray-700">Language:</span> DE (binding)</li>
</ul>
</div>
<div className="rounded-lg border border-amber-200 bg-amber-50 p-5">
<h3 className="text-sm font-semibold text-amber-900 mb-2">Achtung</h3>
<h3 className="text-sm font-semibold text-amber-900 mb-2">Attention</h3>
<p className="text-xs sm:text-sm text-amber-800 leading-relaxed">
Du bestätigst rechtsverbindlich, dass du bevollmächtigt bist im Namen des Unternehmens zu unterschreiben.
You confirm that you are authorized to sign on behalf of the company.
</p>
</div>
</div>
<div>
<div className="rounded-lg border border-gray-200 bg-white relative overflow-hidden">
<div className="flex items-center justify-between p-3 border-b border-gray-200 bg-gray-50">
<h3 className="text-sm font-semibold text-gray-900">Vertragsvorschau (Unternehmen)</h3>
<h3 className="text-sm font-semibold text-gray-900">Company Contract Preview</h3>
<div className="flex items-center gap-2">
<button
type="button"
@ -260,18 +260,18 @@ export default function CompanySignContractPage() {
disabled={previewLoading}
className="inline-flex items-center rounded-md bg-indigo-600 hover:bg-indigo-500 text-white px-2.5 py-1.5 text-xs disabled:opacity-60"
>
{previewLoading ? 'Lade…' : 'Refresh'}
{previewLoading ? 'Loading…' : 'Refresh'}
</button>
</div>
</div>
{previewLoading ? (
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Lade Vorschau</div>
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Loading preview</div>
) : previewError ? (
<div className="h-72 flex items-center justify-center text-xs text-red-600 px-4 text-center">{previewError}</div>
) : previewHtml ? (
<iframe title="Company Contract Preview" className="w-full h-72" srcDoc={previewHtml} />
) : (
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Keine Vorschau verfügbar.</div>
<div className="h-72 flex items-center justify-center text-xs text-gray-500">No preview available.</div>
)}
</div>
</div>
@ -285,7 +285,7 @@ export default function CompanySignContractPage() {
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Unternehmensname *
Company Name *
</label>
<input
value={companyName}
@ -297,7 +297,7 @@ export default function CompanySignContractPage() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Datum *
Date *
</label>
<input
type="date"
@ -308,8 +308,8 @@ export default function CompanySignContractPage() {
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ort *
<label className="block text_sm font-medium text-gray-700 mb-1">
Location *
</label>
<input
value={location}
@ -320,8 +320,8 @@ export default function CompanySignContractPage() {
/>
</div>
<div className="sm:col-span-2 lg:col-span-1">
<label className="block text-sm font-medium text-gray-700 mb-1">
Vertreter Name *
<label className="block text_sm font-medium text-gray-700 mb-1">
Representative Name *
</label>
<input
value={repName}
@ -333,7 +333,7 @@ export default function CompanySignContractPage() {
</div>
<div className="sm:col-span-2 lg:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Vertreter Position / Titel *
Representative Position / Title *
</label>
<input
value={repTitle}
@ -345,7 +345,7 @@ export default function CompanySignContractPage() {
</div>
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Notiz (optional)
Note (optional)
</label>
<input
value={note}
@ -361,7 +361,7 @@ export default function CompanySignContractPage() {
{/* Confirmations */}
<section className="space-y-5">
<h2 className="text-sm font-semibold text-[#0F2460]">Bestätigungen</h2>
<h2 className="text-sm font-semibold text-[#0F2460]">Confirmations</h2>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
type="checkbox"
@ -369,7 +369,7 @@ export default function CompanySignContractPage() {
onChange={e => setAgreeContract(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich bestätige, den vollständigen Vertrag im Namen des Unternehmens gelesen und akzeptiert zu haben.</span>
<span>I confirm I have read and accepted the full contract on behalf of the company.</span>
</label>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
@ -378,7 +378,7 @@ export default function CompanySignContractPage() {
onChange={e => setAgreeData(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich stimme der Verarbeitung der Unternehmens- & personenbezogenen Daten gemäß Datenschutzerklärung zu.</span>
<span>I consent to processing of company and personal data in accordance with the privacy policy.</span>
</label>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
@ -387,7 +387,7 @@ export default function CompanySignContractPage() {
onChange={e => setConfirmSignature(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich bin bevollmächtigt, rechtsverbindlich für dieses Unternehmen zu unterschreiben.</span>
<span>I am authorized to sign legally binding documents for this company.</span>
</label>
</section>
@ -398,7 +398,7 @@ export default function CompanySignContractPage() {
)}
{success && (
<div className="mt-8 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
Vertrag erfolgreich unterzeichnet.
Contract signed successfully. Redirecting shortly
</div>
)}
@ -408,7 +408,7 @@ export default function CompanySignContractPage() {
disabled={submitting || success}
className="inline-flex items-center justify-center rounded-md bg-indigo-600 px-8 py-3 text-sm font-semibold text-white shadow hover:bg-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed transition"
>
{submitting ? 'Signiere…' : success ? 'Signiert' : 'Jetzt signieren'}
{submitting ? 'Signing…' : success ? 'Signed' : 'Sign Now'}
</button>
</div>
</div>

View File

@ -73,13 +73,13 @@ export default function PersonalSignContractPage() {
if (!valid()) {
// 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')
if (fullName.trim().length < 3) issues.push('Full name (min 3 characters)')
if (location.trim().length < 2) issues.push('Location (min 2 characters)')
if (!agreeContract) issues.push('Contract read and understood')
if (!agreeData) issues.push('Privacy policy accepted')
if (!confirmSignature) issues.push('Electronic signature confirmed')
setError(`Bitte vervollständigen: ${issues.join(', ')}`)
setError(`Please complete: ${issues.join(', ')}`)
return
}
@ -147,7 +147,7 @@ export default function PersonalSignContractPage() {
} catch (error: any) {
console.error('Contract signing error:', error)
setError(error.message || 'Signatur fehlgeschlagen. Bitte erneut versuchen.')
setError(error.message || 'Signature failed. Please try again.')
} finally {
setSubmitting(false)
}
@ -184,7 +184,7 @@ export default function PersonalSignContractPage() {
Sign Personal Participation Contract
</h1>
<p className="text-center text-sm text-gray-600 mb-8">
Bitte überprüfe die Vertragsdetails und unterschreibe elektronisch.
Please review the contract details and sign electronically.
</p>
{/* Contract Meta + Preview */}
@ -194,22 +194,22 @@ export default function PersonalSignContractPage() {
<h2 className="text-sm font-semibold text-gray-800 mb-3">Contract Information</h2>
<ul className="space-y-2 text-xs sm:text-sm text-gray-600">
<li><span className="font-medium text-gray-700">Contract ID:</span> PERS-2024-001</li>
<li><span className="font-medium text-gray-700">Version:</span> 1.2 (gültig ab 01.11.2024)</li>
<li><span className="font-medium text-gray-700">Version:</span> 1.2 (valid from 01.11.2024)</li>
<li><span className="font-medium text-gray-700">Jurisdiction:</span> EU / Germany</li>
<li><span className="font-medium text-gray-700">Language:</span> DE (verbindlich)</li>
<li><span className="font-medium text-gray-700">Language:</span> EN (binding)</li>
</ul>
</div>
<div className="rounded-lg border border-indigo-100 bg-indigo-50/60 p-5">
<h3 className="text-sm font-semibold text-indigo-900 mb-2">Hinweis</h3>
<h3 className="text-sm font-semibold text-indigo-900 mb-2">Note</h3>
<p className="text-xs sm:text-sm text-indigo-800 leading-relaxed">
Deine elektronische Signatur ist rechtsverbindlich. Stelle sicher, dass alle Angaben korrekt sind.
Your electronic signature is legally binding. Please ensure all details are correct.
</p>
</div>
</div>
<div>
<div className="rounded-lg border border-gray-200 bg-white relative overflow-hidden">
<div className="flex items-center justify-between p-3 border-b border-gray-200 bg-gray-50">
<h3 className="text-sm font-semibold text-gray-900">Vertragsvorschau</h3>
<h3 className="text-sm font-semibold text-gray-900">Contract Preview</h3>
<div className="flex items-center gap-2">
<button
type="button"
@ -227,13 +227,13 @@ export default function PersonalSignContractPage() {
</div>
</div>
{previewLoading ? (
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Lade Vorschau</div>
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Loading preview</div>
) : previewError ? (
<div className="h-72 flex items-center justify-center text-xs text-red-600 px-4 text-center">{previewError}</div>
) : previewHtml ? (
<iframe title="Contract Preview" className="w-full h-72" srcDoc={previewHtml} />
) : (
<div className="h-72 flex items-center justify-center text-xs text-gray-500">Keine Vorschau verfügbar.</div>
<div className="h-72 flex items-center justify-center text-xs text-gray-500">No preview available.</div>
)}
</div>
</div>
@ -247,7 +247,7 @@ export default function PersonalSignContractPage() {
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
Vollständiger Name (Signatur) *
Full Name (Signature) *
</label>
<input
value={fullName}
@ -257,12 +257,12 @@ export default function PersonalSignContractPage() {
required
/>
<p className="mt-1 text-xs text-gray-500">
Muss deinem amtlichen Ausweis entsprechen.
Must match your official ID.
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Datum *
Date *
</label>
<input
type="date"
@ -274,7 +274,7 @@ export default function PersonalSignContractPage() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ort *
Location *
</label>
<input
value={location}
@ -286,7 +286,7 @@ export default function PersonalSignContractPage() {
</div>
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Notiz (optional)
Note (optional)
</label>
<input
value={note}
@ -302,7 +302,7 @@ export default function PersonalSignContractPage() {
{/* Confirmations */}
<section className="space-y-5">
<h2 className="text-sm font-semibold text-[#0F2460]">Bestätigungen</h2>
<h2 className="text-sm font-semibold text-[#0F2460]">Confirmations</h2>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
type="checkbox"
@ -310,7 +310,7 @@ export default function PersonalSignContractPage() {
onChange={e => setAgreeContract(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich bestätige, dass ich den Vertrag vollständig gelesen und verstanden habe.</span>
<span>I confirm that I have read and understood the contract in full.</span>
</label>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
@ -319,7 +319,7 @@ export default function PersonalSignContractPage() {
onChange={e => setAgreeData(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich stimme der Verarbeitung meiner personenbezogenen Daten gemäß Datenschutzerklärung zu.</span>
<span>I consent to the processing of my personal data in accordance with the privacy policy.</span>
</label>
<label className="flex items-start gap-3 text-sm text-gray-700">
<input
@ -328,7 +328,7 @@ export default function PersonalSignContractPage() {
onChange={e => setConfirmSignature(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
<span>Ich bestätige, dass diese elektronische Unterschrift rechtsverbindlich ist und einer handschriftlichen Signatur entspricht.</span>
<span>I confirm this electronic signature is legally binding and equivalent to a handwritten signature.</span>
</label>
</section>
@ -339,7 +339,7 @@ export default function PersonalSignContractPage() {
)}
{success && (
<div className="mt-8 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
Vertrag erfolgreich unterzeichnet.
Contract signed successfully. Redirecting shortly
</div>
)}
@ -349,7 +349,7 @@ export default function PersonalSignContractPage() {
disabled={submitting || success}
className="inline-flex items-center justify-center rounded-md bg-indigo-600 px-8 py-3 text-sm font-semibold text-white shadow hover:bg-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed transition"
>
{submitting ? 'Signiere…' : success ? 'Signiert' : 'Jetzt signieren'}
{submitting ? 'Signing…' : success ? 'Signed' : 'Sign Now'}
</button>
</div>
</div>

View File

@ -274,7 +274,7 @@ export default function CompanyIdUploadPage() {
)}
{success && (
<div className="mt-6 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
Documents uploaded successfully.
Documents uploaded successfully. Redirecting shortly
</div>
)}

View File

@ -285,7 +285,7 @@ export default function PersonalIdUploadPage() {
)}
{success && (
<div className="mt-6 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
Upload saved successfully.
Upload saved successfully. Redirecting shortly
</div>
)}