api use Query + axios 형태로 변경 예시 use BoardListQuery 추가
@cc5447c24586787063638cef240c8358a1d1d4b4
--- package-lock.json
+++ package-lock.json
... | ... | @@ -1,14 +1,15 @@ |
| 1 | 1 |
{
|
| 2 |
- "name": "react-app", |
|
| 2 |
+ "name": "base-react", |
|
| 3 | 3 |
"version": "0.0.0", |
| 4 | 4 |
"lockfileVersion": 3, |
| 5 | 5 |
"requires": true, |
| 6 | 6 |
"packages": {
|
| 7 | 7 |
"": {
|
| 8 |
- "name": "react-app", |
|
| 8 |
+ "name": "base-react", |
|
| 9 | 9 |
"version": "0.0.0", |
| 10 | 10 |
"dependencies": {
|
| 11 | 11 |
"@tanstack/react-query": "^5.99.0", |
| 12 |
+ "axios": "^1.16.0", |
|
| 12 | 13 |
"react": "^19.2.4", |
| 13 | 14 |
"react-dom": "^19.2.4", |
| 14 | 15 |
"react-router-dom": "^7.14.1", |
... | ... | @@ -1338,6 +1339,23 @@ |
| 1338 | 1339 |
"dev": true, |
| 1339 | 1340 |
"license": "Python-2.0" |
| 1340 | 1341 |
}, |
| 1342 |
+ "node_modules/asynckit": {
|
|
| 1343 |
+ "version": "0.4.0", |
|
| 1344 |
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", |
|
| 1345 |
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", |
|
| 1346 |
+ "license": "MIT" |
|
| 1347 |
+ }, |
|
| 1348 |
+ "node_modules/axios": {
|
|
| 1349 |
+ "version": "1.16.0", |
|
| 1350 |
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", |
|
| 1351 |
+ "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", |
|
| 1352 |
+ "license": "MIT", |
|
| 1353 |
+ "dependencies": {
|
|
| 1354 |
+ "follow-redirects": "^1.16.0", |
|
| 1355 |
+ "form-data": "^4.0.5", |
|
| 1356 |
+ "proxy-from-env": "^2.1.0" |
|
| 1357 |
+ } |
|
| 1358 |
+ }, |
|
| 1341 | 1359 |
"node_modules/balanced-match": {
|
| 1342 | 1360 |
"version": "1.0.2", |
| 1343 | 1361 |
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", |
... | ... | @@ -1401,6 +1419,19 @@ |
| 1401 | 1419 |
}, |
| 1402 | 1420 |
"engines": {
|
| 1403 | 1421 |
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" |
| 1422 |
+ } |
|
| 1423 |
+ }, |
|
| 1424 |
+ "node_modules/call-bind-apply-helpers": {
|
|
| 1425 |
+ "version": "1.0.2", |
|
| 1426 |
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", |
|
| 1427 |
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", |
|
| 1428 |
+ "license": "MIT", |
|
| 1429 |
+ "dependencies": {
|
|
| 1430 |
+ "es-errors": "^1.3.0", |
|
| 1431 |
+ "function-bind": "^1.1.2" |
|
| 1432 |
+ }, |
|
| 1433 |
+ "engines": {
|
|
| 1434 |
+ "node": ">= 0.4" |
|
| 1404 | 1435 |
} |
| 1405 | 1436 |
}, |
| 1406 | 1437 |
"node_modules/callsites": {
|
... | ... | @@ -1480,6 +1511,18 @@ |
| 1480 | 1511 |
"dev": true, |
| 1481 | 1512 |
"license": "MIT" |
| 1482 | 1513 |
}, |
| 1514 |
+ "node_modules/combined-stream": {
|
|
| 1515 |
+ "version": "1.0.8", |
|
| 1516 |
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", |
|
| 1517 |
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", |
|
| 1518 |
+ "license": "MIT", |
|
| 1519 |
+ "dependencies": {
|
|
| 1520 |
+ "delayed-stream": "~1.0.0" |
|
| 1521 |
+ }, |
|
| 1522 |
+ "engines": {
|
|
| 1523 |
+ "node": ">= 0.8" |
|
| 1524 |
+ } |
|
| 1525 |
+ }, |
|
| 1483 | 1526 |
"node_modules/concat-map": {
|
| 1484 | 1527 |
"version": "0.0.1", |
| 1485 | 1528 |
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |
... | ... | @@ -1554,6 +1597,15 @@ |
| 1554 | 1597 |
"dev": true, |
| 1555 | 1598 |
"license": "MIT" |
| 1556 | 1599 |
}, |
| 1600 |
+ "node_modules/delayed-stream": {
|
|
| 1601 |
+ "version": "1.0.0", |
|
| 1602 |
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", |
|
| 1603 |
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", |
|
| 1604 |
+ "license": "MIT", |
|
| 1605 |
+ "engines": {
|
|
| 1606 |
+ "node": ">=0.4.0" |
|
| 1607 |
+ } |
|
| 1608 |
+ }, |
|
| 1557 | 1609 |
"node_modules/detect-libc": {
|
| 1558 | 1610 |
"version": "2.1.2", |
| 1559 | 1611 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", |
... | ... | @@ -1564,12 +1616,71 @@ |
| 1564 | 1616 |
"node": ">=8" |
| 1565 | 1617 |
} |
| 1566 | 1618 |
}, |
| 1619 |
+ "node_modules/dunder-proto": {
|
|
| 1620 |
+ "version": "1.0.1", |
|
| 1621 |
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", |
|
| 1622 |
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", |
|
| 1623 |
+ "license": "MIT", |
|
| 1624 |
+ "dependencies": {
|
|
| 1625 |
+ "call-bind-apply-helpers": "^1.0.1", |
|
| 1626 |
+ "es-errors": "^1.3.0", |
|
| 1627 |
+ "gopd": "^1.2.0" |
|
| 1628 |
+ }, |
|
| 1629 |
+ "engines": {
|
|
| 1630 |
+ "node": ">= 0.4" |
|
| 1631 |
+ } |
|
| 1632 |
+ }, |
|
| 1567 | 1633 |
"node_modules/electron-to-chromium": {
|
| 1568 | 1634 |
"version": "1.5.338", |
| 1569 | 1635 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.338.tgz", |
| 1570 | 1636 |
"integrity": "sha512-KVQQ3xko9/coDX3qXLUEEbqkKT8L+1DyAovrtu0Khtrt9wjSZ+7CZV4GVzxFy9Oe1NbrIU1oVXCwHJruIA1PNg==", |
| 1571 | 1637 |
"dev": true, |
| 1572 | 1638 |
"license": "ISC" |
| 1639 |
+ }, |
|
| 1640 |
+ "node_modules/es-define-property": {
|
|
| 1641 |
+ "version": "1.0.1", |
|
| 1642 |
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", |
|
| 1643 |
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", |
|
| 1644 |
+ "license": "MIT", |
|
| 1645 |
+ "engines": {
|
|
| 1646 |
+ "node": ">= 0.4" |
|
| 1647 |
+ } |
|
| 1648 |
+ }, |
|
| 1649 |
+ "node_modules/es-errors": {
|
|
| 1650 |
+ "version": "1.3.0", |
|
| 1651 |
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", |
|
| 1652 |
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", |
|
| 1653 |
+ "license": "MIT", |
|
| 1654 |
+ "engines": {
|
|
| 1655 |
+ "node": ">= 0.4" |
|
| 1656 |
+ } |
|
| 1657 |
+ }, |
|
| 1658 |
+ "node_modules/es-object-atoms": {
|
|
| 1659 |
+ "version": "1.1.1", |
|
| 1660 |
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", |
|
| 1661 |
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", |
|
| 1662 |
+ "license": "MIT", |
|
| 1663 |
+ "dependencies": {
|
|
| 1664 |
+ "es-errors": "^1.3.0" |
|
| 1665 |
+ }, |
|
| 1666 |
+ "engines": {
|
|
| 1667 |
+ "node": ">= 0.4" |
|
| 1668 |
+ } |
|
| 1669 |
+ }, |
|
| 1670 |
+ "node_modules/es-set-tostringtag": {
|
|
| 1671 |
+ "version": "2.1.0", |
|
| 1672 |
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", |
|
| 1673 |
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", |
|
| 1674 |
+ "license": "MIT", |
|
| 1675 |
+ "dependencies": {
|
|
| 1676 |
+ "es-errors": "^1.3.0", |
|
| 1677 |
+ "get-intrinsic": "^1.2.6", |
|
| 1678 |
+ "has-tostringtag": "^1.0.2", |
|
| 1679 |
+ "hasown": "^2.0.2" |
|
| 1680 |
+ }, |
|
| 1681 |
+ "engines": {
|
|
| 1682 |
+ "node": ">= 0.4" |
|
| 1683 |
+ } |
|
| 1573 | 1684 |
}, |
| 1574 | 1685 |
"node_modules/escalade": {
|
| 1575 | 1686 |
"version": "3.2.0", |
... | ... | @@ -1868,6 +1979,42 @@ |
| 1868 | 1979 |
"dev": true, |
| 1869 | 1980 |
"license": "ISC" |
| 1870 | 1981 |
}, |
| 1982 |
+ "node_modules/follow-redirects": {
|
|
| 1983 |
+ "version": "1.16.0", |
|
| 1984 |
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", |
|
| 1985 |
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", |
|
| 1986 |
+ "funding": [ |
|
| 1987 |
+ {
|
|
| 1988 |
+ "type": "individual", |
|
| 1989 |
+ "url": "https://github.com/sponsors/RubenVerborgh" |
|
| 1990 |
+ } |
|
| 1991 |
+ ], |
|
| 1992 |
+ "license": "MIT", |
|
| 1993 |
+ "engines": {
|
|
| 1994 |
+ "node": ">=4.0" |
|
| 1995 |
+ }, |
|
| 1996 |
+ "peerDependenciesMeta": {
|
|
| 1997 |
+ "debug": {
|
|
| 1998 |
+ "optional": true |
|
| 1999 |
+ } |
|
| 2000 |
+ } |
|
| 2001 |
+ }, |
|
| 2002 |
+ "node_modules/form-data": {
|
|
| 2003 |
+ "version": "4.0.5", |
|
| 2004 |
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", |
|
| 2005 |
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", |
|
| 2006 |
+ "license": "MIT", |
|
| 2007 |
+ "dependencies": {
|
|
| 2008 |
+ "asynckit": "^0.4.0", |
|
| 2009 |
+ "combined-stream": "^1.0.8", |
|
| 2010 |
+ "es-set-tostringtag": "^2.1.0", |
|
| 2011 |
+ "hasown": "^2.0.2", |
|
| 2012 |
+ "mime-types": "^2.1.12" |
|
| 2013 |
+ }, |
|
| 2014 |
+ "engines": {
|
|
| 2015 |
+ "node": ">= 6" |
|
| 2016 |
+ } |
|
| 2017 |
+ }, |
|
| 1871 | 2018 |
"node_modules/fsevents": {
|
| 1872 | 2019 |
"version": "2.3.3", |
| 1873 | 2020 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", |
... | ... | @@ -1883,6 +2030,15 @@ |
| 1883 | 2030 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
| 1884 | 2031 |
} |
| 1885 | 2032 |
}, |
| 2033 |
+ "node_modules/function-bind": {
|
|
| 2034 |
+ "version": "1.1.2", |
|
| 2035 |
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", |
|
| 2036 |
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", |
|
| 2037 |
+ "license": "MIT", |
|
| 2038 |
+ "funding": {
|
|
| 2039 |
+ "url": "https://github.com/sponsors/ljharb" |
|
| 2040 |
+ } |
|
| 2041 |
+ }, |
|
| 1886 | 2042 |
"node_modules/gensync": {
|
| 1887 | 2043 |
"version": "1.0.0-beta.2", |
| 1888 | 2044 |
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", |
... | ... | @@ -1891,6 +2047,43 @@ |
| 1891 | 2047 |
"license": "MIT", |
| 1892 | 2048 |
"engines": {
|
| 1893 | 2049 |
"node": ">=6.9.0" |
| 2050 |
+ } |
|
| 2051 |
+ }, |
|
| 2052 |
+ "node_modules/get-intrinsic": {
|
|
| 2053 |
+ "version": "1.3.0", |
|
| 2054 |
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", |
|
| 2055 |
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", |
|
| 2056 |
+ "license": "MIT", |
|
| 2057 |
+ "dependencies": {
|
|
| 2058 |
+ "call-bind-apply-helpers": "^1.0.2", |
|
| 2059 |
+ "es-define-property": "^1.0.1", |
|
| 2060 |
+ "es-errors": "^1.3.0", |
|
| 2061 |
+ "es-object-atoms": "^1.1.1", |
|
| 2062 |
+ "function-bind": "^1.1.2", |
|
| 2063 |
+ "get-proto": "^1.0.1", |
|
| 2064 |
+ "gopd": "^1.2.0", |
|
| 2065 |
+ "has-symbols": "^1.1.0", |
|
| 2066 |
+ "hasown": "^2.0.2", |
|
| 2067 |
+ "math-intrinsics": "^1.1.0" |
|
| 2068 |
+ }, |
|
| 2069 |
+ "engines": {
|
|
| 2070 |
+ "node": ">= 0.4" |
|
| 2071 |
+ }, |
|
| 2072 |
+ "funding": {
|
|
| 2073 |
+ "url": "https://github.com/sponsors/ljharb" |
|
| 2074 |
+ } |
|
| 2075 |
+ }, |
|
| 2076 |
+ "node_modules/get-proto": {
|
|
| 2077 |
+ "version": "1.0.1", |
|
| 2078 |
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", |
|
| 2079 |
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", |
|
| 2080 |
+ "license": "MIT", |
|
| 2081 |
+ "dependencies": {
|
|
| 2082 |
+ "dunder-proto": "^1.0.1", |
|
| 2083 |
+ "es-object-atoms": "^1.0.0" |
|
| 2084 |
+ }, |
|
| 2085 |
+ "engines": {
|
|
| 2086 |
+ "node": ">= 0.4" |
|
| 1894 | 2087 |
} |
| 1895 | 2088 |
}, |
| 1896 | 2089 |
"node_modules/glob-parent": {
|
... | ... | @@ -1919,6 +2112,18 @@ |
| 1919 | 2112 |
"url": "https://github.com/sponsors/sindresorhus" |
| 1920 | 2113 |
} |
| 1921 | 2114 |
}, |
| 2115 |
+ "node_modules/gopd": {
|
|
| 2116 |
+ "version": "1.2.0", |
|
| 2117 |
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", |
|
| 2118 |
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", |
|
| 2119 |
+ "license": "MIT", |
|
| 2120 |
+ "engines": {
|
|
| 2121 |
+ "node": ">= 0.4" |
|
| 2122 |
+ }, |
|
| 2123 |
+ "funding": {
|
|
| 2124 |
+ "url": "https://github.com/sponsors/ljharb" |
|
| 2125 |
+ } |
|
| 2126 |
+ }, |
|
| 1922 | 2127 |
"node_modules/has-flag": {
|
| 1923 | 2128 |
"version": "4.0.0", |
| 1924 | 2129 |
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", |
... | ... | @@ -1927,6 +2132,45 @@ |
| 1927 | 2132 |
"license": "MIT", |
| 1928 | 2133 |
"engines": {
|
| 1929 | 2134 |
"node": ">=8" |
| 2135 |
+ } |
|
| 2136 |
+ }, |
|
| 2137 |
+ "node_modules/has-symbols": {
|
|
| 2138 |
+ "version": "1.1.0", |
|
| 2139 |
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", |
|
| 2140 |
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", |
|
| 2141 |
+ "license": "MIT", |
|
| 2142 |
+ "engines": {
|
|
| 2143 |
+ "node": ">= 0.4" |
|
| 2144 |
+ }, |
|
| 2145 |
+ "funding": {
|
|
| 2146 |
+ "url": "https://github.com/sponsors/ljharb" |
|
| 2147 |
+ } |
|
| 2148 |
+ }, |
|
| 2149 |
+ "node_modules/has-tostringtag": {
|
|
| 2150 |
+ "version": "1.0.2", |
|
| 2151 |
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", |
|
| 2152 |
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", |
|
| 2153 |
+ "license": "MIT", |
|
| 2154 |
+ "dependencies": {
|
|
| 2155 |
+ "has-symbols": "^1.0.3" |
|
| 2156 |
+ }, |
|
| 2157 |
+ "engines": {
|
|
| 2158 |
+ "node": ">= 0.4" |
|
| 2159 |
+ }, |
|
| 2160 |
+ "funding": {
|
|
| 2161 |
+ "url": "https://github.com/sponsors/ljharb" |
|
| 2162 |
+ } |
|
| 2163 |
+ }, |
|
| 2164 |
+ "node_modules/hasown": {
|
|
| 2165 |
+ "version": "2.0.3", |
|
| 2166 |
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", |
|
| 2167 |
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", |
|
| 2168 |
+ "license": "MIT", |
|
| 2169 |
+ "dependencies": {
|
|
| 2170 |
+ "function-bind": "^1.1.2" |
|
| 2171 |
+ }, |
|
| 2172 |
+ "engines": {
|
|
| 2173 |
+ "node": ">= 0.4" |
|
| 1930 | 2174 |
} |
| 1931 | 2175 |
}, |
| 1932 | 2176 |
"node_modules/hermes-estree": {
|
... | ... | @@ -2410,6 +2654,36 @@ |
| 2410 | 2654 |
"yallist": "^3.0.2" |
| 2411 | 2655 |
} |
| 2412 | 2656 |
}, |
| 2657 |
+ "node_modules/math-intrinsics": {
|
|
| 2658 |
+ "version": "1.1.0", |
|
| 2659 |
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
|
| 2660 |
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", |
|
| 2661 |
+ "license": "MIT", |
|
| 2662 |
+ "engines": {
|
|
| 2663 |
+ "node": ">= 0.4" |
|
| 2664 |
+ } |
|
| 2665 |
+ }, |
|
| 2666 |
+ "node_modules/mime-db": {
|
|
| 2667 |
+ "version": "1.52.0", |
|
| 2668 |
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
|
| 2669 |
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", |
|
| 2670 |
+ "license": "MIT", |
|
| 2671 |
+ "engines": {
|
|
| 2672 |
+ "node": ">= 0.6" |
|
| 2673 |
+ } |
|
| 2674 |
+ }, |
|
| 2675 |
+ "node_modules/mime-types": {
|
|
| 2676 |
+ "version": "2.1.35", |
|
| 2677 |
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
|
| 2678 |
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
|
| 2679 |
+ "license": "MIT", |
|
| 2680 |
+ "dependencies": {
|
|
| 2681 |
+ "mime-db": "1.52.0" |
|
| 2682 |
+ }, |
|
| 2683 |
+ "engines": {
|
|
| 2684 |
+ "node": ">= 0.6" |
|
| 2685 |
+ } |
|
| 2686 |
+ }, |
|
| 2413 | 2687 |
"node_modules/minimatch": {
|
| 2414 | 2688 |
"version": "3.1.5", |
| 2415 | 2689 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", |
... | ... | @@ -2605,6 +2879,15 @@ |
| 2605 | 2879 |
"node": ">= 0.8.0" |
| 2606 | 2880 |
} |
| 2607 | 2881 |
}, |
| 2882 |
+ "node_modules/proxy-from-env": {
|
|
| 2883 |
+ "version": "2.1.0", |
|
| 2884 |
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", |
|
| 2885 |
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", |
|
| 2886 |
+ "license": "MIT", |
|
| 2887 |
+ "engines": {
|
|
| 2888 |
+ "node": ">=10" |
|
| 2889 |
+ } |
|
| 2890 |
+ }, |
|
| 2608 | 2891 |
"node_modules/punycode": {
|
| 2609 | 2892 |
"version": "2.3.1", |
| 2610 | 2893 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", |
--- package.json
+++ package.json
... | ... | @@ -12,6 +12,7 @@ |
| 12 | 12 |
}, |
| 13 | 13 |
"dependencies": {
|
| 14 | 14 |
"@tanstack/react-query": "^5.99.0", |
| 15 |
+ "axios": "^1.16.0", |
|
| 15 | 16 |
"react": "^19.2.4", |
| 16 | 17 |
"react-dom": "^19.2.4", |
| 17 | 18 |
"react-router-dom": "^7.14.1", |
--- src/App.tsx
+++ src/App.tsx
... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 |
import { UserLayout } from './user/UserLayout';
|
| 3 | 3 |
import { UserListPage } from './user/UserListPage';
|
| 4 | 4 |
import {AdminLayout} from "./admin/layout/AdminLayout.tsx";
|
| 5 |
+import {AdminRoute} from "./admin/route/AdminRoute.tsx";
|
|
| 5 | 6 |
|
| 6 | 7 |
type Skin = 'admin' | 'user'; |
| 7 | 8 |
|
... | ... | @@ -53,7 +54,8 @@ |
| 53 | 54 |
</div> |
| 54 | 55 |
|
| 55 | 56 |
{skin === 'admin' ? (
|
| 56 |
- <AdminLayout children={undefined}>
|
|
| 57 |
+ <AdminLayout> |
|
| 58 |
+ <AdminRoute /> |
|
| 57 | 59 |
</AdminLayout> |
| 58 | 60 |
) : ( |
| 59 | 61 |
<UserLayout> |
+++ src/admin/feature/board/api/boardApi.ts
... | ... | @@ -0,0 +1,7 @@ |
| 1 | +import type {BoardSearchParams, BoardListItem} from "../model/board.types.ts"; | |
| 2 | +import {apiClient} from "../../../../api/apiClient.ts"; | |
| 3 | +import type {PageResponse} from "../../../../type/pageResponse.ts"; | |
| 4 | + | |
| 5 | +export async function fetchBoardList(params: BoardSearchParams) { | |
| 6 | + return apiClient.get<PageResponse<BoardListItem>>('/cop/bbs/list.do', params); | |
| 7 | +} |
+++ src/admin/feature/board/hook/useBoardList.ts
... | ... | @@ -0,0 +1,13 @@ |
| 1 | +import type {BoardSearchParams} from "../model/board.types.ts"; | |
| 2 | + | |
| 3 | +import {keepPreviousData, useQuery} from "@tanstack/react-query"; | |
| 4 | +import {fetchBoardList} from "../api/boardApi.ts"; | |
| 5 | + | |
| 6 | +export function useBoardListQuery(searchParams: BoardSearchParams) { | |
| 7 | + | |
| 8 | + return useQuery({ | |
| 9 | + queryKey: ['boardList', searchParams], | |
| 10 | + queryFn: () => fetchBoardList(searchParams), | |
| 11 | + placeholderData: keepPreviousData, | |
| 12 | + }); | |
| 13 | +} |
+++ src/admin/feature/board/model/board.types.ts
... | ... | @@ -0,0 +1,18 @@ |
| 1 | +export interface BoardSearchParams { | |
| 2 | + searchCondition: string | |
| 3 | + searchSortOrder: string | |
| 4 | + searchKeyword: string | |
| 5 | + pageUnit: number, | |
| 6 | + pageIndex: number | |
| 7 | +} | |
| 8 | + | |
| 9 | +export interface BoardListItem { | |
| 10 | + bbsId: string | |
| 11 | + bbsNm: string | |
| 12 | + menuNm: string | |
| 13 | + newCnt: number | |
| 14 | + totCnt: number | |
| 15 | + bbsTyCodeNm: string | |
| 16 | + frstRegisterPnttm: string | |
| 17 | + useAt: 'Y' | 'N' | |
| 18 | +}(No newline at end of file) |
+++ src/admin/feature/board/page/BoardListPage.tsx
... | ... | @@ -0,0 +1,7 @@ |
| 1 | +export const BoardListPage = () => { | |
| 2 | + return ( | |
| 3 | + <div> | |
| 4 | + <h2>게시판 관리</h2> | |
| 5 | + </div> | |
| 6 | + ); | |
| 7 | +}; |
--- src/admin/hook/useMenuList.ts
+++ src/admin/hook/useMenuList.ts
... | ... | @@ -1,78 +1,47 @@ |
| 1 |
-import {useEffect, useState} from 'react';
|
|
| 2 |
-import {apiClient} from '../../api/apiClient';
|
|
| 3 |
-import type {MenuItem} from '../component/menu/MenuList';
|
|
| 1 |
+import {fetchMenuList} from "../../api/menuApi.ts";
|
|
| 2 |
+import {useQuery} from "@tanstack/react-query";
|
|
| 3 |
+import type {MenuItem, MenuItemResponse} from "../../type/menu.ts";
|
|
| 4 | 4 |
|
| 5 |
-type BackendMenuItem = {
|
|
| 6 |
- menuNo?: number | string; |
|
| 7 |
- menuNm?: string; |
|
| 8 |
- url?: string; |
|
| 9 |
- upperMenuId?: number | string; |
|
| 10 |
-}; |
|
| 11 |
- |
|
| 12 |
-type MenuLeftResponse = {
|
|
| 13 |
- head?: BackendMenuItem[]; |
|
| 14 |
- menu?: BackendMenuItem[]; |
|
| 15 |
-}; |
|
| 16 |
- |
|
| 17 |
-function toMenuItem(item: BackendMenuItem): MenuItem {
|
|
| 5 |
+function toMenuItem(item: MenuItemResponse): MenuItem {
|
|
| 18 | 6 |
return {
|
| 19 | 7 |
no: String(item.menuNo ?? ''), |
| 20 | 8 |
name: item.menuNm ?? '', |
| 21 |
- url: item.url ?? '#', |
|
| 9 |
+ url: item.chkURL ?? '#', |
|
| 22 | 10 |
upperNo: String(item.upperMenuId ?? '0'), |
| 23 | 11 |
}; |
| 24 | 12 |
} |
| 25 | 13 |
|
| 26 | 14 |
export const useMenuList = () => {
|
| 27 |
- const [headMenuList, setHeadMenuList] = useState<MenuItem[]>([]); |
|
| 28 |
- const [menuList, setMenuList] = useState<MenuItem[]>([]); |
|
| 29 |
- const [isLoading, setIsLoading] = useState(true); |
|
| 30 |
- const [errorMessage, setErrorMessage] = useState<string | null>(null); |
|
| 15 |
+ const query = useQuery({
|
|
| 16 |
+ queryKey: ['menuList'], |
|
| 17 |
+ queryFn: fetchMenuList, |
|
| 18 |
+ select: (data) => {
|
|
| 19 |
+ console.log(data); |
|
| 20 |
+ const headMenuList = |
|
| 21 |
+ (data.head ?? []) |
|
| 22 |
+ .map(toMenuItem) |
|
| 23 |
+ .filter((item: MenuItem) => item.no && item.name); |
|
| 31 | 24 |
|
| 32 |
- useEffect(() => {
|
|
| 25 |
+ const menuList = |
|
| 26 |
+ (data.menu ?? []) |
|
| 27 |
+ .map(toMenuItem) |
|
| 28 |
+ .filter((item: MenuItem) => item.no && item.name); |
|
| 33 | 29 |
|
| 34 |
- let mounted = true; |
|
| 30 |
+ return {
|
|
| 31 |
+ headMenuList, |
|
| 32 |
+ menuList, |
|
| 33 |
+ }; |
|
| 34 |
+ }, |
|
| 35 | 35 |
|
| 36 |
- apiClient.get<MenuLeftResponse>('/sym/mms/menuLeft.do')
|
|
| 37 |
- .then((data) => {
|
|
| 38 |
- if (!mounted) {
|
|
| 39 |
- return; |
|
| 40 |
- } |
|
| 36 |
+ staleTime: 1000 * 60 * 10, |
|
| 37 |
+ }); |
|
| 41 | 38 |
|
| 42 |
- const nextHeadMenuList = (data.head ?? []).map(toMenuItem).filter((item) => item.no && item.name); |
|
| 43 |
- const nextMenuList = (data.menu ?? []).map(toMenuItem).filter((item) => item.no && item.name); |
|
| 44 |
- |
|
| 45 |
- setHeadMenuList(nextHeadMenuList.length > 0 ? nextHeadMenuList : []); |
|
| 46 |
- setMenuList(nextMenuList.length > 0 ? nextMenuList : []); |
|
| 47 |
- |
|
| 48 |
- setErrorMessage(null); |
|
| 49 |
- }) |
|
| 50 |
- |
|
| 51 |
- .catch((error: Error) => {
|
|
| 52 |
- |
|
| 53 |
- if (!mounted) {
|
|
| 54 |
- return; |
|
| 55 |
- } |
|
| 56 |
- |
|
| 57 |
- setErrorMessage(error ? error.message : '메뉴 조회에 실패했습니다.'); |
|
| 58 |
- }) |
|
| 59 |
- |
|
| 60 |
- .finally(() => {
|
|
| 61 |
- if (mounted) {
|
|
| 62 |
- setIsLoading(false); |
|
| 63 |
- } |
|
| 64 |
- }); |
|
| 65 |
- |
|
| 66 |
- return () => {
|
|
| 67 |
- mounted = false; |
|
| 68 |
- }; |
|
| 69 |
- |
|
| 70 |
- }, []); |
|
| 39 |
+ console.log(query); |
|
| 71 | 40 |
|
| 72 | 41 |
return {
|
| 73 |
- headMenuList, |
|
| 74 |
- menuList, |
|
| 75 |
- isLoading, |
|
| 76 |
- errorMessage, |
|
| 42 |
+ headMenuList: query.data?.headMenuList ?? [], |
|
| 43 |
+ menuList: query.data?.menuList ?? [], |
|
| 44 |
+ isLoading: query.isLoading, |
|
| 45 |
+ errorMessage: query.error instanceof Error ? query.error.message : null, |
|
| 77 | 46 |
}; |
| 78 | 47 |
};(No newline at end of file) |
--- src/admin/route/AdminRoute.tsx
+++ src/admin/route/AdminRoute.tsx
... | ... | @@ -1,8 +1,17 @@ |
| 1 |
+import {Navigate, Route, Routes} from "react-router-dom";
|
|
| 2 |
+import {BoardListPage} from "../feature/board/page/BoardListPage.tsx";
|
|
| 3 |
+import {ADMIN_BBS_MASTER_ROUTE} from "./adminRouteMap.ts";
|
|
| 1 | 4 |
|
| 5 |
+const ReadyPage = () => {
|
|
| 6 |
+ return <div>Preparing menu.</div>; |
|
| 7 |
+}; |
|
| 2 | 8 |
|
| 3 |
-export const adminRoute = () => {
|
|
| 9 |
+export const AdminRoute = () => {
|
|
| 4 | 10 |
return ( |
| 5 |
- <> |
|
| 6 |
- </> |
|
| 11 |
+ <Routes> |
|
| 12 |
+ <Route path="/" element={<Navigate to={ADMIN_BBS_MASTER_ROUTE} replace />} />
|
|
| 13 |
+ <Route path={ADMIN_BBS_MASTER_ROUTE} element={<BoardListPage />} />
|
|
| 14 |
+ <Route path="*" element={<ReadyPage />} />
|
|
| 15 |
+ </Routes> |
|
| 7 | 16 |
); |
| 8 |
-} |
|
| 17 |
+}; |
+++ src/admin/route/adminRouteMap.ts
... | ... | @@ -0,0 +1,22 @@ |
| 1 | +export const ADMIN_ROUTE_PREFIX = '/admin'; | |
| 2 | + | |
| 3 | +export const ADMIN_MENU_CREATE_TREE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/EgovMenuCreatSelectJtree.do`; | |
| 4 | +export const ADMIN_AUTHOR_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/ram/EgovAuthorList.do`; | |
| 5 | +export const ADMIN_MAIN_ZONE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/mainZoneList.do`; | |
| 6 | +export const ADMIN_CONTENT_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/cnt/contentList.do`; | |
| 7 | +export const ADMIN_MAIN_PAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/cmm/main/mainPage.do`; | |
| 8 | +export const ADMIN_BBS_MASTER_ROUTE = `${ADMIN_ROUTE_PREFIX}/cop/bbs/SelectBBSMasterInfs.do`; | |
| 9 | +export const ADMIN_LOGIN_GROUP_POLICY_ROUTE = `${ADMIN_ROUTE_PREFIX}/uat/uap/selectLoginGroupPolicyList.do`; | |
| 10 | +export const ADMIN_MEMBER_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/umt/EgovMberManage.do`; | |
| 11 | +export const ADMIN_POPUP_ZONE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/popupZoneList.do`; | |
| 12 | +export const ADMIN_MENU_CREATE_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/mnu/mcm/EgovMenuCreatManageSelect.do`; | |
| 13 | +export const ADMIN_USER_MANAGE_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/umt/user/EgovUserManage.do`; | |
| 14 | +export const ADMIN_COMMON_CODE_TREE_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/ccm/ccc/EgovCcmCmmnCodeTree.do`; | |
| 15 | +export const ADMIN_AUTHOR_GROUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rgm/EgovAuthorGroupList.do`; | |
| 16 | +export const ADMIN_POPUP_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/pwm/egovPopupList.do`; | |
| 17 | +export const ADMIN_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectWebLogList.do`; | |
| 18 | +export const ADMIN_ROLE_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sec/rmt/EgovRoleList.do`; | |
| 19 | +export const ADMIN_BANNER_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/uss/ion/bnr/selectBannerList.do`; | |
| 20 | +export const ADMIN_USER_WEB_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/NSelectWebLogList.do`; | |
| 21 | +export const ADMIN_LOGIN_LOG_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectLoginLogList.do`; | |
| 22 | +export const ADMIN_LOG_METHOD_LIST_ROUTE = `${ADMIN_ROUTE_PREFIX}/sym/log/clg/SelectLogMethodList.do`; |
--- src/api/apiClient.ts
+++ src/api/apiClient.ts
... | ... | @@ -1,57 +1,46 @@ |
| 1 |
+import axios, {type AxiosRequestConfig, type AxiosResponse} from 'axios';
|
|
| 2 |
+import type {ApiResponse} from "../type/apiResponse.ts";
|
|
| 3 |
+ |
|
| 1 | 4 |
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ''; |
| 2 | 5 |
const ADMIN_LOGIN_URL = '/uat/uia/actionSecurityLogin.do'; |
| 3 | 6 |
const ADMIN_ID = 'admin'; |
| 4 | 7 |
const ADMIN_PASSWORD = '1'; |
| 5 | 8 |
const MAX_RETRY_COUNT = 3; |
| 6 | 9 |
|
| 7 |
-type ApiResponse<T> = {
|
|
| 8 |
- success: boolean; |
|
| 9 |
- message: string; |
|
| 10 |
- data: T; |
|
| 11 |
-} |
|
| 12 |
- |
|
| 13 |
- |
|
| 14 | 10 |
class ApiClient {
|
| 15 | 11 |
private loginPromise: Promise<void> | null = null; |
| 16 | 12 |
|
| 17 |
- private toApiUrl(path: string) {
|
|
| 18 |
- if (/^https?:\/\//i.test(path)) {
|
|
| 19 |
- return path; |
|
| 20 |
- } |
|
| 21 |
- |
|
| 22 |
- return `${API_BASE_URL}${path}`;
|
|
| 23 |
- } |
|
| 24 |
- |
|
| 25 |
- private createHeaders(headers?: HeadersInit) {
|
|
| 26 |
- const result = new Headers(headers); |
|
| 27 |
- |
|
| 28 |
- result.set('X-Requested-With', 'XMLHttpRequest');
|
|
| 29 |
- |
|
| 30 |
- return result; |
|
| 31 |
- } |
|
| 13 |
+ private readonly client = axios.create({
|
|
| 14 |
+ baseURL: API_BASE_URL, |
|
| 15 |
+ withCredentials: true, |
|
| 16 |
+ headers: {
|
|
| 17 |
+ 'X-Requested-With': 'XMLHttpRequest', |
|
| 18 |
+ }, |
|
| 19 |
+ validateStatus: () => true, |
|
| 20 |
+ }); |
|
| 32 | 21 |
|
| 33 | 22 |
private async login() {
|
| 34 | 23 |
if (!this.loginPromise) {
|
| 35 |
- this.loginPromise = fetch(this.toApiUrl(ADMIN_LOGIN_URL), {
|
|
| 36 |
- method: 'POST', |
|
| 37 |
- credentials: 'include', |
|
| 38 |
- headers: {
|
|
| 39 |
- 'Content-Type': 'application/x-www-form-urlencoded', |
|
| 40 |
- 'X-Requested-With': 'XMLHttpRequest', |
|
| 41 |
- }, |
|
| 42 |
- body: new URLSearchParams({
|
|
| 24 |
+ this.loginPromise = this.client.post<ApiResponse<unknown>>( |
|
| 25 |
+ ADMIN_LOGIN_URL, |
|
| 26 |
+ new URLSearchParams({
|
|
| 43 | 27 |
id: ADMIN_ID, |
| 44 | 28 |
password: ADMIN_PASSWORD, |
| 45 | 29 |
}), |
| 46 |
- }).then(async (response) => {
|
|
| 47 |
- if (!response.ok) {
|
|
| 48 |
- throw new Error(`로그인 실패 : ${response.status}`);
|
|
| 30 |
+ {
|
|
| 31 |
+ headers: {
|
|
| 32 |
+ 'Content-Type': 'application/x-www-form-urlencoded', |
|
| 33 |
+ }, |
|
| 34 |
+ }, |
|
| 35 |
+ ).then((response) => {
|
|
| 36 |
+ if (response.status < 200 || response.status >= 300) {
|
|
| 37 |
+ throw new Error(`Login failed: ${response.status}`);
|
|
| 49 | 38 |
} |
| 50 | 39 |
|
| 51 |
- const contentType = response.headers.get('content-type') ?? '';
|
|
| 40 |
+ const contentType = String(response.headers['content-type'] ?? ''); |
|
| 52 | 41 |
|
| 53 |
- if (contentType === 'application/json') {
|
|
| 54 |
- const result = (await response.json()) as ApiResponse<unknown>; |
|
| 42 |
+ if (contentType.includes('application/json')) {
|
|
| 43 |
+ const result = response.data; |
|
| 55 | 44 |
|
| 56 | 45 |
if (!result.success) {
|
| 57 | 46 |
throw new Error(result.message); |
... | ... | @@ -61,72 +50,55 @@ |
| 61 | 50 |
this.loginPromise = null; |
| 62 | 51 |
}); |
| 63 | 52 |
} |
| 53 |
+ |
|
| 64 | 54 |
return this.loginPromise; |
| 65 | 55 |
} |
| 66 | 56 |
|
| 67 |
- private async needsLogin(response: Response) {
|
|
| 68 |
- if(response.status === 401 || response.status === 403) {
|
|
| 57 |
+ private needsLogin(response: AxiosResponse<unknown>) {
|
|
| 58 |
+ if (response.status === 401 || response.status === 403) {
|
|
| 69 | 59 |
return true; |
| 70 | 60 |
} |
| 71 | 61 |
|
| 72 |
- const contentType= response.headers.get("content-type") ?? '';
|
|
| 62 |
+ const contentType = String(response.headers['content-type'] ?? ''); |
|
| 73 | 63 |
|
| 74 |
- if (contentType.includes('text/html')) {
|
|
| 75 |
- |
|
| 76 |
- const html = await response.clone().text(); |
|
| 77 |
- |
|
| 64 |
+ if (contentType.includes('text/html') && typeof response.data === 'string') {
|
|
| 78 | 65 |
return ( |
| 79 |
- html.includes('/uat/uia/actionMain.do') ||
|
|
| 80 |
- html.includes('actionSecurityLogin.do')
|
|
| 66 |
+ response.data.includes('/uat/uia/actionMain.do') ||
|
|
| 67 |
+ response.data.includes('actionSecurityLogin.do')
|
|
| 81 | 68 |
); |
| 82 | 69 |
} |
| 83 | 70 |
|
| 84 |
- if (contentType.includes('application/json')) {
|
|
| 85 |
- |
|
| 86 |
- try {
|
|
| 87 |
- |
|
| 88 |
- const result = (await response.clone().json()) as ApiResponse<unknown>; |
|
| 89 |
- |
|
| 90 |
- return (!result.success && /login|로그인/i.test(result.message ?? '')); |
|
| 91 |
- |
|
| 92 |
- } catch {
|
|
| 93 |
- return false; |
|
| 94 |
- } |
|
| 71 |
+ if (contentType.includes('application/json') && this.isApiResponse(response.data)) {
|
|
| 72 |
+ return !response.data.success && /login|로그인/i.test(response.data.message ?? ''); |
|
| 95 | 73 |
} |
| 96 | 74 |
|
| 97 | 75 |
return false; |
| 98 | 76 |
} |
| 99 |
- private async request( |
|
| 100 |
- path: string, |
|
| 101 |
- init: RequestInit = {},
|
|
| 102 |
- retryCount = 0 |
|
| 103 |
- ): Promise<Response> {
|
|
| 104 | 77 |
|
| 105 |
- const response = await fetch(this.toApiUrl(path), {
|
|
| 106 |
- ...init, |
|
| 107 |
- credentials: 'include', |
|
| 108 |
- headers: this.createHeaders(init.headers), |
|
| 109 |
- redirect: 'manual', |
|
| 110 |
- }); |
|
| 78 |
+ private async request<T>( |
|
| 79 |
+ config: AxiosRequestConfig, |
|
| 80 |
+ retryCount = 0, |
|
| 81 |
+ ): Promise<AxiosResponse<ApiResponse<T>>> {
|
|
| 82 |
+ const response = await this.client.request<ApiResponse<T>>(config); |
|
| 111 | 83 |
|
| 112 |
- if (await this.needsLogin(response)) {
|
|
| 113 |
- |
|
| 84 |
+ if (this.needsLogin(response)) {
|
|
| 114 | 85 |
if (retryCount >= MAX_RETRY_COUNT) {
|
| 115 |
- throw new Error('인증 재시도 횟수 초과');
|
|
| 86 |
+ throw new Error('Authentication retry count exceeded');
|
|
| 116 | 87 |
} |
| 117 | 88 |
|
| 118 | 89 |
await this.login(); |
| 119 | 90 |
|
| 120 |
- return this.request(path, init, retryCount + 1); |
|
| 91 |
+ return this.request<T>(config, retryCount + 1); |
|
| 121 | 92 |
} |
| 122 | 93 |
|
| 123 | 94 |
return response; |
| 124 | 95 |
} |
| 125 | 96 |
|
| 126 |
- async get<T>(path: string): Promise<T> {
|
|
| 127 |
- |
|
| 128 |
- const response = await this.request(path, {
|
|
| 97 |
+ async get<T>(path: string, params?: AxiosRequestConfig['params']): Promise<T> {
|
|
| 98 |
+ const response = await this.request<T>({
|
|
| 99 |
+ url: path, |
|
| 129 | 100 |
method: 'GET', |
| 101 |
+ params, |
|
| 130 | 102 |
}); |
| 131 | 103 |
|
| 132 | 104 |
return this.parseJson<T>(response); |
... | ... | @@ -134,12 +106,12 @@ |
| 134 | 106 |
|
| 135 | 107 |
async post<T>( |
| 136 | 108 |
path: string, |
| 137 |
- body?: unknown |
|
| 109 |
+ body?: unknown, |
|
| 138 | 110 |
): Promise<T> {
|
| 139 |
- |
|
| 140 |
- const response = await this.request(path, {
|
|
| 111 |
+ const response = await this.request<T>({
|
|
| 112 |
+ url: path, |
|
| 141 | 113 |
method: 'POST', |
| 142 |
- body: body ? JSON.stringify(body) : undefined, |
|
| 114 |
+ data: body, |
|
| 143 | 115 |
headers: {
|
| 144 | 116 |
'Content-Type': 'application/json', |
| 145 | 117 |
}, |
... | ... | @@ -148,21 +120,27 @@ |
| 148 | 120 |
return this.parseJson<T>(response); |
| 149 | 121 |
} |
| 150 | 122 |
|
| 151 |
- |
|
| 152 |
- private async parseJson<T>(response: Response): Promise<T> {
|
|
| 153 |
- |
|
| 154 |
- if (!response.ok) {
|
|
| 123 |
+ private parseJson<T>(response: AxiosResponse<ApiResponse<T>>): T {
|
|
| 124 |
+ if (response.status < 200 || response.status >= 300) {
|
|
| 155 | 125 |
throw new Error(`API request failed: ${response.status}`);
|
| 156 | 126 |
} |
| 157 | 127 |
|
| 158 |
- const result = (await response.json()) as ApiResponse<T>; |
|
| 128 |
+ const result = response.data; |
|
| 129 |
+ |
|
| 130 |
+ if (!this.isApiResponse<T>(result)) {
|
|
| 131 |
+ throw new Error('Invalid API response');
|
|
| 132 |
+ } |
|
| 159 | 133 |
|
| 160 | 134 |
if (!result.success) {
|
| 161 | 135 |
throw new Error(result.message ?? 'API request failed'); |
| 162 | 136 |
} |
| 163 | 137 |
|
| 164 |
- return result.data as T; |
|
| 138 |
+ return result.data; |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ private isApiResponse<T>(data: unknown): data is ApiResponse<T> {
|
|
| 142 |
+ return typeof data === 'object' && data !== null && 'success' in data; |
|
| 165 | 143 |
} |
| 166 | 144 |
} |
| 167 | 145 |
|
| 168 |
-export const apiClient = new ApiClient();(No newline at end of file) |
|
| 146 |
+export const apiClient = new ApiClient(); |
--- src/main.tsx
+++ src/main.tsx
... | ... | @@ -1,10 +1,15 @@ |
| 1 |
-import { StrictMode } from 'react';
|
|
| 2 | 1 |
import { createRoot } from 'react-dom/client';
|
| 2 |
+import {QueryClientProvider, QueryClient} from "@tanstack/react-query";
|
|
| 3 |
+import {BrowserRouter} from "react-router-dom";
|
|
| 3 | 4 |
import App from './App'; |
| 4 | 5 |
import './styles/app.css'; |
| 5 | 6 |
|
| 7 |
+const queryClient = new QueryClient(); |
|
| 8 |
+ |
|
| 6 | 9 |
createRoot(document.getElementById('root')!).render(
|
| 7 |
- <StrictMode> |
|
| 8 |
- <App /> |
|
| 9 |
- </StrictMode>, |
|
| 10 |
+ <QueryClientProvider client={queryClient}>
|
|
| 11 |
+ <BrowserRouter> |
|
| 12 |
+ <App /> |
|
| 13 |
+ </BrowserRouter> |
|
| 14 |
+ </QueryClientProvider>, |
|
| 10 | 15 |
); |
+++ src/type/apiResponse.ts
... | ... | @@ -0,0 +1,5 @@ |
| 1 | +export interface ApiResponse<T> { | |
| 2 | + success: boolean; | |
| 3 | + message: string; | |
| 4 | + data: T; | |
| 5 | +}(No newline at end of file) |
+++ src/type/pageResponse.ts
... | ... | @@ -0,0 +1,6 @@ |
| 1 | +export interface PageResponse<T> { | |
| 2 | + list: T[] | |
| 3 | + totalCount: number | |
| 4 | + currentPage: number | |
| 5 | + recordPerPage: number | |
| 6 | +}(No newline at end of file) |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?