feature: add additional information page for personal and company user

This commit is contained in:
DeathKaioken 2025-10-04 00:37:49 +02:00
parent b14c72cb8d
commit affa6912a9
2 changed files with 626 additions and 0 deletions

View File

@ -0,0 +1,317 @@
'use client'
import { useState } from 'react'
import PageLayout from '../../../components/PageLayout'
interface CompanyProfileData {
companyName: string
vatNumber: string
street: string
postalCode: string
city: string
country: string
accountHolder: string
iban: string
bic: string
secondPhone: string
emergencyName: string
emergencyPhone: string
}
const init: CompanyProfileData = {
companyName: '',
vatNumber: '',
street: '',
postalCode: '',
city: '',
country: '',
accountHolder: '',
iban: '',
bic: '',
secondPhone: '',
emergencyName: '',
emergencyPhone: ''
}
export default function CompanyAdditionalInformationPage() {
const [form, setForm] = useState(init)
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState('')
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setForm(p => ({ ...p, [name]: value }))
setError('')
}
const validate = () => {
const required: (keyof CompanyProfileData)[] = [
'companyName','vatNumber','street','postalCode','city','country','accountHolder','iban'
]
for (const k of required) {
if (!form[k].trim()) {
setError('Bitte alle Pflichtfelder ausfüllen.')
return false
}
}
if (!/^([A-Z]{2}\d{2}[A-Z0-9]{10,30})$/i.test(form.iban.replace(/\s+/g,''))) {
setError('Ungültige IBAN.')
return false
}
setError('')
return true
}
const submit = async (e: React.FormEvent) => {
e.preventDefault()
if (loading || success) return
if (!validate()) return
setLoading(true)
try {
// TODO: POST to backend
await new Promise(r => setTimeout(r, 1300))
setSuccess(true)
} catch {
setError('Speichern fehlgeschlagen.')
} finally {
setLoading(false)
}
}
return (
<PageLayout>
<div className="relative flex flex-col flex-1 w-full px-5 sm:px-8 lg:px-12 py-12">
{/* Background */}
<div className="fixed inset-0 -z-10">
<div className="absolute inset-0 -z-20 bg-gradient-to-b from-gray-900/95 via-gray-900/80 to-gray-900" />
<svg aria-hidden="true" className="absolute inset-0 -z-10 h-full w-full stroke-white/10">
<defs>
<pattern id="company-additional-pattern" x="50%" y={-1} width={200} height={200} patternUnits="userSpaceOnUse">
<path d="M.5 200V.5H200" fill="none" stroke="rgba(255,255,255,0.05)" />
</pattern>
</defs>
<rect fill="url(#company-additional-pattern)" width="100%" height="100%" strokeWidth={0} />
</svg>
<div aria-hidden="true" className="absolute top-0 right-0 left-1/2 -ml-24 blur-3xl transform-gpu overflow-hidden lg:ml-24 xl:ml-48">
<div
className="aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-50"
style={{ clipPath: 'polygon(63.1% 29.5%,100% 17.1%,76.6% 3%,48.4% 0%,44.6% 4.7%,54.5% 25.3%,59.8% 49%,55.2% 57.8%,44.4% 57.2%,27.8% 47.9%,35.1% 81.5%,0% 97.7%,39.2% 100%,35.2% 81.4%,97.2% 52.8%,63.1% 29.5%)' }}
/>
</div>
</div>
<form
onSubmit={submit}
className="relative max-w-6xl w-full mx-auto bg-white rounded-2xl shadow-xl ring-1 ring-black/10"
>
<div className="px-6 py-8 sm:px-10 lg:px-16">
<h1 className="text-center text-xl sm:text-2xl font-semibold text-[#0F172A] mb-6">
Complete Company Profile
</h1>
{/* Company Details */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Company Details
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Company Name *
</label>
<input
name="companyName"
value={form.companyName}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
VAT / Reg No. *
</label>
<input
name="vatNumber"
value={form.vatNumber}
onChange={handleChange}
placeholder="e.g. DE123456789"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Street & Number *
</label>
<input
name="street"
value={form.street}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Postal Code *
</label>
<input
name="postalCode"
value={form.postalCode}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
City *
</label>
<input
name="city"
value={form.city}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Country *
</label>
<input
name="country"
value={form.country}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
</div>
</section>
<hr className="my-8 border-gray-200" />
{/* Bank Details */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Bank Details
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Account Holder *
</label>
<input
name="accountHolder"
value={form.accountHolder}
onChange={handleChange}
placeholder="Company / Holder name"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div className="sm:col-span-2 lg:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
IBAN *
</label>
<input
name="iban"
value={form.iban}
onChange={handleChange}
placeholder="DE89 3704 0044 0532 0130 00"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
BIC (optional)
</label>
<input
name="bic"
value={form.bic}
onChange={handleChange}
placeholder="GENODEF1XXX"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
</div>
</section>
<hr className="my-8 border-gray-200" />
{/* Additional Information */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Additional Information
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Second Phone (optional)
</label>
<input
name="secondPhone"
value={form.secondPhone}
onChange={handleChange}
placeholder="+49 123 456 7890"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Emergency Contact Name
</label>
<input
name="emergencyName"
value={form.emergencyName}
onChange={handleChange}
placeholder="Contact name"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Emergency Contact Phone
</label>
<input
name="emergencyPhone"
value={form.emergencyPhone}
onChange={handleChange}
placeholder="+49 123 456 7890"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div className="hidden lg:block" />
</div>
</section>
{error && (
<div className="mt-6 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-600">
{error}
</div>
)}
{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.
</div>
)}
<div className="mt-10 flex justify-end">
<button
type="submit"
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'}
</button>
</div>
</div>
</form>
</div>
</PageLayout>
)
}

View File

@ -0,0 +1,309 @@
'use client'
import { useState } from 'react'
import PageLayout from '../../../components/PageLayout'
interface PersonalProfileData {
dob: string
nationality: string
street: string
postalCode: string
city: string
country: string
accountHolder: string
iban: string
secondPhone: string
emergencyName: string
emergencyPhone: string
}
const initialData: PersonalProfileData = {
dob: '',
nationality: '',
street: '',
postalCode: '',
city: '',
country: '',
accountHolder: '',
iban: '',
secondPhone: '',
emergencyName: '',
emergencyPhone: ''
}
export default function PersonalAdditionalInformationPage() {
const [form, setForm] = useState(initialData)
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState('')
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setForm(p => ({ ...p, [name]: value }))
setError('')
}
const validate = () => {
const requiredKeys: (keyof PersonalProfileData)[] = [
'dob','nationality','street','postalCode','city','country','accountHolder','iban'
]
for (const k of requiredKeys) {
if (!form[k].trim()) {
setError('Bitte alle Pflichtfelder ausfüllen.')
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.')
return false
}
setError('')
return true
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (loading || success) return
if (!validate()) return
setLoading(true)
try {
// TODO: POST to backend
await new Promise(r => setTimeout(r, 1200))
setSuccess(true)
} catch {
setError('Speichern fehlgeschlagen. Bitte erneut versuchen.')
} finally {
setLoading(false)
}
}
return (
<PageLayout>
<div className="relative flex flex-col flex-1 w-full px-5 sm:px-8 lg:px-12 py-12">
{/* Background */}
<div className="fixed inset-0 -z-10">
<div className="absolute inset-0 -z-20 bg-gradient-to-b from-gray-900/95 via-gray-900/80 to-gray-900" />
<svg aria-hidden="true" className="absolute inset-0 -z-10 h-full w-full stroke-white/10">
<defs>
<pattern id="personal-additional-pattern" x="50%" y={-1} width={200} height={200} patternUnits="userSpaceOnUse">
<path d="M.5 200V.5H200" fill="none" stroke="rgba(255,255,255,0.05)" />
</pattern>
</defs>
<rect fill="url(#personal-additional-pattern)" width="100%" height="100%" strokeWidth={0} />
</svg>
<div aria-hidden="true" className="absolute top-0 right-0 left-1/2 -ml-24 blur-3xl transform-gpu overflow-hidden lg:ml-24 xl:ml-48">
<div
className="aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-50"
style={{ clipPath: 'polygon(63.1% 29.5%,100% 17.1%,76.6% 3%,48.4% 0%,44.6% 4.7%,54.5% 25.3%,59.8% 49%,55.2% 57.8%,44.4% 57.2%,27.8% 47.9%,35.1% 81.5%,0% 97.7%,39.2% 100%,35.2% 81.4%,97.2% 52.8%,63.1% 29.5%)' }}
/>
</div>
</div>
<form
onSubmit={handleSubmit}
className="relative max-w-6xl w-full mx-auto bg-white rounded-2xl shadow-xl ring-1 ring-black/10"
>
<div className="px-6 py-8 sm:px-10 lg:px-16">
<h1 className="text-center text-xl sm:text-2xl font-semibold text-[#0F172A] mb-6">
Complete Your Profile
</h1>
{/* Personal Information */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Personal Information
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Date of Birth *
</label>
<input
type="date"
name="dob"
value={form.dob}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nationality *
</label>
<input
name="nationality"
value={form.nationality}
onChange={handleChange}
placeholder="e.g. German"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Street & House Number *
</label>
<input
name="street"
value={form.street}
onChange={handleChange}
placeholder="Street & House Number"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Postal Code *
</label>
<input
name="postalCode"
value={form.postalCode}
onChange={handleChange}
placeholder="e.g. 12345"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
City *
</label>
<input
name="city"
value={form.city}
onChange={handleChange}
placeholder="e.g. Berlin"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Country *
</label>
<input
name="country"
value={form.country}
onChange={handleChange}
placeholder="e.g. Germany"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
</div>
</section>
<hr className="my-8 border-gray-200" />
{/* Bank Details */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Bank Details
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Account Holder *
</label>
<input
name="accountHolder"
value={form.accountHolder}
onChange={handleChange}
placeholder="Full name"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
<div className="sm:col-span-1 lg:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
IBAN *
</label>
<input
name="iban"
value={form.iban}
onChange={handleChange}
placeholder="e.g. DE89 3704 0044 0532 0130 00"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
required
/>
</div>
</div>
</section>
<hr className="my-8 border-gray-200" />
{/* Additional Information */}
<section>
<h2 className="text-sm font-semibold text-[#0F2460] mb-4">
Additional Information
</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div className="sm:col-span-2 lg:col-span-3">
<label className="block text-sm font-medium text-gray-700 mb-1">
Second Phone Number (optional)
</label>
<input
name="secondPhone"
value={form.secondPhone}
onChange={handleChange}
placeholder="+43 660 1234567"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Emergency Contact Name
</label>
<input
name="emergencyName"
value={form.emergencyName}
onChange={handleChange}
placeholder="Contact name"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Emergency Contact Phone
</label>
<input
name="emergencyPhone"
value={form.emergencyPhone}
onChange={handleChange}
placeholder="+43 660 1234567"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
/>
</div>
<div className="hidden lg:block" />
</div>
</section>
{error && (
<div className="mt-6 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-600">
{error}
</div>
)}
{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.
</div>
)}
<div className="mt-10 flex justify-end">
<button
type="submit"
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'}
</button>
</div>
</div>
</form>
</div>
</PageLayout>
)
}