feat: add customer fields to subscription and update invoice handling
- Added new customer fields in SubscribeAboInput type including phone, recipient contract name, and invoice details. - Updated subscription validation to include new required fields. - Modified the subscribeAbo function to handle new customer and invoice fields. - Enhanced the SummaryPage component to manage new form fields for invoice address and payment method. - Removed the toggle for "For myself / For someone else" as the logic has been simplified. - Updated contract template handling to reflect changes in the form data structure.
This commit is contained in:
parent
c8092eb83c
commit
11e3e384bd
120
package-lock.json
generated
120
package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"@tailwindplus/elements": "^1.0.22",
|
||||
"@tailwindui/react": "^0.1.1",
|
||||
"axios": "^1.13.5",
|
||||
"canvg": "^4.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"country-flag-icons": "^1.6.13",
|
||||
@ -111,7 +112,6 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@ -451,7 +451,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
@ -475,7 +474,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
@ -3360,7 +3358,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
|
||||
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@types/webxr": "*",
|
||||
@ -3874,15 +3871,13 @@
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@ -3917,7 +3912,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz",
|
||||
"integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
@ -3992,7 +3986,6 @@
|
||||
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.56.0",
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
@ -4522,7 +4515,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -4992,7 +4984,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@ -5123,6 +5114,39 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-4.0.3.tgz",
|
||||
"integrity": "sha512-fKzMoMBwus3CWo1Uy8XJc4tqqn98RoRrGV6CsIkaNiQT5lOeHuMh4fOt+LXLzn2Wqtr4p/c2TOLz4xtu4oBlFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/raf": "^3.4.0",
|
||||
"raf": "^3.4.1",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||
@ -5378,18 +5402,13 @@
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-has-pseudo/node_modules/postcss-selector-parser": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
|
||||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"dev": true,
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-prefers-color-scheme": {
|
||||
@ -5448,8 +5467,7 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
@ -5901,7 +5919,6 @@
|
||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@ -6087,7 +6104,6 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@ -6858,8 +6874,7 @@
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
|
||||
"integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license.",
|
||||
"peer": true
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
@ -7719,6 +7734,26 @@
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf/node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/raf": "^3.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@ -8825,8 +8860,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
@ -8840,7 +8874,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -8878,7 +8911,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@ -9610,19 +9642,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
@ -9723,7 +9742,6 @@
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
@ -9733,7 +9751,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -9743,7 +9760,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@ -9776,7 +9792,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
|
||||
"integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@ -10034,7 +10049,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
@ -10406,7 +10420,6 @@
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
@ -10665,7 +10678,6 @@
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
@ -10690,7 +10702,6 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
@ -10735,8 +10746,7 @@
|
||||
"version": "0.182.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
|
||||
"integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/three-mesh-bvh": {
|
||||
"version": "0.8.3",
|
||||
@ -11054,7 +11064,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -11476,7 +11485,6 @@
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"@tailwindplus/elements": "^1.0.22",
|
||||
"@tailwindui/react": "^0.1.1",
|
||||
"axios": "^1.13.5",
|
||||
"canvg": "^4.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"country-flag-icons": "^1.6.13",
|
||||
|
||||
@ -11,8 +11,7 @@ export type SubscribeAboInput = {
|
||||
target_user_id?: number
|
||||
recipient_name?: string
|
||||
recipient_email?: string
|
||||
recipient_notes?: string
|
||||
// NEW: customer fields
|
||||
// Customer fields
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
email?: string
|
||||
@ -21,8 +20,22 @@ export type SubscribeAboInput = {
|
||||
city?: string
|
||||
country?: string
|
||||
frequency?: string
|
||||
startDate?: string
|
||||
// NEW: logged-in user id
|
||||
// New contract / contact fields
|
||||
phone?: string
|
||||
recipientContractName?: string
|
||||
recipientAddress?: string
|
||||
paymentMethod?: string
|
||||
invoiceByEmail?: boolean
|
||||
invoiceSameAsShipping?: boolean
|
||||
invoiceFullName?: string
|
||||
invoiceStreet?: string
|
||||
invoicePostalCode?: string
|
||||
invoiceCity?: string
|
||||
invoicePhone?: string
|
||||
invoiceEmail?: string
|
||||
signingCity?: string
|
||||
signatureDataUrl?: string
|
||||
// logged-in user id
|
||||
referred_by?: number | string
|
||||
}
|
||||
|
||||
@ -48,7 +61,7 @@ export async function subscribeAbo(input: SubscribeAboInput) {
|
||||
}
|
||||
|
||||
// NEW: validate customer fields (required in UI)
|
||||
const requiredFields = ['firstName','lastName','email','street','postalCode','city','country','frequency'] as const
|
||||
const requiredFields = ['firstName','lastName','email','street','postalCode','city','country'] as const
|
||||
const missing = requiredFields.filter(k => {
|
||||
const v = (input as any)[k]
|
||||
return typeof v !== 'string' || v.trim() === ''
|
||||
@ -62,7 +75,7 @@ export async function subscribeAbo(input: SubscribeAboInput) {
|
||||
interval_count: input.interval_count ?? 1,
|
||||
is_auto_renew: input.is_auto_renew ?? true,
|
||||
is_for_self: isForSelf,
|
||||
// NEW: include customer fields
|
||||
// Customer fields
|
||||
firstName: input.firstName,
|
||||
lastName: input.lastName,
|
||||
email: input.email,
|
||||
@ -71,7 +84,25 @@ export async function subscribeAbo(input: SubscribeAboInput) {
|
||||
city: input.city,
|
||||
country: input.country?.toUpperCase?.() ?? input.country,
|
||||
frequency: input.frequency,
|
||||
startDate: input.startDate || undefined,
|
||||
// New contract / contact fields
|
||||
phone: input.phone || undefined,
|
||||
recipientContractName: input.recipientContractName || undefined,
|
||||
recipientAddress: input.recipientAddress || undefined,
|
||||
paymentMethod: input.paymentMethod || undefined,
|
||||
invoiceByEmail: input.invoiceByEmail ?? false,
|
||||
invoiceSameAsShipping: input.invoiceSameAsShipping ?? true,
|
||||
signingCity: input.signingCity || undefined,
|
||||
signatureDataUrl: input.signatureDataUrl || undefined,
|
||||
}
|
||||
|
||||
// Include invoice address fields when not same as shipping
|
||||
if (!body.invoiceSameAsShipping) {
|
||||
body.invoiceFullName = input.invoiceFullName || undefined
|
||||
body.invoiceStreet = input.invoiceStreet || undefined
|
||||
body.invoicePostalCode = input.invoicePostalCode || undefined
|
||||
body.invoiceCity = input.invoiceCity || undefined
|
||||
body.invoicePhone = input.invoicePhone || undefined
|
||||
body.invoiceEmail = input.invoiceEmail || undefined
|
||||
}
|
||||
if (hasItems) {
|
||||
body.items = input.items!.map(i => ({
|
||||
@ -92,7 +123,6 @@ export async function subscribeAbo(input: SubscribeAboInput) {
|
||||
if (input.target_user_id != null) body.target_user_id = input.target_user_id
|
||||
if (!isForSelf && input.recipient_email) body.recipient_email = input.recipient_email
|
||||
if (!isForSelf && input.recipient_name) body.recipient_name = input.recipient_name
|
||||
if (!isForSelf && input.recipient_notes) body.recipient_notes = input.recipient_notes
|
||||
// NEW: always include referred_by if provided
|
||||
if (input.referred_by != null) body.referred_by = input.referred_by
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { authFetch } from '../../../utils/authFetch'
|
||||
|
||||
const apiBase = (process.env.NEXT_PUBLIC_API_BASE_URL || '').replace(/\/+$/, '')
|
||||
|
||||
export function useAboContractTemplateHtml() {
|
||||
const [html, setHtml] = useState<string | null>(null)
|
||||
@ -14,10 +17,10 @@ export function useAboContractTemplateHtml() {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch('/templates/abo-contract-template.html', {
|
||||
const res = await authFetch(`${apiBase}/api/contracts/abo/active`, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'text/html' },
|
||||
cache: 'no-store',
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const text = await res.text().catch(() => '')
|
||||
|
||||
@ -55,7 +55,6 @@ export default function SummaryPage() {
|
||||
const [contractPdfError, setContractPdfError] = useState<string | null>(null)
|
||||
const [selections, setSelections] = useState<Record<string, number>>({});
|
||||
const [selectedPlanCapsules, setSelectedPlanCapsules] = useState<60 | 120>(120);
|
||||
const [isForSelf, setIsForSelf] = useState(true);
|
||||
const [signatureDataUrl, setSignatureDataUrl] = useState('')
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
@ -65,11 +64,17 @@ export default function SummaryPage() {
|
||||
postalCode: '',
|
||||
city: '',
|
||||
country: 'DE',
|
||||
frequency: 'monatlich',
|
||||
startDate: '',
|
||||
recipientEmail: '',
|
||||
recipientName: '',
|
||||
recipientNotes: '',
|
||||
phone: '',
|
||||
paymentMethod: 'sepa' as 'sepa' | 'card' | 'sofort',
|
||||
invoiceByEmail: true,
|
||||
invoiceSameAsShipping: true,
|
||||
invoiceFullName: '',
|
||||
invoiceStreet: '',
|
||||
invoicePostalCode: '',
|
||||
invoiceCity: '',
|
||||
invoicePhone: '',
|
||||
invoiceEmail: '',
|
||||
signingCity: '',
|
||||
});
|
||||
const [showThanks, setShowThanks] = useState(false);
|
||||
const [confetti, setConfetti] = useState<{ left: number; delay: number; color: string }[]>([]);
|
||||
@ -83,20 +88,50 @@ export default function SummaryPage() {
|
||||
const templateVariableNamesKey = useMemo(() => templateVariableNames.join('|'), [templateVariableNames])
|
||||
const [contractVariables, setContractVariables] = useState<Record<string, string>>({})
|
||||
|
||||
// Auto-compute contract variables from form state for preview
|
||||
useEffect(() => {
|
||||
if (!templateVariableNamesKey) return
|
||||
setContractVariables(prev => {
|
||||
let changed = false
|
||||
const next: Record<string, string> = { ...prev }
|
||||
for (const name of templateVariableNames) {
|
||||
if (next[name] === undefined) {
|
||||
next[name] = ''
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed ? next : prev
|
||||
})
|
||||
}, [templateVariableNamesKey, templateVariableNames])
|
||||
const fullName = `${form.firstName} ${form.lastName}`.trim()
|
||||
const isCompany = user?.userType === 'company' || user?.user_type === 'company'
|
||||
const invoiceSame = form.invoiceSameAsShipping
|
||||
|
||||
const computed: Record<string, string> = {
|
||||
contractNumber: '(wird generiert)',
|
||||
currentDate: new Date().toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric' }),
|
||||
recipientName: fullName,
|
||||
recipientAddress: `${form.street}, ${form.postalCode} ${form.city}`.trim(),
|
||||
shippingCustomerClass: isCompany ? '' : 'checked',
|
||||
shippingCompanyClass: isCompany ? 'checked' : '',
|
||||
shippingFullName: fullName,
|
||||
shippingStreet: form.street,
|
||||
shippingPostalCode: form.postalCode,
|
||||
shippingCity: form.city,
|
||||
shippingPhone: form.phone,
|
||||
shippingEmail: form.email,
|
||||
invoiceSameAsShippingMark: invoiceSame ? '✓' : '',
|
||||
invoiceCompanyClass: isCompany ? 'checked' : '',
|
||||
invoiceCustomerClass: isCompany ? '' : 'checked',
|
||||
invoiceFullName: invoiceSame ? fullName : form.invoiceFullName,
|
||||
invoiceStreet: invoiceSame ? form.street : form.invoiceStreet,
|
||||
invoicePostalCode: invoiceSame ? form.postalCode : form.invoicePostalCode,
|
||||
invoiceCity: invoiceSame ? form.city : form.invoiceCity,
|
||||
invoicePhone: invoiceSame ? form.phone : form.invoicePhone,
|
||||
invoiceEmail: invoiceSame ? form.email : form.invoiceEmail,
|
||||
fnCheckedClass: '',
|
||||
fnNumber: '',
|
||||
atuCheckedClass: '',
|
||||
atuNumber: '',
|
||||
entrepreneurClass: isCompany ? 'checked' : '',
|
||||
consumerClass: isCompany ? '' : 'checked',
|
||||
paymentSepaClass: form.paymentMethod === 'sepa' ? 'checked' : '',
|
||||
paymentCardClass: form.paymentMethod === 'card' ? 'checked' : '',
|
||||
paymentSofortClass: form.paymentMethod === 'sofort' ? 'checked' : '',
|
||||
invoiceByEmailClass: form.invoiceByEmail ? 'checked' : '',
|
||||
signingCity: form.signingCity,
|
||||
fullName,
|
||||
}
|
||||
setContractVariables(computed)
|
||||
}, [templateVariableNamesKey, form, user, signatureDataUrl])
|
||||
|
||||
const populatedContractHtml = useMemo(() => {
|
||||
if (!contractHtml) return null
|
||||
@ -399,14 +434,14 @@ export default function SummaryPage() {
|
||||
const taxAmountWithShipping = useMemo(() => netWithShipping * taxRate, [netWithShipping, taxRate]);
|
||||
const totalWithTax = useMemo(() => netWithShipping + taxAmountWithShipping, [netWithShipping, taxAmountWithShipping]);
|
||||
|
||||
const handleInput = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
const handleInput = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setForm(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleRecipientNotes = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setForm(prev => ({ ...prev, [name]: value }));
|
||||
const handleCheckbox = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, checked } = e.target;
|
||||
setForm(prev => ({ ...prev, [name]: checked }));
|
||||
};
|
||||
|
||||
const fillFromLoggedInData = () => {
|
||||
@ -435,7 +470,7 @@ export default function SummaryPage() {
|
||||
}));
|
||||
};
|
||||
|
||||
const requiredSelfFields: Array<keyof typeof form> = [
|
||||
const requiredSelfFields = [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'email',
|
||||
@ -443,17 +478,16 @@ export default function SummaryPage() {
|
||||
'postalCode',
|
||||
'city',
|
||||
'country',
|
||||
'frequency',
|
||||
]
|
||||
] as const
|
||||
|
||||
const hasRequiredSelfFields = requiredSelfFields.every(k => form[k].trim() !== '')
|
||||
const hasRequiredGiftFields = isForSelf || form.recipientEmail.trim() !== ''
|
||||
const hasRequiredSelfFields = requiredSelfFields.every(k => String(form[k]).trim() !== '')
|
||||
const hasRequiredInvoiceFields = form.invoiceSameAsShipping || form.invoiceEmail.trim() !== ''
|
||||
|
||||
const canSubmit =
|
||||
selectedEntries.length > 0 &&
|
||||
totalPacks === requiredPacks &&
|
||||
hasRequiredSelfFields &&
|
||||
hasRequiredGiftFields;
|
||||
hasRequiredInvoiceFields;
|
||||
|
||||
const backToSelection = () => router.push('/coffee-abonnements');
|
||||
|
||||
@ -464,10 +498,6 @@ export default function SummaryPage() {
|
||||
setSubmitError(`Order must contain exactly ${requiredPacks} packs (${selectedPlanCapsules} capsules).`)
|
||||
return
|
||||
}
|
||||
if (!isForSelf && !form.recipientEmail.trim()) {
|
||||
setSubmitError('Recipient email is required when the subscription is for someone else.')
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitError(null)
|
||||
setSubmitLoading(true)
|
||||
@ -480,8 +510,7 @@ export default function SummaryPage() {
|
||||
billing_interval: 'month',
|
||||
interval_count: 1,
|
||||
is_auto_renew: true,
|
||||
is_for_self: isForSelf,
|
||||
// NEW: pass customer fields
|
||||
is_for_self: true,
|
||||
firstName: form.firstName.trim(),
|
||||
lastName: form.lastName.trim(),
|
||||
email: form.email.trim(),
|
||||
@ -489,12 +518,22 @@ export default function SummaryPage() {
|
||||
postalCode: form.postalCode.trim(),
|
||||
city: form.city.trim(),
|
||||
country: form.country.trim(),
|
||||
frequency: form.frequency.trim(),
|
||||
startDate: form.startDate.trim() || undefined,
|
||||
recipient_email: isForSelf ? undefined : form.recipientEmail.trim(),
|
||||
recipient_name: isForSelf ? undefined : (form.recipientName.trim() || undefined),
|
||||
recipient_notes: isForSelf ? undefined : (form.recipientNotes.trim() || undefined),
|
||||
// NEW: always include referred_by if available
|
||||
frequency: 'monatlich',
|
||||
phone: form.phone.trim() || undefined,
|
||||
recipientContractName: `${form.firstName} ${form.lastName}`.trim() || undefined,
|
||||
paymentMethod: form.paymentMethod,
|
||||
invoiceByEmail: form.invoiceByEmail,
|
||||
invoiceSameAsShipping: form.invoiceSameAsShipping,
|
||||
...(!form.invoiceSameAsShipping ? {
|
||||
invoiceFullName: form.invoiceFullName.trim() || undefined,
|
||||
invoiceStreet: form.invoiceStreet.trim() || undefined,
|
||||
invoicePostalCode: form.invoicePostalCode.trim() || undefined,
|
||||
invoiceCity: form.invoiceCity.trim() || undefined,
|
||||
invoicePhone: form.invoicePhone.trim() || undefined,
|
||||
invoiceEmail: form.invoiceEmail.trim() || undefined,
|
||||
} : {}),
|
||||
signingCity: form.signingCity.trim() || undefined,
|
||||
signatureDataUrl: signatureDataUrl || undefined,
|
||||
referred_by: typeof currentUserId === 'number' ? currentUserId : undefined,
|
||||
}
|
||||
console.info('[SummaryPage] subscribeAbo payload:', payload)
|
||||
@ -585,31 +624,6 @@ export default function SummaryPage() {
|
||||
>
|
||||
Fill fields with logged in data
|
||||
</button>
|
||||
{/* Toggle: For myself / For someone else */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsForSelf(true)}
|
||||
className={`flex-1 rounded-md px-3 py-2 text-sm font-medium transition ${
|
||||
isForSelf
|
||||
? 'bg-[#1C2B4A] text-white shadow'
|
||||
: 'border border-[#1C2B4A] text-[#1C2B4A] hover:bg-[#1C2B4A]/5'
|
||||
}`}
|
||||
>
|
||||
For myself
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsForSelf(false)}
|
||||
className={`flex-1 rounded-md px-3 py-2 text-sm font-medium transition ${
|
||||
!isForSelf
|
||||
? 'bg-[#1C2B4A] text-white shadow'
|
||||
: 'border border-[#1C2B4A] text-[#1C2B4A] hover:bg-[#1C2B4A]/5'
|
||||
}`}
|
||||
>
|
||||
For someone else
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{/* inputs translated */}
|
||||
<div>
|
||||
@ -645,57 +659,70 @@ export default function SummaryPage() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Delivery interval</label>
|
||||
<select name="frequency" value={form.frequency} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]">
|
||||
<option value="monatlich">Monthly</option>
|
||||
<option value="zweimonatlich">Every 2 months</option>
|
||||
<option value="vierteljährlich">Quarterly</option>
|
||||
</select>
|
||||
<label className="block text-sm font-medium mb-1">Phone (optional)</label>
|
||||
<input name="phone" value={form.phone} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Start date (optional)</label>
|
||||
<input type="date" name="startDate" value={form.startDate} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
|
||||
{/* Payment method */}
|
||||
<div className="mt-6 border-t border-gray-200 pt-4">
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-3">Payment method</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{(['sepa', 'card', 'sofort'] as const).map(method => (
|
||||
<label key={method} className={`flex items-center gap-2 rounded-md border px-4 py-2 cursor-pointer transition ${form.paymentMethod === method ? 'border-[#1C2B4A] bg-[#1C2B4A]/5 font-medium' : 'border-gray-300 hover:bg-gray-50'}`}>
|
||||
<input type="radio" name="paymentMethod" value={method} checked={form.paymentMethod === method} onChange={handleInput} className="accent-[#1C2B4A]" />
|
||||
{method === 'sepa' ? 'SEPA' : method === 'card' ? 'Credit Card' : 'Sofort Banking'}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{!isForSelf && (
|
||||
<>
|
||||
<label className="mt-3 flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" name="invoiceByEmail" checked={form.invoiceByEmail} onChange={handleCheckbox} className="accent-[#1C2B4A]" />
|
||||
Send invoice by email
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Invoice address */}
|
||||
<div className="mt-6 border-t border-gray-200 pt-4">
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-3">Invoice address</h3>
|
||||
<label className="flex items-center gap-2 text-sm mb-3">
|
||||
<input type="checkbox" name="invoiceSameAsShipping" checked={form.invoiceSameAsShipping} onChange={handleCheckbox} className="accent-[#1C2B4A]" />
|
||||
Same as shipping address
|
||||
</label>
|
||||
{!form.invoiceSameAsShipping && (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="sm:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Recipient email</label>
|
||||
<input
|
||||
type="email"
|
||||
name="recipientEmail"
|
||||
value={form.recipientEmail}
|
||||
onChange={handleInput}
|
||||
className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]"
|
||||
/>
|
||||
<label className="block text-sm font-medium mb-1">Full name</label>
|
||||
<input name="invoiceFullName" value={form.invoiceFullName} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Recipient name (optional)</label>
|
||||
<input
|
||||
name="recipientName"
|
||||
value={form.recipientName}
|
||||
onChange={handleInput}
|
||||
className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]"
|
||||
/>
|
||||
<label className="block text-sm font-medium mb-1">Street & No.</label>
|
||||
<input name="invoiceStreet" value={form.invoiceStreet} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Recipient note (optional)</label>
|
||||
<textarea
|
||||
name="recipientNotes"
|
||||
value={form.recipientNotes}
|
||||
onChange={handleRecipientNotes}
|
||||
className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]"
|
||||
rows={3}
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">ZIP</label>
|
||||
<input name="invoicePostalCode" value={form.invoicePostalCode} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">City</label>
|
||||
<input name="invoiceCity" value={form.invoiceCity} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Phone (optional)</label>
|
||||
<input name="invoicePhone" value={form.invoicePhone} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Email</label>
|
||||
<input type="email" name="invoiceEmail" value={form.invoiceEmail} onChange={handleInput} className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Contract preview + signature (frontend only for now) */}
|
||||
{/* Contract preview + signature */}
|
||||
<div className="mt-6 border-t border-gray-200 pt-6">
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">Contract template preview (ABO)</h3>
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">Contract preview (ABO)</h3>
|
||||
<p className="text-xs text-gray-600 mb-3">
|
||||
This is the ABO contract HTML template (populated from the fields below, frontend-only).
|
||||
Contract variables are auto-populated from your form data.
|
||||
</p>
|
||||
|
||||
{contractLoading ? (
|
||||
@ -707,41 +734,24 @@ export default function SummaryPage() {
|
||||
Contract preview could not be loaded: {contractError}
|
||||
</div>
|
||||
) : populatedContractHtml ? (
|
||||
<>
|
||||
{templateVariableNames.length > 0 && (
|
||||
<div className="mb-4 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3">
|
||||
<div className="text-sm font-semibold text-gray-900 mb-2">Contract variables</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{templateVariableNames.map(varName => (
|
||||
<div key={varName}>
|
||||
<label className="block text-xs font-medium mb-1 text-gray-700">{varName}</label>
|
||||
<input
|
||||
value={contractVariables[varName] ?? ''}
|
||||
onChange={e =>
|
||||
setContractVariables(prev => ({ ...prev, [varName]: e.target.value }))
|
||||
}
|
||||
className="w-full rounded border px-3 py-2 bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={openContractPreview}
|
||||
className="inline-flex items-center justify-center rounded-md bg-[#1C2B4A] text-white px-4 py-2 text-sm font-semibold hover:bg-[#1C2B4A]/90"
|
||||
>
|
||||
Open preview
|
||||
</button>
|
||||
</>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openContractPreview}
|
||||
className="inline-flex items-center justify-center rounded-md bg-[#1C2B4A] text-white px-4 py-2 text-sm font-semibold hover:bg-[#1C2B4A]/90"
|
||||
>
|
||||
Open preview
|
||||
</button>
|
||||
) : (
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-700">
|
||||
Contract template is not available.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<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" />
|
||||
</div>
|
||||
<SignaturePad value={signatureDataUrl} onChange={setSignatureDataUrl} />
|
||||
</div>
|
||||
</div>
|
||||
@ -796,9 +806,7 @@ export default function SummaryPage() {
|
||||
</button>
|
||||
{!canSubmit && (
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
{isForSelf
|
||||
? 'Please select coffees and fill all required buyer fields.'
|
||||
: 'Please select coffees and fill all required buyer fields plus recipient email.'}
|
||||
Please select coffees and fill all required buyer fields.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@ -884,7 +892,7 @@ export default function SummaryPage() {
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold">Thanks for your subscription!</h3>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
{isForSelf ? 'Subscription created.' : 'Subscription created, invitation sent.'}
|
||||
Subscription created.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 grid gap-3 sm:grid-cols-2">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user