This commit is contained in:
parent
d78ece0dfd
commit
692f9bdd2a
166
package-lock.json
generated
166
package-lock.json
generated
@ -54,6 +54,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"pdfjs-dist": "^5.2.133",
|
"pdfjs-dist": "^5.2.133",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-big-calendar": "^1.19.2",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
@ -1116,6 +1117,16 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
"node_modules/@radix-ui/number": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||||
@ -2695,6 +2706,18 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@restart/hooks": {
|
||||||
|
"version": "0.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||||
|
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "^2.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
|
||||||
@ -3404,6 +3427,12 @@
|
|||||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/warning": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.11.0",
|
"version": "8.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz",
|
||||||
@ -4858,6 +4887,12 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-arithmetic": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
@ -4868,6 +4903,12 @@
|
|||||||
"url": "https://github.com/sponsors/kossnocorp"
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
@ -5847,6 +5888,11 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/globalize": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/globalize/-/globalize-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA=="
|
||||||
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "15.11.0",
|
"version": "15.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz",
|
||||||
@ -6492,6 +6538,12 @@
|
|||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.castarray": {
|
"node_modules/lodash.castarray": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||||
@ -7009,6 +7061,15 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
|
||||||
|
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.12",
|
"version": "0.30.12",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
|
||||||
@ -7205,6 +7266,12 @@
|
|||||||
"integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==",
|
"integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/memoize-one": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/merge-refs": {
|
"node_modules/merge-refs": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz",
|
||||||
@ -7759,6 +7826,27 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -8485,6 +8573,43 @@
|
|||||||
"react": ">=16.4.1"
|
"react": ">=16.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-big-calendar": {
|
||||||
|
"version": "1.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.2.tgz",
|
||||||
|
"integrity": "sha512-2orH+TOXPJBlQGwSl9ZnTK2WZR9OfVf0r1s8mnbpjvtENZfmWHP6nXqxmten1vkvzOMqefVGjh5GurM27HHOZw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.7",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
|
"date-arithmetic": "^4.1.0",
|
||||||
|
"dayjs": "^1.11.7",
|
||||||
|
"dom-helpers": "^5.2.1",
|
||||||
|
"globalize": "^0.1.1",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"luxon": "^3.2.1",
|
||||||
|
"memoize-one": "^6.0.0",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"moment-timezone": "^0.5.40",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-overlays": "^5.2.1",
|
||||||
|
"uncontrollable": "^7.2.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.14.0 || ^17 || ^18 || ^19",
|
||||||
|
"react-dom": "^16.14.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-big-calendar/node_modules/clsx": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-day-picker": {
|
"node_modules/react-day-picker": {
|
||||||
"version": "8.10.1",
|
"version": "8.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
|
||||||
@ -8559,6 +8684,12 @@
|
|||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-lifecycles-compat": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-loading-skeleton": {
|
"node_modules/react-loading-skeleton": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz",
|
||||||
@ -8595,6 +8726,26 @@
|
|||||||
"react": ">=18"
|
"react": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-overlays": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.8",
|
||||||
|
"@popperjs/core": "^2.11.6",
|
||||||
|
"@restart/hooks": "^0.4.7",
|
||||||
|
"@types/warning": "^3.0.0",
|
||||||
|
"dom-helpers": "^5.2.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"uncontrollable": "^7.2.1",
|
||||||
|
"warning": "^4.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.3.0",
|
||||||
|
"react-dom": ">=16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-pdf": {
|
"node_modules/react-pdf": {
|
||||||
"version": "9.2.1",
|
"version": "9.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.2.1.tgz",
|
||||||
@ -9686,6 +9837,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uncontrollable": {
|
||||||
|
"version": "7.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||||
|
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.6.3",
|
||||||
|
"@types/react": ">=16.9.11",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"react-lifecycles-compat": "^3.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
|
|||||||
@ -57,6 +57,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"pdfjs-dist": "^5.2.133",
|
"pdfjs-dist": "^5.2.133",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-big-calendar": "^1.19.2",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import "aos/dist/aos.css";
|
|||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { useAuth } from "@/context/AuthContext";
|
|||||||
const AdminDashboard = () => {
|
const AdminDashboard = () => {
|
||||||
const { isAuthenticated, username, isAdmin, logout } = useAuth();
|
const { isAuthenticated, username, isAdmin, logout } = useAuth();
|
||||||
|
|
||||||
|
const defaultImage = "/images/tgl-ball.png";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto py-12 px-4">
|
<div className="max-w-6xl mx-auto py-12 px-4">
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { set } from "date-fns";
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_URL;
|
const apiBase = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
@ -115,6 +116,38 @@ const TeamDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCarouselUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if(!files) return;
|
||||||
|
|
||||||
|
const uploadPaths: string[] = JSON.parse(carouselImages || "[]");
|
||||||
|
|
||||||
|
for (const file of files){
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", file);
|
||||||
|
|
||||||
|
const res = await fetch(`${apiBase}/api/teams/${id}/carousel-upload`, {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(res.ok){
|
||||||
|
const {path} = await res.json();
|
||||||
|
uploadPaths.push(path);
|
||||||
|
}else{
|
||||||
|
toast.error("Fehler beim Hochladen des Bildes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCarouselImages(JSON.stringify(uploadPaths));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeCarouselImage = (index: number) => {
|
||||||
|
const paths: string[] = JSON.parse(carouselImages || "[]");
|
||||||
|
paths.splice(index, 1);
|
||||||
|
setCarouselImages(JSON.stringify(paths));
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) return <p className="text-center py-12">Lade Team...</p>;
|
if (loading) return <p className="text-center py-12">Lade Team...</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -144,11 +177,31 @@ const TeamDetail = () => {
|
|||||||
<label className="text-gray-700">Neue Spieler gesucht?</label>
|
<label className="text-gray-700">Neue Spieler gesucht?</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Textarea
|
<div>
|
||||||
value={carouselImages}
|
<label className="block mb-1 font-medium text-gray-700">Karussell-Bilder</label>
|
||||||
onChange={(e) => setCarouselImages(e.target.value)}
|
<input
|
||||||
placeholder="Karussell-Bilder (JSON-Array z.B. [\"//uploads/x.jpg\"])"
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
multiple
|
||||||
|
onChange={handleCarouselUpload}
|
||||||
|
className="block"
|
||||||
/>
|
/>
|
||||||
|
<div className="grid grid-cols-3 gap-4 mt-4">
|
||||||
|
{JSON.parse(carouselImages || "[]").map((img: string, idx: number) => (
|
||||||
|
<div key={idx} className="relative group">
|
||||||
|
<img src={`${apiBase}${img}`} className="rounded shadow" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeCarouselImage(idx)}
|
||||||
|
className="absolute top-1 right-1 bg-red-600 text-white rounded-full px-2 py-1 text-xs opacity-80 group-hover:opacity-100"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<Button onClick={handleUpdateTeam} className="bg-frog-500 hover:bg-frog-600 text-white">
|
<Button onClick={handleUpdateTeam} className="bg-frog-500 hover:bg-frog-600 text-white">
|
||||||
Team speichern
|
Team speichern
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const AboutSection = () => {
|
const AboutSection = () => {
|
||||||
return (
|
return (
|
||||||
<section id="about" className="py-16">
|
<section id="about" className="py-16">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
<div className="space-y-6 text-lg">
|
||||||
<div>
|
<h2 className="text-3xl font-bold text-gray-900">Über TG Laudenbach</h2>
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-6">Über TG Laudenbach</h2>
|
|
||||||
<div className="space-y-4 text-lg">
|
|
||||||
<p>
|
<p>
|
||||||
Der Volleyballverein TG Laudenbach wurde 1974 gegründet und blickt auf eine erfolgreiche Geschichte zurück.
|
Der Volleyballverein TG Laudenbach wurde 1974 gegründet und blickt auf eine erfolgreiche Geschichte zurück.
|
||||||
Wir sind mehr als nur ein Sportverein – wir sind eine Gemeinschaft aus begeisterten Volleyballern jeden Alters und Spielniveaus.
|
Wir sind mehr als nur ein Sportverein – wir sind eine Gemeinschaft aus begeisterten Volleyballern jeden Alters und Spielniveaus.
|
||||||
@ -21,38 +18,6 @@ const AboutSection = () => {
|
|||||||
Neben dem sportlichen Erfolg ist uns auch das Miteinander wichtig. Regelmäßige Vereinsfeste, gemeinsame Ausflüge und
|
Neben dem sportlichen Erfolg ist uns auch das Miteinander wichtig. Regelmäßige Vereinsfeste, gemeinsame Ausflüge und
|
||||||
unser jährliches Beachvolleyball-Turnier sorgen für ein aktives Vereinsleben.
|
unser jährliches Beachvolleyball-Turnier sorgen für ein aktives Vereinsleben.
|
||||||
</p>
|
</p>
|
||||||
<div className="pt-4">
|
|
||||||
<Button className="bg-frog-500 hover:bg-frog-600">
|
|
||||||
Mehr über uns erfahren
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="rounded-xl overflow-hidden shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://images.unsplash.com/photo-1574271143515-5cddf8da19be?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
|
||||||
alt="Teamfoto TG Laudenbach"
|
|
||||||
className="w-full h-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-6">
|
|
||||||
<div className="rounded-xl overflow-hidden shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://images.unsplash.com/photo-1529676468461-bd9955e9e4f8?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80"
|
|
||||||
alt="Volleyball Training"
|
|
||||||
className="w-full h-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl overflow-hidden shadow-md">
|
|
||||||
<img
|
|
||||||
src="https://images.unsplash.com/photo-1612214070475-1e73f478188c?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80"
|
|
||||||
alt="Volleyballhalle"
|
|
||||||
className="w-full h-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
82
src/components/EventsSection.tsx
Normal file
82
src/components/EventsSection.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { CalendarDays, Clock } from "lucide-react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const mockEvents = [
|
||||||
|
{
|
||||||
|
title: "Heimspiel Herren 1 vs. TSG Nußloch 5",
|
||||||
|
date: "2025-06-08",
|
||||||
|
location: "Bergstraßenhalle Laudenbach",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sommerfest der Abteilung",
|
||||||
|
date: "2025-06-15",
|
||||||
|
location: "Vereinsheim",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Training Jugend U16",
|
||||||
|
date: "2025-06-17",
|
||||||
|
location: "Halle 2",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const EventsSection = () => {
|
||||||
|
const getDaysUntil = (dateStr: string) => {
|
||||||
|
const today = new Date();
|
||||||
|
const target = new Date(dateStr);
|
||||||
|
const diffTime = target.getTime() - today.getTime();
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
return diffDays;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-gray-50 py-12" id="events">
|
||||||
|
<div className="section-container text-center">
|
||||||
|
<h2 className="text-2xl md:text-3xl font-bold mb-4 flex justify-center items-center gap-2">
|
||||||
|
|
||||||
|
Kommende Events
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 mb-6 text-sm md:text-base">
|
||||||
|
Spieltage & Events im Überblick
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap justify-center gap-4">
|
||||||
|
{mockEvents.map((event, i) => {
|
||||||
|
const daysLeft = getDaysUntil(event.date);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="bg-white shadow-sm rounded-xl p-4 text-left border border-gray-200 text-sm w-[280px]"
|
||||||
|
>
|
||||||
|
<p className="text-gray-500 text-xs mb-1">
|
||||||
|
📅 {new Date(event.date).toLocaleDateString("de-DE")}
|
||||||
|
</p>
|
||||||
|
<h3 className="font-semibold text-base">{event.title}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-1">{event.location}</p>
|
||||||
|
<div className="flex items-center gap-1 text-primary text-xs mt-2">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
{daysLeft > 0
|
||||||
|
? `Noch ${daysLeft} Tag${daysLeft > 1 ? "e" : ""}`
|
||||||
|
: daysLeft === 0
|
||||||
|
? "Heute!"
|
||||||
|
: "Bereits vorbei"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<Link
|
||||||
|
to="/events"
|
||||||
|
className="inline-block bg-primary text-white text-sm py-2 px-4 rounded-lg hover:bg-primary/90 transition"
|
||||||
|
>
|
||||||
|
Zum Eventkalender
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventsSection;
|
||||||
@ -74,17 +74,18 @@ const Navbar = () => {
|
|||||||
|
|
||||||
{isTeamsOpen && (
|
{isTeamsOpen && (
|
||||||
<div className="absolute left-0 mt-2 w-40 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-10">
|
<div className="absolute left-0 mt-2 w-40 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-10">
|
||||||
<Link to="/teams/damen1" className="block px-4 py-2 text-gray-700 hover:bg-frog-50 hover:text-frog-600">Damen 1</Link>
|
<Link to="/teams/4" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 1</Link>
|
||||||
<Link to="/teams/damen2" className="block px-4 py-2 text-gray-700 hover:bg-frog-50 hover:text-frog-600">Damen 2</Link>
|
<Link to="/teams/5" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 2</Link>
|
||||||
<Link to="/teams/herren1" className="block px-4 py-2 text-gray-700 hover:bg-frog-50 hover:text-frog-600">Herren 1</Link>
|
<Link to="/teams/1" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 1</Link>
|
||||||
<Link to="/teams/herren2" className="block px-4 py-2 text-gray-700 hover:bg-frog-50 hover:text-frog-600">Herren 2</Link>
|
<Link to="/teams/2" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 2</Link>
|
||||||
|
<Link to="/teams/9" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Mixed</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<button onClick={() => navigateAndScroll("gallery")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium">Galerie</button>
|
<button onClick={() => navigateAndScroll("gallery")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium">Galerie</button>
|
||||||
<button onClick={() => navigateAndScroll("about")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium">Über uns</button>
|
<button onClick={() => navigateAndScroll("about")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium whitespace-nowrap">Über uns</button>
|
||||||
<button onClick={() => navigateAndScroll("contact")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium">Kontakt</button>
|
<button onClick={() => navigateAndScroll("contact")} className="text-gray-700 hover:text-frog-600 px-3 py-2 rounded-md font-medium">Kontakt</button>
|
||||||
|
|
||||||
<Link to="/mitglied-werden" className="w-full">
|
<Link to="/mitglied-werden" className="w-full">
|
||||||
@ -171,10 +172,11 @@ const Navbar = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div className="block px-3 py-2 rounded-md text-base font-medium text-gray-700">Teams</div>
|
<div className="block px-3 py-2 rounded-md text-base font-medium text-gray-700">Teams</div>
|
||||||
<div className="pl-6">
|
<div className="pl-6">
|
||||||
<Link to="/teams/damen1" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 1</Link>
|
<Link to="/teams/4" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 1</Link>
|
||||||
<Link to="/teams/damen2" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 2</Link>
|
<Link to="/teams/5" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Damen 2</Link>
|
||||||
<Link to="/teams/herren1" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 1</Link>
|
<Link to="/teams/1" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 1</Link>
|
||||||
<Link to="/teams/herren2" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 2</Link>
|
<Link to="/teams/2" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Herren 2</Link>
|
||||||
|
<Link to="/teams/9" className="block px-3 py-1 text-gray-700 hover:text-frog-600">Mixed</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/context/AuthContext";
|
||||||
|
|
||||||
const PrivateRoute = ({ children }: { children: JSX.Element }) => {
|
const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated, isAuthLoading } = useAuth();
|
||||||
|
|
||||||
|
if (isAuthLoading) {
|
||||||
|
return <div className="text-center py-12">🔒 Authentifizierung wird geprüft...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return <Navigate to="/admin/login" replace />;
|
return <Navigate to="/admin/login" replace />;
|
||||||
@ -11,4 +15,4 @@ const PrivateRoute = ({ children }: { children: JSX.Element }) => {
|
|||||||
return children;
|
return children;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrivateRoute;
|
export default ProtectedRoute;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ interface AuthContextType {
|
|||||||
login: (token: string) => void;
|
login: (token: string) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
|
isAuthLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType>({
|
const AuthContext = createContext<AuthContextType>({
|
||||||
@ -19,36 +20,47 @@ const AuthContext = createContext<AuthContextType>({
|
|||||||
login: () => {},
|
login: () => {},
|
||||||
logout: () => {},
|
logout: () => {},
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
|
isAuthLoading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [token, setToken] = useState<string | null>(null);
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const [username, setUsername] = useState<string | null>(null);
|
const [username, setUsername] = useState<string | null>(null);
|
||||||
const [role, setRole] = useState<string | null>(null);
|
const [role, setRole] = useState<string | null>(null);
|
||||||
|
const [isAuthLoading, setIsAuthLoading] = useState(true); // NEU
|
||||||
|
|
||||||
const isAdmin = role === "admin";
|
const isAdmin = role === "admin";
|
||||||
|
|
||||||
const isAuthenticated = !!token;
|
const isAuthenticated = !!token;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedToken = localStorage.getItem("token");
|
const storedToken = localStorage.getItem("token");
|
||||||
if (storedToken) {
|
if (storedToken) {
|
||||||
setToken(storedToken);
|
|
||||||
try {
|
try {
|
||||||
const decoded: any = jwtDecode(storedToken);
|
const decoded: any = jwtDecode(storedToken);
|
||||||
|
setToken(storedToken);
|
||||||
setUsername(decoded.username);
|
setUsername(decoded.username);
|
||||||
setRole(decoded.role);
|
setRole(decoded.role);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Token konnte nicht gelesen werden");
|
console.error("Token konnte nicht gelesen werden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsAuthLoading(false); // Wichtig, auch wenn kein Token
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = (newToken: string) => {
|
const login = (newToken: string) => {
|
||||||
localStorage.setItem("token", newToken);
|
localStorage.setItem("token", newToken);
|
||||||
setToken(newToken);
|
setToken(newToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded: any = jwtDecode(newToken);
|
||||||
|
setUsername(decoded.username);
|
||||||
|
setRole(decoded.role);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Token konnte nicht gelesen werden");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
setToken(null);
|
setToken(null);
|
||||||
@ -57,10 +69,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ token, username, role, isAdmin, login, logout, isAuthenticated }}>
|
<AuthContext.Provider value={{
|
||||||
|
token, username, role, isAdmin,
|
||||||
|
login, logout, isAuthenticated,
|
||||||
|
isAuthLoading
|
||||||
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useAuth = () => useContext(AuthContext);
|
export const useAuth = () => useContext(AuthContext);
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import Beitraege from "./pages/Beitraege";
|
|||||||
|
|
||||||
import { ThemeProvider } from "@/context/ThemeContext";
|
import { ThemeProvider } from "@/context/ThemeContext";
|
||||||
import VideosPage from "./pages/VideosPage";
|
import VideosPage from "./pages/VideosPage";
|
||||||
|
import EventsPage from "./pages/EventsPage";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +78,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||||||
<Route path="/impressum" element={<Layout><Impressum /></Layout>} />
|
<Route path="/impressum" element={<Layout><Impressum /></Layout>} />
|
||||||
<Route path="/beitraege" element={<Layout><Beitraege /></Layout>} />
|
<Route path="/beitraege" element={<Layout><Beitraege /></Layout>} />
|
||||||
<Route path="/videos" element={<Layout><VideosPage/></Layout>} />
|
<Route path="/videos" element={<Layout><VideosPage/></Layout>} />
|
||||||
|
<Route path="/events" element={<Layout><EventsPage /></Layout>} />
|
||||||
|
|
||||||
|
|
||||||
<Route path="/admin" element={<PrivateRoute><Layout><AdminDashboard /></Layout></PrivateRoute>} />
|
<Route path="/admin" element={<PrivateRoute><Layout><AdminDashboard /></Layout></PrivateRoute>} />
|
||||||
|
|||||||
71
src/pages/EventsPage.tsx
Normal file
71
src/pages/EventsPage.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
|
||||||
|
import "react-big-calendar/lib/css/react-big-calendar.css";
|
||||||
|
import { parse, startOfWeek, format, getDay } from "date-fns";
|
||||||
|
import { de } from "d:/Projects/web/tg/volleyball-dev-frontend/node_modules/date-fns/locale/de";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
de: de,
|
||||||
|
};
|
||||||
|
|
||||||
|
const localizer = dateFnsLocalizer({
|
||||||
|
format,
|
||||||
|
parse,
|
||||||
|
startOfWeek: () => startOfWeek(new Date(), { locale: de }),
|
||||||
|
getDay,
|
||||||
|
locales,
|
||||||
|
});
|
||||||
|
|
||||||
|
const events = [
|
||||||
|
{
|
||||||
|
title: "Heimspiel Herren 1",
|
||||||
|
start: new Date("2025-06-08T15:00:00"),
|
||||||
|
end: new Date("2025-06-08T17:00:00"),
|
||||||
|
allDay: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sommerfest",
|
||||||
|
start: new Date("2025-06-15"),
|
||||||
|
end: new Date("2025-06-15"),
|
||||||
|
allDay: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Training Jugend U16",
|
||||||
|
start: new Date("2025-06-17T18:00:00"),
|
||||||
|
end: new Date("2025-06-17T19:30:00"),
|
||||||
|
allDay: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const EventsPage = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-white py-12 px-4 md:px-12">
|
||||||
|
<h1 className="text-3xl font-bold mb-6 text-center">Eventkalender</h1>
|
||||||
|
<div className="bg-white rounded-xl shadow-md p-4">
|
||||||
|
<Calendar
|
||||||
|
localizer={localizer}
|
||||||
|
events={events}
|
||||||
|
startAccessor="start"
|
||||||
|
endAccessor="end"
|
||||||
|
style={{ height: 600 }}
|
||||||
|
views={["month", "week", "agenda"]}
|
||||||
|
messages={{
|
||||||
|
today: "Heute",
|
||||||
|
previous: "Zurück",
|
||||||
|
next: "Weiter",
|
||||||
|
month: "Monat",
|
||||||
|
week: "Woche",
|
||||||
|
day: "Tag",
|
||||||
|
agenda: "Liste",
|
||||||
|
date: "Datum",
|
||||||
|
time: "Uhrzeit",
|
||||||
|
event: "Event",
|
||||||
|
noEventsInRange: "Keine Events im gewählten Zeitraum",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventsPage;
|
||||||
@ -7,6 +7,7 @@ import TeamSection from "@/components/TeamSection";
|
|||||||
import GallerySection from "@/components/GallerySection";
|
import GallerySection from "@/components/GallerySection";
|
||||||
import AboutSection from "@/components/AboutSection";
|
import AboutSection from "@/components/AboutSection";
|
||||||
import ContactSection from "@/components/ContactSection";
|
import ContactSection from "@/components/ContactSection";
|
||||||
|
import EventsSection from "../components/EventsSection";
|
||||||
|
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -27,6 +28,8 @@ const Index = () => {
|
|||||||
<Hero />
|
<Hero />
|
||||||
<div id="news" />
|
<div id="news" />
|
||||||
<NewsSection />
|
<NewsSection />
|
||||||
|
<div id="events" />
|
||||||
|
<EventsSection/>
|
||||||
<div id="team" />
|
<div id="team" />
|
||||||
<TeamSection />
|
<TeamSection />
|
||||||
<div id="gallery" />
|
<div id="gallery" />
|
||||||
|
|||||||
@ -3,25 +3,28 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { useAuth } from "@/context/AuthContext";
|
||||||
|
|
||||||
|
|
||||||
const apiBase = import.meta.env.VITE_API_URL;
|
const apiBase = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const [email, setEmail] = useState(""); // Wird eigentlich Username sein!
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const navigate = useNavigate();
|
|
||||||
const [loginAttempts, setLoginAttempts] = useState(0);
|
const [loginAttempts, setLoginAttempts] = useState(0);
|
||||||
|
|
||||||
const [captcha, setCaptcha] = useState("");
|
const [captcha, setCaptcha] = useState("");
|
||||||
const [showCaptcha, setShowCaptcha] = useState(false);
|
const [showCaptcha, setShowCaptcha] = useState(false);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { login } = useAuth(); // <-- Context-Funktion holen
|
||||||
|
|
||||||
const handleLogin = async (e: React.FormEvent) => {
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError("");
|
setError("");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Captcha check
|
||||||
if (showCaptcha && captcha.toLowerCase() !== "laudenbach") {
|
if (showCaptcha && captcha.toLowerCase() !== "laudenbach") {
|
||||||
setError("Captcha falsch. Bitte versuche es erneut.");
|
setError("Captcha falsch. Bitte versuche es erneut.");
|
||||||
return;
|
return;
|
||||||
@ -42,6 +45,7 @@ const LoginPage = () => {
|
|||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
localStorage.setItem("token", data.token);
|
localStorage.setItem("token", data.token);
|
||||||
|
login(data.token);
|
||||||
navigate("/admin");
|
navigate("/admin");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -51,13 +55,11 @@ const LoginPage = () => {
|
|||||||
setError("Login fehlgeschlagen. Bitte prüfe Benutzername und Passwort.");
|
setError("Login fehlgeschlagen. Bitte prüfe Benutzername und Passwort.");
|
||||||
const newAttempts = loginAttempts + 1;
|
const newAttempts = loginAttempts + 1;
|
||||||
setLoginAttempts(newAttempts);
|
setLoginAttempts(newAttempts);
|
||||||
|
if (newAttempts >= 5) {
|
||||||
if(newAttempts >= 3) {
|
|
||||||
setShowCaptcha(true);
|
setShowCaptcha(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user