feat: add required fields for signing city and signature to subscription process

This commit is contained in:
seaznCode 2026-03-20 16:27:49 +01:00
parent b47a517bd5
commit ba28c54ae8
3 changed files with 39 additions and 10 deletions

View File

@ -6,9 +6,11 @@ type Props = {
value: string value: string
onChange: (dataUrl: string) => void onChange: (dataUrl: string) => void
className?: string className?: string
required?: boolean
error?: string | null
} }
export default function SignaturePad({ value, onChange, className }: Props) { export default function SignaturePad({ value, onChange, className, required = false, error = null }: Props) {
const canvasRef = useRef<HTMLCanvasElement | null>(null) const canvasRef = useRef<HTMLCanvasElement | null>(null)
const isDrawing = useRef(false) const isDrawing = useRef(false)
@ -137,7 +139,9 @@ export default function SignaturePad({ value, onChange, className }: Props) {
return ( return (
<div className={className}> <div className={className}>
<div className="flex items-center justify-between gap-2 mb-2"> <div className="flex items-center justify-between gap-2 mb-2">
<p className="text-sm font-medium text-gray-900">Signature</p> <p className="text-sm font-medium text-gray-900">
Signature{required ? ' *' : ''}
</p>
<button <button
type="button" type="button"
onClick={clear} onClick={clear}
@ -146,7 +150,7 @@ export default function SignaturePad({ value, onChange, className }: Props) {
Clear Clear
</button> </button>
</div> </div>
<div className="rounded-lg border border-gray-300 bg-white"> <div className={`rounded-lg border bg-white ${error ? 'border-red-400 ring-1 ring-red-200' : 'border-gray-300'}`}>
<canvas <canvas
ref={canvasRef} ref={canvasRef}
className="block w-full h-36 touch-none" className="block w-full h-36 touch-none"
@ -159,8 +163,8 @@ export default function SignaturePad({ value, onChange, className }: Props) {
onTouchEnd={endDrawing} onTouchEnd={endDrawing}
/> />
</div> </div>
<p className="mt-2 text-xs text-gray-500"> <p className={`mt-2 text-xs ${error ? 'text-red-700' : 'text-gray-500'}`}>
{value ? 'Signature captured.' : 'Draw your signature in the box.'} {error || (value ? 'Signature captured.' : 'Draw your signature in the box.')}
</p> </p>
</div> </div>
) )

View File

@ -70,6 +70,14 @@ export async function subscribeAbo(input: SubscribeAboInput) {
throw new Error(`Missing required fields: ${missing.join(', ')}`) throw new Error(`Missing required fields: ${missing.join(', ')}`)
} }
if (typeof input.signingCity !== 'string' || input.signingCity.trim() === '') {
throw new Error('signingCity is required')
}
if (typeof input.signatureDataUrl !== 'string' || input.signatureDataUrl.trim() === '') {
throw new Error('signatureDataUrl is required')
}
const body: any = { const body: any = {
billing_interval: input.billing_interval ?? 'month', billing_interval: input.billing_interval ?? 'month',
interval_count: input.interval_count ?? 1, interval_count: input.interval_count ?? 1,

View File

@ -510,12 +510,16 @@ export default function SummaryPage() {
const hasRequiredSelfFields = requiredSelfFields.every(k => String(form[k]).trim() !== '') const hasRequiredSelfFields = requiredSelfFields.every(k => String(form[k]).trim() !== '')
const hasRequiredInvoiceFields = form.invoiceSameAsShipping || form.invoiceEmail.trim() !== '' const hasRequiredInvoiceFields = form.invoiceSameAsShipping || form.invoiceEmail.trim() !== ''
const hasSigningCity = form.signingCity.trim() !== ''
const hasSignature = signatureDataUrl.trim() !== ''
const canSubmit = const canSubmit =
selectedEntries.length > 0 && selectedEntries.length > 0 &&
totalPacks === requiredPacks && totalPacks === requiredPacks &&
hasRequiredSelfFields && hasRequiredSelfFields &&
hasRequiredInvoiceFields; hasRequiredInvoiceFields &&
hasSigningCity &&
hasSignature;
const backToSelection = () => router.push('/coffee-abonnements'); const backToSelection = () => router.push('/coffee-abonnements');
@ -527,6 +531,16 @@ export default function SummaryPage() {
return return
} }
if (!hasSigningCity) {
setSubmitError('Signing city is required.')
return
}
if (!hasSignature) {
setSubmitError('Signature is required.')
return
}
setSubmitError(null) setSubmitError(null)
setSubmitLoading(true) setSubmitLoading(true)
try { try {
@ -781,10 +795,13 @@ export default function SummaryPage() {
<div className="mt-4 space-y-3"> <div className="mt-4 space-y-3">
<div> <div>
<label className="block text-sm font-medium mb-1">Ort (Signing City)</label> <label className="block text-sm font-medium mb-1">Ort (Signing City) *</label>
<input type="text" name="signingCity" value={form.signingCity} onChange={handleInput} className="w-full max-w-xs rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" placeholder="z.B. Wien" /> <input type="text" name="signingCity" value={form.signingCity} onChange={handleInput} className={`w-full max-w-xs rounded border px-3 py-2 bg-white focus:outline-none focus:ring-2 focus:ring-[#1C2B4A] ${!hasSigningCity && submitError ? 'border-red-400' : 'border-gray-300'}`} placeholder="z.B. Wien" />
{!hasSigningCity && submitError && (
<p className="mt-1 text-xs text-red-700">Ort ist erforderlich.</p>
)}
</div> </div>
<SignaturePad value={signatureDataUrl} onChange={setSignatureDataUrl} /> <SignaturePad value={signatureDataUrl} onChange={setSignatureDataUrl} required error={!hasSignature && submitError ? 'Signature is required.' : null} />
</div> </div>
</div> </div>
@ -838,7 +855,7 @@ export default function SummaryPage() {
</button> </button>
{!canSubmit && ( {!canSubmit && (
<p className="text-xs text-gray-500 mt-2"> <p className="text-xs text-gray-500 mt-2">
Please select coffees and fill all required buyer fields. Please select coffees and fill all required buyer fields, signing city, and signature.
</p> </p>
)} )}
</div> </div>