From b2065e678efca5433d90bd07539cf188169d07f3 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 20:36:29 +0900 Subject: [PATCH 01/10] edit router --- admin/src/router.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/admin/src/router.jsx b/admin/src/router.jsx index fe466ac..0b1671f 100644 --- a/admin/src/router.jsx +++ b/admin/src/router.jsx @@ -5,6 +5,8 @@ import MiniQuiz from '@/pages/miniQuiz/MiniQuiz'; import MiniQuizAnswer from '@/pages/miniQuizAnswer/MiniQuizAnswer'; import Draw from '@/pages/draw/Draw'; import Reward from '@/pages/reward/Reward'; +import AdminEventStatus from '@/pages/AdminEventStatus/AdminEventStatus'; +import UploadReward from '@/pages/UploadReward/UploadReward'; const router = createBrowserRouter([ { @@ -24,6 +26,18 @@ const router = createBrowserRouter([ path: 'reward', element: , }, + { + path: 'adminEventStatus', + element: , + }, + { + path: 'uploadReward', + element: , + }, + { + path: 'adminEventStatus', + element: , + }, ], }, ]); From 0c3bab64d307b6926ea250188a2c903c2617d975 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 20:36:51 +0900 Subject: [PATCH 02/10] add library jszip --- admin/package.json | 1 + admin/yarn.lock | 85 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/admin/package.json b/admin/package.json index 829f34f..a26e3d2 100644 --- a/admin/package.json +++ b/admin/package.json @@ -18,6 +18,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.5.1", "json-server": "^1.0.0-beta.1", + "jszip": "^3.10.1", "micro-slider": "^1.1.0", "postcss": "^8.4.39", "prop-types": "^15.8.1", diff --git a/admin/yarn.lock b/admin/yarn.lock index 05c9741..2fb0707 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -2185,6 +2185,11 @@ core-js-compat@^3.36.1, core-js-compat@^3.37.1: dependencies: browserslist "^4.23.0" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cosmiconfig@^8.1.3: version "8.3.6" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz" @@ -3171,6 +3176,11 @@ ignore@^5.2.0: resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" @@ -3197,7 +3207,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.0: +inherits@2, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3416,6 +3426,11 @@ isarray@^2.0.5: resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" @@ -3528,6 +3543,16 @@ json5@^2.2.3: object.assign "^4.1.4" object.values "^1.1.6" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" @@ -3555,6 +3580,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" @@ -3894,6 +3926,11 @@ paginator@^1.0.0: resolved "https://registry.yarnpkg.com/paginator/-/paginator-1.0.0.tgz#7565702af9ab9616dca61fc22c70eba2a4357265" integrity sha512-j2Y5AtF/NrXOEU9VVOQBGHnj81NveRQ/cDzySywqsWrAj+cxivMpMCkYJOds3ulQiDU4rQBWc0WoyyXMXOmuMA== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -4039,6 +4076,11 @@ prettier@^3.3.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + "prop-types@15.x.x - 16.x.x", prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" @@ -4121,6 +4163,19 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -4290,6 +4345,11 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz" @@ -4333,6 +4393,11 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -4490,6 +4555,13 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -4612,6 +4684,15 @@ tailwindcss@^3.4.6: resolve "^1.22.2" sucrase "^3.32.0" +tar@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== + dependencies: + block-stream "*" + fstream "^1.0.12" + inherits "2" + tar@^7.4.3: version "7.4.3" resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" @@ -4794,7 +4875,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@^1.0.2: +util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== From 223bf7c41e0c8d6f0de88c3313d174ad0c103371 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 20:44:15 +0900 Subject: [PATCH 03/10] edit tabHeader --- admin/src/components/header/NavLinkItem.jsx | 27 ++++++++++ admin/src/components/header/TabHeader.jsx | 56 +++------------------ 2 files changed, 34 insertions(+), 49 deletions(-) create mode 100644 admin/src/components/header/NavLinkItem.jsx diff --git a/admin/src/components/header/NavLinkItem.jsx b/admin/src/components/header/NavLinkItem.jsx new file mode 100644 index 0000000..6e2f31f --- /dev/null +++ b/admin/src/components/header/NavLinkItem.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; + +function NavLinkItem({ path, value }) { + return ( + + `${ + isActive + ? 'text-body-3-bold text-neutral-black border-b-black border-b-solid border-b-[3px]' + : 'text-detail-1-regular text-neutral-500 hover:scale-110 transition-transform duration-300' + }` + } + > + {value} + + ); +} + +NavLinkItem.propTypes = { + path: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, +}; + +export default NavLinkItem; diff --git a/admin/src/components/header/TabHeader.jsx b/admin/src/components/header/TabHeader.jsx index eae1293..cb26491 100644 --- a/admin/src/components/header/TabHeader.jsx +++ b/admin/src/components/header/TabHeader.jsx @@ -1,57 +1,15 @@ import React from 'react'; -import { NavLink } from 'react-router-dom'; +import NavLinkItem from '@/components/header/NavLinkItem'; function TabHeader() { return (
- - `${ - isActive - ? 'text-body-3-bold text-neutral-black border-b-black border-b-solid border-b-[3px]' - : 'text-detail-1-regular text-neutral-500 hover:scale-110 transition-transform duration-300' - }` - } - > - 미니퀴즈 질문 - - - `${ - isActive - ? 'text-body-3-bold text-neutral-black border-b-black border-b-solid border-b-[3px]' - : 'text-detail-1-regular text-neutral-500 hover:scale-110 transition-transform duration-300' - }` - } - > - 미니퀴즈 답변 - - - `${ - isActive - ? 'text-body-3-bold text-neutral-black border-b-black border-b-solid border-b-[3px]' - : 'text-detail-1-regular text-neutral-500 hover:scale-110 transition-transform duration-300' - }` - } - > - 응모 결과 - - - `${ - isActive - ? 'text-body-3-bold text-neutral-black border-b-black border-b-solid border-b-[3px]' - : 'text-detail-1-regular text-neutral-500 hover:scale-110 transition-transform duration-300' - }` - } - > - 상품 목록 - + + + + + +
); } From 68f4dd3e92717c06e73194bfba282de68317bc6e Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 22:15:49 +0900 Subject: [PATCH 04/10] made API for formData --- admin/src/api/indexFormData.js | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 admin/src/api/indexFormData.js diff --git a/admin/src/api/indexFormData.js b/admin/src/api/indexFormData.js new file mode 100644 index 0000000..0b57211 --- /dev/null +++ b/admin/src/api/indexFormData.js @@ -0,0 +1,51 @@ +const ApiRequest = async (url, method, body) => { + const accessToken = localStorage.getItem('userInfo'); + + try { + const options = { + method, + headers: { + ...(accessToken && { + Authorization: `Bearer ${accessToken}`, + }), + }, + credentials: 'include', + }; + + const formData = new FormData(); + for (const key in body) { + formData.append(key, body[key]); + } + options.body = formData; + + const response = await fetch( + `${import.meta.env.VITE_API_URL}${url}`, + options, + ); + const result = await response.json(); + return result; + } catch (error) { + console.log('API 통신 실패 : ', error); + throw error; + } +}; + +export const post = (url, body) => { + return ApiRequest(url, 'POST', body); +}; + +export const get = url => { + return ApiRequest(url, 'GET', null); +}; + +export const put = (url, body) => { + return ApiRequest(url, 'PUT', body); +}; + +export const patch = (url, body) => { + return ApiRequest(url, 'PATCH', body); +}; + +export const del = (url, body, header) => { + return ApiRequest(url, 'DELETE', body, header); +}; From 15d22e873256e4081277794b82434bb26af61925 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 22:20:50 +0900 Subject: [PATCH 05/10] add UploadReward API --- admin/src/api/UploadReward/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 admin/src/api/UploadReward/index.js diff --git a/admin/src/api/UploadReward/index.js b/admin/src/api/UploadReward/index.js new file mode 100644 index 0000000..1a9e0b5 --- /dev/null +++ b/admin/src/api/UploadReward/index.js @@ -0,0 +1,9 @@ +import { post } from '@/api/indexFormData'; + +const postQuizReward = (file, quizDate) => + post('/admin/quizReward', { + file, + quizDate, + }); + +export { postQuizReward }; From 2207a7762c9502eae28b509d8686e52de870e0ce Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 22:21:20 +0900 Subject: [PATCH 06/10] edit AdminEventStatus --- admin/src/pages/AdminEventStatus/AdminEventStatus.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/src/pages/AdminEventStatus/AdminEventStatus.jsx b/admin/src/pages/AdminEventStatus/AdminEventStatus.jsx index e65bfd7..6479b02 100644 --- a/admin/src/pages/AdminEventStatus/AdminEventStatus.jsx +++ b/admin/src/pages/AdminEventStatus/AdminEventStatus.jsx @@ -4,8 +4,9 @@ import PrizeTable from '@/pages/AdminEventStatus/PrizeTable'; function AdminEventStatus() { return ( -
+
+
); From cbecc263146108501ab39cbb53ccc6026831ae03 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 22:44:58 +0900 Subject: [PATCH 07/10] add value prop to BlackButton so edit the files --- admin/src/components/buttons/BlackButton.jsx | 5 +++-- admin/src/pages/draw/Draw.jsx | 2 +- admin/src/pages/miniQuiz/MiniQuiz.jsx | 2 +- admin/src/pages/miniQuizAnswer/MiniQuizAnswer.jsx | 2 +- admin/src/pages/reward/Reward.jsx | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/admin/src/components/buttons/BlackButton.jsx b/admin/src/components/buttons/BlackButton.jsx index 1d5c058..352b9e0 100644 --- a/admin/src/components/buttons/BlackButton.jsx +++ b/admin/src/components/buttons/BlackButton.jsx @@ -1,18 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; -function BlackButton({ onClickFunc }) { +function BlackButton({ value, onClickFunc }) { return ( ); } BlackButton.propTypes = { + value: PropTypes.string.isRequired, onClickFunc: PropTypes.func.isRequired, }; diff --git a/admin/src/pages/draw/Draw.jsx b/admin/src/pages/draw/Draw.jsx index 697a0bb..d28fc28 100644 --- a/admin/src/pages/draw/Draw.jsx +++ b/admin/src/pages/draw/Draw.jsx @@ -40,7 +40,7 @@ function Draw() {
- +
); diff --git a/admin/src/pages/miniQuiz/MiniQuiz.jsx b/admin/src/pages/miniQuiz/MiniQuiz.jsx index b7a8c4d..4e28843 100644 --- a/admin/src/pages/miniQuiz/MiniQuiz.jsx +++ b/admin/src/pages/miniQuiz/MiniQuiz.jsx @@ -58,7 +58,7 @@ function MiniQuiz() {
- setOpenModal(true)} /> + setOpenModal(true)} />
{openModal && ( - setOpenModal(true)} /> + setOpenModal(true)} /> {openModal && (
- +
); From 46ffb2c7414020aa4ffcf2712dba4fab93b491d4 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 23:18:48 +0900 Subject: [PATCH 08/10] UploadReward component --- admin/src/pages/UploadReward/UploadReward.jsx | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 admin/src/pages/UploadReward/UploadReward.jsx diff --git a/admin/src/pages/UploadReward/UploadReward.jsx b/admin/src/pages/UploadReward/UploadReward.jsx new file mode 100644 index 0000000..0b3ab77 --- /dev/null +++ b/admin/src/pages/UploadReward/UploadReward.jsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect, useContext } from 'react'; +import JSZip from 'jszip'; +import BlackButton from '@/components/buttons/BlackButton'; +import AdminEditHeader from '@/components/header/AdminEditHeader'; +import { postQuizReward } from '@/api/UploadReward'; +import ModalFrame from '@/components/modal/ModalFrame'; +import { DateContext } from '@/context/dateContext'; +import '@/styles/global.css'; + +function UploadReward() { + const [fileCount, setFileCount] = useState(null); + const [errorMessage, setErrorMessage] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); // 파일 객체를 저장할 상태 + const [totalReward, setTotalReward] = useState(null); + const introduce = '파일을 여기로 드래그하거나 클릭하여 선택'; + const [openModal, setOpenModal] = useState(false); + const { dateInfo } = useContext(DateContext); + + useEffect(() => { + setTotalReward(5); // 가져오는 api + }, []); + + const handleClick = () => { + if (!selectedFile) { + setErrorMessage('업로드할 파일이 없습니다.'); + return; + } + setOpenModal(true); + }; + + const handleSubmit = async () => { + try { + setIsLoading(true); + const response = await postQuizReward(selectedFile, dateInfo); + setOpenModal(false); + console.log(response); + //확인 필요 + if (response.ok); + else { + setErrorMessage('파일 업로드에 실패했습니다.'); + } + } catch (error) { + setErrorMessage('파일 업로드 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + const handleFileChange = async files => { + setErrorMessage(''); + setFileCount(null); + setIsLoading(true); + + if (files.length > 1) { + setErrorMessage('zip 파일 한 개만 업로드 가능합니다.'); + setIsLoading(false); + return; + } + + const file = files[0]; + if (!file) { + setErrorMessage( + '파일 업로드 과정에서 오류가 있습니다. 다시 시도 부탁드립니다.', + ); + setIsLoading(false); + return; + } + + const fileExtension = file.name.split('.').pop().toLowerCase(); + if (fileExtension !== 'zip') { + setErrorMessage( + '확장자는 대소문자 상관없이 zip이어야 합니다. 확장자를 다시 확인 부탁드립니다.', + ); + setIsLoading(false); + return; + } + + try { + const zip = new JSZip(); + const zipContent = await zip.loadAsync(file); + const fileKeys = Object.keys(zipContent.files); + + // 디렉토리 파일이 포함되어 있는지 체크 + const containsDirectory = fileKeys.some(key => zipContent.files[key].dir); + // 이후 이미지 형식의 파일만 있는지 확인하는 과정도 있으면 좋을 거 같음 + + if (containsDirectory) { + setErrorMessage( + '디렉토리(폴더)가 포함된 ZIP 파일은 업로드할 수 없습니다. 파일만을 선택하여 압축해주세요!', + ); + setIsLoading(false); + return; + } + const validFileCount = fileKeys.reduce((count, key) => { + if (!key.startsWith('__MACOSX/')) { + return count + 1; + } + return count; + }, 0); + + if (validFileCount === 0) { + setErrorMessage('ZIP 파일이 비어 있습니다.'); + } else if (validFileCount !== totalReward) { + console.log(validFileCount); + setErrorMessage(`파일의 개수는 ${totalReward}이어야 합니다.`); + } else { + setFileCount(validFileCount); + setSelectedFile(file); + } + } catch (error) { + setErrorMessage('zip 파일을 읽는 동안 오류가 발생했습니다.'); + setFileCount(null); + } + setIsLoading(false); + }; + + const handleDrop = event => { + event.preventDefault(); + const files = event.dataTransfer.files; + handleFileChange(files); + }; + + const handleDragOver = event => { + event.preventDefault(); + }; + + const handleDeleteFile = () => { + setSelectedFile(null); // 파일 객체 초기화 + setFileCount(null); + setErrorMessage(''); + }; + + return ( +
+ +
+ {selectedFile ? ( +
파일을 삭제하시려면 클릭하세요.
+ ) : ( +
+ 폴더 안에 들어가서 파일만을 선택하여 압축한 zip 파일을 + 업로드해주세요. +
+ )} + {errorMessage && ( +
{errorMessage}
+ )} + {fileCount === totalReward && !errorMessage && ( + <> +
업로드 가능합니다.
+ + )} + + +
+ {openModal && ( + setOpenModal(false)} + onClickYes={() => handleSubmit()} + /> + )} +
+ ); +} + +export default UploadReward; From 3520e4ca568dfcf47460d2d50c3ef379f8e9b172 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Thu, 15 Aug 2024 23:39:57 +0900 Subject: [PATCH 09/10] done UploadReward --- admin/src/pages/UploadReward/UploadReward.jsx | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/admin/src/pages/UploadReward/UploadReward.jsx b/admin/src/pages/UploadReward/UploadReward.jsx index 0b3ab77..bb6f4f5 100644 --- a/admin/src/pages/UploadReward/UploadReward.jsx +++ b/admin/src/pages/UploadReward/UploadReward.jsx @@ -16,6 +16,7 @@ function UploadReward() { const introduce = '파일을 여기로 드래그하거나 클릭하여 선택'; const [openModal, setOpenModal] = useState(false); const { dateInfo } = useContext(DateContext); + const [processMessage, setProcessMessage] = useState('업로드 가능합니다.'); useEffect(() => { setTotalReward(5); // 가져오는 api @@ -34,10 +35,10 @@ function UploadReward() { setIsLoading(true); const response = await postQuizReward(selectedFile, dateInfo); setOpenModal(false); - console.log(response); //확인 필요 - if (response.ok); - else { + if (response.message === 'success') { + setProcessMessage('파일 업로드를 완료했습니다.'); + } else { setErrorMessage('파일 업로드에 실패했습니다.'); } } catch (error) { @@ -81,17 +82,34 @@ function UploadReward() { const zipContent = await zip.loadAsync(file); const fileKeys = Object.keys(zipContent.files); - // 디렉토리 파일이 포함되어 있는지 체크 - const containsDirectory = fileKeys.some(key => zipContent.files[key].dir); - // 이후 이미지 형식의 파일만 있는지 확인하는 과정도 있으면 좋을 거 같음 + // 허용된 확장자 목록 + const allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg']; + + // 디렉토리 파일이 포함되어 있거나, 허용되지 않은 확장자가 포함된 경우 체크 + const hasInvalidFiles = fileKeys.some(key => { + if (zipContent.files[key].dir) { + setErrorMessage( + '디렉토리(폴더)가 포함된 ZIP 파일은 업로드할 수 없습니다. 파일만을 선택하여 압축해주세요!', + ); + return true; // 디렉토리 포함 + } - if (containsDirectory) { - setErrorMessage( - '디렉토리(폴더)가 포함된 ZIP 파일은 업로드할 수 없습니다. 파일만을 선택하여 압축해주세요!', - ); + const fileExt = key.split('.').pop().toLowerCase(); + if (!allowedExtensions.includes(fileExt)) { + setErrorMessage( + `허용되지 않은 파일 형식이 포함되어 있습니다: ${fileExt}`, + ); + return true; // 허용되지 않은 확장자 포함 + } + + return false; + }); + + if (hasInvalidFiles) { setIsLoading(false); return; } + const validFileCount = fileKeys.reduce((count, key) => { if (!key.startsWith('__MACOSX/')) { return count + 1; @@ -148,7 +166,7 @@ function UploadReward() { )} {fileCount === totalReward && !errorMessage && ( <> -
업로드 가능합니다.
+
{processMessage}
)}