diff --git a/README.md b/README.md index 3809796..4eb0ff2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - -# Annaville SDA Church — Modern, Accessible Website + +# Annaville SDA Church — Modern, Accessible Website React + Vite + Tailwind. WCAG 2.2 AA, Core Web Vitals-minded. Text-first hero keeps H1 as LCP. @@ -22,9 +22,16 @@ docker compose up --build ``` ### Notes -- Mobile sticky bar (📞 Call • 🧭 Directions • 📝 Plan a Visit) is persistent. +- Mobile sticky bar (📞 Call • 🧭 Directions • 📝 Plan a Visit) is persistent. - GA events in `src/utils/analytics.js`: `cta_click`, `click_to_call`, `open_directions`, `visit_form_start`, `visit_form_submit`, `newsletter_signup`, `event_details_view`, `sermon_play`. - Local WOFF2 fonts preloaded; replace placeholders with real files before production. - JSON-LD is injected on Home (Organization, Website, FAQ) and Event/Sermon detail pages. # annaville-sda-site + +## Events Admin +- \ +pm run dev\ starts both the Vite client (5173) and the Express API (4001). +- Set the \ADMIN_TOKEN\ environment variable before running the server (defaults to \dev-secret\). +- Admin dashboard: http://localhost:5173/admin (login via token, manage events, add/edit/delete). +- API endpoints live under \/api/events\ and require the \X-Admin-Token\ header for write operations. diff --git a/package-lock.json b/package-lock.json index 1ba5321..bdb6138 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "annaville-sda-church", "version": "1.0.0", "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "multer": "^2.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet-async": "^2.0.5", @@ -16,6 +19,7 @@ "devDependencies": { "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.18", + "concurrently": "^9.2.1", "postcss": "^8.4.47", "tailwindcss": "^3.4.10", "vite": "^5.4.2" @@ -34,20 +38,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -64,9 +54,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, "license": "MIT", "engines": { @@ -74,22 +64,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -221,27 +211,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", - "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -298,18 +288,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", - "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", + "@babel/types": "^7.28.4", "debug": "^4.3.1" }, "engines": { @@ -317,9 +307,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -750,6 +740,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -768,9 +769,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -844,9 +845,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", - "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", "cpu": [ "arm" ], @@ -858,9 +859,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", - "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", "cpu": [ "arm64" ], @@ -872,9 +873,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", - "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", "cpu": [ "arm64" ], @@ -886,9 +887,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", - "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", "cpu": [ "x64" ], @@ -900,9 +901,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", - "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", "cpu": [ "arm64" ], @@ -914,9 +915,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", - "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", "cpu": [ "x64" ], @@ -928,9 +929,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", - "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", "cpu": [ "arm" ], @@ -942,9 +943,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", - "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", "cpu": [ "arm" ], @@ -956,9 +957,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", - "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", "cpu": [ "arm64" ], @@ -970,9 +971,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", - "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", "cpu": [ "arm64" ], @@ -983,10 +984,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", - "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", "cpu": [ "loong64" ], @@ -998,9 +999,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", - "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", "cpu": [ "ppc64" ], @@ -1012,9 +1013,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", - "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", "cpu": [ "riscv64" ], @@ -1026,9 +1027,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", - "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", "cpu": [ "riscv64" ], @@ -1040,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", - "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", "cpu": [ "s390x" ], @@ -1054,9 +1055,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", - "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", "cpu": [ "x64" ], @@ -1068,9 +1069,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", - "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", "cpu": [ "x64" ], @@ -1081,10 +1082,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", - "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", "cpu": [ "arm64" ], @@ -1096,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", - "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", "cpu": [ "ia32" ], @@ -1109,10 +1124,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", - "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", "cpu": [ "x64" ], @@ -1196,10 +1225,23 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -1210,13 +1252,16 @@ } }, "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -1243,6 +1288,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1295,6 +1346,16 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", + "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1308,6 +1369,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1332,9 +1413,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", - "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", "dev": true, "funding": [ { @@ -1352,9 +1433,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001735", - "electron-to-chromium": "^1.5.204", - "node-releases": "^2.0.19", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { @@ -1364,6 +1446,61 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1375,9 +1512,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", - "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", "dev": true, "funding": [ { @@ -1395,6 +1532,36 @@ ], "license": "CC-BY-4.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/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1433,6 +1600,84 @@ "node": ">= 6" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1463,6 +1708,67 @@ "node": ">= 6" } }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1470,6 +1776,37 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1499,10 +1836,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1516,6 +1852,15 @@ } } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1530,6 +1875,20 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1537,10 +1896,16 @@ "dev": true, "license": "MIT" }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { - "version": "1.5.209", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.209.tgz", - "integrity": "sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A==", + "version": "1.5.223", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz", + "integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==", "dev": true, "license": "ISC" }, @@ -1551,6 +1916,45 @@ "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1600,6 +2004,63 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1653,6 +2114,23 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1670,6 +2148,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -1684,6 +2171,15 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1703,7 +2199,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1719,6 +2214,53 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -1753,11 +2295,44 @@ "node": ">=10.13.0" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1766,6 +2341,49 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -1775,6 +2393,15 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1847,6 +2474,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1954,6 +2587,36 @@ "yallist": "^3.0.2" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1978,6 +2641,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -1994,6 +2678,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -2004,13 +2697,85 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "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/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -2042,10 +2807,19 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", "dev": true, "license": "MIT" }, @@ -2073,7 +2847,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2089,6 +2862,39 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -2096,6 +2902,15 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2137,6 +2952,16 @@ "dev": true, "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2225,10 +3050,20 @@ } }, "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -2236,10 +3071,6 @@ "engines": { "node": "^12 || ^14 || >= 16" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.4.21" } @@ -2327,6 +3158,34 @@ "dev": true, "license": "MIT" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2348,6 +3207,46 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -2445,6 +3344,20 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2458,6 +3371,16 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -2491,9 +3414,9 @@ } }, "node_modules/rollup": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", - "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "dev": true, "license": "MIT", "dependencies": { @@ -2507,29 +3430,47 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.49.0", - "@rollup/rollup-android-arm64": "4.49.0", - "@rollup/rollup-darwin-arm64": "4.49.0", - "@rollup/rollup-darwin-x64": "4.49.0", - "@rollup/rollup-freebsd-arm64": "4.49.0", - "@rollup/rollup-freebsd-x64": "4.49.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", - "@rollup/rollup-linux-arm-musleabihf": "4.49.0", - "@rollup/rollup-linux-arm64-gnu": "4.49.0", - "@rollup/rollup-linux-arm64-musl": "4.49.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", - "@rollup/rollup-linux-ppc64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-musl": "4.49.0", - "@rollup/rollup-linux-s390x-gnu": "4.49.0", - "@rollup/rollup-linux-x64-gnu": "4.49.0", - "@rollup/rollup-linux-x64-musl": "4.49.0", - "@rollup/rollup-win32-arm64-msvc": "4.49.0", - "@rollup/rollup-win32-ia32-msvc": "4.49.0", - "@rollup/rollup-win32-x64-msvc": "4.49.0", + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2554,6 +3495,42 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -2573,6 +3550,49 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -2602,6 +3622,91 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2625,6 +3730,32 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -2690,9 +3821,9 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -2752,6 +3883,22 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2839,6 +3986,25 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -2846,6 +4012,42 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -2881,13 +4083,21 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "dev": true, "license": "MIT", "dependencies": { @@ -3007,22 +4217,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3058,6 +4252,44 @@ "node": ">=8" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3077,6 +4309,80 @@ "engines": { "node": ">= 14.6" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 594273b..961f305 100644 --- a/package.json +++ b/package.json @@ -4,21 +4,28 @@ "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "concurrently \"npm:dev:client\" \"npm:dev:server\"", + "dev:client": "vite", + "dev:server": "node server/index.js", "build": "vite build", - "preview": "vite preview --port 5173" + "preview": "vite preview --port 5173", + "start:server": "node server/index.js" }, "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "multer": "^2.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.26.0", - "react-helmet-async": "^2.0.5" + "react-helmet-async": "^2.0.5", + "react-router-dom": "^6.26.0" }, "devDependencies": { - "vite": "^5.4.2", "@vitejs/plugin-react": "^4.3.1", - "tailwindcss": "^3.4.10", + "autoprefixer": "^10.4.18", + "concurrently": "^9.2.1", "postcss": "^8.4.47", - "autoprefixer": "^10.4.18" + "tailwindcss": "^3.4.10", + "vite": "^5.4.2" } -} \ No newline at end of file +} diff --git a/public/assets/children_ministry_craft.png b/public/assets/children_ministry_craft.png index e724bdc..3346c30 100644 Binary files a/public/assets/children_ministry_craft.png and b/public/assets/children_ministry_craft.png differ diff --git a/public/assets/family_entry.png b/public/assets/family_entry.png index e9529f1..49a3e5e 100644 Binary files a/public/assets/family_entry.png and b/public/assets/family_entry.png differ diff --git a/public/assets/hero_golden_hour.png b/public/assets/hero_golden_hour.png index 0a88be5..387aaad 100644 Binary files a/public/assets/hero_golden_hour.png and b/public/assets/hero_golden_hour.png differ diff --git a/public/assets/potluck.png b/public/assets/potluck.png index 8e7a701..b2f559a 100644 Binary files a/public/assets/potluck.png and b/public/assets/potluck.png differ diff --git a/public/assets/speeking.png b/public/assets/speeking.png index e5a855a..699b2fb 100644 Binary files a/public/assets/speeking.png and b/public/assets/speeking.png differ diff --git a/public/assets/youth_vespers.png b/public/assets/youth_vespers.png index 63293a0..766dd31 100644 Binary files a/public/assets/youth_vespers.png and b/public/assets/youth_vespers.png differ diff --git a/server/data/events.json b/server/data/events.json new file mode 100644 index 0000000..afca228 --- /dev/null +++ b/server/data/events.json @@ -0,0 +1,28 @@ +[ + { + "id": "57fb4ac3-3896-44a0-a61b-42f3bed558d5", + "slug": "test", + "createdAt": "2025-09-24T15:57:31.513Z", + "updatedAt": "2025-09-24T15:57:31.513Z", + "title": "test", + "description": "Test", + "date": "2025-10-10", + "time": "10:00 AM", + "location": "Fellowship Hall", + "category": "Youth", + "image": "/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729451448.jpeg" + }, + { + "id": "6e1b9995-e153-478a-a2d6-713e48d05891", + "slug": "eg", + "createdAt": "2025-09-24T18:18:07.806Z", + "updatedAt": "2025-09-24T18:18:07.806Z", + "title": "eg", + "description": "HSAHA", + "date": "2025-10-12", + "time": "12:30 PM", + "location": "Erkrath", + "category": "hi", + "image": "/uploads/atos-logo-blau-jpg-1758737887080.jpg" + } +] \ No newline at end of file diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..3ba5fa3 --- /dev/null +++ b/server/index.js @@ -0,0 +1,234 @@ +import express from 'express' +import cors from 'cors' +import { fileURLToPath } from 'url' +import { promises as fs } from 'fs' +import path from 'path' +import crypto from 'crypto' +import multer from 'multer' + +const app = express() +const port = process.env.PORT || 4001 +const adminToken = process.env.ADMIN_TOKEN || 'timo' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const dataDir = path.join(__dirname, 'data') +const dataPath = path.join(dataDir, 'events.json') +const uploadsDir = path.join(__dirname, 'uploads') + +await fs.mkdir(uploadsDir, { recursive: true }) + +const storage = multer.diskStorage({ + destination: (req, file, cb) => cb(null, uploadsDir), + filename: (req, file, cb) => { + const ext = path.extname(file.originalname || '') || '.png' + const base = (file.originalname || 'upload') + .replace(/[^a-z0-9]+/gi, '-') + .replace(/^-+|-+$/g, '') + .toLowerCase() || 'upload' + const unique = `${base}-${Date.now()}${ext}` + cb(null, unique) + } +}) + +const allowedMimeTypes = new Set([ + 'image/png', + 'image/jpeg', + 'image/gif', + 'image/webp', + 'image/svg+xml' +]) + +const upload = multer({ + storage, + limits: { fileSize: 5 * 1024 * 1024 }, + fileFilter: (req, file, cb) => { + if (allowedMimeTypes.has(file.mimetype)) { + cb(null, true) + } else { + cb(new Error('Only image uploads are allowed')) + } + } +}) + +async function ensureDataFile() { + try { + await fs.access(dataPath) + } catch { + await fs.mkdir(dataDir, { recursive: true }) + await fs.writeFile(dataPath, '[]', 'utf-8') + } +} + +function slugify(text) { + return text + .toString() + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') +} + +async function readEvents() { + await ensureDataFile() + const raw = await fs.readFile(dataPath, 'utf-8') + try { + return JSON.parse(raw) + } catch (error) { + console.error('Failed to parse events file, resetting to []', error) + await fs.writeFile(dataPath, '[]', 'utf-8') + return [] + } +} + +async function writeEvents(events) { + await fs.writeFile(dataPath, JSON.stringify(events, null, 2), 'utf-8') +} + +function requireAuth(req, res, next) { + const token = req.header('x-admin-token') + if (!token || token !== adminToken) { + return res.status(401).json({ error: 'Unauthorized' }) + } + return next() +} + +function asyncHandler(fn) { + return (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch(next) + } +} + +function buildEventPayload(input, base = {}) { + const allowed = ['title', 'description', 'date', 'time', 'location', 'category', 'image'] + const payload = { ...base } + for (const key of allowed) { + if (key in input && input[key] !== undefined) { + payload[key] = typeof input[key] === 'string' ? input[key].trim() : input[key] + } + } + return payload +} + +app.use(cors()) +app.use(express.json({ limit: '1mb' })) +app.use('/uploads', express.static(uploadsDir)) + +app.get('/api/health', (req, res) => { + res.json({ status: 'ok' }) +}) + +app.get('/api/events', asyncHandler(async (req, res) => { + const events = await readEvents() + const sorted = events.slice().sort((a, b) => new Date(a.date) - new Date(b.date)) + res.json(sorted) +})) + +app.get('/api/events/:slug', asyncHandler(async (req, res) => { + const events = await readEvents() + const event = events.find(e => e.slug === req.params.slug) + if (!event) { + return res.status(404).json({ error: 'Event not found' }) + } + res.json(event) +})) + +app.post('/api/events', requireAuth, asyncHandler(async (req, res) => { + const data = buildEventPayload(req.body) + if (!data.title || !data.date) { + return res.status(400).json({ error: 'title and date are required' }) + } + + const events = await readEvents() + const baseSlug = slugify(req.body.slug || data.title || '') || `event-${Date.now()}` + let uniqueSlug = baseSlug + let suffix = 1 + while (events.some(event => event.slug === uniqueSlug)) { + uniqueSlug = `${baseSlug}-${suffix++}` + } + + const now = new Date().toISOString() + const newEvent = { + id: crypto.randomUUID(), + slug: uniqueSlug, + createdAt: now, + updatedAt: now, + ...data, + } + + events.push(newEvent) + await writeEvents(events) + res.status(201).json(newEvent) +})) + +app.patch('/api/events/:slug', requireAuth, asyncHandler(async (req, res) => { + const events = await readEvents() + const index = events.findIndex(event => event.slug === req.params.slug) + if (index === -1) { + return res.status(404).json({ error: 'Event not found' }) + } + + const event = events[index] + const updated = buildEventPayload(req.body, event) + let slugToUse = event.slug + if (req.body.slug && req.body.slug !== event.slug) { + const requestedSlug = slugify(req.body.slug) + let uniqueSlug = requestedSlug || event.slug + let suffix = 1 + while (events.some((e, i) => i !== index && e.slug === uniqueSlug)) { + uniqueSlug = `${requestedSlug}-${suffix++}` + } + slugToUse = uniqueSlug + } + + const merged = { + ...event, + ...updated, + slug: slugToUse, + updatedAt: new Date().toISOString(), + } + + events[index] = merged + await writeEvents(events) + res.json(merged) +})) + +app.delete('/api/events/:slug', requireAuth, asyncHandler(async (req, res) => { + const events = await readEvents() + const index = events.findIndex(event => event.slug === req.params.slug) + if (index === -1) { + return res.status(404).json({ error: 'Event not found' }) + } + + const [removed] = events.splice(index, 1) + await writeEvents(events) + res.json({ success: true, removed }) +})) + +app.post('/api/uploads', requireAuth, upload.single('file'), (req, res) => { + if (!req.file) { + return res.status(400).json({ error: 'No file uploaded' }) + } + const relativeUrl = `/uploads/${req.file.filename}` + const absoluteUrl = `${req.protocol}://${req.get('host')}${relativeUrl}` + res.status(201).json({ url: absoluteUrl, path: relativeUrl }) +}) + +app.get('/api/admin/verify', requireAuth, (req, res) => { + res.json({ ok: true }) +}) + +app.use((err, req, res, next) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ error: err.message }) + } + if (err && err.message === 'Only image uploads are allowed') { + return res.status(400).json({ error: err.message }) + } + console.error(err) + res.status(500).json({ error: 'Internal server error' }) +}) + +app.listen(port, () => { + console.log(`Events API listening on port ${port}`) +}) diff --git a/server/test.js b/server/test.js new file mode 100644 index 0000000..6a1927e --- /dev/null +++ b/server/test.js @@ -0,0 +1,6 @@ +import express from "express" + +const app = express() + +app.listen(4010, () => console.log('listening 4010')) + diff --git a/server/uploads/atos-logo-blau-jpg-1758726846869.jpg b/server/uploads/atos-logo-blau-jpg-1758726846869.jpg new file mode 100644 index 0000000..7077ae6 Binary files /dev/null and b/server/uploads/atos-logo-blau-jpg-1758726846869.jpg differ diff --git a/server/uploads/atos-logo-blau-jpg-1758737887080.jpg b/server/uploads/atos-logo-blau-jpg-1758737887080.jpg new file mode 100644 index 0000000..7077ae6 Binary files /dev/null and b/server/uploads/atos-logo-blau-jpg-1758737887080.jpg differ diff --git a/server/uploads/atos-logo-png-1758727442813.png b/server/uploads/atos-logo-png-1758727442813.png new file mode 100644 index 0000000..921e54d Binary files /dev/null and b/server/uploads/atos-logo-png-1758727442813.png differ diff --git a/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729410230.jpeg b/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729410230.jpeg new file mode 100644 index 0000000..844f399 Binary files /dev/null and b/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729410230.jpeg differ diff --git a/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729451448.jpeg b/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729451448.jpeg new file mode 100644 index 0000000..844f399 Binary files /dev/null and b/server/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729451448.jpeg differ diff --git a/src/components/Cards.jsx b/src/components/Cards.jsx index 9dda552..57838e4 100644 --- a/src/components/Cards.jsx +++ b/src/components/Cards.jsx @@ -1,15 +1,34 @@ - -import React from 'react' +import React from 'react' import { Link } from 'react-router-dom' -export function EventCard({ e }){ +const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '') + +function resolveEventImage(value, fallback) { + if (typeof value === 'string') { + const trimmed = value.trim() + if (!trimmed) return fallback + if (/^(?:https?:)?\/\//i.test(trimmed) || trimmed.startsWith('data:')) { + return trimmed + } + const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}` + if (apiBaseUrl) { + return `${apiBaseUrl}${path}` + } + return path + } + return fallback +} + +export function EventCard({ e }) { const dt = new Date(e.date) - const mon = dt.toLocaleString('en', { month:'short' }) - const day = dt.getDate() - - // Map specific event names to images + const hasValidDate = !Number.isNaN(dt) + const mon = hasValidDate ? dt.toLocaleString('en', { month: 'short' }) : '' + const day = hasValidDate ? dt.getDate() : '' + + const details = [e.time, e.location].filter(Boolean).join(' | ') + const getEventImage = (title) => { - const lowerTitle = title.toLowerCase() + const lowerTitle = `${title || ''}`.toLowerCase() if (lowerTitle.includes('community sabbath lunch')) { return '/assets/potluck.png' } @@ -28,16 +47,18 @@ export function EventCard({ e }){ if (lowerTitle.includes('welcome') || lowerTitle.includes('committee')) { return '/assets/welcome_commite.png' } - // Default event image return '/assets/potluck.png' } - + + const defaultImage = getEventImage(e.title) + const imageSrc = resolveEventImage(e.image, defaultImage) + return ( -
+
- {`${e.title}

{e.title}

-
{e.time} • {e.location}
+
+ {hasValidDate ? `${mon} ${day}` : e.date} + {details ? ` | ${details}` : ''} +

{e.description}

- - Details — {e.title} + Details - {e.title}
) } -export function MinistryCard({ m }){ - // Map specific ministry names to images +export function MinistryCard({ m }) { const getMinistryImage = (name) => { const lowerName = name.toLowerCase() - if (lowerName === 'children\'s ministry') { + if (lowerName === "children's ministry") { return '/assets/children_ministry_craft.png' } if (lowerName === 'youth ministry') { @@ -75,16 +98,15 @@ export function MinistryCard({ m }){ if (lowerName === 'adult sabbath school') { return '/assets/speeking.png' } - if (lowerName === 'women\'s ministry') { + if (lowerName === "women's ministry") { return '/assets/pray_heart.png' } - if (lowerName === 'men\'s ministry') { + if (lowerName === "men's ministry") { return '/assets/family_entry.png' } if (lowerName === 'community outreach') { return '/assets/welcome_commite.png' } - // Fallback for other ministries if (lowerName.includes('children') || lowerName.includes('kids')) { return '/assets/children_ministry_craft.png' } @@ -97,15 +119,14 @@ export function MinistryCard({ m }){ if (lowerName.includes('prayer') || lowerName.includes('pray')) { return '/assets/pray_heart.png' } - // Default ministry image return '/assets/welcome_commite.png' } - + return (
- {`${m.name}{m.meeting}
Leader: {m.leader}
- @@ -127,13 +148,13 @@ export function MinistryCard({ m }){ ) } -export function SermonCard({ s }){ +export function SermonCard({ s }) { return ( -
+
- {`Sermon

{s.title}

-
{s.speaker} • {new Date(s.date).toLocaleDateString()}
+
{s.speaker} | {new Date(s.date).toLocaleDateString()}

{s.summary}

- - Watch/Listen — {s.title} + Watch/Listen - {s.title}
diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index d2f4705..ebfda8a 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -83,10 +83,11 @@ export default function Footer(){
© {year} Annaville Seventh-day Adventist Church. All rights reserved.
-
+
Privacy Policy Terms of Use Accessibility + Admin Events
diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 22514cf..5026734 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -7,6 +7,7 @@ const navItems = [ { to:'/about', label:'ABOUT US' }, { to:'/services', label:'SERVICES' }, { to:'/resources', label:'RESOURCES' }, + { to:'/events', label:'EVENTS' }, { to:'/prayer-requests', label:'PRAYER REQUESTS' }, { to:'/calendar', label:'CALENDAR' }, { to:'/beliefs', label:'OUR BELIEFS' }, diff --git a/src/hooks/useEvents.js b/src/hooks/useEvents.js new file mode 100644 index 0000000..3ce0e7e --- /dev/null +++ b/src/hooks/useEvents.js @@ -0,0 +1,30 @@ +import { useCallback, useEffect, useState } from 'react' +import { getEvents } from '../utils/api' + +export function useEvents(autoLoad = true) { + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(autoLoad) + const [error, setError] = useState(null) + + const load = useCallback(async () => { + setLoading(true) + setError(null) + try { + const data = await getEvents() + setEvents(Array.isArray(data) ? data : []) + } catch (err) { + console.error('Failed to load events', err) + setError(err) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + if (autoLoad) { + load() + } + }, [autoLoad, load]) + + return { events, loading, error, reload: load, setEvents } +} diff --git a/src/main.jsx b/src/main.jsx index 4921c65..c92e88a 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,7 +1,6 @@ - -import React from 'react' +import React from 'react' import ReactDOM from 'react-dom/client' -import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom' import { HelmetProvider } from 'react-helmet-async' import './index.css' import App from './App' @@ -15,25 +14,52 @@ import Beliefs from './pages/Beliefs' import Contact from './pages/Contact' import Privacy from './pages/Privacy' import Terms from './pages/Terms' +import Events from './pages/Events' +import EventDetail from './pages/EventDetail' +import AdminLayout from './pages/admin/AdminLayout' +import AdminLogin from './pages/admin/AdminLogin' +import AdminEvents from './pages/admin/AdminEvents' +import AdminEventForm from './pages/admin/AdminEventForm' +import RequireAdmin from './pages/admin/RequireAdmin' import { initAnalytics } from './utils/analytics' import { initGA, initGTM } from './utils/analytics-config' -const router = createBrowserRouter([{ - path: '/', - element: , - children: [ - { index:true, element: }, - { path:'about', element: }, - { path:'services', element: }, - { path:'resources', element: }, - { path:'prayer-requests', element: }, - { path:'calendar', element: }, - { path:'beliefs', element: }, - { path:'contact', element: }, - { path:'privacy', element: }, - { path:'terms', element: } - ] -}]) +const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { index: true, element: }, + { path: 'about', element: }, + { path: 'services', element: }, + { path: 'resources', element: }, + { path: 'prayer-requests', element: }, + { path: 'calendar', element: }, + { path: 'beliefs', element: }, + { path: 'contact', element: }, + { path: 'privacy', element: }, + { path: 'terms', element: }, + { path: 'events', element: }, + { path: 'events/:slug', element: } + ] + }, + { + path: '/admin', + element: , + children: [ + { index: true, element: }, + { path: 'login', element: }, + { + element: , + children: [ + { path: 'events', element: }, + { path: 'events/new', element: }, + { path: 'events/:slug/edit', element: } + ] + } + ] + } +]) // Initialize analytics after DOM is ready document.addEventListener('DOMContentLoaded', () => { diff --git a/src/pages/About.jsx b/src/pages/About.jsx index fcea807..abeb5c9 100644 --- a/src/pages/About.jsx +++ b/src/pages/About.jsx @@ -1,82 +1,115 @@ +import React from 'react' +import { Helmet } from 'react-helmet-async' +import { Link } from 'react-router-dom' +import StaticMap from '../components/StaticMap' - import React from 'react' - import { Helmet } from 'react-helmet-async' - import { Link } from 'react-router-dom' - import StaticMap from '../components/StaticMap' +export default function About(){ + return ( + <> + + About Us - Annaville Seventh-day Adventist Church + + - export default function About() { - return ( - <> - - About Us - Annaville Seventh-day Adventist Church - - +
+
+
+

+ Welcome to Annaville SDA Church +

+

+ A Community Growing Together in Faith, Hope, and Service +

+

+ We are a family of believers in Corpus Christi, Texas, seeking to know Jesus, grow in faith, and serve our + community. Whether you have been a long-time member or are visiting for the first time, you are welcome here. +

+
+ Plan Your Visit + Contact the Church Office +
+
+
+ Church members praying together +
+
+
-
-
-

About Us

- -
-
-

Our Mission

-

- To know Jesus, grow in faith, and serve our community. -

- -

- We are a welcoming Seventh-day Adventist congregation in Annaville. -

- -

Service Times

-
-
-

Sabbath School

-

Begins at 9:30 a.m. each Saturday

-
-
-

Divine Worship

-

Begins at 11:00 a.m. each Saturday

-
-
-

Potluck Fellowship Dinner

-

Immediately following worship services. Visitors are encouraged to stay and fellowship.

-
+
+
+
+
+
+

Our Mission

+

+ To know Jesus, grow in faith, and serve our community. We believe the gospel transforms lives and that the + church is called to share hope and practical compassion with everyone we meet. +

+

+ You will find vibrant worship, thoughtful Bible study, and many ways to connect through ministries for + children, youth, adults, and families. +

+
+ +
+

Weekly Service Times

+
+
+

Sabbath School

+

Saturdays at 9:30 AM

-
- -
-

Location

-

- We are located at 2710 Violet Road in Corpus Christi, Texas. -

-

- Click on the link below to see a map to our church location. -

- -
- - View Map of 2710 Violet Rd, Corpus Christi, TX 78410 - - - Get Driving Directions - +
+

Divine Worship

+

Saturdays at 11:00 AM

+
+
+

Potluck Fellowship Dinner

+

Immediately following the worship service

- -
-
- - ) - } + +
+
+ +
+
+

Visit Us

+

+ We are located at 2710 Violet Road in Corpus Christi, Texas. Parking is available on site, and our + hospitality team will gladly help you find your way around. +

+ +

+ Need a ride? Contact the church office and ask about transportation assistance. +

+
+
+
+
+
+ + ) +} diff --git a/src/pages/Calendar.jsx b/src/pages/Calendar.jsx index 8399685..e6db1c1 100644 --- a/src/pages/Calendar.jsx +++ b/src/pages/Calendar.jsx @@ -1,7 +1,33 @@ -import React from 'react' +import React, { useMemo } from 'react' +import { Link } from 'react-router-dom' import SEOHead from '../components/SEOHead' +import { useEvents } from '../hooks/useEvents' + +function formatDateRange(dateStr, timeStr) { + const date = new Date(dateStr) + if (Number.isNaN(date)) { + return `${dateStr}${timeStr ? ` - ${timeStr}` : ''}` + } + return `${date.toLocaleDateString(undefined, { + weekday: 'long', + month: 'long', + day: 'numeric', + })}${timeStr ? ` - ${timeStr}` : ''}` +} export default function Calendar() { + const { events, loading, error } = useEvents() + + const upcoming = useMemo(() => { + const today = new Date() + today.setHours(0, 0, 0, 0) + return events.filter(event => { + const date = new Date(event.date) + if (Number.isNaN(date)) return true + return date >= today + }) + }, [events]) + return ( <>
-

- No upcoming events at this time. Please check back later for updates. -

+ {loading && ( +

Loading events...

+ )} + {error && !loading && ( +

Unable to load events right now. Please try again later.

+ )} + {!loading && !error && upcoming.length === 0 && ( +

+ No upcoming events at this time. Please check back later for updates. +

+ )} + {!loading && !error && upcoming.length > 0 && ( +
    + {upcoming.map(event => ( +
  • +

    {event.title}

    +
    {formatDateRange(event.date, event.time)}{event.location ? ` - ${event.location}` : ''}
    +

    {event.description}

    + + View details + +
  • + ))} +
+ )}
diff --git a/src/pages/EventDetail.jsx b/src/pages/EventDetail.jsx index 92a1413..648f238 100644 --- a/src/pages/EventDetail.jsx +++ b/src/pages/EventDetail.jsx @@ -1,61 +1,268 @@ - -import React, { useEffect } from 'react' -import { useParams } from 'react-router-dom' +import React, { useEffect, useState } from 'react' +import { Link, useParams } from 'react-router-dom' import { Helmet } from 'react-helmet-async' -import events from '../data/events.json' +import { getEvent } from '../utils/api' import { googleCalendarUrl, downloadICS } from '../utils/calendar' import { track, events as ga } from '../utils/analytics' -export default function EventDetail(){ +const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '') + +function parseTime(timeStr) { + if (!timeStr) return '10:00' + const match = `${timeStr}`.match(/(\d+):(\d+)\s*(AM|PM)/i) + if (!match) return '10:00' + + let hours = parseInt(match[1], 10) + const minutes = match[2] + const period = match[3].toUpperCase() + + if (period === 'PM' && hours !== 12) hours += 12 + if (period === 'AM' && hours === 12) hours = 0 + + return `${hours.toString().padStart(2, '0')}:${minutes}` +} + +function resolveImageUrl(value, fallback) { + if (typeof value === 'string') { + const trimmed = value.trim() + if (!trimmed) return fallback + if (/^(?:https?:)?\/\//i.test(trimmed) || trimmed.startsWith('data:')) { + return trimmed + } + const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}` + if (apiBaseUrl) { + return `${apiBaseUrl}${path}` + } + return path + } + return fallback +} + +function getFallbackImage(event) { + const title = `${event?.title || ''}`.toLowerCase() + if (title.includes('vespers')) return '/assets/youth_vespers.png' + if (title.includes('food') || title.includes('community')) return '/assets/family_entry.png' + if (title.includes('lunch') || title.includes('dinner') || title.includes('potluck')) return '/assets/potluck.png' + return '/assets/potluck.png' +} + +function formatDate(dateStr) { + const date = new Date(dateStr) + if (Number.isNaN(date.getTime())) return dateStr + return date.toLocaleDateString(undefined, { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + }) +} + +function formatTime(timeStr) { + if (!timeStr) return 'TBA' + return timeStr +} + +export default function EventDetail() { const { slug } = useParams() - const e = events.find(x => x.slug === slug) + const [event, setEvent] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) - useEffect(()=>{ if (e) track(ga.EVENT_DETAILS_VIEW,{slug:e.slug}) },[slug]) + useEffect(() => { + let ignore = false + async function load() { + setLoading(true) + setError(null) + try { + const data = await getEvent(slug) + if (!ignore) { + setEvent(data) + } + } catch (err) { + if (!ignore) { + setError(err) + setEvent(null) + } + } finally { + if (!ignore) { + setLoading(false) + } + } + } + load() + return () => { + ignore = true + } + }, [slug]) - if(!e) return

Event not found.

+ useEffect(() => { + if (event) { + track(ga.EVENT_DETAILS_VIEW, { slug: event.slug }) + } + }, [event]) - // Parse time from 12-hour format to 24-hour format - const parseTime = (timeStr) => { - if (!timeStr) return '10:00' - const match = timeStr.match(/(\d+):(\d+)\s*(AM|PM)/i) - if (!match) return '10:00' - - let hours = parseInt(match[1]) - const minutes = match[2] - const period = match[3].toUpperCase() - - if (period === 'PM' && hours !== 12) hours += 12 - if (period === 'AM' && hours === 12) hours = 0 - - return `${hours.toString().padStart(2, '0')}:${minutes}` + if (loading) { + return ( +
+
+

Loading event...

+
+
+ ) } - const time24 = parseTime(e.time || '10:00 AM') - const start = new Date(`${e.date}T${time24}:00`) + if (error) { + return ( +
+
+

Unable to load this event. Please try again later.

+
+
+ ) + } + + if (!event) { + return ( +
+
+

Event not found.

+
+
+ ) + } + + const time24 = parseTime(event.time || '10:00 AM') + const start = new Date(`${event.date}T${time24}:00`) const end = new Date(start.getTime() + 60 * 60 * 1000) + const fallbackImage = getFallbackImage(event) + const coverImage = resolveImageUrl(event.image, fallbackImage) + const displayDate = formatDate(event.date) + const displayTime = formatTime(event.time) + const eventUrl = typeof window !== 'undefined' ? window.location.href : '' const jsonLd = { - "@context":"https://schema.org","@type":"Event","name":e.title, - "startDate":start.toISOString(),"endDate":end.toISOString(), - "eventAttendanceMode":"https://schema.org/OfflineEventAttendanceMode", - "location":{"@type":"Place","name":"Annaville SDA Church","address":"2710 Violet Rd, Corpus Christi, TX 78410"}, - "description":e.description + '@context': 'https://schema.org', + '@type': 'Event', + name: event.title, + startDate: start.toISOString(), + endDate: end.toISOString(), + eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode', + location: { + '@type': 'Place', + name: 'Annaville SDA Church', + address: '2710 Violet Rd, Corpus Christi, TX 78410', + }, + description: event.description, } return ( -
+
- {e.title} | Events + {event.title} | Events -
-

{e.title}

-
{e.date} • {e.time} • {e.location}
-

{e.description}

-
- Add to Google Calendar - +
+ + ← Back to all events + +
+
+
+ {`${event.title} +
+
+
+

+ {event.category || 'Featured Event'} +

+

{event.title}

+

+ {displayDate} • {displayTime}{event.location ? ` • ${event.location}` : ''} +

+
+
+ {`${event.description || ''}`.split(/\n{2,}/).map((paragraph, idx) => ( +

{paragraph}

+ ))} +
+
+ + Add to Google Calendar + + +
+
+
+
diff --git a/src/pages/Events.jsx b/src/pages/Events.jsx index 3e7db9f..4be5b4b 100644 --- a/src/pages/Events.jsx +++ b/src/pages/Events.jsx @@ -1,27 +1,209 @@ - -import React, { useState } from 'react' +import React, { useMemo, useState } from 'react' import { Helmet } from 'react-helmet-async' -import events from '../data/events.json' +import { Link } from 'react-router-dom' import { EventCard } from '../components/Cards' +import { useEvents } from '../hooks/useEvents' -const filters = ['This Month','Next Month','Family','Outreach'] +const filters = ['All', 'This Month', 'Next Month', 'Family', 'Outreach'] -export default function Events(){ - const [active, setActive] = useState('This Month') +function isSameMonth(dateStr, baseDate) { + const date = new Date(dateStr) + if (Number.isNaN(date)) return false return ( -
- Events | Annaville SDA Church -
-

Events

-
- {filters.map(f => ( - - ))} -
-
- {events.map(e => )} -
-
-
+ date.getFullYear() === baseDate.getFullYear() && + date.getMonth() === baseDate.getMonth() + ) +} + +function filterEvents(events, activeFilter) { + const now = new Date() + if (activeFilter === 'All') { + return events + } + + if (activeFilter === 'This Month') { + return events.filter(event => isSameMonth(event.date, now)) + } + + if (activeFilter === 'Next Month') { + const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) + return events.filter(event => isSameMonth(event.date, nextMonth)) + } + + const category = activeFilter.toLowerCase() + return events.filter(event => (event.category || '').toLowerCase().includes(category)) +} + +function getValidDate(event) { + const date = new Date(event.date) + return Number.isNaN(date.getTime()) ? null : date +} + +function formatEventDate(dateStr) { + const date = new Date(dateStr) + if (Number.isNaN(date.getTime())) return dateStr || 'Date TBA' + return date.toLocaleDateString(undefined, { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + }) +} + +function formatEventTime(timeStr) { + if (!timeStr) return 'Time TBA' + return timeStr +} + +export default function Events() { + const [active, setActive] = useState('All') + const { events, loading, error } = useEvents(true) + + const futureEvents = useMemo(() => { + const today = new Date() + today.setHours(0, 0, 0, 0) + return events.filter(event => { + const date = new Date(event.date) + if (Number.isNaN(date.getTime())) return true + return date >= today + }) + }, [events]) + + const actionableEvents = useMemo( + () => futureEvents.filter(event => Boolean(getValidDate(event))), + [futureEvents] + ) + + const nextEvent = useMemo(() => { + if (actionableEvents.length > 0) { + return actionableEvents + .slice() + .sort((a, b) => getValidDate(a) - getValidDate(b))[0] + } + return futureEvents[0] || null + }, [actionableEvents, futureEvents]) + + const upcomingEvents = useMemo( + () => filterEvents(futureEvents, active), + [futureEvents, active] + ) + + const totalUpcoming = futureEvents.length + const familyCount = futureEvents.filter(event => (event.category || '').toLowerCase().includes('family')).length + const outreachCount = futureEvents.filter(event => (event.category || '').toLowerCase().includes('outreach')).length + + const nextEventDate = nextEvent ? formatEventDate(nextEvent.date) : null + const nextEventTime = nextEvent ? formatEventTime(nextEvent.time) : null + + return ( + <> + Events | Annaville SDA Church + +
+
+
+

+ Gather. Serve. Grow. +

+

+ Upcoming Events & Community Moments +

+

+ There is always something happening at Annaville SDA Church. Explore ways to worship, volunteer, + and connect with families throughout the Corpus Christi community. +

+
+
+

+ {totalUpcoming} +

+

Upcoming gatherings

+
+
+

+ {familyCount} +

+

Family-focused events

+
+
+

+ {outreachCount} +

+

Community outreach

+
+
+
+ +
+ {nextEvent ? ( + <> + Next gathering +

{nextEvent.title}

+
+
{nextEventDate}
+
{nextEventTime}{nextEvent.location ? ` - ${nextEvent.location}` : ''}
+
+

+ {nextEvent.description || 'We would love for you to join us. Everyone is welcome!'} +

+ + View Event Details + + + ) : ( +
+

Check back soon

+

+ We are finalizing new activities and will post them here shortly. Stay tuned! +

+
+ )} +
+
+
+ +
+
+
+
+

Browse Events

+
+ {filters.map(filter => ( + + ))} +
+
+ +
+ {loading && ( +

Loading events...

+ )} + {error && !loading && ( +

Unable to load events right now. Please try again later.

+ )} + {!loading && !error && upcomingEvents.length === 0 && ( +
+ No upcoming events match this filter. Try another category or check back soon. +
+ )} + {!loading && !error && upcomingEvents.length > 0 && ( +
+ {upcomingEvents.map(event => ( + + ))} +
+ )} +
+
+
+
+ ) } diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index b846dc7..360dd30 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,13 +1,39 @@ - -import React from 'react' +import React, { useMemo } from 'react' import { Helmet } from 'react-helmet-async' import { Link } from 'react-router-dom' import { TextHero } from '../components/Hero' import VisitForm from '../components/VisitForm' import StaticMap from '../components/StaticMap' -import { track, events } from '../utils/analytics' +import { useEvents } from '../hooks/useEvents' + +function formatEventDate(dateStr, timeStr) { + const date = new Date(dateStr) + if (Number.isNaN(date)) { + return dateStr + } + const formattedDate = date.toLocaleDateString(undefined, { + weekday: 'short', + month: 'short', + day: 'numeric', + }) + return timeStr ? `${formattedDate} - ${timeStr}` : formattedDate +} export default function Home() { + const { events, loading, error } = useEvents() + + const upcoming = useMemo(() => { + const today = new Date() + today.setHours(0, 0, 0, 0) + return events + .filter(event => { + const date = new Date(event.date) + if (Number.isNaN(date)) return true + return date >= today + }) + .slice(0, 3) + }, [events]) + return ( <> @@ -126,10 +152,35 @@ export default function Home() {

Upcoming Events

-
-

- No upcoming events at this time. Please check back later for updates. -

+
+ {loading && ( +

Loading upcoming events...

+ )} + {error && !loading && ( +

Unable to load events right now. Please try again later.

+ )} + {!loading && !error && upcoming.length === 0 && ( +

+ No upcoming events at this time. Please check back later for updates. +

+ )} + {!loading && !error && upcoming.length > 0 && ( +
    + {upcoming.map(event => ( +
  • +

    {event.title}

    +
    + {formatEventDate(event.date, event.time)} + {event.location ? ` - ${event.location}` : ''} +
    +

    {event.description}

    + + View details + +
  • + ))} +
+ )}
diff --git a/src/pages/MinistryDetail.jsx b/src/pages/MinistryDetail.jsx index aa0b32b..769e600 100644 --- a/src/pages/MinistryDetail.jsx +++ b/src/pages/MinistryDetail.jsx @@ -1,38 +1,46 @@ - -import React from 'react' +import React, { useMemo } from 'react' import { useParams, Link } from 'react-router-dom' import { Helmet } from 'react-helmet-async' import ministries from '../data/ministries.json' -import events from '../data/events.json' +import { useEvents } from '../hooks/useEvents' import LazyImage from '../components/LazyImage' -export default function MinistryDetail(){ +export default function MinistryDetail() { const { slug } = useParams() - const m = ministries.find(x => x.slug === slug) - - if(!m) return ( -
-
-
-

Ministry Not Found

-

The ministry you're looking for doesn't exist.

- Back to Ministries -
-
-
- ) + const { events: eventItems } = useEvents() + const ministry = ministries.find(x => x.slug === slug) - // Filter events that might be related to this ministry - const relatedEvents = events.filter(e => - e.title.toLowerCase().includes(m.category.toLowerCase()) || - e.description.toLowerCase().includes(m.category.toLowerCase()) - ).slice(0, 3) + const relatedEvents = useMemo(() => { + if (!ministry) return [] + const category = `${ministry.category || ''}`.toLowerCase() + return eventItems + .filter(event => { + const title = `${event.title || ''}`.toLowerCase() + const description = `${event.description || ''}`.toLowerCase() + return title.includes(category) || description.includes(category) + }) + .slice(0, 3) + }, [eventItems, ministry]) + + if (!ministry) { + return ( +
+
+
+

Ministry Not Found

+

The ministry you're looking for doesn't exist.

+ Back to Ministries +
+
+
+ ) + } return ( <> - {m.name} - Annaville Seventh-day Adventist Church - + {ministry.name} - Annaville Seventh-day Adventist Church + {/* Hero Section */} @@ -41,29 +49,29 @@ export default function MinistryDetail(){
- {m.category} + {ministry.category}
-

{m.name}

-

{m.description}

+

{ministry.name}

+

{ministry.description}

- 🕒 - {m.meeting} + �Y' + {ministry.meeting}
- 📍 - {m.where} + �Y"? + {ministry.where}
- 👥 - {m.ages} + �Y'� + {ministry.ages}
@@ -83,9 +91,9 @@ export default function MinistryDetail(){

What We Do

- {m.activities.map((activity, index) => ( + {ministry.activities.map((activity, index) => (
- + �o" {activity}
))} @@ -97,15 +105,15 @@ export default function MinistryDetail(){

Upcoming Events

- {relatedEvents.map(e => ( + {relatedEvents.map(event => ( -

{e.title}

-

{e.date} • {e.time}

-

{e.description}

+

{event.title}

+

{event.date} - {event.time}

+

{event.description}

))}
@@ -116,12 +124,12 @@ export default function MinistryDetail(){

Frequently Asked Questions

- {m.faq.map((item, index) => ( + {ministry.faq.map((item, index) => (

{item.question}

- + �-�
@@ -142,29 +150,29 @@ export default function MinistryDetail(){

Ministry Leader

-

{m.leader}

+

{ministry.leader}

Meeting Details

-

{m.meeting}

-

{m.where}

+

{ministry.meeting}

+

{ministry.where}

@@ -182,7 +190,7 @@ export default function MinistryDetail(){ to="/ministries" className="block text-primary hover:text-primaryHover transition-colors" > - ← Back to All Ministries + ��? Back to All Ministries Other Ministries
{ministries - .filter(ministry => ministry.slug !== m.slug) + .filter(other => other.slug !== ministry.slug) .slice(0, 3) - .map(ministry => ( + .map(other => ( -

{ministry.name}

-

{ministry.meeting}

+

{other.name}

+

{other.meeting}

))}
diff --git a/src/pages/Services.jsx b/src/pages/Services.jsx index 23b6dc6..c9f39d3 100644 --- a/src/pages/Services.jsx +++ b/src/pages/Services.jsx @@ -1,131 +1,132 @@ -import React from 'react' +import React from 'react' import { Helmet } from 'react-helmet-async' -export default function Services() { - const services = [ - { - title: "Sabbath School", - time: "9:30 AM", - day: "Saturday", - description: "Interactive Bible study and discussion groups for all ages. Join us for meaningful conversations about Scripture and practical Christian living.", - icon: "📖", - color: "from-blue-500 to-blue-600" - }, - { - title: "Divine Worship", - time: "11:00 AM", - day: "Saturday", - description: "Our main worship service featuring inspiring music, prayer, and biblical messages that speak to everyday life and spiritual growth.", - icon: "⛪", - color: "from-primary to-primaryDeep" - }, - { - title: "Potluck Fellowship", - time: "After Worship", - day: "Saturday", - description: "Stay after the service for a delicious meal and warm fellowship. Visitors are especially welcome to join us for this time of community.", - icon: "🍽️", - color: "from-green-500 to-green-600" - } - ] +const services = [ + { + title: 'Sabbath School', + image: '/assets/family_entry.png', + time: '9:30 AM', + day: 'Saturday', + description: 'Interactive Bible study and discussion groups for all ages. Join us for meaningful conversations about Scripture and practical Christian living.', + tagColor: 'bg-blue-100 text-blue-800' + }, + { + title: 'Divine Worship', + image: '/assets/speeking.png', + time: '11:00 AM', + day: 'Saturday', + description: 'Our main worship service featuring inspiring music, prayer, and biblical messages that speak to everyday life and spiritual growth.', + tagColor: 'bg-primary/15 text-primary' + }, + { + title: 'Potluck Fellowship', + image: '/assets/potluck.png', + time: 'After Worship', + day: 'Saturday', + description: 'Stay after the service for a delicious meal and warm fellowship. Visitors are especially welcome to join us for this time of community.', + tagColor: 'bg-green-100 text-green-700' + } +] - const additionalInfo = [ - { - title: "Church Bus Service", - description: "Transportation available for those who need assistance getting to church. Please contact us to arrange pickup.", - icon: "🚌" - }, - { - title: "Family-Friendly Environment", - description: "Children are welcome in all our services. We also offer age-appropriate activities and care during worship times.", - icon: "👨‍👩‍👧‍👦" - }, - { - title: "Visitor Welcome", - description: "First time visiting? We're excited to meet you! Greeters are available to help you feel at home.", - icon: "🤝" - } - ] +const additionalInfo = [ + { + title: 'Church Bus Service', + description: 'Transportation is available for those who need assistance getting to church. Please contact us to arrange pickup.', + icon: '🚌' + }, + { + title: 'Family-Friendly Environment', + description: 'Children are welcome in all our services. We also offer age-appropriate activities and care during worship times.', + icon: '👨‍👩‍👧‍👦' + }, + { + title: 'Visitor Welcome', + description: "First time visiting? We're excited to meet you! Greeters are available to help you feel at home.", + icon: '🤝' + } +] +export default function Services(){ return ( <> Services - Annaville Seventh-day Adventist Church - - + + - {/* Hero Section */}
-
-

Worship Services

+
+

Worship Services

- Join us every Saturday for inspiring worship, meaningful Bible study, and warm fellowship. - All are welcome to experience the love of Christ in our community. + Join us every Saturday for inspiring worship, meaningful Bible study, and warm fellowship. All are welcome to experience the love of Christ in our community.

- {/* Main Services Section */}

Weekly Services

- The Annaville SDA Church offers worship services for members, non-members, or anyone interested - in learning more about practical Christian living from the Word of God. + The Annaville SDA Church offers worship services for members, non-members, and anyone interested in learning more about practical Christian living from the Word of God.

- {/* Services Cards */}
- {services.map((service, index) => ( -
-
-
-
- {service.icon} -
-
-

{service.title}

-
- + {services.map(service => ( +
+
+
+ {`${service.title} +
+
+
+

{service.title}

+
+ {service.time} {service.day}
-

- {service.description} -

+

+ {service.description} +

-
+
))} - {/* Image Section */}
- Church bus and family entry - People walking up steps to the church entrance

