diff --git a/package-lock.json b/package-lock.json index 883e25c0..1dad5a3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,12 @@ "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", "@fullcalendar/react": "^6.1.15", + "@types/react-datepicker": "^4.19.5", + "date-fns": "^2.30.0", "moment": "2.30.1", "next": "14.2.14", "react": "^18", - "react-datepicker": "^7.5.0", + "react-datepicker": "^4.21.0", "react-dom": "^18", "react-icons": "^5.3.0", "tailwind-merge": "^2.5.3" @@ -53,25 +55,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -109,59 +125,10 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", - "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "dependencies": { - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", - "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.8" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.25", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.25.tgz", - "integrity": "sha512-hZOmgN0NTOzOuZxI1oIrDu3Gcl8WViIkvPMpB4xdd4QD6xAMtwgwr3VPoiyH/bLtRcS1cDnhxLSD1NsMJmwh/A==", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" - }, "node_modules/@fullcalendar/core": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", - "license": "MIT", "dependencies": { "preact": "~10.12.1" } @@ -170,7 +137,6 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", - "license": "MIT", "peerDependencies": { "@fullcalendar/core": "~6.1.15" } @@ -179,7 +145,6 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", - "license": "MIT", "peerDependencies": { "@fullcalendar/core": "~6.1.15" } @@ -188,7 +153,6 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.15.tgz", "integrity": "sha512-L0b9hybS2J4e7lq6G2CD4nqriyLEqOH1tE8iI6JQjAMTVh5JicOo5Mqw+fhU5bJ7hLfMw2K3fksxX3Ul1ssw5w==", - "license": "MIT", "peerDependencies": { "@fullcalendar/core": "~6.1.15", "react": "^16.7.0 || ^17 || ^18 || ^19", @@ -537,6 +501,15 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -570,9 +543,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.16.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.14.tgz", - "integrity": "sha512-vtgGzjxLF7QT88qRHtXMzCWpAAmwonE7fwgVjFtXosUva2oSpnIEc3gNO9P7uIfOxKnii2f79/xtOnfreYtDaA==", + "version": "20.17.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.1.tgz", + "integrity": "sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==", "dev": true, "dependencies": { "undici-types": "~6.19.2" @@ -581,19 +554,28 @@ "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", - "dev": true, + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-datepicker": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.6.tgz", + "integrity": "sha512-uH5fzxt9eXxnc+hDCy/iRSFqU2+9lR/q2lAmaG4WILMai1o3IOdpcV+VSypzBFJLTEC2jrfeDXcdol0CJVMq4g==", + "dependencies": { + "@popperjs/core": "^2.9.2", + "@types/react": "*", + "date-fns": "^2.0.1", + "react-popper": "^2.2.5" + } + }, "node_modules/@types/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", @@ -816,9 +798,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1140,9 +1122,9 @@ } }, "node_modules/axe-core": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.1.tgz", - "integrity": "sha512-qPC9o+kD8Tir0lzNGLeghbOrWMr3ZJpaRlCIb6Uobt/7N4FiEDvqUMnxzCHRHmg8vOg14kr5gVNyScRmbMaJ9g==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", "dev": true, "engines": { "node": ">=4" @@ -1278,9 +1260,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001673", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz", + "integrity": "sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==", "funding": [ { "type": "opencollective", @@ -1348,6 +1330,11 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -1434,14 +1421,6 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1510,8 +1489,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -1571,12 +1549,18 @@ } }, "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, "node_modules/debug": { @@ -1667,9 +1651,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.42", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.42.tgz", - "integrity": "sha512-gIfKavKDw1mhvic9nbzA5lZw8QSHpdMwLwXc0cWidQz9B15pDoDdDH4boIatuFfeoCatb3a/NGL6CYRVFxGZ9g==", + "version": "1.5.47", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.47.tgz", + "integrity": "sha512-zS5Yer0MOYw4rtK2iq43cJagHZ8sXN0jDHDKzB+86gSBSAI4v07S97mcq+Gs2vclAxSh1j7vOAHxSVgduiiuVQ==", "dev": true }, "node_modules/emoji-regex": { @@ -2122,9 +2106,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.1.tgz", - "integrity": "sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "dependencies": { "aria-query": "^5.3.2", @@ -2135,7 +2119,6 @@ "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "es-iterator-helpers": "^1.1.0", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", @@ -4700,14 +4683,16 @@ } }, "node_modules/react-datepicker": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.5.0.tgz", - "integrity": "sha512-6MzeamV8cWSOcduwePHfGqY40acuGlS1cG//ePHT6bVbLxWyqngaStenfH03n1wbzOibFggF66kWaBTb1SbTtQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", + "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", "dependencies": { - "@floating-ui/react": "^0.26.23", - "clsx": "^2.1.1", - "date-fns": "^3.6.0", - "prop-types": "^15.8.1" + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0", + "react-popper": "^2.3.0" }, "peerDependencies": { "react": "^16.9.0 || ^17 || ^18", @@ -4726,6 +4711,11 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-icons": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", @@ -4739,6 +4729,33 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-onclickoutside": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz", + "integrity": "sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4781,6 +4798,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", @@ -5456,11 +5478,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" - }, "node_modules/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", @@ -5775,6 +5792,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 25132897..3bc85f0a 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", "@fullcalendar/react": "^6.1.15", + "@types/react-datepicker": "^4.19.5", + "date-fns": "^2.30.0", "moment": "2.30.1", "next": "14.2.14", "react": "^18", - "react-datepicker": "^7.5.0", + "react-datepicker": "^4.21.0", "react-dom": "^18", "react-icons": "^5.3.0", "tailwind-merge": "^2.5.3" diff --git a/public/assets/icons/SVG/icon/icon_calendar.svg b/public/assets/icons/SVG/icon/icon_calendar.svg new file mode 100644 index 00000000..7248ec71 --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_calendar.svg @@ -0,0 +1 @@ +자산 9 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_divider.svg b/public/assets/icons/SVG/icon/icon_divider.svg new file mode 100644 index 00000000..9c44bbe8 --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_divider.svg @@ -0,0 +1 @@ +자산 2 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_event.svg b/public/assets/icons/SVG/icon/icon_event.svg new file mode 100644 index 00000000..ee9c09b8 --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_event.svg @@ -0,0 +1 @@ +자산 10 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_image.svg b/public/assets/icons/SVG/icon/icon_image.svg new file mode 100644 index 00000000..1e02e3de --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_image.svg @@ -0,0 +1 @@ +자산 6 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_link.svg b/public/assets/icons/SVG/icon/icon_link.svg new file mode 100644 index 00000000..e15785f9 --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_link.svg @@ -0,0 +1 @@ +자산 3 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_text.svg b/public/assets/icons/SVG/icon/icon_text.svg new file mode 100644 index 00000000..dabbaaa1 --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_text.svg @@ -0,0 +1 @@ +자산 7 \ No newline at end of file diff --git a/public/assets/icons/SVG/icon/icon_video.svg b/public/assets/icons/SVG/icon/icon_video.svg new file mode 100644 index 00000000..76f9f5cc --- /dev/null +++ b/public/assets/icons/SVG/icon/icon_video.svg @@ -0,0 +1 @@ +자산 8 \ No newline at end of file diff --git a/public/assets/icons/SVG/item/item_dashed.svg b/public/assets/icons/SVG/item/item_dashed.svg new file mode 100644 index 00000000..982f0cc5 --- /dev/null +++ b/public/assets/icons/SVG/item/item_dashed.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/icons/SVG/item/item_dotted.svg b/public/assets/icons/SVG/item/item_dotted.svg new file mode 100644 index 00000000..a78a5cb4 --- /dev/null +++ b/public/assets/icons/SVG/item/item_dotted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/icons/SVG/item/item_line.svg b/public/assets/icons/SVG/item/item_line.svg new file mode 100644 index 00000000..c28d608a --- /dev/null +++ b/public/assets/icons/SVG/item/item_line.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/icons/SVG/item/item_zigzag.svg b/public/assets/icons/SVG/item/item_zigzag.svg new file mode 100644 index 00000000..064a6ef2 --- /dev/null +++ b/public/assets/icons/SVG/item/item_zigzag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/admin/(block)/block-menu.tsx b/src/app/admin/(block)/block-menu.tsx index 442a4698..5e77e8fc 100644 --- a/src/app/admin/(block)/block-menu.tsx +++ b/src/app/admin/(block)/block-menu.tsx @@ -27,51 +27,54 @@ const BlockMenu = ({ isOpen, setIsOpen }: Props) => { ]; return ( -
-
-
-

블록 선택하기

- -
- 블록 타입 -
    - {blockTypes.map((item, index) => { - return ( -
  • - -
    - {item.title} -
    -
    -

    {item.title}

    - {item.text} -
    - - {index !== blockTypes.length - 1 && ( -
    - -
    - )} -
  • - ); - })} -
+ +
+
+

블록 선택하기

+
+ 블록 타입 +
    + {blockTypes.map((item, index) => { + return ( +
  • + +
    + {item.title} +
    +
    +

    {item.title}

    + {item.text} +
    + + {index !== blockTypes.length - 1 && ( +
    + +
    + )} +
  • + ); + })} +
); diff --git a/src/app/admin/(block)/calendar/components/calendar-header.tsx b/src/app/admin/(block)/calendar/components/calendar-header.tsx index afcddde8..3be7d1d1 100644 --- a/src/app/admin/(block)/calendar/components/calendar-header.tsx +++ b/src/app/admin/(block)/calendar/components/calendar-header.tsx @@ -1,7 +1,5 @@ "use client"; -import Link from "next/link"; import Image from "next/image"; -import { ClientRoute } from "@config/route"; import { useRouter } from "next/navigation"; export default function CalendarHeader() { @@ -12,24 +10,15 @@ export default function CalendarHeader() { }; return ( -
- - Back - -

캘린더 블록

-

+ <> +

진행/예정된 일정이 1개 이상이어야
캘린더 블록을 공개할 수 있습니다

-
-
+
+ ); } diff --git a/src/app/admin/(block)/calendar/components/calendar-view.tsx b/src/app/admin/(block)/calendar/components/calendar-view.tsx index 06bed072..a35a7f20 100644 --- a/src/app/admin/(block)/calendar/components/calendar-view.tsx +++ b/src/app/admin/(block)/calendar/components/calendar-view.tsx @@ -5,20 +5,15 @@ import Image from "next/image"; import FullCalendar from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; import interactionPlugin from "@fullcalendar/interaction"; -import { EventContentArg, EventClickArg } from "@fullcalendar/core"; +import { EventContentArg } from "@fullcalendar/core"; import { Schedule } from "./types"; interface CalendarViewProps { schedules: Schedule[]; - hasUserSchedules: boolean; } -const CalendarView: React.FC = ({ - schedules, - hasUserSchedules, -}) => { - const initialDate = hasUserSchedules ? new Date() : new Date(2023, 0, 1); - const [currentMonth, setCurrentMonth] = useState(initialDate); +const CalendarView: React.FC = ({ schedules }) => { + const [currentMonth, setCurrentMonth] = useState(new Date()); const calendarRef = React.useRef(null); const eventColors = [ @@ -59,10 +54,7 @@ const CalendarView: React.FC = ({ end: schedule.endDate, backgroundColor: getBackgroundColor(schedule, index), borderColor: "transparent", - classNames: schedule.url - ? ["calendar-event", "cursor-pointer"] - : ["calendar-event", "cursor-default"], - extendedProps: { url: schedule.url }, + classNames: ["calendar-event"], })); }, [schedules]); @@ -126,13 +118,6 @@ const CalendarView: React.FC = ({ ); }; - const handleEventClick = (clickInfo: EventClickArg) => { - const url = clickInfo.event.extendedProps.url; - if (url) { - window.open(url, "_blank"); - } - }; - return (
@@ -141,11 +126,10 @@ const CalendarView: React.FC = ({ ref={calendarRef} plugins={[dayGridPlugin, interactionPlugin]} initialView="dayGridMonth" - initialDate={initialDate} + initialDate={new Date()} headerToolbar={false} events={getEvents()} eventContent={renderEventContent} - eventClick={handleEventClick} height="auto" firstDay={0} eventDisplay="block" diff --git a/src/app/admin/(block)/calendar/components/date-time-input.tsx b/src/app/admin/(block)/calendar/components/date-time-input.tsx new file mode 100644 index 00000000..95ee1973 --- /dev/null +++ b/src/app/admin/(block)/calendar/components/date-time-input.tsx @@ -0,0 +1,101 @@ +"use client"; + +import Image from "next/image"; +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import { FaRegCalendar, FaRegClock } from "react-icons/fa6"; + +interface DateTimeInputProps { + label: string; + dateValue: string; + timeValue: string; + onDateChange: (value: string) => void; + onTimeChange: (value: string) => void; + minDate?: string; + required?: boolean; +} + +export default function DateTimeInput({ + label, + dateValue, + timeValue, + onDateChange, + onTimeChange, + minDate, + required = false, +}: DateTimeInputProps) { + const handleDateChange = (date: Date | null) => { + if (date) { + const formattedDate = date.toISOString().split("T")[0]; + onDateChange(formattedDate); + } + }; + + const handleTimeChange = (date: Date | null) => { + if (date) { + const formattedTime = date.toLocaleTimeString("ko-KR", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); + onTimeChange(formattedTime); + } + }; + + const selectedDate = dateValue ? new Date(dateValue) : null; + const selectedTime = timeValue ? new Date(`1970-01-01T${timeValue}`) : null; + const minDateTime = minDate ? new Date(minDate) : undefined; + + return ( +
+ +
+
+ +
+ +
+
+
+ +
+ Open +
+
+
+
+ ); +} diff --git a/src/app/admin/(block)/calendar/components/list-view.tsx b/src/app/admin/(block)/calendar/components/list-view.tsx index 4070d77a..1512dc35 100644 --- a/src/app/admin/(block)/calendar/components/list-view.tsx +++ b/src/app/admin/(block)/calendar/components/list-view.tsx @@ -61,12 +61,6 @@ const getScheduleStatus = (schedule: Schedule, index: number) => { }; const ListView: React.FC = ({ schedules }) => { - const handleClick = (url: string) => { - if (url) { - window.open(url, "_blank"); - } - }; - return (
@@ -74,14 +68,10 @@ const ListView: React.FC = ({ schedules }) => {
{schedules.map((schedule, index) => { const status = getScheduleStatus(schedule, index); - const hasUrl = Boolean(schedule.url); return (
hasUrl && handleClick(schedule.url!)} + className="relative flex items-start pb-6" >
{ if (mode === "edit" && initialData) { const startDateTime = new Date(initialData.dateStart); @@ -59,45 +64,46 @@ export default function ScheduleForm({ } }, [mode, initialData]); - const fetchCalendarBlock = useCallback(async () => { - if (mode === "edit") return; + // API에서 데이터 1회 호출 + useEffect(() => { + const fetchCalendarBlock = async () => { + if (mode === "edit") return; - try { - const token = sessionStorage.getItem("token"); - if (!token) { - throw new Error("로그인이 필요합니다."); - } + try { + const token = sessionStorage.getItem("token"); + if (!token) { + throw new Error("로그인이 필요합니다."); + } - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/api/link/list`, - { - headers: { - Authorization: `Bearer ${token}`, + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/api/link/list`, + { + headers: { + Authorization: `Bearer ${token}`, + }, }, - }, - ); + ); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } - const data = await response.json(); - if (data.code === 200 && Array.isArray(data.data)) { - const existingCalendarBlock = data.data.find( - (item: CalendarBlock) => item.type === 7, - ); - if (existingCalendarBlock) { - setCalendarBlock(existingCalendarBlock); + const data = await response.json(); + if (data.code === 200 && Array.isArray(data.data)) { + const existingCalendarBlock = data.data.find( + (item: CalendarBlock) => item.type === 7, + ); + if (existingCalendarBlock) { + setCalendarBlock(existingCalendarBlock); + } } + } catch (error) { + console.error("Error fetching calendar block:", error); } - } catch (error) { - console.error("Error fetching calendar block:", error); - } - }, [mode]); + }; - useEffect(() => { fetchCalendarBlock(); - }, [fetchCalendarBlock]); + }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -211,174 +217,54 @@ export default function ScheduleForm({ (_, i) => `${i.toString().padStart(2, "0")}:00`, ); - return ( -
-
- -
- setStartDate(e.target.value)} - className={`min-w-[160px] flex-1 rounded-md p-2 ${ - startDate ? "border-[#FFCAB5] bg-[#FEF1E5]" : "border-gray-300" - }`} - required - /> -
-
- Clock -
- setStartTime(e.target.value)} - className="w-full appearance-none rounded-md border-0 bg-transparent p-2 focus:outline-none focus:ring-0" - placeholder="시간" - required - readOnly - /> -
setShowStartTime(!showStartTime)} - > - Open -
- {showStartTime && ( -
- {timeOptions.map((time, i) => ( -
{ - setStartTime(time); - setShowStartTime(false); - }} - > - {time} -
- ))} -
- )} -
-
-
+ const summitButtonDisabled = + !startDate || !startTime || !endDate || !endTime || !title; -
- -
- setEndDate(e.target.value)} - className={`min-w-[120px] flex-1 rounded-md p-2 ${ - endDate ? "border-[#FFCAB5] bg-[#FEF1E5]" : "border-gray-300" - }`} - required - /> -
-
- Clock -
- setEndTime(e.target.value)} - className="w-full appearance-none rounded-md border-0 bg-transparent p-2 focus:outline-none focus:ring-0" - placeholder="시간" - required - readOnly - /> -
setShowEndTime(!showEndTime)} - > - Open -
- {showEndTime && ( -
- {timeOptions.map((time, i) => ( -
{ - setEndTime(time); - setShowEndTime(false); - }} - > - {time} -
- ))} -
- )} -
-
-
- -
- - setTitle(e.target.value)} - className="rounded-md border border-gray-300 p-2 text-sm placeholder-gray-300 focus:border-[#FFCAB5] focus:outline-none focus:ring-[#FFCAB5]" - placeholder="알리고 싶은 일정 내용이 잘 드러나면 좋아요" - required - /> -
- -
- - setUrl(e.target.value)} - className="rounded-md border border-gray-300 p-2 text-sm placeholder-gray-300 focus:border-[#FFCAB5] focus:outline-none focus:ring-[#FFCAB5]" - placeholder="일정에 관심 있을 때 이동시키고 싶은 링크가 있나요?" - /> -
- -
- -
+ text={mode === "edit" ? "수정 완료" : "추가 완료"} + disabled={summitButtonDisabled} + /> +
); } diff --git a/src/app/admin/(block)/calendar/components/schedule-list.tsx b/src/app/admin/(block)/calendar/components/schedule-list.tsx index aa16f40a..3e45b3fe 100644 --- a/src/app/admin/(block)/calendar/components/schedule-list.tsx +++ b/src/app/admin/(block)/calendar/components/schedule-list.tsx @@ -1,6 +1,7 @@ import Image from "next/image"; import { useRouter } from "next/navigation"; import { useState, useEffect } from "react"; +import { BsCalendarXFill } from "react-icons/bs"; interface Schedule { id: number; @@ -54,13 +55,7 @@ const getScheduleStatus = (schedule: Schedule) => { function EmptyState({ message }: { message: React.ReactNode }) { return (
- 빈 캘린더 +

{message}

); @@ -80,12 +75,6 @@ function ScheduleItem({ router.push(`/admin/calendar/manage?mode=edit&id=${schedule.id}`); }; - const handleClick = (url: string) => { - if (url) { - window.open(url, "_blank"); - } - }; - return (
@@ -94,10 +83,7 @@ function ScheduleItem({ > {status.text}
-
schedule.url && handleClick(schedule.url)} - > +
{formatDate(schedule.dateStart)} ~ {formatDate(schedule.dateEnd)}
@@ -261,7 +247,7 @@ export default function ScheduleList() { }); return ( -
+ <>

추가한 모든 일정

)} -
+ ); } diff --git a/src/app/admin/(block)/calendar/components/style-setting.tsx b/src/app/admin/(block)/calendar/components/style-setting.tsx index df32c969..d015d20c 100644 --- a/src/app/admin/(block)/calendar/components/style-setting.tsx +++ b/src/app/admin/(block)/calendar/components/style-setting.tsx @@ -109,7 +109,7 @@ export default function StyleSetting() { const currentSchedules = hasUserSchedules ? schedules : sampleSchedules; return ( -
+

스타일 설정

)} diff --git a/src/app/admin/(block)/calendar/manage/page.tsx b/src/app/admin/(block)/calendar/manage/page.tsx index 2c539a7c..5f41a274 100644 --- a/src/app/admin/(block)/calendar/manage/page.tsx +++ b/src/app/admin/(block)/calendar/manage/page.tsx @@ -92,21 +92,23 @@ function ScheduleContent() { }, [mode, router, searchParams]); return ( -
-
-
-

+ +

{mode === "edit" ? "일정 수정하기" : "일정 추가하기"}

-

+ +

입력하는 진행기간에 따라
전체 일정이 최근 날짜 순서로 자동 정렬됩니다. diff --git a/src/app/admin/(block)/calendar/page.tsx b/src/app/admin/(block)/calendar/page.tsx index 4ad71038..71f1addb 100644 --- a/src/app/admin/(block)/calendar/page.tsx +++ b/src/app/admin/(block)/calendar/page.tsx @@ -1,17 +1,21 @@ "use client"; +import Layout from "../components/layout"; import CalendarHeader from "./components/calendar-header"; import ScheduleList from "./components/schedule-list"; import StyleSetting from "./components/style-setting"; export default function CalendarPage() { + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + return false; + }; + return ( - <> -

- - - -
- + + + + + ); } diff --git a/src/app/admin/(block)/divider/components/divider-preview.tsx b/src/app/admin/(block)/divider/components/divider-preview.tsx index 10f77ac9..8677f33c 100644 --- a/src/app/admin/(block)/divider/components/divider-preview.tsx +++ b/src/app/admin/(block)/divider/components/divider-preview.tsx @@ -14,25 +14,25 @@ const DividerContent = ({ type }: DividerContentProps) => { const commonClasses = "flex h-12 items-center justify-center"; switch (type) { - case "공백": + case "Space": return
; - case "점선": - case "실선": + case "Dashed": + case "Solid": return (
); - case "포인트": + case "Point": return
· · ·
; - case "지그재그": + case "Zigzag": return (
지그재그 diff --git a/src/app/admin/(block)/divider/components/divider-selector.tsx b/src/app/admin/(block)/divider/components/divider-selector.tsx index 5b545b03..fb4c0a52 100644 --- a/src/app/admin/(block)/divider/components/divider-selector.tsx +++ b/src/app/admin/(block)/divider/components/divider-selector.tsx @@ -10,13 +10,18 @@ export default function DividerSelector({ onSelect, selected, }: DividerSelectorProps) { - const dividers: Divider[] = [ - { name: "공백", icon: " " }, - { name: "점선", icon: "┈┈" }, - { name: "실선", icon: "───" }, - { name: "포인트", icon: "· · ·" }, + const dividers: { + name: DividerType; + displayName: string; + icon: string | JSX.Element; + }[] = [ + { name: "Space", displayName: "공백", icon: " " }, + { name: "Dashed", displayName: "점선", icon: "┈┈" }, + { name: "Solid", displayName: "실선", icon: "───" }, + { name: "Point", displayName: "포인트", icon: "· · ·" }, { - name: "지그재그", + name: "Zigzag", + displayName: "지그재그", icon: ( {divider.icon} - {divider.name} + {divider.displayName}
))}
diff --git a/src/app/admin/(block)/divider/page.tsx b/src/app/admin/(block)/divider/page.tsx index ebb0f05d..a46708d2 100644 --- a/src/app/admin/(block)/divider/page.tsx +++ b/src/app/admin/(block)/divider/page.tsx @@ -10,7 +10,7 @@ import AddButton from "@app/admin/(block)/components/buttons/add-button"; import { getSequence } from "lib/get-sequence"; export default function DividerPage() { - const [selectedDivider, setSelectedDivider] = useState("공백"); + const [selectedDivider, setSelectedDivider] = useState("Space"); const handleAddDivider = async () => { try { @@ -55,11 +55,11 @@ export default function DividerPage() { const getDividerStyle = (dividerType: DividerType): number => { const styles: Record = { - 공백: 1, - 점선: 2, - 실선: 3, - 포인트: 4, - 지그재그: 5, + Space: 1, + Dashed: 2, + Solid: 3, + Point: 4, + Zigzag: 5, }; return styles[dividerType] || 1; }; diff --git a/src/app/admin/(block)/divider/types.ts b/src/app/admin/(block)/divider/types.ts index 3266cb80..c9efe67e 100644 --- a/src/app/admin/(block)/divider/types.ts +++ b/src/app/admin/(block)/divider/types.ts @@ -1,4 +1,4 @@ -export type DividerType = "공백" | "점선" | "실선" | "포인트" | "지그재그"; +export type DividerType = "Space" | "Dashed" | "Solid" | "Point" | "Zigzag"; export interface Divider { name: DividerType; diff --git a/src/app/admin/(block)/event/components/calendar.tsx b/src/app/admin/(block)/event/components/calendar.tsx deleted file mode 100644 index d8acffea..00000000 --- a/src/app/admin/(block)/event/components/calendar.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import DatePicker from "react-datepicker"; - -export default function Calendar({ - startDate, - setStartDate, - endDate, - setEndDate, - startTime, - setStartTime, - endTime, - setEndTime, -}: { - startDate: Date | null; - setStartDate: (date: Date | null) => void; - endDate: Date | null; - setEndDate: (date: Date | null) => void; - startTime: Date | null; - setStartTime: (date: Date | null) => void; - endTime: Date | null; - setEndTime: (date: Date | null) => void; -}) { - return ( -
- -
- {/* 시작 날짜 및 시간 선택 */} -
- - setStartDate(date)} - dateFormat="yyyy.MM.dd" - placeholderText="날짜 선택" - enableTabLoop={false} - className="w-full max-w-[180px] rounded-lg border-2 p-2" - /> - setStartTime(date)} - showTimeSelect - showTimeSelectOnly - timeIntervals={15} - timeCaption="시간 선택" - dateFormat="HH:mm" - placeholderText="시간 선택" - className="w-full max-w-[180px] rounded-lg border-2 p-2" - /> -
- {/* 종료 날짜 및 시간 선택 */} -
- - setEndDate(date)} - dateFormat="yyyy.MM.dd" - placeholderText="날짜 선택" - minDate={startDate || undefined} - enableTabLoop={false} - className="w-full max-w-[180px] rounded-lg border-2 p-2" - /> - setEndTime(date)} - showTimeSelect - showTimeSelectOnly - timeIntervals={15} - timeCaption="시간 선택" - dateFormat="HH:mm" - placeholderText="시간 선택" - className="w-full max-w-[180px] rounded-lg border-2 p-2" - /> -
-
-
- ); -} diff --git a/src/app/admin/(block)/event/components/event-date-picker.tsx b/src/app/admin/(block)/event/components/event-date-picker.tsx new file mode 100644 index 00000000..f9f68ec9 --- /dev/null +++ b/src/app/admin/(block)/event/components/event-date-picker.tsx @@ -0,0 +1,158 @@ +import DatePicker from "react-datepicker"; +import { registerLocale } from "react-datepicker"; +import ko from "date-fns/locale/ko"; +import "react-datepicker/dist/react-datepicker.css"; +import { useState } from "react"; +import { twMerge } from "tailwind-merge"; +import { IoIosArrowForward, IoIosArrowBack } from "react-icons/io"; + +registerLocale("ko", ko); + +export default function EventDatePicker({ + startDate, + setStartDate, + endDate, + setEndDate, + startTime, + setStartTime, + endTime, + setEndTime, +}: { + startDate: Date | null; + setStartDate: (date: Date | null) => void; + endDate: Date | null; + setEndDate: (date: Date | null) => void; + startTime: Date | null; + setStartTime: (date: Date | null) => void; + endTime: Date | null; + setEndTime: (date: Date | null) => void; +}) { + const [isStartCalendarOpen, setIsStartCalendarOpen] = useState(false); + const [isEndCalendarOpen, setIsEndCalendarOpen] = useState(false); + + return ( +
+ +
+ {/* 시작 날짜 및 시간 선택 */} +
+ + setStartDate(date)} + dateFormat="yyyy.MM.dd" + placeholderText="날짜 선택" + locale={ko} + enableTabLoop={false} + className="w-full max-w-[150px] rounded-lg border-2 px-3 py-2 focus:border-black focus:placeholder:text-black" + onCalendarOpen={() => setIsStartCalendarOpen(true)} + onCalendarClose={() => setIsStartCalendarOpen(false)} + popperPlacement="bottom-start" + dayClassName={( + date, // 주말은 빨간색, 평일은 회색인데 적용 안됨 + ) => + date.getDay() === 0 || date.getDay() === 6 + ? "text-red-500" + : "text-gray-800" + } + renderCustomHeader={({ date, decreaseMonth, increaseMonth }) => ( +
+ + {date.getFullYear()}년 {date.getMonth() + 1}월 + +
+ + +
+
+ )} + /> +
+ setStartTime(date)} + showTimeSelect + showTimeSelectOnly + timeIntervals={30} + timeCaption="" + locale={ko} + dateFormat="HH:mm" + placeholderText="시간 선택" + className="w-full max-w-[150px] rounded-lg border-2 px-3 py-2 focus:border-black focus:placeholder:text-black" + /> +
+
+ {/* 종료 날짜 및 시간 선택 */} +
+ + setEndDate(date)} + dateFormat="yyyy.MM.dd" + placeholderText="날짜 선택" + locale={ko} + minDate={startDate || undefined} + enableTabLoop={false} + className="w-full max-w-[150px] rounded-lg border-2 px-3 py-2 focus:border-black focus:placeholder:text-black" + popperClassName="end-datepicker-popper" + onCalendarOpen={() => setIsEndCalendarOpen(true)} + onCalendarClose={() => setIsEndCalendarOpen(false)} + renderCustomHeader={({ date, decreaseMonth, increaseMonth }) => ( +
+ + {date.getFullYear()}년 {date.getMonth() + 1}월 + +
+ + +
+
+ )} + /> +
+ setEndTime(date)} + showTimeSelect + showTimeSelectOnly + timeIntervals={30} + timeCaption="" + locale={ko} + dateFormat="HH:mm" + placeholderText="시간 선택" + className="w-full max-w-[150px] rounded-lg border-2 px-3 py-2 focus:border-black focus:placeholder:text-black" + /> +
+
+
+
+ ); +} diff --git a/src/app/admin/(block)/event/components/event-form.tsx b/src/app/admin/(block)/event/components/event-form.tsx index 04e544aa..c0f36765 100644 --- a/src/app/admin/(block)/event/components/event-form.tsx +++ b/src/app/admin/(block)/event/components/event-form.tsx @@ -1,7 +1,7 @@ "use client"; import { FormEvent, useState } from "react"; -import Calendar from "./calendar"; +import EventDatePicker from "./event-date-picker"; import EventPreview from "./event-preview"; import Layout from "../../components/layout"; import ButtonBox from "../../components/buttons/button-box"; @@ -77,8 +77,8 @@ export default function EventForm() { alert("이벤트 블록이 성공적으로 추가되었습니다🥰"); router.push("/admin"); - const responseData = await response.json(); - console.log(responseData); + // const responseData = await response.json(); + // console.log(responseData); } catch (error) { throw new Error( error instanceof Error ? error.message : "An error occurred", @@ -135,7 +135,7 @@ export default function EventForm() { maxLength={100} /> - (""); - const [isExpanded, setIsExpanded] = useState(false); // 토글 on/off + const [isExpanded, setIsExpanded] = useState(false); // 카드 확장 여부 const [isDescriptionOverflowing, setIsDescriptionOverflowing] = - useState(false); // 입력된 설명 텍스트 너비 판단 - const [isTitleOverflowing, setIsTitleOverflowing] = useState(false); // 입력된 타이틀 텍스트 너비 판단 + useState(false); // 설명 텍스트 넘침 여부 + const [isTitleOverflowing, setIsTitleOverflowing] = useState(false); // 타이틀 텍스트 넘침 여부 const descriptionRef = useRef(null); const titleRef = useRef(null); @@ -52,6 +52,7 @@ export default function EventPreview({ setTimeLeft(""); } }; + // 남은 시간 업데이트 (종료 시간이 변경될 때마다 재계산) useEffect(() => { calculateTimeLeft(); diff --git a/src/app/admin/components/calendar-block.tsx b/src/app/admin/components/calendar-block.tsx index 2555386c..bd5843a3 100644 --- a/src/app/admin/components/calendar-block.tsx +++ b/src/app/admin/components/calendar-block.tsx @@ -1,4 +1,44 @@ -export default function CalendarBlock() { +"use client"; +import { useEffect, useState } from "react"; + +interface CalendarProps { + dateStart: string | null; + dateEnd: string | null; +} + +export default function CalendarBlock({ dateStart, dateEnd }: CalendarProps) { + useEffect(() => { + const checked = dateChecker(); + console.log(checked); + function checkSchedule() { + if (checked == "open") { + setOpen(1); + } else if (checked == "soon") { + setSoon(1); + } else if (checked == "closed") { + setClosed(1); + } + } + checkSchedule(); + }, []); + + const [open, setOpen] = useState(0); + const [soon, setSoon] = useState(0); + const [closed, setClosed] = useState(0); + + function dateChecker() { + const now = new Date(); + const started = new Date(dateStart as string); + const ended = new Date(dateEnd as string); + if (started <= now && now <= ended) { + return "open"; + } else if (now < started) { + return "soon"; + } else { + return "closed"; + } + } + return ( <>
@@ -8,9 +48,9 @@ export default function CalendarBlock() {
closed
-
0개
-
1개
-
0개
+
{open}개
+
{soon}개
+
{closed}개
diff --git a/src/app/admin/components/event-block.tsx b/src/app/admin/components/event-block.tsx index 44634fec..0c18ae40 100644 --- a/src/app/admin/components/event-block.tsx +++ b/src/app/admin/components/event-block.tsx @@ -1,4 +1,24 @@ -export default function EventBlock() { +interface EventBlockProps { + title: string | null; + dateStart: string | null; + dateEnd: string | null; +} + +export default function EventBlock({ + title, + dateStart, + dateEnd, +}: EventBlockProps) { + const dateStarted = new Date(dateStart as string); + const startedYear = dateStarted.getFullYear(); + const startedMonth = (dateStarted.getMonth() + 1).toString().padStart(2, "0"); + const startedDay = dateStarted.getDate().toString().padStart(2, "0"); + + const dateEnded = new Date(dateEnd as string); + const endedYear = dateEnded.getFullYear(); + const endedMonth = (dateEnded.getMonth() + 1).toString().padStart(2, "0"); + const endedDay = dateEnded.getDate().toString().padStart(2, "0"); + return ( <>
@@ -8,10 +28,8 @@ export default function EventBlock() {
일정
-
10월 이벤트
-
- 24.10.04 10:00 ~ 24.10.11 18:00 -
+
{title}
+
{`${startedYear}-${startedMonth}-${startedDay} ~ ${endedYear}-${endedMonth}-${endedDay}`}
diff --git a/src/app/admin/components/home-menu.tsx b/src/app/admin/components/home-menu.tsx new file mode 100644 index 00000000..82b1b221 --- /dev/null +++ b/src/app/admin/components/home-menu.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { ClientRoute } from "@config/route"; +import { useRouter } from "next/navigation"; +import Contour from "@app/admin/(block)/components/contour"; + +const HomeMenu = () => { + const [isMenuOn, setIsMenuOn] = useState(false); + const router = useRouter(); + async function handleLogout() { + try { + // 인증 관련 데이터 제거 + sessionStorage.removeItem("token"); + document.cookie = + "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + + router.push(ClientRoute.MAIN as string); + router.refresh(); + } catch (error) { + console.error("로그아웃 중 오류 발생:", error); + } + } + + return ( + <> + {isMenuOn && ( + + )} +
+ + {isMenuOn && ( +
+
    +
  • + Admin +
  • + +
  • + +
  • +
+
+ )} +
+ + ); +}; + +export default HomeMenu; diff --git a/src/app/admin/components/image-block.tsx b/src/app/admin/components/image-block.tsx index 74ca1415..39d31c66 100644 --- a/src/app/admin/components/image-block.tsx +++ b/src/app/admin/components/image-block.tsx @@ -13,7 +13,7 @@ export default function ImageBlock({ url, imgUrl, title }: ImageBlockProps) {
미리보기 -
-
-

+
+
+
+
+

{title}

+
+
); diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 12798fd3..8e47a2ea 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -6,9 +6,10 @@ import { useState, useEffect, useRef } from "react"; import Link from "next/link"; import { ClientRoute } from "@config/route"; import EmptyBlock from "@app/intro/components/UI/empty-block"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { postBlock } from "../../lib/post-block"; import BlockMenu from "@app/admin/(block)/block-menu"; +import HomeMenu from "@app/admin/components/home-menu"; interface Block { id: number; @@ -73,6 +74,8 @@ export default function Admin() { const dragItem = useRef(null); const dragOverItem = useRef(null); const router = useRouter(); + const pathname = usePathname(); + const isAdmin = pathname === "/admin"; async function getBlocks() { const token = sessionStorage.getItem("token"); @@ -134,7 +137,8 @@ export default function Admin() { return (
-
+
+ {!isAdmin && } profile )) )} -
-
- -
- -
- + {isAdmin && ( + <> +
+
+ +
+ +
+ + + )}
); } diff --git a/src/app/intro/components/basicblock.tsx b/src/app/intro/components/basicblock.tsx index 1fa997da..98cc5b80 100644 --- a/src/app/intro/components/basicblock.tsx +++ b/src/app/intro/components/basicblock.tsx @@ -31,6 +31,7 @@ interface Block { dragStart: (position: number) => void; dragEnter: (position: number) => void; drop: () => void; + isAdmin: boolean; } export default function BasicBlock({ id, @@ -52,6 +53,7 @@ export default function BasicBlock({ dragStart, dragEnter, drop, + isAdmin, }: Block) { const [isOpen, setIsOpen] = useState(false); @@ -104,7 +106,7 @@ export default function BasicBlock({ case 1: return ; case 2: - return ; + return ; case 3: return ( @@ -112,11 +114,13 @@ export default function BasicBlock({ case 4: return ; case 5: - return ; + return ( + + ); case 6: return ; case 7: - return ; + return ; default: return <>; } @@ -163,38 +167,43 @@ export default function BasicBlock({ onDragOver={(e) => e.preventDefault()} >
-
- -
-
- grabber -
-
- -
+ {isAdmin && ( + <> +
+ +
+
+ grabber +
+
+ +
+ + )}
+
@@ -209,36 +218,40 @@ export default function BasicBlock({ {setTitle(type)}
-
- - -
+ {isAdmin ? ( +
+ + +
+ ) : ( + <> + )}
{renderComponent(type)}
diff --git a/src/app/join/components/animated-text.tsx b/src/app/join/components/animated-text.tsx new file mode 100644 index 00000000..833c34f3 --- /dev/null +++ b/src/app/join/components/animated-text.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { twMerge } from "tailwind-merge"; + +function AnimatedText({ + isVisible, + children, +}: { + isVisible: boolean; + children: React.ReactNode; +}) { + const [isRemoving, setIsRemoving] = useState(false); + const [showText, setShowText] = useState(isVisible); + + useEffect(() => { + if (isVisible) { + setShowText(true); + setIsRemoving(false); + } else if (showText) { + setIsRemoving(true); + setTimeout(() => setShowText(false), 600); // 애니메이션 시간 후 DOM에서 제거 + } + }, [isVisible, showText]); + + if (!showText) return null; + + return ( +

+ {children} +

+ ); +} + +export default AnimatedText; diff --git a/src/app/join/page.tsx b/src/app/join/page.tsx index f647127b..5e6c3e10 100644 --- a/src/app/join/page.tsx +++ b/src/app/join/page.tsx @@ -1,14 +1,251 @@ -import { Metadata } from "next"; +"use client"; -//metadata -export const metadata: Metadata = { - title: "Join", -}; +import FormInput from "@app/admin/(block)/components/form-input"; +import { ClientRoute } from "@config/route"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { twMerge } from "tailwind-merge"; +import AnimatedText from "./components/animated-text"; export default function Join() { + const [userId, setUserId] = useState(""); + const [name, setName] = useState(""); + const [password, setPassword] = useState(""); + const [passwordConfirm, setPasswordConfirm] = useState(""); + const [email, setEmail] = useState(""); + + const [isUserIdFocused, setIsUserIdFocused] = useState(false); + const [isNameFocused, setIsNameFocused] = useState(false); + const [isPasswordFocused, setIsPasswordFocused] = useState(false); + const [isPasswordConfirmFocused, setIsPasswordConfirmFocused] = + useState(false); + const [isEmailFocused, setIsEmailFocused] = useState(false); + + const [isUserIdTouched, setIsUserIdTouched] = useState(false); + const [isNameTouched, setIsNameTouched] = useState(false); + const [isPasswordTouched, setIsPasswordTouched] = useState(false); + const [isPasswordConfirmTouched, setIsPasswordConfirmTouched] = + useState(false); + const [isEmailTouched, setIsEmailTouched] = useState(false); + + const isDisabled = + !userId || + !name || + !password || + !passwordConfirm || + !email || + password !== passwordConfirm; + + const router = useRouter(); + + async function handleJoin(e: React.FormEvent) { + e.preventDefault(); + if (isDisabled || password !== passwordConfirm) return; + + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/api/user/add`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: name, + userId: userId, + password: password, + email: email, + }), + }, + ); + + const infor = await response.json(); + if (response.ok) { + alert("회원가입 성공!"); + router.push(ClientRoute.LOGIN as string); + } else { + alert( + "회원가입 실패: " + + (infor.message || "알 수 없는 오류가 발생했습니다."), + ); + } + } catch (error) { + console.log(error); + alert("회원가입 중 오류가 발생했습니다. 다시 시도해 주세요."); + } + } + return ( -
-

회원가입 페이지

+
+
+

IN MY LINK 회원가입 페이지입니다!

+

필수 정보를 입력해 주세요.

+
+ +
+ +

IN MY LINK 회원가입

+

+ 이미 가입하셨나요? + + 로그인 하기 + +

+
+ +
+ {/* 아이디 필드 */} +
+
+ + + {/* 말풍선 아이콘 */} +
+ question +
+
+ 👈 아이디는 boomco 주소로 사용됩니다 (변경불가) +
+
+
+ +
+ http://link.boomco.com/ + setUserId(e.target.value)} + onFocus={() => setIsUserIdFocused(true)} + onBlur={() => { + setIsUserIdFocused(false); + setIsUserIdTouched(true); + }} + required + className={twMerge( + "flex-1 rounded-lg border p-3", + isUserIdFocused ? "inserted" : "border-gray-300", + )} + /> +
+ + 필수 입력 정보입니다 + +
+ + {/* 이름 필드 */} + setName(e.target.value)} + onFocus={() => setIsNameFocused(true)} + onBlur={() => { + setIsNameFocused(false); + setIsNameTouched(true); + }} + className={twMerge(isNameFocused ? "inserted" : "")} + /> + + 필수 입력 정보입니다 + + + {/* 비밀번호 필드 */} + setPassword(e.target.value)} + onFocus={() => setIsPasswordFocused(true)} + onBlur={() => { + setIsPasswordFocused(false); + setIsPasswordTouched(true); + }} + className={twMerge(isPasswordFocused ? "inserted" : "")} + /> + + 필수 입력 정보입니다 + + + {/* 비밀번호 확인 필드 */} + setPasswordConfirm(e.target.value)} + onFocus={() => setIsPasswordConfirmFocused(true)} + onBlur={() => { + setIsPasswordConfirmFocused(false); + setIsPasswordConfirmTouched(true); + }} + className={twMerge(isPasswordConfirmFocused ? "inserted" : "")} + /> + + 필수 입력 정보입니다 + + + 비밀번호가 일치하지 않습니다 + + + {/* 이메일 필드 */} + setEmail(e.target.value)} + onFocus={() => setIsEmailFocused(true)} + onBlur={() => { + setIsEmailFocused(false); + setIsEmailTouched(true); + }} + className={twMerge(isEmailFocused ? "inserted" : "")} + /> + + 필수 입력 정보입니다 + + + {/* 가입 완료 버튼 */} + +
); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 372ea7f9..a34f43ea 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,16 +1,26 @@ "use client"; -import { useState } from "react"; -import { useRouter } from "next/navigation"; +import FormInput from "@app/admin/(block)/components/form-input"; import { ClientRoute } from "@config/route"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { twMerge } from "tailwind-merge"; export default function Login() { const [userId, setUserId] = useState(""); const [password, setPassword] = useState(""); + const [isUserIdFocused, setIsUserIdFocused] = useState(false); + const [isPasswordFocused, setIsPasswordFocused] = useState(false); + const router = useRouter(); + const isDisabled = !userId || !password; + async function handleLogin(e: React.FormEvent) { e.preventDefault(); + if (isDisabled) return; try { const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/api/login`, @@ -42,27 +52,58 @@ export default function Login() { } return ( -
-
- - - +
+ {/* 스크린 리더 전용 텍스트*/} +
+

in my link 의 로그인 페이지 입니다!

+

아이디와 비밀번호를 입력해 주세요.

+

가입하신 적이 없으시다면 회원가입을 진행해 주세요.

+
+
+ +

로그인

+
+ + setUserId(e.target.value)} + onFocus={() => setIsUserIdFocused(true)} + onBlur={() => setIsUserIdFocused(false)} + className={twMerge(isUserIdFocused ? "inserted" : "")} + /> + setPassword(e.target.value)} + onFocus={() => setIsPasswordFocused(true)} + onBlur={() => setIsPasswordFocused(false)} + className={twMerge(isPasswordFocused ? "inserted" : "")} + /> +
+ + + IN MY LINK 무료 회원가입 + +
); diff --git a/src/app/page.tsx b/src/app/page.tsx index 023f57af..d1f7d5f8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,38 +1,22 @@ "use client"; -import { ClientRoute } from "@config/route"; -import Link from "next/link"; import { useRouter } from "next/navigation"; +import { useEffect } from "react"; +import Admin from "@app/admin/page"; -export default function Intro() { +export default function Page() { const router = useRouter(); // 로그아웃 함수: 세션 스토리지와 쿠키에서 토큰 제거 후 리다이렉트 - async function handleLogout() { - try { - // 인증 관련 데이터 제거 - sessionStorage.removeItem("token"); - document.cookie = - "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - await router.push(ClientRoute.MAIN as string); - router.refresh(); - } catch (error) { - console.error("로그아웃 중 오류 발생:", error); + useEffect(() => { + const token = sessionStorage.getItem("token"); + if (!token) { + console.log(token, "token"); + router.push("/intro"); } - } + // return () => sessionStorage.removeItem("token"); + }, []); - return ( - <> -

Home

-

in my link

- - Admin - - - - - ); + return ; } diff --git a/src/components/sections/admin/sample.tsx b/src/components/sections/admin/sample.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/middleware.ts b/src/middleware.ts index 798000a8..d293894e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -7,6 +7,7 @@ export function middleware(request: NextRequest) { if ( !token && request.nextUrl.pathname !== "/login" && + request.nextUrl.pathname !== "/join" && request.nextUrl.pathname !== "/intro" ) { return NextResponse.redirect(new URL("/intro", request.url)); diff --git a/src/styles/datepicker-custom.css b/src/styles/datepicker-custom.css new file mode 100644 index 00000000..badb971b --- /dev/null +++ b/src/styles/datepicker-custom.css @@ -0,0 +1,135 @@ +.react-datepicker-popper { + transform: translate(-50%, -50%) !important; + top: 50% !important; + left: 50% !important; +} + +.react-datepicker { + border: none !important; + max-width: 536px; + max-height: 315px; +} + +/* calendar */ +.react-datepicker .react-datepicker__header { + background-color: white; + border-bottom: none; + padding-top: 10px; + padding-bottom: 0; +} + +.react-datepicker .react-datepicker__day-names { + background-color: white; + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + margin: 0; + padding: 0 30px; +} + +.react-datepicker .react-datepicker__day-name { + color: gray; + width: auto; + margin: 0; + padding: 0 5px; +} + +.react-datepicker__month-container { + border: none; + font-weight: 600; +} + +.react-datepicker__month { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 20px; + padding-bottom: 20px; +} + +.react-datepicker__week { + display: flex; + justify-content: center; + gap: 42px; +} + +.react-datepicker__triangle { + display: none; +} + +.react-datepicker__month-container { + float: none !important; +} + +.react-datepicker__day--keyboard-selected { + background-color: var(--primary-200) !important; +} + +.react-datepicker__day--selected { + background-color: var(--primary-400) !important; +} + +/* end calendar */ +.react-datepicker-popper.end-datepicker-popper { + transform: translate(-50%, -27%) !important; + top: 50% !important; + left: 50% !important; +} + +/* time */ +.react-datepicker.react-datepicker--time-only { + width: 150px; + height: 224px; +} + +.date-picker-start-time-container .react-datepicker-popper { + transform: translateX(-50%) !important; + top: 70px !important; + left: 48.7% !important; + box-shadow: + 0 3px 6px rgba(0, 0, 0, 0.16), + 0 3px 6px rgba(0, 0, 0, 0.23); + border-radius: 8px; +} + +.date-picker-end-time-container .react-datepicker-popper { + transform: translate(-50%, -50%) !important; + top: -30px !important; + left: 48.7% !important; + box-shadow: + 0 3px 6px rgba(0, 0, 0, 0.16), + 0 3px 6px rgba(0, 0, 0, 0.23); + border-radius: 8px; +} + +/* .react-datepicker__time { + width: 150px; +} */ + +.react-datepicker__time-container { + float: none !important; + width: 150px; +} + +.react-datepicker__time-container + .react-datepicker__time + .react-datepicker__time-box + ul.react-datepicker__time-list { + font-size: larger; +} + +.react-datepicker__time-container + .react-datepicker__time + .react-datepicker__time-box + ul.react-datepicker__time-list + li.react-datepicker__time-list-item { + padding: 5px 12px !important; +} + +.react-datepicker__time-container + .react-datepicker__time + .react-datepicker__time-box { + width: 150px !important; + text-align: start !important; +} diff --git a/src/styles/global.css b/src/styles/global.css index 60a92a06..f03a766f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,3 +1,5 @@ +@import "./datepicker-custom.css"; + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/tailwind.config.ts b/tailwind.config.ts index 64827665..bc681626 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -26,9 +26,14 @@ const config: Config = { "0%": { opacity: "0%" }, "100%": { opacity: "100%" }, }, + fadeOut: { + "0%": { opacity: "100%" }, + "100%": { opacity: "0%" }, + }, }, animation: { insideout: "insideout 0.6s ease-in-out", + fadeOut: "fadeOut 0.6s ease-in-out", }, borderWidth: { 1: "1px",