Fully functional

This commit is contained in:
sebivh
2025-09-21 17:41:48 +02:00
parent 09bce907ba
commit 31034cb81d
11 changed files with 693 additions and 51 deletions

17
global.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
// global.d.ts
import { IStaticMethods } from "flyonui/flyonui";
declare global {
interface Window {
// Optional third-party libraries
_;
$: typeof import("jquery");
jQuery: typeof import("jquery");
DataTable;
Dropzone;
HSStaticMethods: IStaticMethods;
}
}
export {};

326
package-lock.json generated
View File

@@ -9,15 +9,20 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@sumup/sdk": "^0.0.4", "@sumup/sdk": "^0.0.4",
"flyonui": "^2.4.0",
"next": "15.5.3", "next": "15.5.3",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"ws": "^8.18.3"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/tabler": "^1.2.23",
"@iconify/tailwind4": "^1.0.6",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/ws": "^8.18.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
@@ -35,6 +40,30 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@antfu/install-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
"integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"package-manager-detector": "^1.3.0",
"tinyexec": "^1.0.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@antfu/utils": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz",
"integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
@@ -45,6 +74,82 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@iconify-json/tabler": {
"version": "1.2.23",
"resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.23.tgz",
"integrity": "sha512-Knb8ykgMwB5uSoqrDv1xIdJYM03OP06OXjN6QvGXaTNtdHkW9ciKA+aftz6lwM2jwJA+bsUWeDzLBlPt2uJm4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify/tailwind4": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@iconify/tailwind4/-/tailwind4-1.0.6.tgz",
"integrity": "sha512-43ZXe+bC7CuE2LCgROdqbQeFYJi/J7L/k1UpSy8KDQlWVsWxPzLSWbWhlJx4uRYLOh1NRyw02YlDOgzBOFNd+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "^2.0.0",
"@iconify/utils": "^2.2.1"
},
"funding": {
"url": "https://github.com/sponsors/cyberalien"
},
"peerDependencies": {
"tailwindcss": ">= 4"
}
},
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
"dev": true,
"license": "MIT"
},
"node_modules/@iconify/utils": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz",
"integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@antfu/install-pkg": "^1.0.0",
"@antfu/utils": "^8.1.0",
"@iconify/types": "^2.0.0",
"debug": "^4.4.0",
"globals": "^15.14.0",
"kolorist": "^1.8.0",
"local-pkg": "^1.0.0",
"mlly": "^1.7.4"
}
},
"node_modules/@img/colour": { "node_modules/@img/colour": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
@@ -994,6 +1099,29 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001743", "version": "1.0.30001743",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
@@ -1030,6 +1158,13 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/confbox": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"dev": true,
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -1037,6 +1172,24 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
@@ -1061,6 +1214,35 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/exsolve": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"dev": true,
"license": "MIT"
},
"node_modules/flyonui": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/flyonui/-/flyonui-2.4.0.tgz",
"integrity": "sha512-gXlZLY1/XcywnitWCB4EVbUNU8NSA92RRyi5WGRK5wyQunQEHRW/AvTUyZoiNIxjVC/VRnnO0vkLPsETXsj9RQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.7.1"
}
},
"node_modules/globals": {
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1078,6 +1260,13 @@
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
}, },
"node_modules/kolorist": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"dev": true,
"license": "MIT"
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.30.1", "version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
@@ -1317,6 +1506,24 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.19", "version": "0.30.19",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
@@ -1366,6 +1573,45 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"ufo": "^1.6.1"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -1464,12 +1710,38 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/package-manager-detector": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz",
"integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==",
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -1499,6 +1771,23 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/react": { "node_modules/react": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -1653,6 +1942,13 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/tinyexec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"dev": true,
"license": "MIT"
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -1673,6 +1969,13 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/ufo": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -1680,6 +1983,27 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",

View File

@@ -9,15 +9,20 @@
}, },
"dependencies": { "dependencies": {
"@sumup/sdk": "^0.0.4", "@sumup/sdk": "^0.0.4",
"flyonui": "^2.4.0",
"next": "15.5.3", "next": "15.5.3",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"ws": "^8.18.3"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/tabler": "^1.2.23",
"@iconify/tailwind4": "^1.0.6",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/ws": "^8.18.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }

View File

@@ -19,8 +19,20 @@ const addToCart = async (barcode: number) => {
'GROCY-API-KEY': "VCPlNborGO2t8rs08cvqodalf3AjecRwgexWzkIk221mtshLoM" 'GROCY-API-KEY': "VCPlNborGO2t8rs08cvqodalf3AjecRwgexWzkIk221mtshLoM"
} }
}); });
const userfields = await request2.json(); const userfields = await request2.json();
console.log(`Price of product ${data.product.name} is ${userfields.kuelschrankpreis} EUR`);
if ( data.product.picture_file_name ) {
const image_fetch = await fetch(`https://development.vonhelmersen.online/api/files/productpictures/${btoa(data.product.picture_file_name)}`, {
method: 'GET',
headers: {
'accept': "application/octet-stream",
'GROCY-API-KEY': "VCPlNborGO2t8rs08cvqodalf3AjecRwgexWzkIk221mtshLoM"
}});
const img_blob = await image_fetch.blob()
return {name: data.product.name, price: userfields.kuelschrankpreis, img_blob: img_blob};
} else {
return {name: data.product.name, price: userfields.kuelschrankpreis}; return {name: data.product.name, price: userfields.kuelschrankpreis};
}
} }
export default addToCart; export default addToCart;

View File

@@ -1,6 +1,7 @@
"use server" "use server"
const API_KEY = "sup_sk_t6Jf0FwYUAkbjxzvbs6e3BkIePxm0OR3m" const API_KEY = "sup_sk_t6Jf0FwYUAkbjxzvbs6e3BkIePxm0OR3m"
const HOSTNAME = "kasse.fet.at"
import SumUp from "@sumup/sdk"; import SumUp from "@sumup/sdk";
@@ -13,10 +14,12 @@ const checkout = async (total: number) => {
const merchantCode = (await client.merchant.getMerchantProfile()).merchant_code || ""; const merchantCode = (await client.merchant.getMerchantProfile()).merchant_code || "";
const readerId = (await client.readers.list(merchantCode)).items[0].id || ""; const readerId = (await client.readers.list(merchantCode)).items[0].id || "";
const respomse = await client.readers.createCheckout(merchantCode, readerId, {total_amount: {value: total * 100, currency: "EUR", minor_unit: 2}}); const response = await client.readers.createCheckout(merchantCode, readerId, {
total_amount: {value: total * 100, currency: "EUR", minor_unit: 2},
return_url: `https://${HOSTNAME}/order-status`
});
await new Promise(resolve => setTimeout(resolve, 10000)); await new Promise(resolve => setTimeout(resolve, 10000));
const checkout = await client.transactions.get(merchantCode, {client_transaction_id: respomse.data?.client_transaction_id || ""}); return response.data?.client_transaction_id || "";
console.log("Checkout status:", checkout);
} }
export default checkout; export default checkout;

View File

@@ -0,0 +1,57 @@
// app/api/order-status/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { EventEmitter } from "stream";
export const orderEvents = new EventEmitter();
export async function GET(req: NextRequest) {
// const orderId = params.id;
const orderId = req.nextUrl.searchParams.get("id");
return new Response(
new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
const listener = (data: { id: string; event_type: string }) => {
// TODO!!!! UNCOMMEND THIS
// if (data.orderId != orderId) {
// return;
// }
console.error("THIS SHOULD BE UNCOMMENTED");
console.log("Sending event:", data);
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
};
// orderEvents.on("update", listener);
orderEvents.addListener("update", listener);
// cleanup when client disconnects
req.signal.addEventListener("abort", () => {
orderEvents.off("update", listener);
controller.close();
});
},
}),
{
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
}
);
}
export async function POST(req: NextRequest) {
const body = await req.json();
console.log("Webhook received:", body);
const event_type = body.event_type || "";
const id = body.id || "";
orderEvents.emit("update", { id, event_type });
return NextResponse.json({ message: 'Webhook received' });
}

View File

@@ -1,26 +1,11 @@
@import "tailwindcss"; @import "tailwindcss";
@plugin "flyonui";
@import "flyonui/variants.css";
@plugin "@iconify/tailwind4";
@source "./node_modules/flyonui/flyonui.js";
:root { :root {
--background: #ffffff; --background: #ffffff;
--foreground: #171717; --foreground: #171717;
} }
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono, Inter_Tight } from "next/font/google";
import "./globals.css"; import "./globals.css";
import FlyonuiScript from '../components/FlyonuiScript';
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@@ -24,10 +25,14 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body <body>
className={`${geistSans.variable} ${geistMono.variable} antialiased`} <div className="p-6">
> <div className={`flex w-full flex-row justify-center items-center mb-4 bg-primary h-16 rounded-2xl`}>
<h1 className="text-3xl text-white">Baroness</h1>
</div>
{children} {children}
</div>
<FlyonuiScript />
</body> </body>
</html> </html>
); );

View File

@@ -1,10 +1,20 @@
"use client" "use client"
import addToCart from "@/actions/add-to-cart"; import addToCart from "@/actions/add-to-cart";
import checkout from "@/actions/checkout"; import checkout from "@/actions/checkout";
import { useState } from "react"; import { useEffect, useState } from "react";
const createTableImage = (img_blob: Blob | undefined) => {
if (!img_blob) {
return <div className="flex h-full w-full items-center justify-center text-3xl">?</div>
} else {
return <img src={URL.createObjectURL(img_blob || "")} alt="product image" />
}
}
const quick_products = [ {name: "Cola"}, {name: "Fanta"}, {name: "Wasser"}, {name: "Red Bull"}, {name: "Bier"}, {name: "Wein"} ];
export default function Home() { export default function Home() {
const [ cart, setCart ] = useState<{name: string, price: number}[]>([]); const [ cart, setCart ] = useState<{name: string, price: number, img_blob?: Blob | undefined}[]>([]);
const handleSubmit = async (formData: FormData) => { const handleSubmit = async (formData: FormData) => {
const barcode = formData.get("barcode")? Number(formData.get("barcode")) : 0; const barcode = formData.get("barcode")? Number(formData.get("barcode")) : 0;
addToCart(barcode).then((item) => { addToCart(barcode).then((item) => {
@@ -17,25 +27,197 @@ export default function Home() {
cart.forEach((item) => { cart.forEach((item) => {
total += (Number(item.price)); total += (Number(item.price));
}); });
checkout(total); const client_checkout_id = checkout(total);
setCart([]); setStatus("Initializing");
window.HSOverlay.open('#transparent-modal');
} }
const [status, setStatus] = useState("NONE");
useEffect(() => {
const but = document.getElementById("open-modal");
but?.addEventListener("click", () => {
window.HSOverlay.open('#failed-modal');
});
}, []);
useEffect(() => {
const es = new EventSource(`/api/order-status/`);
console.log("EventSource created");
es.onmessage = (event) => {
const data = JSON.parse(event.data);
setStatus(data.event_type);
console.log("Received event:", data);
switch(data.event_type) {
case "PENDING":
setStatus("Verarbeiteung...");
break;
case "PAID":
// Order is completed
setStatus("Erfolgreich bezahlt!");
window.HSOverlay.close('#transparent-modal')
window.HSOverlay.open('#success-modal');
setStatus("NONE");
setCart([]);
break;
case "FAILED":
window.HSOverlay.open('#failed-modal');
setStatus("NONE");
break;
default:
// Unknown event type
break;
}
};
return () => es.close();
}, []);
return ( return (
<> <>
<h1>Barcode:</h1> <div className="flex flex-row px-6 gap-6">
<form action={handleSubmit}> <div className="w-3/5">
<input type="number" name="barcode" placeholder="Enter barcode" /> <div className="border-base-content/25 w-full rounded-lg border">
<button type="submit">Submit</button> <div className="overflow-x-auto">
</form> <table className="table">
<h2>Cart:</h2> <tbody>
<ul> <tr>
{cart.map((item, index) => ( <td><span className="icon-[tabler--barcode] size-16"></span></td>
<li key={index}>{item.name}: {item.price} EUR</li> <td>Scanne den Barcode deines Produkts um es dem Warenkorb hinzuzufügen.</td>
</tr>
<tr>
<td><span className="icon-[tabler--shopping-cart] size-16"></span></td>
<td><span>Überprüfe deinen Warenkorb und entferne Produkte, die du nicht kaufen möchtest.</span><br /><span> Klicke anschließend auf Bezahlen.</span></td>
</tr>
<tr>
<td><span className="icon-[tabler--credit-card] size-16"></span></td>
<td>Bezahlen mit Karte am Karten Terminal</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2 className="mt-6 text-xl">Schnellauswahl:</h2>
<div className="grid grid-cols-4 gap-4">
{quick_products.map((item, index) => (
<button key={index} className="btn btn-primary" onClick={async () => {
const new_item = await addToCart(item.name === "Bier" ? 400014 : item.name === "Wein" ? 400015 : item.name === "Cola" ? 400000 : item.name === "Fanta" ? 400001 : item.name === "Wasser" ? 400002 : item.name === "Red Bull" ? 400003 : 0);
const newCart = [...cart, new_item];
setCart(newCart);
}}>{item.name}</button>
))} ))}
</ul> </div>
<form action={handleCheckout}> <h2 className="text-xl">Barcode:</h2>
<button type="submit">Checkout</button> <form action={handleSubmit}>
<div className="input max-w-sm" data-input-number>
<input type="text" name="barcode" aria-label="Input barcode" data-input-text-input />
<span className="my-auto flex gap-3">
<button type="button" className="btn btn-primary btn-soft size-5.5 min-h-0 rounded-sm p-0" aria-label="Decrement button" data-input-number-decrement >
<span className="icon-[tabler--minus] size-3.5 shrink-0"></span>
</button>
<button type="button" className="btn btn-primary btn-soft size-5.5 min-h-0 rounded-sm p-0" aria-label="Increment button" data-input-number-increment >
<span className="icon-[tabler--plus] size-3.5 shrink-0"></span>
</button>
</span>
</div>
<button className="btn btn-primary" type="submit">Submit</button>
</form> </form>
<button id="open-modal" className="btn btn-primary">Show Modal</button>
</div>
<div className="w-2/5">
<h2>Cart:</h2>
<table className="table-borderless table-striped table ">
<thead>
<tr className="border-0 bg-base-300/20 *:first:rounded-s-md *:last:rounded-e-md">
<th>Bild</th>
<th>Name</th>
<th>Preis</th>
<th></th>
</tr>
</thead>
<tbody>
{cart.map((item, index) => (
<tr key={index}>
<td className="avatar">
<div className="bg-base-content/10 h-10 w-10 rounded-md">
{ createTableImage(item.img_blob) }
</div>
</td>
<td>{item.name}</td>
<td>{item.price}</td>
<td><button className="btn btn-square bg-error" onClick={
() => {
const newCart = cart.filter((_, i) => i !== index);
setCart(newCart);
}
}><span className="icon-[tabler--trash] size-4.5 shrink-0"></span></button></td>
</tr>
))}
</tbody>
</table>
<form action={handleCheckout}>
<button className="btn btn-square btn-primary" aria-label="Icon Button" type="submit"><span className="icon-[tabler--shopping-cart] size-4.5 shrink-0"></span></button>
</form>
</div>
</div>
<div id="transparent-modal" className="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden place-items-center" role="dialog" tabIndex={-1}>
<div className="modal-dialog">
<div className="modal-content text-white bg-primary shadow-none">
<div className="modal-header">
<h3 className="modal-title text-white">Bitte bezahle auf dem Karten Terminal</h3>
</div>
<div className="modal-body">
<div className="flex w-full flex-col justify-center items-center">
<span className="loading loading-infinity size-56"></span>
<span>Status: {status}</span>
</div>
</div>
<div className="modal-footer">
</div>
</div>
</div>
</div>
<div id="success-modal" className="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden place-items-center" role="dialog" tabIndex={-1}>
<div className="modal-dialog">
<div className="modal-content text-white bg-success shadow-none">
<div className="modal-header">
<h3 className="modal-title text-white">Danke für die Spende!</h3>
</div>
<div className="modal-body">
<div className="flex w-full flex-col justify-center items-center">
<span className="icon-[tabler--check] size-56"></span>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-overlay="#success-modal">Close</button>
</div>
</div>
</div>
</div>
<div id="failed-modal" className="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden place-items-center" role="dialog" tabIndex={-1}>
<div className="modal-dialog">
<div className="modal-content text-white bg-red-800 shadow-none">
<div className="modal-header">
<h3 className="modal-title text-white">Das hat leider nicht funktioniert! Bitte versuche es erneut!</h3>
</div>
<div className="modal-body">
<div className="flex w-full flex-col justify-center items-center">
<span className="icon-[tabler--poo] size-56"></span>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-overlay="#failed-modal">Close</button>
</div>
</div>
</div>
</div>
</> </>
) )
} }

View File

@@ -2,10 +2,15 @@
import getReaderList from "@/actions/getReaderList"; import getReaderList from "@/actions/getReaderList";
import registerReader from "@/actions/registerReader"; import registerReader from "@/actions/registerReader";
import { useState } from "react"; import { useEffect, useState } from "react";
const Page = () => { const Page = () => {
const [ readers, setReaders ] = useState<string[]>(); const [ readers, setReaders ] = useState<string[]>();
useEffect(() => {
getReaderList().then((readerList) => {
setReaders(readerList);
});
}, readers);
const handleRegisterReader = async (formData: FormData) => { const handleRegisterReader = async (formData: FormData) => {
const readername = formData.get("readername")?.toString() || ""; const readername = formData.get("readername")?.toString() || "";
const pairingcode = formData.get("pairingcode")?.toString() || ""; const pairingcode = formData.get("pairingcode")?.toString() || "";

View File

@@ -0,0 +1,47 @@
// FlyonuiScript.tsx
'use client';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
// Optional third-party libraries
// import $ from 'jquery';
// import _ from 'lodash';
// import noUiSlider from 'nouislider';
// import 'datatables.net';
// import 'dropzone/dist/dropzone-min.js';
// window.$ = $;
// window._ = _;
// window.jQuery = $;
// window.DataTable = $.fn.dataTable;
// window.noUiSlider = noUiSlider;
async function loadFlyonUI() {
return import('flyonui/flyonui');
}
export default function FlyonuiScript() {
const path = usePathname();
useEffect(() => {
const initFlyonUI = async () => {
await loadFlyonUI();
};
initFlyonUI();
}, []);
useEffect(() => {
setTimeout(() => {
if (
window.HSStaticMethods &&
typeof window.HSStaticMethods.autoInit === 'function'
) {
window.HSStaticMethods.autoInit();
}
}, 100);
}, [path]);
return null;
}