Plan Your Visit

- We're located at 2710 Violet Road in Corpus Christi. - Parking is available on-site, and our greeters will help you find your way. + We're located at 2710 Violet Road in Corpus Christi. Parking is available on-site, and our greeters will help you find your way.

- + Get Directions
@@ -136,7 +137,6 @@ export default function Services() {
- {/* Additional Information Section */}
@@ -148,13 +148,13 @@ export default function Services() {
- {additionalInfo.map((info, index) => ( -
+ {additionalInfo.map(item => ( +
-
{info.icon}
-

{info.title}

+ +

{item.title}

- {info.description} + {item.description}

@@ -164,27 +164,19 @@ export default function Services() {
- {/* Call to Action Section */}

Ready to Join Us?

- We'd love to welcome you this Saturday! Have questions about our services or need assistance? - Don't hesitate to reach out. + We'd love to welcome you this Saturday! Have questions about our services or need assistance? Don't hesitate to reach out.

diff --git a/src/pages/admin/AdminEventForm.jsx b/src/pages/admin/AdminEventForm.jsx new file mode 100644 index 0000000..8b2b3ea --- /dev/null +++ b/src/pages/admin/AdminEventForm.jsx @@ -0,0 +1,374 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { createEvent, getAdminToken, getEvent, updateEvent, uploadImage } from '../../utils/api' + +const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '') + +function resolveImageUrl(value) { + if (!value) return '' + const trimmed = value.trim() + if (!trimmed) return '' + if (/^(?:https?:)?\/\//i.test(trimmed) || trimmed.startsWith('data:')) { + return trimmed + } + const path = trimmed.startsWith('/') ? trimmed : `/${trimmed}` + if (apiBaseUrl) { + return `${apiBaseUrl}${path}` + } + return path +} + + +const emptyForm = { + title: '', + date: '', + time: '', + location: '', + description: '', + category: '', + image: '', + slug: '', +} + +export default function AdminEventForm() { + const { slug } = useParams() + const isEdit = Boolean(slug) + const navigate = useNavigate() + const [form, setForm] = useState(emptyForm) + const [loading, setLoading] = useState(isEdit) + const [saving, setSaving] = useState(false) + const [error, setError] = useState('') + const [selectedFile, setSelectedFile] = useState(null) + const [uploading, setUploading] = useState(false) + const [uploadError, setUploadError] = useState('') + const [uploadMessage, setUploadMessage] = useState('') + const fileInputRef = useRef(null) + const previewSrc = resolveImageUrl(form.image) + + useEffect(() => { + if (!isEdit) return + + let ignore = false + async function loadEvent() { + setLoading(true) + setError('') + try { + const data = await getEvent(slug) + if (!ignore) { + setForm({ + title: data.title || '', + date: data.date || '', + time: data.time || '', + location: data.location || '', + description: data.description || '', + category: data.category || '', + image: data.image || '', + slug: data.slug || '', + }) + } + } catch (err) { + console.error('Failed to load event for editing', err) + if (!ignore) { + setError(err.message || 'Unable to load event data.') + } + } finally { + if (!ignore) { + setLoading(false) + } + } + } + + loadEvent() + return () => { + ignore = true + } + }, [isEdit, slug]) + + const handleChange = (event) => { + const { name, value } = event.target + setForm((prev) => ({ ...prev, [name]: value })) + } + + const handleFileSelect = (event) => { + const file = event.target.files && event.target.files[0] ? event.target.files[0] : null + setSelectedFile(file) + setUploadError('') + setUploadMessage('') + } + + const handleUpload = async () => { + setUploadError('') + setUploadMessage('') + + if (!selectedFile) { + setUploadError('Select an image file before uploading.') + return + } + + const token = getAdminToken() + if (!token) { + setError('No admin token found. Please log in again.') + return + } + + setUploading(true) + try { + const { url } = await uploadImage(selectedFile, token) + setForm(prev => ({ ...prev, image: url })) + setSelectedFile(null) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + setUploadMessage('Image uploaded successfully.') + } catch (err) { + console.error('Failed to upload image', err) + setUploadError(err.message || 'Unable to upload image. Please try again.') + } finally { + setUploading(false) + } + } + + const handleSubmit = async (event) => { + event.preventDefault() + setError('') + + if (!form.title || !form.date) { + setError('Title and date are required.') + return + } + + const token = getAdminToken() + if (!token) { + setError('No admin token found. Please log in again.') + return + } + + setSaving(true) + let imageValue = form.image + + try { + if (selectedFile) { + setUploadError('') + setUploadMessage('') + setUploading(true) + try { + const { url, path } = await uploadImage(selectedFile, token) + imageValue = path || url + setForm(prev => ({ ...prev, image: imageValue })) + setSelectedFile(null) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + setUploadMessage('Image uploaded successfully.') + } catch (err) { + console.error('Failed to upload image', err) + setError(err.message || 'Unable to upload image. Please try again.') + setSaving(false) + setUploading(false) + return + } finally { + setUploading(false) + } + } + + const payload = { + title: form.title, + date: form.date, + time: form.time, + location: form.location, + description: form.description, + category: form.category, + image: imageValue, + } + + if (form.slug) { + payload.slug = form.slug + } + + if (isEdit) { + await updateEvent(slug, payload, token) + } else { + await createEvent(payload, token) + } + + navigate('/admin/events', { replace: true }) + } catch (err) { + console.error('Failed to save event', err) + setError(err.message || 'Unable to save event. Please try again.') + } finally { + setSaving(false) + } + } + + return ( +
+

+ {isEdit ? 'Edit Event' : 'Add New Event'} +

+

+ Provide the event details below. Title and date are required. Slug is optional and generated automatically if left blank. +

+ + {error &&
{error}
} + + {loading ? ( +

Loading event...

+ ) : ( +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +