296 lines
13 KiB
TypeScript
296 lines
13 KiB
TypeScript
'use client'
|
|
|
|
import PageLayout from '../../../components/PageLayout'
|
|
import { DocumentArrowUpIcon, XMarkIcon } from '@heroicons/react/24/outline'
|
|
import { useCompanyUploadId } from './hooks/useCompanyUploadId'
|
|
import useAuthStore from '../../../store/authStore' // NEW
|
|
import { useEffect, useState } from 'react' // NEW
|
|
import { useRouter } from 'next/navigation' // NEW
|
|
|
|
const DOC_TYPES = ['Personalausweis', 'Reisepass', 'Führerschein', 'Aufenthaltstitel']
|
|
|
|
export default function CompanyIdUploadPage() {
|
|
const {
|
|
// values
|
|
docNumber, setDocNumber,
|
|
docType, setDocType,
|
|
issueDate, setIssueDate,
|
|
hasBack, setHasBack, setExtraFile,
|
|
frontFile, extraFile,
|
|
frontPreview, extraPreview,
|
|
submitting, error, success,
|
|
frontRef, extraRef,
|
|
inputBase,
|
|
// handlers
|
|
handleFile, onDrop, clearFile, dropHandlers, openPicker, submit,
|
|
} = useCompanyUploadId()
|
|
|
|
const user = useAuthStore(s => s.user) // NEW
|
|
const router = useRouter() // NEW
|
|
const [blocked, setBlocked] = useState(false) // NEW
|
|
|
|
// Guard: only 'company' users allowed on this page
|
|
useEffect(() => {
|
|
const ut = (user as any)?.userType || (user as any)?.role
|
|
console.log('🧭 UploadID Guard [company]: userType =', ut)
|
|
if (ut && ut !== 'company') {
|
|
console.warn('🚫 UploadID Guard [company]: access denied for userType:', ut, '-> redirecting to personal upload')
|
|
setBlocked(true)
|
|
router.replace('/quickaction-dashboard/register-upload-id/personal')
|
|
} else if (ut === 'company') {
|
|
console.log('✅ UploadID Guard [company]: access granted')
|
|
}
|
|
}, [user, router])
|
|
|
|
if (blocked) {
|
|
return (
|
|
<PageLayout>
|
|
<div className="min-h-[50vh] flex items-center justify-center">
|
|
<div className="text-center text-sm text-gray-600">
|
|
Redirecting to the correct upload page…
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<PageLayout>
|
|
<div className="relative flex flex-col flex-1 w-full px-5 lg:px-10 py-10">
|
|
{/* Background (same as personal) */}
|
|
<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-id-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-id-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-7xl w-full mx-auto bg-white rounded-2xl shadow-xl ring-1 ring-black/10 overflow-hidden">
|
|
<div className="px-6 py-8 sm:px-12 lg:px-16">
|
|
<h1 className="text-xl sm:text-2xl font-semibold text-gray-900 mb-1">
|
|
Company Contact Person Identity Verification
|
|
</h1>
|
|
<p className="text-sm text-gray-600 mb-8">
|
|
Please upload clear photos of both sides of the company contact person's ID document.
|
|
</p>
|
|
|
|
{/* Fields: 3 in one row on md+ with unified inputs */}
|
|
<div className="grid gap-6 md:grid-cols-3">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Contact Person ID Number *
|
|
</label>
|
|
<input
|
|
value={docNumber}
|
|
onChange={e => setDocNumber(e.target.value)}
|
|
className={`${inputBase} ${docNumber ? 'text-gray-900' : 'text-gray-700'}`}
|
|
placeholder="Enter contact person's ID number"
|
|
required
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-600">
|
|
Enter the ID number exactly as shown on the document
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Document Type *
|
|
</label>
|
|
<select
|
|
value={docType}
|
|
onChange={e => setDocType(e.target.value)}
|
|
className={`${inputBase} ${docType ? 'text-gray-900' : 'text-gray-700'}`}
|
|
required
|
|
>
|
|
<option value="">Select document type</option>
|
|
{DOC_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Expiry Date *
|
|
</label>
|
|
<input
|
|
type="date"
|
|
value={issueDate}
|
|
onChange={e => setIssueDate(e.target.value)}
|
|
placeholder="tt.mm.jjjj"
|
|
className={`${inputBase} ${issueDate ? 'text-gray-900' : 'text-gray-700'} appearance-none [&::-webkit-calendar-picker-indicator]:opacity-80`}
|
|
required
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-600">
|
|
Enter the expiry date shown on your document
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Back side toggle */}
|
|
<div className="mt-8 flex items-center gap-3">
|
|
<span className="text-sm font-medium text-gray-700">
|
|
Does ID have a Backside?
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => setHasBack(v => { const next = !v; if (!next) setExtraFile(null); return next })}
|
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 focus:outline-none ${hasBack ? 'bg-indigo-600' : 'bg-gray-300'}`}
|
|
aria-pressed={hasBack}
|
|
>
|
|
<span className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ${hasBack ? 'translate-x-5' : 'translate-x-0'}`} />
|
|
</button>
|
|
<span className="text-sm text-gray-700">{hasBack ? 'Yes' : 'No'}</span>
|
|
</div>
|
|
|
|
{/* Upload Areas */}
|
|
<div className={`mt-8 grid gap-6 items-stretch ${hasBack ? 'grid-cols-1 md:grid-cols-2' : 'grid-cols-1'}`}>
|
|
{/* Upload ID Front Side */}
|
|
<div
|
|
{...dropHandlers}
|
|
onDrop={e => onDrop(e, 'front')}
|
|
onClick={() => openPicker('front')}
|
|
className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-indigo-400 hover:bg-indigo-50/40 cursor-pointer transition"
|
|
>
|
|
<input
|
|
ref={frontRef}
|
|
type="file"
|
|
accept="image/*,application/pdf"
|
|
className="hidden"
|
|
onChange={e => { const f = e.target.files?.[0]; if (f) handleFile(f, 'front') }}
|
|
/>
|
|
{frontFile ? (
|
|
<div className="flex w-full max-w-full flex-col items-center">
|
|
{/* Preview only for images */}
|
|
{frontPreview && (
|
|
<img
|
|
src={frontPreview}
|
|
alt="Primary document preview"
|
|
className="w-full max-h-56 object-contain rounded-md bg-white border border-gray-200"
|
|
/>
|
|
)}
|
|
<p className="mt-3 text-sm font-medium text-gray-800 break-all">{frontFile.name}</p>
|
|
<button
|
|
type="button"
|
|
onClick={e => { e.stopPropagation(); clearFile('front') }}
|
|
className="mt-3 inline-flex items-center gap-1 rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-100"
|
|
>
|
|
<XMarkIcon className="h-4 w-4" /> Remove
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<DocumentArrowUpIcon className="h-10 w-10 text-gray-400 group-hover:text-indigo-500 mb-4 transition" />
|
|
<p className="text-sm font-medium text-indigo-600 group-hover:text-indigo-500">
|
|
Click to upload front side
|
|
</p>
|
|
<p className="mt-2 text-xs text-gray-500">
|
|
or drag and drop
|
|
<br />
|
|
PNG, JPG, JPEG up to 10MB
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Upload ID Back Side */}
|
|
{hasBack && (
|
|
<div
|
|
{...dropHandlers}
|
|
onDrop={e => onDrop(e, 'extra')}
|
|
onClick={() => openPicker('extra')}
|
|
className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-indigo-400 hover:bg-indigo-50/40 cursor-pointer transition"
|
|
>
|
|
<input
|
|
ref={extraRef}
|
|
type="file"
|
|
accept="image/*,application/pdf"
|
|
className="hidden"
|
|
onChange={e => { const f = e.target.files?.[0]; if (f) handleFile(f, 'extra') }}
|
|
/>
|
|
{extraFile ? (
|
|
<div className="flex w-full max-w-full flex-col items-center">
|
|
{/* Preview only for images */}
|
|
{extraPreview && (
|
|
<img
|
|
src={extraPreview}
|
|
alt="Supporting document preview"
|
|
className="w-full max-h-56 object-contain rounded-md bg-white border border-gray-200"
|
|
/>
|
|
)}
|
|
<p className="mt-3 text-sm font-medium text-gray-800 break-all">{extraFile.name}</p>
|
|
<button
|
|
type="button"
|
|
onClick={e => { e.stopPropagation(); clearFile('extra') }}
|
|
className="mt-3 inline-flex items-center gap-1 rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-100"
|
|
>
|
|
<XMarkIcon className="h-4 w-4" /> Remove
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<DocumentArrowUpIcon className="h-10 w-10 text-gray-400 group-hover:text-indigo-500 mb-4 transition" />
|
|
<p className="text-sm font-medium text-indigo-600 group-hover:text-indigo-500">
|
|
Click to upload back side
|
|
</p>
|
|
<p className="mt-2 text-xs text-gray-500">
|
|
or drag and drop
|
|
<br />
|
|
PNG, JPG, JPEG up to 10MB
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="mt-8 rounded-lg bg-indigo-50/60 border border-indigo-100 px-5 py-5">
|
|
<p className="text-sm font-semibold text-indigo-900 mb-3">
|
|
Please ensure your ID documents:
|
|
</p>
|
|
<ul className="text-sm text-indigo-800 space-y-1 list-disc pl-5">
|
|
<li>Are clearly visible and readable</li>
|
|
<li>Show all four corners</li>
|
|
<li>Are not expired</li>
|
|
<li>Have good lighting (no shadows or glare)</li>
|
|
<li>{hasBack ? 'Both front and back sides are uploaded' : 'Front side is uploaded'}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mt-6 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600">
|
|
{error}
|
|
</div>
|
|
)}
|
|
{success && (
|
|
<div className="mt-6 rounded-md border border-green-300 bg-green-50 px-4 py-3 text-sm text-green-700">
|
|
Dokumente erfolgreich hochgeladen.
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-8 flex justify-end">
|
|
<button
|
|
type="submit"
|
|
disabled={submitting || success}
|
|
className="inline-flex items-center justify-center rounded-md bg-indigo-600 px-6 py-3 text-sm font-semibold text-white shadow hover:bg-indigo-500 disabled:bg-gray-400 disabled:cursor-not-allowed transition"
|
|
>
|
|
{submitting ? 'Uploading…' : success ? 'Saved' : 'Upload & Continue'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</PageLayout>
|
|
)
|
|
}
|