From 1bc3a1b3a61a07238cb00eb432e22b9b36a2ca61 Mon Sep 17 00:00:00 2001 From: egovframesupport Date: Mon, 18 May 2026 16:18:42 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B5=AD=EC=A0=95=EC=9B=90=20=EB=B3=B4?= =?UTF-8?q?=EC=95=88=EC=A0=90=EA=B2=80=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 1 - .eslintrc.cjs | 2 +- .gitignore | 5 +- README.md | 25 +++--- src/App.jsx | 5 +- src/api/egovFetch.js | 39 ++------ src/api/egovFetch.jsx | 82 ----------------- src/components/EgovAttachFile.jsx | 20 ++--- src/components/EgovHeader.jsx | 32 +++---- src/components/EgovImageGallery.jsx | 3 - src/components/EgovPaging.jsx | 5 -- src/components/EgovSelect.jsx | 2 - src/components/EgovViewTemplate.jsx | 6 -- src/components/leftmenu/EgovLeftNavInform.jsx | 4 - src/components/sns/SnsKakaoBt.jsx | 15 +++- src/components/sns/SnsKakaoCallback.jsx | 45 +++++++--- src/components/sns/SnsNaverBt.jsx | 16 ++-- src/components/sns/SnsNaverCallback.jsx | 45 +++++++--- src/config/index.jsx | 4 - src/constants/code.jsx | 22 ----- src/constants/url.jsx | 89 ------------------ src/contexts/AuthContext.jsx | 58 ++++++++++++ src/main.jsx | 2 +- src/pages/about/EgovAboutLocation.jsx | 2 +- src/pages/admin/board/EgovAdminBoardEdit.jsx | 8 -- src/pages/admin/board/EgovAdminBoardList.jsx | 9 -- .../admin/gallery/EgovAdminGalleryDetail.jsx | 6 -- .../admin/gallery/EgovAdminGalleryEdit.jsx | 11 --- .../admin/gallery/EgovAdminGalleryList.jsx | 8 -- .../admin/manager/EgovAdminPasswordUpdate.jsx | 6 -- .../admin/members/EgovAdminMemberEdit.jsx | 10 --- .../admin/members/EgovAdminMemberList.jsx | 12 --- .../admin/notice/EgovAdminNoticeDetail.jsx | 6 -- .../admin/notice/EgovAdminNoticeEdit.jsx | 11 --- .../admin/notice/EgovAdminNoticeList.jsx | 8 -- .../schedule/EgovAdminScheduleDetail.jsx | 7 -- .../admin/schedule/EgovAdminScheduleEdit.jsx | 11 --- .../admin/schedule/EgovAdminScheduleList.jsx | 36 -------- src/pages/admin/usage/EgovAdminUsageEdit.jsx | 9 -- src/pages/admin/usage/EgovAdminUsageList.jsx | 9 -- src/pages/inform/daily/EgovDailyDetail.jsx | 6 -- src/pages/inform/daily/EgovDailyList.jsx | 9 -- .../inform/gallery/EgovGalleryDetail.jsx | 6 -- src/pages/inform/gallery/EgovGalleryEdit.jsx | 14 +-- src/pages/inform/gallery/EgovGalleryList.jsx | 8 -- src/pages/inform/notice/EgovNoticeDetail.jsx | 16 ++-- src/pages/inform/notice/EgovNoticeEdit.jsx | 21 ++--- src/pages/inform/notice/EgovNoticeList.jsx | 18 ++-- src/pages/inform/weekly/EgovWeeklyList.jsx | 20 ----- src/pages/login/EgovLogin.jsx | 7 -- src/pages/login/EgovLoginContent.jsx | 53 ++++++----- src/pages/main/EgovMain.jsx | 12 +-- src/pages/mypage/EgovMypageEdit.jsx | 22 +---- src/routes/index.jsx | 90 ++++--------------- src/utils/bbsFormVaildator.jsx | 13 --- src/utils/calc.jsx | 1 - src/utils/logger.js | 44 +++++++++ src/utils/oauthState.js | 15 ++++ src/utils/passwordHash.js | 17 ++++ vite.config.js | 3 + 60 files changed, 340 insertions(+), 751 deletions(-) delete mode 100644 src/api/egovFetch.jsx delete mode 100644 src/config/index.jsx delete mode 100644 src/constants/code.jsx delete mode 100644 src/constants/url.jsx create mode 100644 src/contexts/AuthContext.jsx delete mode 100644 src/utils/bbsFormVaildator.jsx delete mode 100644 src/utils/calc.jsx create mode 100644 src/utils/logger.js create mode 100644 src/utils/oauthState.js create mode 100644 src/utils/passwordHash.js diff --git a/.env.development b/.env.development index a1c6550..e625fab 100644 --- a/.env.development +++ b/.env.development @@ -9,6 +9,5 @@ VITE_APP_EGOV_CONTEXT_URL=localhost:8080 #SNS 간편로그인 변수 VITE_APP_NAVER_CLIENTID=YOUR_CLIENT_ID VITE_APP_NAVER_CALLBACKURL=http://localhost:3000/login/naver/callback -VITE_APP_STATE=egovframe VITE_APP_KAKAO_CLIENTID=YOUR_CLIENT_ID VITE_APP_KAKAO_CALLBACKURL=http://localhost:3000/login/kakao/callback \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e4fcfb5..1f2e152 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -12,7 +12,7 @@ module.exports = { settings: { react: { version: "18.2" } }, plugins: ["react-refresh"], rules: { - "react/jsx-no-target-blank": "off", + "react/jsx-no-target-blank": "error", "react/prop-types": ["off"], "react-refresh/only-export-components": [ "warn", diff --git a/.gitignore b/.gitignore index 717edd6..6a545a4 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ yarn-debug.log* yarn-error.log* # code -.history \ No newline at end of file +.history + +# Secrets +secrets/ \ No newline at end of file diff --git a/README.md b/README.md index 2ff8218..b254a34 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ![sh1](https://github.com/user-attachments/assets/41757fa0-b976-435a-81ac-e163e2846998) -1. 최초 관리자 계정 설정은 **[ 로그인계정 : admin , 로그인암호 : 1 ]** 로 설정되어 있다. +1. 최초 관리자 계정 설정은 **[ 로그인계정 : admin , 로그인암호 : Admin@1234 ]** 로 설정되어 있다. 2. 메인 화면 우측 상단의 **회원가입** 버튼을 통해 사용자 계정을 생성 가능하다. 3. 기본 기능이나 예시 메뉴를 실무적으로 추가 커스터마이징하여 활용할 수 있다. @@ -37,11 +37,14 @@ 1. 최초 관리자 계정을 통한 로그인이 가능하다. 2. **회원가입** 버튼을 통해 생성한 사용자 계정을 통한 로그인이 가능하다. (사용자 계정은 일부 메뉴 접근이 제한된다) 3. 로그인 창 하단의 소셜 로그인 버튼으로 네이버 및 카카오 계정으로 로그인이 가능하다. 이 경우의 권한은 사용자 계정과 동일하다. -4. 소셜 로그인 기능의 사용을 위해서는 사전에 **[네이버 개발자 센터](https://developers.naver.com/main/)** 및 **[카카오 개발자 센터](https://developers.kakao.com/)** 에서 Client ID와 Client Secret을 발급 받은 후 Callback URL을 프론트엔드와 백엔드 환경 설정 파일에 등록해야 한다. -5. 프론트엔드 환경 설정 파일은 본 애플리케이션의 `.env.development` (개발 환경) 및 `.env.production` (빌드 시 사용) 을 참고한다. -6. 백엔드 환경 설정 파일은 [심플 홈페이지 Backend](https://github.com/eGovFramework/egovframe-template-simple-backend.git) 의 `application.properties` 를 참고한다. -7. 네이버 소셜 로그인에 대한 상세 사항은 **[네이버 로그인 API 문서](https://developers.naver.com/docs/login/api/api.md)** 를 참조 가능하다. -8. 카카오 소셜 로그인에 대한 상세 사항은 **[카카오 로그인 API 문서](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaologin)** 를 참조 가능하다. +4. 소셜 로그인 기능의 사용을 위해서는 사전에 **[네이버 개발자 센터](https://developers.naver.com/main/)** 및 **[카카오 개발자 센터](https://developers.kakao.com/)** 에서 Client ID·Client Secret 발급 및 Callback URL 등록을 진행한 뒤, 받은 값을 아래와 같이 입력한다. + - **네이버** — 백엔드 `application.properties` 의 `Sns.naver.clientId`, `Sns.naver.clientSecret`, `Sns.naver.callbackUrl` / 프론트엔드 `.env.development` 의 `VITE_APP_NAVER_CLIENTID`, `VITE_APP_NAVER_CALLBACKURL` + - **카카오** — 백엔드 `application.properties` 의 `Sns.kakao.clientId`, `Sns.kakao.callbackUrl` / 프론트엔드 `.env.development` 의 `VITE_APP_KAKAO_CLIENTID`, `VITE_APP_KAKAO_CALLBACKURL` + - ※ Client Secret 은 백엔드에만 등록 (프론트엔드 번들에 포함되면 안 됨). 카카오는 본 프로젝트에서 Secret 미사용. + - ※ OAuth CSRF 방어용 `state` 값은 로그인 시도 시 자동으로 무작위 생성되므로 별도 환경변수 설정 불필요. +5. 프론트엔드 환경 설정 파일은 개발 환경은 `.env.development`, 빌드 산출물 환경은 `.env.production` 을 사용한다. 백엔드 환경 설정 파일은 [심플 홈페이지 Backend](https://github.com/eGovFramework/egovframe-template-simple-backend.git) 의 `application.properties` 를 참고한다. +6. 네이버 소셜 로그인에 대한 상세 사항은 **[네이버 로그인 API 문서](https://developers.naver.com/docs/login/api/api.md)** 를 참조 가능하다. +7. 카카오 소셜 로그인에 대한 상세 사항은 **[카카오 로그인 API 문서](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaologin)** 를 참조 가능하다. ### 사이트 소개 화면 @@ -91,12 +94,12 @@ ## 환경 설정 -프로젝트에서 사용된 환경 프로그램 정보는 다음과 같다. +프로젝트 구동에 필요한 환경 프로그램 정보는 다음과 같다. -| 프로그램 명 | 버전 명 | +| 프로그램 명 | 버전 명 | | :---------- | :------- | -| Node.js | v18.12.0 | -| NPM | v8.19.2 | +| Node.js | v20.19 ~ v22 LTS | +| NPM | v10 이상 | ## BackEnd 구동 @@ -153,7 +156,7 @@ npm run preview ``` ```bash -# 테스트 대상 파일 경로는 vite.config.js에 명시되어 있으며 디폴트로 EgovMain.jsx의 테스트를 실행한다. +# 테스트 대상은 vite.config.js 의 test.include 패턴에 따라 수집된다. # watch 모드로 테스트를 실행할 경우에는 아래 명령어를 사용한다. npm run test diff --git a/src/App.jsx b/src/App.jsx index 475939e..70b20a7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ import RootRoutes from "@/routes"; import { BrowserRouter as Router } from "react-router-dom"; +import { AuthProvider } from "@/contexts/AuthContext"; import "@/css/base.css"; import "@/css/layout.css"; @@ -11,7 +12,9 @@ function App() { return (
- + + +
); diff --git a/src/api/egovFetch.js b/src/api/egovFetch.js index 73c2c7f..f5b4a44 100644 --- a/src/api/egovFetch.js +++ b/src/api/egovFetch.js @@ -3,6 +3,7 @@ import { SERVER_URL } from "../config"; import URL from "@/constants/url"; import CODE from "@/constants/code"; import { getSessionItem, setSessionItem } from "@/utils/storage"; +import { logger } from "@/utils/logger"; export function getQueryString(params) { return `?${Object.entries(params) @@ -11,40 +12,18 @@ export function getQueryString(params) { } export function requestFetch(url, requestOptions, handler, errorHandler) { - console.groupCollapsed("requestFetch"); - console.log("requestFetch [URL] : ", SERVER_URL + url); - console.log("requestFetch [requestOption] : ", requestOptions); - - // Login 했을경우 JWT 설정 - const sessionUser = getSessionItem("loginUser"); - const sessionUserId = sessionUser?.id || null; - const jToken = getSessionItem("jToken"); - if (sessionUserId != null && sessionUserId !== undefined) { - if (!requestOptions["headers"]) requestOptions["headers"] = {}; - if (!requestOptions["headers"]["Authorization"]) - requestOptions["headers"]["Authorization"] = null; - requestOptions["headers"]["Authorization"] = jToken; - } - - //CORS ISSUE 로 인한 조치 - origin 및 credentials 추가 - // origin 추가 - if (!requestOptions["origin"]) { - requestOptions = { ...requestOptions, origin: SERVER_URL }; - } - // credentials 추가 + // JWT는 httpOnly 쿠키로 전달 — credentials: "include" 로 브라우저가 자동 첨부 if (!requestOptions["credentials"]) { requestOptions = { ...requestOptions, credentials: "include" }; } fetch(SERVER_URL + url, requestOptions) .then((response) => { - // response Stream. Not completion object - //console.log("requestFetch [Response Stream] ", response); return response.json(); }) .then((resp) => { if (Number(resp.resultCode) === Number(CODE.RCV_ERROR_AUTH)) { - alert("Login Alert"); //index.jsx라우터파일에 jwtAuthentication 함수로 공통 인증을 사용하는 코드 추가로 alert 원상복구 + alert("Login Alert"); setSessionItem("loginUser", { id: "" }); window.location.href = URL.LOGIN; return false; @@ -53,17 +32,14 @@ export function requestFetch(url, requestOptions, handler, errorHandler) { } }) .then((resp) => { - console.groupCollapsed("requestFetch.then()"); - console.log("requestFetch [response] ", resp); if (typeof handler === "function") { handler(resp); } else { - console.log("egov fetch handler not assigned!"); + logger.warn("egov fetch handler not assigned"); } - console.groupEnd("requestFetch.then()"); }) .catch((error) => { - console.error("There was an error!"); // 26.03.04 KISA 보안취약점 조치 : error 객체 제거 + logger.error("requestFetch error"); // 26.03.04 KISA 보안취약점 조치 : error 객체 미노출 if (error === "TypeError: Failed to fetch") { alert("서버와의 연결이 원활하지 않습니다. 서버를 확인하세요."); } @@ -71,12 +47,7 @@ export function requestFetch(url, requestOptions, handler, errorHandler) { if (typeof errorHandler === "function") { errorHandler(error); } else { - console.error("egov error handler not assigned!"); alert("요청 처리 중 오류가 발생했습니다."); // 26.03.04 KISA 보안취약점 조치 : 메시지 대체 } - }) - .finally(() => { - console.log("requestFetch finally end"); - console.groupEnd("requestFetch"); }); } diff --git a/src/api/egovFetch.jsx b/src/api/egovFetch.jsx deleted file mode 100644 index 73c2c7f..0000000 --- a/src/api/egovFetch.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import { SERVER_URL } from "../config"; - -import URL from "@/constants/url"; -import CODE from "@/constants/code"; -import { getSessionItem, setSessionItem } from "@/utils/storage"; - -export function getQueryString(params) { - return `?${Object.entries(params) - .map((e) => e.join("=")) - .join("&")}`; -} - -export function requestFetch(url, requestOptions, handler, errorHandler) { - console.groupCollapsed("requestFetch"); - console.log("requestFetch [URL] : ", SERVER_URL + url); - console.log("requestFetch [requestOption] : ", requestOptions); - - // Login 했을경우 JWT 설정 - const sessionUser = getSessionItem("loginUser"); - const sessionUserId = sessionUser?.id || null; - const jToken = getSessionItem("jToken"); - if (sessionUserId != null && sessionUserId !== undefined) { - if (!requestOptions["headers"]) requestOptions["headers"] = {}; - if (!requestOptions["headers"]["Authorization"]) - requestOptions["headers"]["Authorization"] = null; - requestOptions["headers"]["Authorization"] = jToken; - } - - //CORS ISSUE 로 인한 조치 - origin 및 credentials 추가 - // origin 추가 - if (!requestOptions["origin"]) { - requestOptions = { ...requestOptions, origin: SERVER_URL }; - } - // credentials 추가 - if (!requestOptions["credentials"]) { - requestOptions = { ...requestOptions, credentials: "include" }; - } - - fetch(SERVER_URL + url, requestOptions) - .then((response) => { - // response Stream. Not completion object - //console.log("requestFetch [Response Stream] ", response); - return response.json(); - }) - .then((resp) => { - if (Number(resp.resultCode) === Number(CODE.RCV_ERROR_AUTH)) { - alert("Login Alert"); //index.jsx라우터파일에 jwtAuthentication 함수로 공통 인증을 사용하는 코드 추가로 alert 원상복구 - setSessionItem("loginUser", { id: "" }); - window.location.href = URL.LOGIN; - return false; - } else { - return resp; - } - }) - .then((resp) => { - console.groupCollapsed("requestFetch.then()"); - console.log("requestFetch [response] ", resp); - if (typeof handler === "function") { - handler(resp); - } else { - console.log("egov fetch handler not assigned!"); - } - console.groupEnd("requestFetch.then()"); - }) - .catch((error) => { - console.error("There was an error!"); // 26.03.04 KISA 보안취약점 조치 : error 객체 제거 - if (error === "TypeError: Failed to fetch") { - alert("서버와의 연결이 원활하지 않습니다. 서버를 확인하세요."); - } - - if (typeof errorHandler === "function") { - errorHandler(error); - } else { - console.error("egov error handler not assigned!"); - alert("요청 처리 중 오류가 발생했습니다."); // 26.03.04 KISA 보안취약점 조치 : 메시지 대체 - } - }) - .finally(() => { - console.log("requestFetch finally end"); - console.groupEnd("requestFetch"); - }); -} diff --git a/src/components/EgovAttachFile.jsx b/src/components/EgovAttachFile.jsx index d68e7d6..4749e57 100644 --- a/src/components/EgovAttachFile.jsx +++ b/src/components/EgovAttachFile.jsx @@ -13,7 +13,6 @@ function EgovAttachFile({ fnDeleteFile, posblAtchFileNumber, }) { - console.groupCollapsed("EgovAttachFile"); // posblAtchFileNumber는 수정일 경우에만 값이 넘어오므로 방어 로직 // 해당 컴포넌트는 스케줄 화면과 공유하며, 스케줄에서는 첨부파일을 1개 넣을 수 있으므로 디폴트 값을 1로 설정 @@ -27,14 +26,16 @@ function EgovAttachFile({ const navigate = useNavigate(); function onClickDownFile(atchFileId, fileSn) { - window.open( - SERVER_URL + "/file?atchFileId=" + atchFileId + "&fileSn=" + fileSn + "" - ); + // 화이트리스트 검증 — atchFileId 는 Base64+URL-safe 문자만 허용, fileSn 은 숫자만 + if (!/^[A-Za-z0-9+/=_-]+$/.test(String(atchFileId))) return; + if (!/^\d+$/.test(String(fileSn))) return; + const url = + `${SERVER_URL}/file?atchFileId=${encodeURIComponent(atchFileId)}` + + `&fileSn=${encodeURIComponent(fileSn)}`; + window.open(url, "_blank", "noopener,noreferrer"); } function onClickDeleteFile(atchFileId, fileSn, fileIndex) { - console.log("onClickDeleteFile Params : ", atchFileId, fileSn, fileIndex); - const requestOptions = { method: "POST", headers: { @@ -46,10 +47,7 @@ function EgovAttachFile({ }), }; EgovNet.requestFetch(`/file`, requestOptions, function (resp) { - console.log("===>>> board file delete= ", resp); if (Number(resp.resultCode) === Number(CODE.RCV_SUCCESS)) { - // 성공 - console.log("Deleted fileIndex = ", fileIndex); // eslint-disable-next-line no-unused-vars const _deleteFile = boardFiles.splice(fileIndex, 1); const _boardFiles = Object.assign([], boardFiles); @@ -66,7 +64,6 @@ function EgovAttachFile({ } function onChangeFileInput(e) { - console.log("===>>> e = " + e.target.files[0]); if ( e.target.files.length + (boardFiles?.length || 0) > posblAtchFileNumber @@ -116,9 +113,6 @@ function EgovAttachFile({ filesTag.push(
); }); } - console.log("filesTag : ", filesTag); - console.groupEnd("EgovAttachFile"); - return (
첨부파일
diff --git a/src/components/EgovHeader.jsx b/src/components/EgovHeader.jsx index 2fa0d18..2a32aee 100644 --- a/src/components/EgovHeader.jsx +++ b/src/components/EgovHeader.jsx @@ -7,16 +7,16 @@ import CODE from "@/constants/code"; import logoImg from "/assets/images/logo_w.png"; import logoImgMobile from "/assets/images/logo_m.png"; -import { getSessionItem, setSessionItem } from "@/utils/storage"; +import { setSessionItem } from "@/utils/storage"; +import { useAuth } from "@/contexts/AuthContext"; function EgovHeader() { - console.group("EgovHeader"); - console.log("[Start] EgovHeader ------------------------------"); - - const sessionUser = getSessionItem("loginUser"); - const sessionUserId = sessionUser?.id; - const sessionUserName = sessionUser?.name; - const sessionUserSe = sessionUser?.userSe; + // 메뉴·로그아웃 표시는 백엔드 /auth/me 결과(AuthContext)를 진실 소스로 사용 + const { user, roles, clear } = useAuth(); + const sessionUserId = user?.id; + const sessionUserName = user?.name; + const sessionUserSe = user?.userSe; + const isAdmin = roles.includes("ROLE_ADMIN"); const navigate = useNavigate(); @@ -39,11 +39,10 @@ function EgovHeader() { credentials: "include", }; EgovNet.requestFetch(logOutUrl, requestOptions, function (resp) { - console.log("===>>> logout resp= ", resp); if (parseInt(resp.resultCode) === parseInt(CODE.RCV_SUCCESS)) { - //onChangeLogin({ loginVO: {} }); setSessionItem("loginUser", { id: "" }); - setSessionItem("jToken", null); + clear(); // AuthContext 인증 상태 초기화 + // ACCESS_TOKEN 쿠키는 백엔드 /auth/logout 에서 삭제됨 window.alert("로그아웃되었습니다!"); navigate(URL.MAIN); // PC와 Mobile 열린메뉴 닫기 @@ -55,14 +54,11 @@ function EgovHeader() { }); }; - console.log("------------------------------EgovHeader [End]"); - console.groupEnd("EgovHeader"); - return ( //
- + 홈페이지 템플릿 소개 페이지로 이동 @@ -116,7 +112,7 @@ function EgovHeader() { 알림마당 - {sessionUserSe === "ADM" && ( + {isAdmin && (
  • - {sessionUserSe === "ADM" && ( + {isAdmin && (

    사이트관리

      @@ -539,7 +535,7 @@ function EgovHeader() {
    - {sessionUserSe === "ADM" && ( + {isAdmin && ( <>

    사이트관리 diff --git a/src/components/EgovImageGallery.jsx b/src/components/EgovImageGallery.jsx index e54465c..dd118be 100644 --- a/src/components/EgovImageGallery.jsx +++ b/src/components/EgovImageGallery.jsx @@ -17,9 +17,6 @@ function EgovImageGallery({ boardFiles }) { ); }); } - console.log("filesTag : ", filesTag); - console.groupEnd("EgovAttachFile"); - return
    {filesTag}
    ; } diff --git a/src/components/EgovPaging.jsx b/src/components/EgovPaging.jsx index e957b7a..c1b6b02 100644 --- a/src/components/EgovPaging.jsx +++ b/src/components/EgovPaging.jsx @@ -1,6 +1,4 @@ function EgovPaging(props) { - console.groupCollapsed("EgovPaging"); - console.log("EgovPaging [props] : ", props); let paginationTag = []; @@ -111,9 +109,6 @@ function EgovPaging(props) { paginationTag.push(lastPageTag); } } - console.log("paginationTag", paginationTag); - console.groupEnd("EgovPaging"); - return (
      {paginationTag}
    diff --git a/src/components/EgovSelect.jsx b/src/components/EgovSelect.jsx index 5a7d812..1d69562 100644 --- a/src/components/EgovSelect.jsx +++ b/src/components/EgovSelect.jsx @@ -1,5 +1,4 @@ function EgovSelect({ id, name, title, options, setValue, setter }) { - console.log("egovSelect", id, name, title, options, setValue, setter); return (