diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d00932e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,16 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[Bug]" +labels: '' +assignees: '' + +--- + +## 오류 사진 및 설명 + + +## 발생 위치 + + +## 해결시도 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b8b8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,15 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature]" +labels: '' +assignees: '' + +--- + +## 작업할 내용 + +## 해야할 일 +- [ ] todo1 +- [ ] todo2 +- [ ] todo3 diff --git a/package-lock.json b/package-lock.json index 2b48850..fcdd50b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "@toast-ui/react-editor": "^3.1.3", "axios": "^0.24.0", "dayjs": "^1.10.7", "formik": "^2.2.9", @@ -24,11 +25,14 @@ "react": "^17.0.2", "react-circular-progressbar": "^2.0.4", "react-dom": "^17.0.2", + "react-error-boundary": "^3.1.4", "react-intersection-observer": "^8.33.1", + "react-query": "^3.34.16", "react-router-dom": "^6.2.1", "react-scripts": "5.0.0", "react-textarea-autosize": "^8.3.3", "react-transition-group": "^4.4.2", + "remove-markdown": "^0.3.0", "styled-components": "^5.3.3", "web-vitals": "^2.1.3" }, @@ -3816,6 +3820,32 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@toast-ui/editor": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-3.1.3.tgz", + "integrity": "sha512-4W8nKIhct4bGOKNMkYY2nGzt2k+8LUWlINwGZvCbNgIo6WKlcOarsbWD0o8stOAleaq2TeG6ixIvYK/wTG0OxA==", + "dependencies": { + "dompurify": "^2.3.3", + "prosemirror-commands": "^1.1.9", + "prosemirror-history": "^1.1.3", + "prosemirror-inputrules": "^1.1.3", + "prosemirror-keymap": "^1.1.4", + "prosemirror-model": "^1.14.1", + "prosemirror-state": "^1.3.4", + "prosemirror-view": "^1.18.7" + } + }, + "node_modules/@toast-ui/react-editor": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@toast-ui/react-editor/-/react-editor-3.1.3.tgz", + "integrity": "sha512-k5W53y/R3cZvSH3UfDgeT8L1k8MpRri4O9hcTeuXtnbkkCtPQjt0m696tKrZvSXRNeqa4mKT0m8uNbHJAqWD4g==", + "dependencies": { + "@toast-ui/editor": "^3.1.3" + }, + "peerDependencies": { + "react": "^17.0.1" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -5400,6 +5430,14 @@ "node": ">= 8.0.0" } }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5522,6 +5560,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -6998,6 +7051,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz", + "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==" + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -11636,6 +11694,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12056,6 +12119,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -12154,6 +12226,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12430,6 +12507,14 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.1.30", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", @@ -12873,6 +12958,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -12951,6 +13041,11 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.1.tgz", + "integrity": "sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -14444,6 +14539,79 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/prosemirror-commands": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", + "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.2.0.tgz", + "integrity": "sha512-B9v9xtf4fYbKxQwIr+3wtTDNLDZcmMMmGiI3TAPShnUzvo+Rmv1GiUrsQChY1meetHl7rhML2cppF3FTs7f7UQ==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz", + "integrity": "sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.1.5.tgz", + "integrity": "sha512-8SZgPH3K+GLsHL2wKuwBD9rxhsbnVBTwpHCO4VUO5GmqUQlxd/2GtBVWTsyLq4Dp3N9nGgPd3+lZFKUDuVp+Vw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.16.1.tgz", + "integrity": "sha512-r1/w0HDU40TtkXp0DyKBnFPYwd8FSlUSJmGCGFv4DeynfeSlyQF2FD0RQbVEMOe6P3PpUSXM6LZBV7W/YNZ4mA==", + "dependencies": { + "orderedmap": "^1.1.0" + } + }, + "node_modules/prosemirror-state": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.3.4.tgz", + "integrity": "sha512-Xkkrpd1y/TQ6HKzN3agsQIGRcLckUMA9u3j207L04mt8ToRgpGeyhbVv0HI7omDORIBHjR29b7AwlATFFf2GLA==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.3.4.tgz", + "integrity": "sha512-gTsg3UIeaFuEY6+YmNPMgTpEkCKPedkFIUnsPpOMbclU701fEVI/e4VOXACXh3BO5rZJaBbEBwrnzB0mLp6eBA==", + "dependencies": { + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.23.6.tgz", + "integrity": "sha512-B4DAzriNpI/AVoW0Lu6SVfX00jZZQxOVwdBQEjWlRbCdT9V0pvk4GQJ3JTFaib+b6BcPdRZ3MjWXz2xvV1rblA==", + "dependencies": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14753,6 +14921,21 @@ "react": "17.0.2" } }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-error-overlay": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", @@ -14776,6 +14959,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-query": { + "version": "3.34.16", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.34.16.tgz", + "integrity": "sha512-7FvBvjgEM4YQ8nPfmAr+lJfbW95uyW/TVjFoi2GwCkF33/S8ajx45tuPHPFGWs4qYwPy1mzwxD4IQfpUDrefNQ==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15184,6 +15392,16 @@ "node": ">= 0.10" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=" + }, + "node_modules/remove-markdown": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.3.0.tgz", + "integrity": "sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -15496,6 +15714,11 @@ "node": ">=8" } }, + "node_modules/rope-sequence": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.2.tgz", + "integrity": "sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -17167,6 +17390,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -17343,6 +17575,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==" + }, "node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", @@ -20775,6 +21012,29 @@ "@babel/runtime": "^7.12.5" } }, + "@toast-ui/editor": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-3.1.3.tgz", + "integrity": "sha512-4W8nKIhct4bGOKNMkYY2nGzt2k+8LUWlINwGZvCbNgIo6WKlcOarsbWD0o8stOAleaq2TeG6ixIvYK/wTG0OxA==", + "requires": { + "dompurify": "^2.3.3", + "prosemirror-commands": "^1.1.9", + "prosemirror-history": "^1.1.3", + "prosemirror-inputrules": "^1.1.3", + "prosemirror-keymap": "^1.1.4", + "prosemirror-model": "^1.14.1", + "prosemirror-state": "^1.3.4", + "prosemirror-view": "^1.18.7" + } + }, + "@toast-ui/react-editor": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@toast-ui/react-editor/-/react-editor-3.1.3.tgz", + "integrity": "sha512-k5W53y/R3cZvSH3UfDgeT8L1k8MpRri4O9hcTeuXtnbkkCtPQjt0m696tKrZvSXRNeqa4mKT0m8uNbHJAqWD4g==", + "requires": { + "@toast-ui/editor": "^3.1.3" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -22036,6 +22296,11 @@ "tryer": "^1.0.1" } }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -22136,6 +22401,21 @@ "fill-range": "^7.0.1" } }, + "broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "requires": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -23232,6 +23512,11 @@ "domelementtype": "^2.2.0" } }, + "dompurify": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz", + "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==" + }, "domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -26582,6 +26867,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -26910,6 +27200,15 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==" }, + "match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "requires": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -26983,6 +27282,11 @@ "picomatch": "^2.2.3" } }, + "microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -27183,6 +27487,14 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, + "nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=", + "requires": { + "big-integer": "^1.6.16" + } + }, "nanoid": { "version": "3.1.30", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", @@ -27500,6 +27812,11 @@ "es-abstract": "^1.19.1" } }, + "oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -27557,6 +27874,11 @@ "word-wrap": "^1.2.3" } }, + "orderedmap": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.1.tgz", + "integrity": "sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==" + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -28528,6 +28850,79 @@ } } }, + "prosemirror-commands": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz", + "integrity": "sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-history": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.2.0.tgz", + "integrity": "sha512-B9v9xtf4fYbKxQwIr+3wtTDNLDZcmMMmGiI3TAPShnUzvo+Rmv1GiUrsQChY1meetHl7rhML2cppF3FTs7f7UQ==", + "requires": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "rope-sequence": "^1.3.0" + } + }, + "prosemirror-inputrules": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz", + "integrity": "sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw==", + "requires": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-keymap": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.1.5.tgz", + "integrity": "sha512-8SZgPH3K+GLsHL2wKuwBD9rxhsbnVBTwpHCO4VUO5GmqUQlxd/2GtBVWTsyLq4Dp3N9nGgPd3+lZFKUDuVp+Vw==", + "requires": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "prosemirror-model": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.16.1.tgz", + "integrity": "sha512-r1/w0HDU40TtkXp0DyKBnFPYwd8FSlUSJmGCGFv4DeynfeSlyQF2FD0RQbVEMOe6P3PpUSXM6LZBV7W/YNZ4mA==", + "requires": { + "orderedmap": "^1.1.0" + } + }, + "prosemirror-state": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.3.4.tgz", + "integrity": "sha512-Xkkrpd1y/TQ6HKzN3agsQIGRcLckUMA9u3j207L04mt8ToRgpGeyhbVv0HI7omDORIBHjR29b7AwlATFFf2GLA==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-transform": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.3.4.tgz", + "integrity": "sha512-gTsg3UIeaFuEY6+YmNPMgTpEkCKPedkFIUnsPpOMbclU701fEVI/e4VOXACXh3BO5rZJaBbEBwrnzB0mLp6eBA==", + "requires": { + "prosemirror-model": "^1.0.0" + } + }, + "prosemirror-view": { + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.23.6.tgz", + "integrity": "sha512-B4DAzriNpI/AVoW0Lu6SVfX00jZZQxOVwdBQEjWlRbCdT9V0pvk4GQJ3JTFaib+b6BcPdRZ3MjWXz2xvV1rblA==", + "requires": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -28748,6 +29143,14 @@ "scheduler": "^0.20.2" } }, + "react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-error-overlay": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", @@ -28769,6 +29172,16 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-query": { + "version": "3.34.16", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.34.16.tgz", + "integrity": "sha512-7FvBvjgEM4YQ8nPfmAr+lJfbW95uyW/TVjFoi2GwCkF33/S8ajx45tuPHPFGWs4qYwPy1mzwxD4IQfpUDrefNQ==", + "requires": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -29074,6 +29487,16 @@ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=" + }, + "remove-markdown": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.3.0.tgz", + "integrity": "sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=" + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -29296,6 +29719,11 @@ } } }, + "rope-sequence": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.2.tgz", + "integrity": "sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg==" + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -30566,6 +30994,15 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, + "unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "requires": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -30701,6 +31138,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==" + }, "w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", diff --git a/package.json b/package.json index fa13207..cebe668 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "@toast-ui/react-editor": "^3.1.3", "axios": "^0.24.0", "dayjs": "^1.10.7", "formik": "^2.2.9", @@ -19,11 +20,14 @@ "react": "^17.0.2", "react-circular-progressbar": "^2.0.4", "react-dom": "^17.0.2", + "react-error-boundary": "^3.1.4", "react-intersection-observer": "^8.33.1", + "react-query": "^3.34.16", "react-router-dom": "^6.2.1", "react-scripts": "5.0.0", "react-textarea-autosize": "^8.3.3", "react-transition-group": "^4.4.2", + "remove-markdown": "^0.3.0", "styled-components": "^5.3.3", "web-vitals": "^2.1.3" }, diff --git a/public/assets/hot.svg b/public/assets/hot.svg index c96dd68..2ff665a 100644 --- a/public/assets/hot.svg +++ b/public/assets/hot.svg @@ -1,4 +1,4 @@ - + diff --git a/public/assets/new.svg b/public/assets/new.svg new file mode 100644 index 0000000..352a372 --- /dev/null +++ b/public/assets/new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/App.jsx b/src/App.jsx index 9e335e0..6289c2b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,15 @@ -import * as React from 'react'; -import { useState, createContext } from 'react'; +// 2022 1 30 1차 배포 +import { useState, createContext, useEffect, useContext } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate, } from 'react-router-dom'; +import { QueryClient, QueryClientProvider, QueryCache } from 'react-query'; + +import { UserService } from 'Network'; + import { MainPage, ProfilePage, @@ -22,9 +26,7 @@ import { AlarmPage, ErrorPage, } from './Pages'; -import Loading from './Components/Loading'; -import { useContext } from 'react'; -import UserService from './Network/UserService'; +import { Loading } from 'Components'; export const AuthContext = createContext(); @@ -33,7 +35,7 @@ const AuthProvider = ({ children }) => { const [isLoading, setIsLoading] = useState(true); const [curUser, setCurUser] = useState(null); - React.useEffect(() => { + useEffect(() => { const initState = async () => { let response; try { @@ -63,6 +65,7 @@ const PrivateRouteCheckAuth = ({ children }) => { } else { if (auth.state !== 401) return children; else return ; + // return children; } }; @@ -70,144 +73,159 @@ const PrivateRouteCheckFtAuth = ({ children }) => { const auth = useContext(AuthContext); if (auth.state === 200) return children; else return ; + // return children; }; +const queryClient = new QueryClient({ + defaultOptions: { + onError: () => { + console.log('시발'); + }, + queries: { retry: false, suspense: true }, + }, + // queryCache: new QueryCache({ + // onError: () => alert('시발'), + // }), +}); + // 글 보기 : 모드view?글id=12 or view/글id // 글 작성 : free?mode=write const App = () => { return ( - - - - } /> - } - /> - - - - - - } - /> - {/* - - - - - } - /> */} - - - - - - - } - /> - - - - - - } - /> - - - - - - - } - /> - - - - - - - } - /> - - - - - } - /> - - - - - - } - /> - - - - - - } - /> - - - - - - } - /> - - - - - - } - /> - - - - } - /> - } /> - - - + + + + + } /> + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + + } + /> + + + + + + } + /> + + + + + + + } + /> + + + + + + + } + /> + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + } + /> + } /> + + + + ); }; @@ -220,9 +238,3 @@ export default App; // 42world.kr/board/free // 42world.kr/board/anony // 42world.kr/profile - -// const isLogin = () => { -// const response = UserService.getUser(); -// if (response) return true; -// return false; -// }; diff --git a/src/Components/Footer.jsx b/src/Components/Footer.jsx index 7ec3615..47ed49a 100644 --- a/src/Components/Footer.jsx +++ b/src/Components/Footer.jsx @@ -1,10 +1,10 @@ -import React from 'react'; -import GlobalStyled from '../Styled/Global.styled'; +import GlobalStyled from 'Styled/Global.styled'; import Styled from './Footer.styled'; const githubLink = 'https://github.com/42-world'; const emailLink = 'mailto:42world.official@gmail.com'; -const articleLink = 'https://euimin.notion.site/42WORLD-925997bb2e7245b48fca5afeb298db76'; +const articleLink = + 'https://euimin.notion.site/42WORLD-925997bb2e7245b48fca5afeb298db76'; const Footer = ({ isProfile }) => { return ( diff --git a/src/Components/Footer.styled.js b/src/Components/Footer.styled.js index a990c6d..bd841a8 100644 --- a/src/Components/Footer.styled.js +++ b/src/Components/Footer.styled.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; -import GlobalStyled from '../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const Footer = styled.div` background: ${props => diff --git a/src/Components/Header.jsx b/src/Components/Header.jsx index 04444b8..408fd13 100644 --- a/src/Components/Header.jsx +++ b/src/Components/Header.jsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react'; import { useNavigate, useSearchParams, useLocation } from 'react-router-dom'; + import qs from 'qs'; import MenuModal from './MenuModal'; // import NotiModal from './NotiModal'; - import MenuIcon from '@mui/icons-material/Menu'; import SearchIcon from '@mui/icons-material/Search'; import NotificationsIcon from '@mui/icons-material/Notifications'; @@ -12,8 +12,8 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import ArrowBackIosRoundedIcon from '@mui/icons-material/ArrowBackIosRounded'; import SwipeableDrawer from '@mui/material/SwipeableDrawer'; +import GlobalStyled from 'Styled/Global.styled'; import Styled from './Header.styled'; -import GlobalStyled from '../Styled/Global.styled'; // const SearchBar = () => { // const [search, setSearch] = useState(''); @@ -60,7 +60,6 @@ const Header = () => { const handleOpenMenu = (anchor, open) => { if (!queryData.mode) { setSearchParams({ ...queryData, mode: 'menu' }); - // searchParams.append('mode', 'menu'); setIsMenuModal(true); } else { delete queryData.mode; @@ -78,19 +77,11 @@ const Header = () => { const handleBackButton = () => { navi(-1); }; - // const handleOpenNoti = () => { - // if (loca.search === '') { - // setSearchParams('mode=noti'); - // setIsNotiModal(true); - // } else { - // setSearchParams(''); - // setIsNotiModal(false); - // } + + // const handleToggleSearch = () => { + // setIsSearch(!isSearch); // }; - const handleToggleSearch = () => { - setIsSearch(!isSearch); - }; useEffect(() => { if (queryData.mode === 'menu') { setIsMenuModal(true); @@ -138,10 +129,10 @@ const Header = () => { sx={{ color: GlobalStyled.theme.headerIconColor }} onClick={() => navi('/profile')} /> - {/* navi('/alarm')} - /> */} + /> {/*{isSearch && }*/} @@ -160,10 +151,6 @@ const Header = () => { > - {/**/} - {/**/} - {/**/} - {/**/} ); }; diff --git a/src/Components/Header.styled.js b/src/Components/Header.styled.js index 0651d2f..7fb010a 100644 --- a/src/Components/Header.styled.js +++ b/src/Components/Header.styled.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; -import GlobalStyled from '../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const HeaderStyleDiv = styled.div` background-color: ${GlobalStyled.theme.secondary}; diff --git a/src/Components/Loading.jsx b/src/Components/Loading.jsx index fd974b2..40395ba 100644 --- a/src/Components/Loading.jsx +++ b/src/Components/Loading.jsx @@ -1,7 +1,7 @@ -import React from 'react'; -import Styled from './Loading.styled'; import { CircularProgress } from '@mui/material'; +import Styled from './Loading.styled'; + const Loading = () => { return ( diff --git a/src/Components/Loading.styled.js b/src/Components/Loading.styled.js index a0ed600..929c079 100644 --- a/src/Components/Loading.styled.js +++ b/src/Components/Loading.styled.js @@ -1,4 +1,5 @@ import styled from 'styled-components'; + const LoadingBox = styled.div` height: 100vh; width: 100vw; diff --git a/src/Components/MenuModal.jsx b/src/Components/MenuModal.jsx index cd74edd..1de1565 100644 --- a/src/Components/MenuModal.jsx +++ b/src/Components/MenuModal.jsx @@ -7,7 +7,7 @@ import BookIcon from '@mui/icons-material/Book'; import Box from '@mui/material/Box'; import List from '@mui/material/List'; -import GlobalStyled from '../Styled/Global.styled'; +import GlobalStyled from 'Styled/Global.styled'; import Styled from './MenuModal.styled'; const siteMap = [ diff --git a/src/Components/MenuModal.styled.js b/src/Components/MenuModal.styled.js index d612a5b..28963f8 100644 --- a/src/Components/MenuModal.styled.js +++ b/src/Components/MenuModal.styled.js @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import Link from '@mui/material/Link'; const MenuModalDiv = styled.div` diff --git a/src/Components/NotiModal.jsx b/src/Components/NotiModal.jsx index fb42c04..a840209 100644 --- a/src/Components/NotiModal.jsx +++ b/src/Components/NotiModal.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; -import UserService from '../Network/UserService'; + +import UserService from 'Network/UserService'; import Modal from '@mui/material/Modal'; import CloseIcon from '@mui/icons-material/Close'; diff --git a/src/Components/PreviewArticle.jsx b/src/Components/PreviewArticle.jsx index a2d5318..25931db 100644 --- a/src/Components/PreviewArticle.jsx +++ b/src/Components/PreviewArticle.jsx @@ -1,14 +1,13 @@ +import { getArticleTime, isNewArticle } from 'Utils/dayjsUtils'; +import removeMarkdown from 'remove-markdown'; + import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; import SmsOutlined from '@mui/icons-material/SmsOutlined'; -import dayjs from 'dayjs'; import Styled from './PreviewArticle.styled'; -const PreviewArticle = ({ article, onClickArticle }) => { - const getArticleTime = time => - dayjs(time).isSame(dayjs(), 'day') - ? dayjs(time).format('HH:mm') - : dayjs(time).format('MM/DD'); +const PreviewArticle = ({ article, isBestArticle, onClickArticle }) => { + const getPlainText = text => removeMarkdown(text).replaceAll('\\', ''); return ( { onClick={onClickArticle} article={article} > -
{article.title}
-
{article.content}
+
+ {isBestArticle && } + {isNewArticle(article.createdAt) && } + {article.title} +
+
{getPlainText(article.content)}
{article.writer &&

{article.writer.nickname}

}

{getArticleTime(article.createdAt)}

diff --git a/src/Components/PreviewArticle.styled.js b/src/Components/PreviewArticle.styled.js index 9d2adab..fe88cdb 100644 --- a/src/Components/PreviewArticle.styled.js +++ b/src/Components/PreviewArticle.styled.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; -import GlobalStyled from '../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const PreviewArticleDiv = styled.div` cursor: pointer; @@ -14,10 +15,10 @@ const PreviewArticleDiv = styled.div` background-color: ${GlobalStyled.theme.previewArticleBackgroundColor}; .top { - display: inline-block; + display: flex; font-size: 0.95rem; font-weight: 700; - align-items: left; + align-items: center; margin-bottom: 0.15rem; width: 100%; @@ -25,6 +26,9 @@ const PreviewArticleDiv = styled.div` white-space: nowrap; text-overflow: ellipsis; } + .top>* { + padding-right: 0.3rem; + } .middle { display: inline-block; font-size: 0.75rem; diff --git a/src/Components/PreviewArticleNoti.jsx b/src/Components/PreviewArticleNoti.jsx index 13a9737..e5cfb01 100644 --- a/src/Components/PreviewArticleNoti.jsx +++ b/src/Components/PreviewArticleNoti.jsx @@ -1,12 +1,8 @@ +import { getArticleTime } from 'Utils/dayjsUtils'; + import Styled from './PreviewArticle.styled'; -import dayjs from 'dayjs'; const PreviewArticleNoti = ({ article, onClickArticle }) => { - const getArticleTime = time => - dayjs(time).isSame(dayjs(), 'day') - ? dayjs(time).format('HH:mm') - : dayjs(time).format('MM/DD'); - return ( { diff --git a/src/Components/ProfileHeader.styled.js b/src/Components/ProfileHeader.styled.js index 49e3c1e..51f9f4f 100644 --- a/src/Components/ProfileHeader.styled.js +++ b/src/Components/ProfileHeader.styled.js @@ -1,12 +1,13 @@ import styled from 'styled-components'; -import GlobalStyled from '../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const ProfileHeaderDiv = styled.div` display: flex; flex-direction: row; justify-content: space-between; align-items: center; - height: ${GlobalStyled.theme.headerHeight}; + min-height: ${GlobalStyled.theme.headerHeight}; background-color: ${GlobalStyled.theme.secondary}; padding: 0.25rem 0.8rem; box-sizing: border-box; diff --git a/src/Components/index.jsx b/src/Components/index.jsx index a2a864e..164759d 100644 --- a/src/Components/index.jsx +++ b/src/Components/index.jsx @@ -5,6 +5,7 @@ import MenuModal from './MenuModal'; import NotiModal from './NotiModal'; import ProfileHeader from './ProfileHeader'; import Footer from './Footer'; +import Loading from './Loading'; export { Footer, @@ -14,4 +15,5 @@ export { MenuModal, NotiModal, ProfileHeader, + Loading, }; diff --git a/src/Network/APIType.js b/src/Network/APIType.js index f23d84d..117f8dd 100644 --- a/src/Network/APIType.js +++ b/src/Network/APIType.js @@ -3,13 +3,18 @@ import axios from 'axios'; export function url(path) { // const version = 'v1'; // process.env.REACT_APP_API_VERSION - return `https://api.42world.kr${path}`; - // return `http://localhost:8888${path}`; + return `https://api.42world.kr${path}`; // 실제 배포 API 서버 + // return `http://api-alpha.42world.kr:8888${path}`; // 개발용 서버, 토큰 하드코딩으로 들어있어야 함. + // return `http://localhost:8888${path}`; // Live share 할 때 서버 } export function AXIOS(option) { return axios({ ...option, withCredentials: true, + // headers: { + // // https://api-alpha.42world.kr 로 요청 시 필요. 배포 시에는 주석화 + // Authorization: process.env.REACT_APP_MASTER_ACCESS_TOKEN, // 루트에 .env 파일 만든 후 REACT_APP_MASTER_ACCESS_TOKEN을 가져옴. + // }, }); } diff --git a/src/Network/ArticleService.js b/src/Network/ArticleService.js index 8dc863a..4cdee94 100644 --- a/src/Network/ArticleService.js +++ b/src/Network/ArticleService.js @@ -1,4 +1,5 @@ import * as API from './APIType'; +import { useQuery } from 'react-query'; const articleUrl = path => { return `${API.url('/articles')}${path}`; @@ -91,10 +92,30 @@ const ArticleService = { * `200` : success \ * `401` : fail */ - getArticles: async (categoryId, page) => { + getAllArticles: async (categoryId, page) => { const method = 'GET'; const url = articleUrl(''); const take = 1000; + // const take = 3; + const params = { categoryId, page, take }; + + let response; + try { + response = await API.AXIOS({ + params, + method, + url, + }); + } catch (error) { + alert(error); + } + return response.data; + }, + getArticles: async (categoryId, page) => { + const method = 'GET'; + const url = articleUrl(''); + // const take = 1000; + const take = 3; const params = { categoryId, page, take }; let response; @@ -144,21 +165,23 @@ const ArticleService = { * `200` : success * `401` : fail */ - getArticlesById: async articlesId => { + getArticleById: async articleId => { const method = 'GET'; - const url = articleUrl(`/${articlesId}`); + const url = articleUrl(`/${articleId}`); - let response; - try { - response = await API.AXIOS({ - method, - url, - }); - } catch (error) { - alert(error); - } + const response = await API.AXIOS({ + method, + url, + }); return response.data; }, + // useArticle(articleId) { + // return useQuery( + // ['getArticleById', articleId], + // async () => await this.getArticleById(articleId), + // { suspense: true }, + // ); + // }, /** * **UPDATE** One Articles By Articles ID * @param {string} articlesId @@ -195,15 +218,10 @@ const ArticleService = { const method = 'DELETE'; const url = articleUrl(`/${articlesId}`); - let response; - try { - response = await API.AXIOS({ - method, - url, - }); - } catch (error) { - alert(error); - } + const response = await API.AXIOS({ + method, + url, + }); return response.data; }, /** @@ -237,18 +255,25 @@ const ArticleService = { const url = articleUrl(`/${articlesId}/comments`); const params = { order, page, take }; - let response; - try { - response = await API.AXIOS({ - method, - url, - params, - }); - } catch (error) { - alert(error); - } + const response = await API.AXIOS({ + method, + url, + params, + }); return response.data; }, + // useComments(articleId, order, page, take) { + // return useQuery( + // ['getCommentsById'], + // async () => + // await ArticleService.getArticlesCommentsById( + // articleId, + // order, + // page, + // take, + // ), + // ); + // }, editArticles: async (articlesId, articles) => { const method = 'PUT'; const url = articleUrl(`/${articlesId}`); diff --git a/src/Network/ArticleService_old.js b/src/Network/ArticleService_old.js deleted file mode 100644 index 8a162af..0000000 --- a/src/Network/ArticleService_old.js +++ /dev/null @@ -1,93 +0,0 @@ -// # 게시글 /articles -import { Article, Comment } from '../Entities'; -import * as API from './APIType'; - -const authUrl = path => { - return `${API.url('/artiles')}path`; -}; - -// - 가져오기 -// - 카테고리별 게시글 목록 GET /articles?category=”anonymous” -// - 게시글 상세 GET /articles/:id -// - 조회수 갱신까지 같이 한다. <❗️추후 수정 여지 있음❗️> -// - 댓글 가져오기 GET /articles/:id/comments -// - 추가하기 POST /articles -// - 수정하기 -// - 본문 / 제목 수정하기 PUT /articles/:id -// - 지우기 DELETE /articles/:id - -const generateRandomArticle = () => { - const id = 1; - const category_id = 1; - const writer_id = 1; - const title = 'this is title'; - const content = 'this is content'; - const view_count = 1; - const comment_count = 2; - const liked_count = 3; - - return new Article( - id, - category_id, - writer_id, - title, - content, - view_count, - comment_count, - liked_count, - ); -}; - -const generateRandomComment = () => { - const id = 1; - const article_id = 1; - const writer_id = 1; - const content = 'this is comment'; - - return new Comment(id, article_id, writer_id, content); -}; - -const ArticleService = { - fetchAllArticle: category_id => { - return [ - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - generateRandomArticle(), - ]; - }, - fetchArticle: id => { - return generateRandomArticle(); - }, - fetchArticleComments: id => { - return [ - generateRandomComment(), - generateRandomComment(), - generateRandomComment(), - ]; - }, - createArticle: (title, content) => { - return true; - }, - updateArticle: (id, title, content) => { - return true; - }, - deleteArticle: id => { - return true; - }, -}; - -export default ArticleService; diff --git a/src/Network/CommentService_old.js b/src/Network/CommentService_old.js deleted file mode 100644 index 89fd53a..0000000 --- a/src/Network/CommentService_old.js +++ /dev/null @@ -1,28 +0,0 @@ -// # 댓글 /comments - -// - 쓰기 POST /comments/:id -// - 수정하기 PUT /comments/:id -// - 삭제하기 DELETE /comments/:id - -const generateRandomComment = () => { - const id = 1; - const article_id = 1; - const writer_id = 1; - const content = "this is comment"; - - return new Comment(id, article_id, writer_id, content); -}; - -const CommentService = { - createComment: (article_id, writer_id, content) => { - return generateRandomComment(); - }, - updateComment: (id) => { - return true; - }, - deleteComment: (id) => { - return true; - }, -}; - -export default CommentService; diff --git a/src/Network/ImageService.js b/src/Network/ImageService.js new file mode 100644 index 0000000..fd774de --- /dev/null +++ b/src/Network/ImageService.js @@ -0,0 +1,42 @@ +import axios from 'axios'; +import * as API from './APIType'; + +const imageUrl = () => { + return `${API.url('/image')}`; +}; + +const ImageService = { + getUploadUrl: async () => { + try { + const method = 'POST'; + const url = imageUrl(); + + const { data } = await API.AXIOS({ + method, + url, + }); + const { uploadUrl } = data; + return uploadUrl; + } catch (err) { + console.log('Error: ', err); + console.error(err); + } + }, + + uploadImage: async image => { + try { + const uploadUrl = await ImageService.getUploadUrl(); // 이미지 업로드 할 url 생성 + + const blobData = image; + + await axios.put(uploadUrl, blobData); // 이미지 업로드 + + return uploadUrl.split('?')[0]; // 업로드 된 이미지 링크를 return + } catch (err) { + console.log('Error: ', err); + console.error(err); + } + }, +}; + +export default ImageService; diff --git a/src/Network/NotificationService.js b/src/Network/NotificationService.js index 77742d4..05de1ed 100644 --- a/src/Network/NotificationService.js +++ b/src/Network/NotificationService.js @@ -34,7 +34,7 @@ const NotificationService = { */ readAllNotifications: async () => { const method = 'PATCH'; - const url = notificationUrl(''); + const url = notificationUrl('/readall'); let response; try { response = await API.AXIOS({ diff --git a/src/Network/ReactionService.js b/src/Network/ReactionService.js index 27f22be..9d4264a 100644 --- a/src/Network/ReactionService.js +++ b/src/Network/ReactionService.js @@ -13,15 +13,10 @@ const ReactionService = { createArticleReactionHeart: async id => { const method = 'POST'; const url = reactionUrl(`/${id}`); - let response; - try { - response = await API.AXIOS({ - method, - url, - }); - } catch (error) { - console.log('error'); - } + const response = await API.AXIOS({ + method, + url, + }); return response.data; }, @@ -34,15 +29,10 @@ const ReactionService = { createCommentReactionHeart: async (articleId, commentId) => { const method = 'POST'; const url = reactionUrl(`/${articleId}/comments/${commentId}`); - let response; - try { - response = await API.AXIOS({ - method, - url, - }); - } catch (error) { - console.log('error'); - } + const response = await API.AXIOS({ + method, + url, + }); return response.data; }, }; diff --git a/src/Network/UserService_old.js b/src/Network/UserService_old.js deleted file mode 100644 index bc482d5..0000000 --- a/src/Network/UserService_old.js +++ /dev/null @@ -1,94 +0,0 @@ -import { User, Notification } from '../Entities'; -import * as API from './APIType'; -// # 유저 /users - -// - 인증 -// - 로그아웃 DELETE /users/logout -// - 깃헙 로그인 인증 POST /users/github -// - 유저 42 인증 요청 -// - 42 로그인으로 인증 요청 POST /users/42auth/login -// - 42 이메일로 인증 요청 POST /users/42auth/email -// - 회원 탈퇴 DELETE /users -// - 정보 -// - 로그인 및 내 정보 가져오기 GET /users -// - 프로필 정보 수정 PUT /users/profile -// - 닉네임 중복확인 GET /users/nickname -// - 알람 -// - 가져오기 GET /users/alarm -// - 읽음 PUT /users/alarm/readall <❗️추후 수정 여지 있음❗️> - -const generateRandomUser = () => { - const id = 1; - const nickname = 'ycha'; - const is_authenticated = true; - const role = 'CADET'; - const charactor = 'https://picsum.photos/200/200'; - - return new User(id, nickname, is_authenticated, role, charactor); -}; - -const generateRandomNotification = () => { - const id = 1; - const user_id = 'ycha'; - const type = 'NEW_COMMENT'; - const content = 'asdf'; - const time = new Date(); - const is_read = true; - - return new Notification(id, user_id, type, content, time, is_read); -}; - -const UserService = { - Auth: { - signOut: () => {}, - validateGithub: token => { - return true; - }, - validate42Login: intra_token => { - return true; - }, - validate42Email: intra_id => { - return true; - }, - deleteUser: () => {}, - }, - Info: { - fetchUser: async () => { - try { - const method = 'GET'; - const headers = {}; - const body = {}; - const url = API.url('path'); - const response = await API.AXIOS({ - method, - headers, - body, - url, - }); - } catch (error) { - console.log('error'); - } - return generateRandomUser(); - }, - updateUserProfile: (character, nickname) => { - return true; - }, - checkDuplicateNickname: nickname => { - return true; - }, - }, - Noti: { - fetchNotification: () => { - return [ - generateRandomNotification(), - generateRandomNotification(), - generateRandomNotification(), - ]; - }, - updateNotification: () => { - return true; - }, - }, -}; - -export default UserService; diff --git a/src/Network/index.js b/src/Network/index.js new file mode 100644 index 0000000..b221bd5 --- /dev/null +++ b/src/Network/index.js @@ -0,0 +1,25 @@ +import ArticleService from './ArticleService'; +import AuthService from './AuthService'; +import BestService from './BestService'; +import CategoryService from './CategoryService'; +import CheckInService from './CheckInService'; +import CommentService from './CommentService'; +import FtAuthService from './FtAuthService'; +import NotificationService from './NotificationService'; +import ReactionService from './ReactionService'; +import UserService from './UserService'; +import ImageService from './ImageService'; + +export { + ArticleService, + AuthService, + BestService, + CategoryService, + CheckInService, + CommentService, + FtAuthService, + NotificationService, + ReactionService, + UserService, + ImageService, +}; diff --git a/src/Pages/AlarmPage/AlarmPage.jsx b/src/Pages/AlarmPage/AlarmPage.jsx index 57f802d..f94c760 100644 --- a/src/Pages/AlarmPage/AlarmPage.jsx +++ b/src/Pages/AlarmPage/AlarmPage.jsx @@ -1,7 +1,8 @@ -import { ProfileHeader } from '../../Components'; -import GlobalStyled from '../../Styled/Global.styled'; +import { ProfileHeader } from 'Components'; import { AlarmBody } from './Components'; +import GlobalStyled from 'Styled/Global.styled'; + const AlarmPage = () => { return ( diff --git a/src/Pages/AlarmPage/Components/AlarmArticle.styled.js b/src/Pages/AlarmPage/Components/AlarmArticle.styled.js index bfb2e5c..5e3741c 100644 --- a/src/Pages/AlarmPage/Components/AlarmArticle.styled.js +++ b/src/Pages/AlarmPage/Components/AlarmArticle.styled.js @@ -1,31 +1,44 @@ import styled from 'styled-components'; +import GlobalStyled from 'Styled/Global.styled'; const AlramArticlesDiv = styled.div` - padding: 1rem 0; + padding: 0; `; const AlramArticleDiv = styled.div` - cursor: pointer; box-sizing: border-box; width: 100%; display: flex; flex-direction: row; justify-content: space-between; align-items: center; - padding: 0.2rem 0.45rem; - border-bottom: 1px solid #e6e6e6; + padding: 0.7rem 0.5rem; + border-bottom: 1px solid #ddd; background-color: #fff; + ${props => (props.isNotice ? `cursor: default;` : `cursor: pointer;`)} + ${props => + props.isRead && `background-color: ${GlobalStyled.theme.textColorLight};`} + ${props => + props.isNotice && `background-color: ${GlobalStyled.theme.primary}99;`} .left { - font-size: 0.9rem; - font-weight: 300; + word-break: keep-all; + white-space: nowrap; + min-width: 2.5rem; + font-size: 0.7rem; + font-weight: 400; } .middle { - font-size: 0.9rem; + font-size: 0.8rem; font-weight: 500; + margin-left: 0.8rem; + margin-right: auto; + //margin: 0 0.8rem; } .right { - font-size: 0.9rem; + word-break: keep-all; + white-space: nowrap; + font-size: 0.7rem; font-weight: 300; } `; diff --git a/src/Pages/AlarmPage/Components/AlarmBody.jsx b/src/Pages/AlarmPage/Components/AlarmBody.jsx index 6e5207e..1774a34 100644 --- a/src/Pages/AlarmPage/Components/AlarmBody.jsx +++ b/src/Pages/AlarmPage/Components/AlarmBody.jsx @@ -1,32 +1,32 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import dayjs from 'dayjs'; -import { getCategoryById } from '../../../Utils'; +import NotificationService from 'Network/NotificationService'; +import { getArticleTime } from 'Utils/dayjsUtils'; + import Styled from './AlarmArticle.styled.js'; -import NotificationService from '../../../Network/NotificationService'; const alarmType = (context, type) => { let message; switch (type) { case 'NEW_COMMENT': { - message = '글에 답글이 달렸어요.'; + message = ' 글에 답글이 달렸어요.'; break; } default: - message = '글을 확인해보세요.'; + message = ' 글을 확인해보세요.'; } return '"' + context + '"' + message; }; const AlarmBody = () => { - const [alarmArticles, setAlarmArticles] = useState(); + const [alarmArticles, setAlarmArticles] = useState([]); + const navi = useNavigate(); const mainTextLen = 10; const moveArticles = articleId => { - alert('구현 중입니다!'); - // navi(`/article/${articleId}`); + navi(`/article/${articleId}`); }; const previewMainText = article => { @@ -37,23 +37,22 @@ const AlarmBody = () => { return alarmType(context, article.type); }; - const getArticleTime = time => dayjs(time).format('MM/DD HH:mm'); - - const categoryName = article => { - return getCategoryById(article.userId); + const readAlarm = async () => { + const read = await NotificationService.readAllNotifications(); }; useEffect(() => { const getArticles = async () => { const response = await NotificationService.getNotifications(); // 알람 API 필요 - setAlarmArticles(response); + setAlarmArticles(response.reverse()); }; getArticles(); + readAlarm(); // 글을 받아온 후 알림을 읽는다. 다음 번 불러올 때 이번에 읽어온 글의 isRead가 true가 된다. }, []); return ( - +
공지
42월드 많이 이용해주세요!
01/30 00:00
@@ -63,18 +62,15 @@ const AlarmBody = () => { return ( moveArticles(article.userId)} + isRead={article.isRead} + isNotice={false} + onClick={() => moveArticles(article.articleId)} > - {/*
{categoryName(article)}
*/}
새 댓글
{previewMainText(article)}
{getArticleTime(article.createdAt)}
- - // 인기글 가져오기, 지금은 보류. ); })}
diff --git a/src/Pages/ArticlePage/ArticlePage.jsx b/src/Pages/ArticlePage/ArticlePage.jsx index a511ff5..02b5d15 100644 --- a/src/Pages/ArticlePage/ArticlePage.jsx +++ b/src/Pages/ArticlePage/ArticlePage.jsx @@ -1,29 +1,32 @@ -import { useEffect, useState } from 'react'; +import { Suspense, useContext } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; import { useParams } from 'react-router-dom'; -import { Body, CommentContainer } from './Components'; -import { Header } from '../../Components'; + +import { Header } from 'Components'; +import { Article } from './Components'; +import { Comments } from './Components'; +import { Loading } from 'Components'; import Styled from './ArticlePage.styled'; -import ArticleService from 'Network/ArticleService'; +import { ErrorPage } from 'Pages'; +import { AuthContext } from 'App'; +import CreateComment from './Components/CreateComment'; + const ArticlePage = () => { + const { curUser } = useContext(AuthContext); const { id } = useParams(); - const [categoryId, setCategoryId] = useState(0); - const getCate = async () => { - const result = await ArticleService.getArticlesById(id); - setCategoryId(result.categoryId); - }; - useEffect(() => { - getCate(); - }, []); return ( - <> + }>
- - - {categoryId !== 3 && } - - + }> + +
+ + + + + ); }; diff --git a/src/Pages/ArticlePage/ArticlePage.styled.js b/src/Pages/ArticlePage/ArticlePage.styled.js index 01fc17c..7f7549d 100644 --- a/src/Pages/ArticlePage/ArticlePage.styled.js +++ b/src/Pages/ArticlePage/ArticlePage.styled.js @@ -1,115 +1,87 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import GlobalStyled from 'Styled/Global.styled'; -const ProfileImage = styled.img` - ${props => (props.width ? `width: ${props.width};` : 'width: 2.5rem;')} - ${props => - props.width ? `min-width: ${props.width};` : 'min-width: 2.5rem;'} - ${props => (props.width ? `height: ${props.width};` : 'height: 2.5rem;')} - border-radius: 10%; - border: 0px; -`; - -const CommentContent = styled.div` +const ArticlePageDiv = styled.div` display: flex; - flex-direction: row; - align-items: flex-end; - - .text { - padding: 0.5rem 0.8rem; - background: ${GlobalStyled.theme.textColorLightGray}; - border-radius: 0.5rem; - font-size: 0.75rem; - font-weight: 300; - margin-right: 0.3rem; - } + flex-direction: column; + align-items: center; - .liked_count { + .comment_list_div { display: flex; + flex-direction: column; align-items: center; - justify-content: center; - flex-direction: row; - margin-bottom: 0.2rem; - margin-right: 1rem; - font-size: 0.75rem; - color: ${GlobalStyled.theme.likedCountColor}; - - svg { - width: 1rem; - height: 1rem; - margin-right: 0.1rem; - cursor: pointer; - } - - &::after { - content: '${props => { - if (props.liked_count > 0) return props.liked_count; - else return ''; - }}'; + width: 100%; } } `; -const CreateCommentDiv = styled.div` - position: sticky; - bottom: 1rem; +/// Article +const ArticleViewDiv = styled.div` display: flex; - align-items: center; - justify-content: center; - flex-direction: row; - + flex-direction: column; width: 100%; - margin: 1rem; - @media (min-width: ${GlobalStyled.theme.mobileMinWidth}) { - width: ${GlobalStyled.theme.desktopWidth}; - } - - form { - width: 90%; - background-color: ${GlobalStyled.theme.textColorLight}; - border-radius: 2rem; + .content_top { display: flex; flex-direction: row; - align-items: center; - justify-content: space-between; - padding: 0.5rem 0.5rem 0.5rem 1rem; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.19); + align-items: flex-end; + padding: 0.7rem; + width: 100%; + border-bottom: 1px solid ${GlobalStyled.theme.borderColor}; - input { - width: 90%; - color: ${GlobalStyled.theme.textColor}; - background-color: rgba(0, 0, 0, 0); - border: none; - &::placeholder { - color: ${GlobalStyled.theme.textColorGray}; - } - &:focus { - outline: none; - border: none; + .title { + display: flex; + flex-direction: column; + width: 100%; + + h1 { + font-size: 1rem; + font-weight: 600; + color: ${GlobalStyled.theme.textColor}; + margin-bottom: 0.2rem; + padding-right: 0.5rem; } - } - button { - box-sizing: content-box; - border: none; - background: none; - background-color: ${GlobalStyled.theme.primary}; - color: ${GlobalStyled.theme.textColorWhite}; - width: 40px; - height: 40px; - margin-left: 0.5rem; - border-radius: 50%; - box-shadow: none; + .info { + display: flex; + flex-direction: row; + align-items: center; - &:hover { - background-color: ${GlobalStyled.theme.primary}; + h2 { + color: ${GlobalStyled.theme.textColorGray}; + font-size: 0.7rem; + font-weight: 300; + margin-right: 0.8rem; + } + + .edit_article { + display: flex; + flex-direction: row; + align-items: flex-end; + margin: 0 0.5rem; + margin-left: auto; + + button { + padding: 0; + border: none; + width: max-content; + background: none; + cursor: pointer; + margin: 0 0.3rem; + font-size: 0.7rem; + font-weight: 400; + } + } } } } + + .toastui-editor-contents { + padding: 0.7rem; + } `; -const ArticleLikedDiv = styled.div` +const ArticleReactionDiv = styled.div` display: flex; align-items: center; justify-content: center; @@ -133,8 +105,10 @@ const ArticleLikedDiv = styled.div` }}'; } `; +/// -const ArticleCommentDiv = styled.div` +/// Comments +const CommentsCountDiv = styled.div` display: flex; flex-direction: row; width: 100%; @@ -162,139 +136,188 @@ const ArticleCommentDiv = styled.div` } `; -const ArticlePageDiv = styled.div` +const CommentViewDiv = styled.div` + border-top: 1px solid #e6e6e6; + padding: 0.5rem 0.8rem; + width: 100%; display: flex; flex-direction: column; - align-items: center; - .comment_list_div { + .info { + margin-bottom: 0.4rem; display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - width: 100%; + ${props => props.isMine && `flex-direction: row-reverse;`} + + .text { + margin: 0rem 0.7rem; + ${props => props.isMine && `text-align: right;`} + h1 { + color: ${GlobalStyled.theme.textColor}; + font-size: 0.9rem; + font-weight: 600; + line-height: 1.1; + } + h2 { + color: ${GlobalStyled.theme.textColorGray}; + font-size: 0.4rem; + font-weight: 400; + } + } + } - .comment_div { - border-top: 1px solid #e6e6e6; - padding: 0.5rem 0.8rem; - width: 100%; - display: flex; - flex-direction: column; + .text { + margin-left: 0.2rem; + word-break: break-all; + } +`; - .info { - margin-bottom: 0.4rem; - display: flex; - flex-direction: row; - align-items: center; +const CommentContent = styled.div` + display: flex; + ${props => + props.isMine + ? css` + flex-direction: row-reverse; + ` + : css` + flex-direction: row; + `} + align-items: flex-end; - .text { - margin-left: 0.7rem; - h1 { - color: ${GlobalStyled.theme.textColor}; - font-size: 0.9rem; - font-weight: 600; - line-height: 1.1; - } - h2 { - color: ${GlobalStyled.theme.textColorGray}; - font-size: 0.4rem; - font-weight: 400; - } - } - } + .text { + padding: 0.5rem 0.8rem; + ${props => + props.isMine + ? css` + background: ${GlobalStyled.theme.textColorMint}; + color: white; + ` + : css` + background: ${GlobalStyled.theme.textColorLightGray}; + `} + border-radius: 0.5rem; + font-size: 0.75rem; + font-weight: 300; + margin: 0 0.3rem; + } - .text { - margin-left: 0.2rem; - word-break: break-all; - } + .delete_button { + background: none; + border: none; + font-size: 0.7rem; + white-space: nowrap; + margin-bottom: 0.2rem; + margin-left: 1rem; + margin-right: 0.2rem; + color: ${GlobalStyled.theme.textColorSecondary}; + } + + .liked_count { + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + margin-bottom: 0.2rem; + margin-right: 1rem; + font-size: 0.75rem; + color: ${GlobalStyled.theme.likedCountColor}; + + svg { + width: 1rem; + height: 1rem; + margin-right: 0.1rem; + cursor: pointer; } - .comment_count { + &::after { + content: '${props => { + if (props.liked_count > 0) return props.liked_count; + else return ''; + }}'; } } +`; +/// - .content_div { - display: flex; - flex-direction: column; - width: 100%; - - .content_top { - display: flex; - flex-direction: row; - align-items: flex-end; - padding: 0.7rem; - width: 100%; - border-bottom: 1px solid ${GlobalStyled.theme.borderColor}; +/// CreateComment +const CreateCommentViewDiv = styled.div` + position: sticky; + bottom: 1rem; + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; - .title { - display: flex; - flex-direction: column; - width: 100%; - - h1 { - font-size: 1rem; - font-weight: 600; - color: ${GlobalStyled.theme.textColor}; - margin-bottom: 0.2rem; - padding-right: 0.5rem; - } + width: 100%; + margin: 1rem; - .info { - display: flex; - flex-direction: row; - align-items: center; + @media (min-width: ${GlobalStyled.theme.mobileMinWidth}) { + width: ${GlobalStyled.theme.desktopWidth}; + } - h2 { - color: ${GlobalStyled.theme.textColorGray}; - font-size: 0.7rem; - font-weight: 300; - margin-right: 0.8rem; - } + form { + width: 90%; + background-color: ${GlobalStyled.theme.textColorLight}; + border-radius: 2rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.5rem 0.5rem 1rem; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.19); - .edit_article { - display: flex; - flex-direction: row; - align-items: flex-end; - margin: 0 0.5rem; - margin-left: auto; - - button { - padding: 0; - border: none; - width: max-content; - background: none; - cursor: pointer; - margin: 0 0.3rem; - font-size: 0.7rem; - font-weight: 400; - } - } - } + input { + width: 90%; + color: ${GlobalStyled.theme.textColor}; + background-color: rgba(0, 0, 0, 0); + border: none; + &::placeholder { + color: ${GlobalStyled.theme.textColorGray}; + } + &:focus { + outline: none; + border: none; } } - .content_middle { - padding: 0.7rem; - font-size: 0.85rem; - font-weight: 400; - - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; + button { + box-sizing: content-box; + border: none; + background: none; + background-color: ${GlobalStyled.theme.primary}; + color: ${GlobalStyled.theme.textColorWhite}; + width: 40px; + height: 40px; + margin-left: 0.5rem; + border-radius: 50%; + box-shadow: none; - color: ${GlobalStyled.theme.textColor}; + &:hover { + background-color: ${GlobalStyled.theme.primary}; + } } } `; +const ProfileImage = styled.img` + ${props => (props.width ? `width: ${props.width};` : 'width: 2.5rem;')} + ${props => + props.width ? `min-width: ${props.width};` : 'min-width: 2.5rem;'} + ${props => (props.width ? `height: ${props.width};` : 'height: 2.5rem;')} + border-radius: 10%; + border: 0px; +`; + const Styled = { ArticlePageDiv, - ProfileImage, + ArticleViewDiv, + ArticleReactionDiv, + CommentsCountDiv, + CommentViewDiv, CommentContent, - ArticleLikedDiv, - ArticleCommentDiv, - CreateCommentDiv, + CreateCommentViewDiv, + ProfileImage, }; export default Styled; diff --git a/src/Pages/ArticlePage/Components/Article.jsx b/src/Pages/ArticlePage/Components/Article.jsx new file mode 100644 index 0000000..fa278de --- /dev/null +++ b/src/Pages/ArticlePage/Components/Article.jsx @@ -0,0 +1,44 @@ +import { useNavigate } from 'react-router-dom'; + +import '@toast-ui/editor/dist/toastui-editor.css'; + +import { getCategoryById, getProfile } from 'Utils'; +import { useArticle, useLikeArticle, useDeleteArticle } from './hooks'; +import { getArticleTime } from 'Utils/dayjsUtils'; +import ArticleView from './ArticleView'; + +const Article = ({ articleId, currentUserId }) => { + const navi = useNavigate(); + const { data } = useArticle(articleId); + const likeArticle = useLikeArticle(articleId); + const deleteArticle = useDeleteArticle(articleId); + + const handleClickEdit = () => { + navi(`/article/${articleId}/edit`, { state: { article: data } }); + }; + + const handleClickCategory = () => { + navi(`/category/${data.categoryId}`); + }; + + const props = { + handleClickCategory, + category: getCategoryById(data.categoryId), + title: data.title, + writer: data.writer.nickname, + time: getArticleTime(data.createdAt), + viewCount: data.viewCount, + isModifiable: data.writer.id === currentUserId, + handleClickEdit, + handleClickDelete: deleteArticle.mutate, + profileSrc: getProfile.findProfileById(data.writer.character), + content: data.content, + isReactionPossible: data.categoryId !== 3, + likeCount: data.likeCount, + handleClickLike: likeArticle.mutate, + isLike: data.isLike, + }; + return ; +}; + +export default Article; diff --git a/src/Pages/ArticlePage/Components/ArticleView.jsx b/src/Pages/ArticlePage/Components/ArticleView.jsx new file mode 100644 index 0000000..7b0bfae --- /dev/null +++ b/src/Pages/ArticlePage/Components/ArticleView.jsx @@ -0,0 +1,63 @@ +import { Viewer } from '@toast-ui/react-editor'; + +import { FavoriteBorder } from '@mui/icons-material'; +import FavoriteIcon from '@mui/icons-material/Favorite'; + +import GlobalStyled from 'Styled/Global.styled'; +import Styled from '../ArticlePage.styled'; + +const ArticleView = ({ + handleClickCategory, + category, + title, + writer, + time, + viewCount, + isModifiable, + handleClickEdit, + handleClickDelete, + profileSrc, + content, + isReactionPossible, + likeCount, + handleClickLike, + isLike, +}) => { + return ( + + +
{category}
+
+
+
+

{title}

+
+

{writer}

+

{time}

+

조회수 {viewCount}

+ {isModifiable && ( +
+ + +
+ )} +
+
+ +
+ + {isReactionPossible && ( + + + {isLike ? : } + + + )} +
+ ); +}; + +export default ArticleView; diff --git a/src/Pages/ArticlePage/Components/Body.jsx b/src/Pages/ArticlePage/Components/Body.jsx deleted file mode 100644 index 3301147..0000000 --- a/src/Pages/ArticlePage/Components/Body.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import { FavoriteBorder, SmsOutlined } from '@mui/icons-material'; -import FavoriteIcon from '@mui/icons-material/Favorite'; -import { useState, useEffect, useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { getCategoryById } from '../../../Utils'; - -import ArticleService from '../../../Network/ArticleService'; -import { AuthContext } from '../../../App'; -import GlobalStyled from '../../../Styled/Global.styled'; -import dayjs from 'dayjs'; -import Styled from '../ArticlePage.styled'; -import ReactionService from 'Network/ReactionService'; -import { getProfileImg } from 'Utils/profileList'; - -const Body = ({ articleId, categoryId }) => { - const [article, setArticle] = useState(); - const [isLike, setIsLike] = useState(false); - const [likeCount, setLikeCount] = useState(0); - const navi = useNavigate(); - const handleClickEdit = () => { - navi(`/article/${articleId}/edit`); - }; - const handleClickDelete = () => { - ArticleService.deleteArticles(articleId); - // navi(`/category/${categoryId}`); - navi(-1); - }; - const { curUser } = useContext(AuthContext); - useEffect(() => { - const fetch = async () => { - const res = await ArticleService.getArticlesById(articleId); - setArticle(res); - setIsLike(res.isLike); - setLikeCount(res.likeCount); - }; - - fetch(); - }, []); - - const getArticleTime = time => - dayjs(time).isSame(dayjs(), 'day') - ? dayjs(time).format('HH:mm') - : dayjs(time).format('MM/DD'); - - const handleClickLike = async () => { - const data = await ReactionService.createArticleReactionHeart(articleId); - setIsLike(data.isLike); - setLikeCount(data.likeCount); - }; - - if (!article) return <>; - return ( -
- { - navi(`/category/${article.categoryId}`); - }} - > -
{getCategoryById(article.categoryId)}
-
-
-
-

{article.title}

-
-

{article.writer.nickname}

-

{getArticleTime(article.createdAt)}

-

조회수 {article.viewCount}

- {article.writer.id === curUser.id && ( -
- - {/* */} -
- )} -
-
- -
-
{article.content}
-
- {categoryId !== 3 && ( - - - {isLike ? : } - - - )} -
-
- ); -}; - -export default Body; diff --git a/src/Pages/ArticlePage/Components/Comment.jsx b/src/Pages/ArticlePage/Components/Comment.jsx index 4645ef3..d6a1e79 100644 --- a/src/Pages/ArticlePage/Components/Comment.jsx +++ b/src/Pages/ArticlePage/Components/Comment.jsx @@ -1,55 +1,27 @@ -import React, { useContext, useState } from 'react'; -import ReactionService from 'Network/ReactionService'; -import Styled from '../ArticlePage.styled'; -import { FavoriteBorder } from '@mui/icons-material'; -import FavoriteIcon from '@mui/icons-material/Favorite'; +import React from 'react'; -import dayjs from 'dayjs'; -import { AuthContext } from 'App'; -import { getProfileImg } from 'Utils/profileList'; +import { getProfile } from 'Utils'; +import { getArticleTime } from 'Utils/dayjsUtils'; +import CommentView from './CommentView'; -const Comment = ({ articleId, comment, isLikeInitial, likeCountInitial }) => { - const [isLike, setIsLike] = useState(isLikeInitial); - const [likeCount, setLikeCount] = useState(likeCountInitial); - const auth = useContext(AuthContext); +import { useDeleteComment, useLikeComment } from './hooks'; - const getArticleTime = time => - dayjs(time).isSame(dayjs(), 'day') - ? dayjs(time).format('HH:mm') - : dayjs(time).format('MM/DD'); +const Comment = ({ currentUserId, articleId, comment }) => { + const likeComment = useLikeComment(comment.id, articleId); + const deleteComment = useDeleteComment(comment.id, articleId); - const handleClickLike = async id => { - const res = await ReactionService.createCommentReactionHeart(articleId, id); - - setIsLike(res.isLike); - setLikeCount(res.likeCount); + const props = { + isMine: currentUserId === comment.writer.id, + profileSrc: getProfile.findProfileById(comment.writer.character), + writer: comment.writer.nickname, + time: getArticleTime(comment.createdAt), + likeCount: comment.likeCount, + content: comment.content, + handleClickDelete: deleteComment.mutate, + handleClickLike: likeComment.mutate, + isLike: comment.isLike, }; - return ( - <> -
-
- -
-
-

{comment?.writer?.nickname}

-

{getArticleTime(comment?.updatedAt)}

-
-
- -
{comment.content}
- handleClickLike(comment?.id)} - > - {isLike ? : } - -
-
- - ); + return ; }; -export default Comment; +export default React.memo(Comment); diff --git a/src/Pages/ArticlePage/Components/CommentContainer.jsx b/src/Pages/ArticlePage/Components/CommentContainer.jsx deleted file mode 100644 index bafd185..0000000 --- a/src/Pages/ArticlePage/Components/CommentContainer.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -import Fab from '@mui/material/Fab'; -import ArrowUpwardRoundedIcon from '@mui/icons-material/ArrowUpwardRounded'; - -import ArticleService from '../../../Network/ArticleService'; -import CommentService from '../../../Network/CommentService'; -import { SmsOutlined } from '@mui/icons-material'; -import Styled from '../ArticlePage.styled'; -import Comment from './Comment'; - -const CommentContainer = ({ articleId }) => { - const lastComment = useRef(); - const [comments, setComments] = useState([]); - const [totalCount, setTotalCount] = useState(); - - const handleCreateComment = newComment => { - setComments(prev => prev.concat(newComment)); - lastComment.current.scrollIntoView(); - fetchComment(); - }; - - const fetchComment = async () => { - const res = await ArticleService.getArticlesCommentsById( - articleId, - 'ASC', - 1, - 1000, // 한번에 1000개 긁어옴. 어떻게 할 지 결정 후 바꿔야 함. - ); - setComments(res?.data || []); - setTotalCount(res?.meta.totalCount); - }; - - useEffect(() => { - fetchComment(); - }, []); - - return ( -
- - - - {comments && - comments.map(comment => ( - - ))} - - - -
-
- ); -}; - -const CreateComment = ({ articleId, handleCreateComment }) => { - const [input, setInput] = useState(''); - const handleChange = e => { - if (e.target.value.length < 420) { - setInput(e.target.value); - } - }; - const handleClickSubmit = async e => { - e.preventDefault(); - if (input === '') { - return; - } - - const res = await CommentService.createComments({ - content: input, - articleId: +articleId, - }); - if (res) { - handleCreateComment(res); - setInput(''); - } - }; - return ( -
- - - - -
- ); -}; - -export default CommentContainer; diff --git a/src/Pages/ArticlePage/Components/CommentView.jsx b/src/Pages/ArticlePage/Components/CommentView.jsx new file mode 100644 index 0000000..69ffa15 --- /dev/null +++ b/src/Pages/ArticlePage/Components/CommentView.jsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { FavoriteBorder } from '@mui/icons-material'; +import FavoriteIcon from '@mui/icons-material/Favorite'; + +import Styled from '../ArticlePage.styled'; + +const CommentView = ({ + isMine, + profileSrc, + writer, + time, + likeCount, + content, + handleClickDelete, + handleClickLike, + isLike, +}) => { + return ( + +
+ +
+
+

{writer}

+

{time}

+
+
+ +
{content}
+ {isMine ? ( + + ) : ( + + {isLike ? : } + + )} +
+
+ ); +}; + +export default React.memo(CommentView); diff --git a/src/Pages/ArticlePage/Components/Comments.jsx b/src/Pages/ArticlePage/Components/Comments.jsx new file mode 100644 index 0000000..604d8f8 --- /dev/null +++ b/src/Pages/ArticlePage/Components/Comments.jsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { useArticle, useComments } from './hooks'; +import Comment from './Comment'; +import { SmsOutlined } from '@mui/icons-material'; + +import Styled from '../ArticlePage.styled'; + +const Comments = ({ articleId, currentUserId }) => { + const { + data: { categoryId }, + } = useArticle(articleId); + const { + data: { meta, data }, + } = useComments(articleId, 'ASC', 1, 1000); + + return ( +
+ + + + {categoryId !== 3 && + data.map(comment => ( + + ))} +
+ ); +}; + +export default React.memo(Comments); diff --git a/src/Pages/ArticlePage/Components/CreateComment.jsx b/src/Pages/ArticlePage/Components/CreateComment.jsx new file mode 100644 index 0000000..1390bb3 --- /dev/null +++ b/src/Pages/ArticlePage/Components/CreateComment.jsx @@ -0,0 +1,28 @@ +import { useRef } from 'react'; + +import CreateCommentView from './CreateCommentView'; +import useInput from './useInput'; +import { useArticle, useComments, useCreateComment } from './hooks'; + +const CreateComment = ({ articleId }) => { + const [input, handleChange, reset] = useInput(); + const lastComment = useRef(); + const createComment = useCreateComment(input, articleId, lastComment); + + const handleClickSubmit = e => { + e.preventDefault(); + createComment.mutate(); + reset(); + }; + + const props = { + handleClickSubmit, + input, + handleChange, + placeholder: '댓글을 입력하세요', + lastComment, + }; + return ; +}; + +export default CreateComment; diff --git a/src/Pages/ArticlePage/Components/CreateCommentView.jsx b/src/Pages/ArticlePage/Components/CreateCommentView.jsx new file mode 100644 index 0000000..1e7f2a5 --- /dev/null +++ b/src/Pages/ArticlePage/Components/CreateCommentView.jsx @@ -0,0 +1,33 @@ +import Fab from '@mui/material/Fab'; +import ArrowUpwardRoundedIcon from '@mui/icons-material/ArrowUpwardRounded'; + +import Styled from '../ArticlePage.styled'; + +const CreateCommentView = ({ + handleClickSubmit, + input, + handleChange, + placeholder, + lastComment, +}) => { + return ( + <> + +
+ + + + +
+
+
+ + ); +}; + +export default CreateCommentView; diff --git a/src/Pages/ArticlePage/Components/hooks.js b/src/Pages/ArticlePage/Components/hooks.js new file mode 100644 index 0000000..a7b96f9 --- /dev/null +++ b/src/Pages/ArticlePage/Components/hooks.js @@ -0,0 +1,128 @@ +import { ArticleService, ReactionService, CommentService } from 'Network'; +import { useQuery, useMutation, useQueryClient } from 'react-query'; +import { useNavigate } from 'react-router'; + +export function useArticle(articleId) { + return useQuery( + ['getArticleById', articleId], + async () => await ArticleService.getArticleById(articleId), + { suspense: true }, + ); +} + +export function useComments(articleId, order, page, take) { + return useQuery(['getCommentsByArticleId', articleId], async () => { + return await ArticleService.getArticlesCommentsById( + articleId, + order, + page, + take, + ); + }); +} + +export function useLikeArticle(articleId) { + const queryClient = useQueryClient(); + return useMutation( + async () => await ReactionService.createArticleReactionHeart(articleId), + { + onSuccess: result => { + queryClient.setQueryData(['getArticleById', articleId], data => { + return { + ...data, + isLike: result.isLike, + likeCount: result.likeCount, + }; + }); + }, + }, + ); +} + +export function useDeleteArticle(articleId) { + const navigate = useNavigate(); + return useMutation( + async () => await ArticleService.deleteArticles(articleId), + { + onSuccess: () => { + navigate(-1); + }, + }, + ); +} + +// 좀 보기 싫긴 함.. +export function useCreateComment(input, articleId, lastComment) { + const queryClient = useQueryClient(); + return useMutation( + async () => { + return await CommentService.createComments({ + content: input, + articleId: +articleId, + }); + }, + { + onSuccess: async () => { + await queryClient.invalidateQueries([ + 'getCommentsByArticleId', + articleId, + ]); + if (lastComment.current) lastComment.current.scrollIntoView(); + }, + }, + ); +} + +export function useLikeComment(commentId, articleId) { + const queryClient = useQueryClient(); + return useMutation( + async () => + await ReactionService.createCommentReactionHeart(articleId, commentId), + { + // onMutate: () => { + // return { id: commentId }; + // }, + // onSuccess: (result, _, { id }) => { + // queryClient.setQueryData( + // ['getCommentsByArticleId', articleId], + // data => { + // console.log(data.data.find(comment => comment.id === id)['isLike']); + // return { ...data }; + // }, + // ); + // }, + onSuccess: () => { + // 새로 fetch + queryClient.invalidateQueries(['getCommentsByArticleId', articleId]); + }, + }, + ); +} + +export function useDeleteComment(commentId, articleId) { + const queryClient = useQueryClient(); + return useMutation( + async () => await CommentService.deleteComments(commentId), + { + onMutate: () => { + return { id: commentId }; + }, + onSuccess: (_, __, { id }) => { + // 새로 fetch + queryClient.invalidateQueries(['getCommentsByArticleId', articleId]); + + // 얘는 캐시된 데이터만 프론트 자체적으로 업데이트 + // meta 정보가 새로 업데이트 돼야 해서 아마 새로 fetch해와야할듯..? + // queryClient.setQueryData( + // ['getCommentsByArticleId', articleId], + // data => { + // const newData = data.data.filter(comment => comment.id !== id); + // console.log(data.meta); + // data.meta.totalCount -= 1; + // return { data: newData, meta: data.meta }; + // }, + // ); + }, + }, + ); +} diff --git a/src/Pages/ArticlePage/Components/index.jsx b/src/Pages/ArticlePage/Components/index.jsx index 2acb63a..2d4ac59 100644 --- a/src/Pages/ArticlePage/Components/index.jsx +++ b/src/Pages/ArticlePage/Components/index.jsx @@ -1,4 +1,4 @@ -import Body from './Body'; -import CommentContainer from './CommentContainer'; +import Article from './Article'; +import Comments from './Comments'; -export { Body, CommentContainer }; +export { Article, Comments }; diff --git a/src/Pages/ArticlePage/Components/useInput.js b/src/Pages/ArticlePage/Components/useInput.js new file mode 100644 index 0000000..c1ae89f --- /dev/null +++ b/src/Pages/ArticlePage/Components/useInput.js @@ -0,0 +1,15 @@ +import { useCallback, useState } from 'react'; + +function useInput() { + const [input, setInput] = useState(''); + + const handleChange = useCallback(e => { + if (e.target.value.length < 420) setInput(e.target.value); + }, []); + + const reset = useCallback(() => setInput(''), []); + + return [input, handleChange, reset]; +} + +export default useInput; diff --git a/src/Pages/AuthPage/AuthPage.jsx b/src/Pages/AuthPage/AuthPage.jsx index 27ce087..46dfb41 100644 --- a/src/Pages/AuthPage/AuthPage.jsx +++ b/src/Pages/AuthPage/AuthPage.jsx @@ -1,11 +1,12 @@ -import { ProfileHeader } from "../../Components"; -import { Auth } from "./Components"; -import GlobalStyled from "../../Styled/Global.styled"; +import { ProfileHeader } from 'Components'; +import { Auth } from './Components'; + +import GlobalStyled from 'Styled/Global.styled'; const AuthPage = () => { return ( - + ); diff --git a/src/Pages/AuthPage/Components/Auth.jsx b/src/Pages/AuthPage/Components/Auth.jsx index fc1bc2b..20fe0bf 100644 --- a/src/Pages/AuthPage/Components/Auth.jsx +++ b/src/Pages/AuthPage/Components/Auth.jsx @@ -1,11 +1,12 @@ import { useContext, useEffect, useState, useMemo } from 'react'; -import Styled from './Auth.styled'; -import FtAuthService from '../../../Network/FtAuthService'; -import { LoadingButton } from '@mui/lab'; -import { TextField } from '@mui/material'; -import SendIcon from '@mui/icons-material/Send'; import { useNavigate } from 'react-router-dom'; -import { AuthContext } from '../../../App'; + +import { AuthContext } from 'App'; +import { FtAuthService } from 'Network'; + +import { LoadingButton } from '@mui/lab'; + +import Styled from './Auth.styled'; const AuthRequestInformation = ({ intraId }) => { return ( @@ -34,8 +35,8 @@ const AuthRequestCheckStep = ({ handleSendReset }) => { ); }; -function checkKor(str) { - const regExp = /[ㄱ-ㅎㅏ-ㅣ가-힣]/g; +function checkIntraId(str) { + const regExp = /[A-Za-z0-9-]/g; if (regExp.test(str)) { return true; } else { @@ -68,7 +69,7 @@ const Auth = () => { }); }; const handleAuthenticate = () => { - if (input.email === '' || checkKor(input.email)) { + if (input.email === '' || !checkIntraId(input.email)) { setIsError(true); setTimeout(() => { setIsError(false); diff --git a/src/Pages/AuthPage/Components/Auth.styled.js b/src/Pages/AuthPage/Components/Auth.styled.js index d1d8a81..097b629 100644 --- a/src/Pages/AuthPage/Components/Auth.styled.js +++ b/src/Pages/AuthPage/Components/Auth.styled.js @@ -1,7 +1,9 @@ import styled, { keyframes } from 'styled-components'; -import GlobalStyled from '../../../Styled/Global.styled'; import { TextField } from '@mui/material'; + +import GlobalStyled from 'Styled/Global.styled'; + const fadeIn = keyframes` 0% { opacity: 0; diff --git a/src/Pages/CategoryPage/CategoryPage.jsx b/src/Pages/CategoryPage/CategoryPage.jsx index 0fd5f5b..7c21ae7 100644 --- a/src/Pages/CategoryPage/CategoryPage.jsx +++ b/src/Pages/CategoryPage/CategoryPage.jsx @@ -1,4 +1,4 @@ -import { Header } from '../../Components'; +import { Header } from 'Components'; import CategoryBody from './Components'; import Styled from './CategoryPage.styled.js'; diff --git a/src/Pages/CategoryPage/CategoryPage.styled.js b/src/Pages/CategoryPage/CategoryPage.styled.js index 66d2873..e215f02 100644 --- a/src/Pages/CategoryPage/CategoryPage.styled.js +++ b/src/Pages/CategoryPage/CategoryPage.styled.js @@ -1,4 +1,6 @@ import styled from 'styled-components'; +import GlobalStyled from 'Styled/Global.styled'; + import ListItem from '@mui/material/ListItem'; const MenuModal = styled.div` @@ -27,6 +29,35 @@ const MainBody = styled.div` } } } + .category_form { + * { + border: none !important; + border-bottom: none !important; + } + *:hover { + border: none !important; + border-bottom: none !important; + } + *::before { + border: none !important; + border-bottom: none !important; + } + *::after { + border: none !important; + border-bottom: none !important; + } + + select { + color: ${GlobalStyled.theme.categoryNameTextColor}; + font-size: 1.1rem; + font-weight: 700; + padding-top: 0; + padding-bottom: 0; + } + svg { + color: ${GlobalStyled.theme.categoryNameTextColor}; + } + } `; const CusListItem = styled(ListItem)` diff --git a/src/Pages/CategoryPage/Components/Body.styled.js b/src/Pages/CategoryPage/Components/Body.styled.js index 54c4fb1..87d9a06 100644 --- a/src/Pages/CategoryPage/Components/Body.styled.js +++ b/src/Pages/CategoryPage/Components/Body.styled.js @@ -1,9 +1,10 @@ import styled from 'styled-components'; -import List from '@mui/material/List'; -import GlobalStyled from '../../../Styled/Global.styled'; +import List from '@mui/material/List'; import Fab from '@mui/material/Fab'; +import GlobalStyled from 'Styled/Global.styled'; + const CreateButton = styled(Fab)` .fiVBwH { && { diff --git a/src/Pages/CategoryPage/Components/CategoryBody.jsx b/src/Pages/CategoryPage/Components/CategoryBody.jsx index 6476734..6c486ac 100644 --- a/src/Pages/CategoryPage/Components/CategoryBody.jsx +++ b/src/Pages/CategoryPage/Components/CategoryBody.jsx @@ -1,82 +1,79 @@ import { useState, useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { PreviewArticleNoti, PreviewArticle } from '../../../Components'; -import { getCategoryByUrl } from '../../../Utils'; - -import ArticleService from '../../../Network/ArticleService'; -import Styled from './Body.styled'; -import GlobalStyled from '../../../Styled/Global.styled'; +import { getCategoryByUrl } from 'Utils'; +import { ArticleService } from 'Network'; +import { PreviewArticleNoti, PreviewArticle } from 'Components'; import CircularProgress from '@mui/material/CircularProgress'; -import CreateIcon from '@mui/icons-material/Create'; +import NativeSelect from '@mui/material/NativeSelect'; +import FormControl from '@mui/material/FormControl'; import Fab from '@mui/material/Fab'; +import CreateIcon from '@mui/icons-material/Create'; + +import GlobalStyled from 'Styled/Global.styled'; +import Styled from './Body.styled'; const CategoryBody = () => { const [articles, setArticles] = useState([]); - const [isLoaded, setIsLoaded] = useState(true); - const [page, setPage] = useState(1); - const [hasNextPage, setHasNextPage] = useState(true); + const [isLoaded, setIsLoaded] = useState(false); + // const [page, setPage] = useState(1); + let page = 1; + // const [hasNextPage, setHasNextPage] = useState(true); + let hasNextPage = true; const [target, setTarget] = useState(null); - const [curCate, setCurCate] = useState(''); + const cateList = ['자유 게시판', '익명 게시판', '공지 게시판']; const loca = useLocation(); const navi = useNavigate(); const categoryId = loca.pathname.split('/')[2]; const handleClickWrite = () => { - navi(`${loca.pathname}/create`); + navi('/create'); }; const handleClickArticles = id => { navi(`/article/${id}`); }; - const setInitalArticles = async () => { - setIsLoaded(true); - const result = await ArticleService.getArticles(categoryId); - const meta = result.meta; - - setArticles(result.data); - setIsLoaded(false); - setHasNextPage(meta.hasNextPage); + const handleChangeCate = id => { + navi(`/category/${parseInt(id) + 1}`); }; - useEffect(() => { - if (categoryId !== '1' && categoryId !== '3') { - alert('준비 중입니다!'); - navi('/'); - } - setCurCate(getCategoryByUrl(loca)); - setInitalArticles(); - }, []); - - // 무한 스크롤 임시 정지 - - const getMoreItem = async () => { + const getMoreArticles = async () => { if (!hasNextPage) return; setIsLoaded(true); - // 실제 API 통신처럼 비동기로 받아오는 것을 구현하기 위해 1.5 초 뒤에 데이터를 갱신한다. - // resolve, reject는 각각 성공 시, 실패 시의 동작을 의미. reject를 생략하니 reslove의 경우만 익명함수로 처리해주었다. - // (categoryId); const result = await ArticleService.getArticles(categoryId, page); const newData = result.data; const meta = result.meta; - - setPage(prevPage => prevPage + 1); - setHasNextPage(meta.hasNextPage); + // setPage(prevPage => prevPage + 1); + page += 1; + // setHasNextPage(meta.hasNextPage); + hasNextPage = meta.hasNextPage; setArticles(prevList => prevList.concat(newData)); setIsLoaded(false); }; const onIntersect = async ([entry], observer) => { if (entry.isIntersecting && !isLoaded) { + // if (page === 1 && hasNextPage) console.log("리렌더링 확인") observer.unobserve(entry.target); - await getMoreItem(); + await getMoreArticles(); observer.observe(entry.target); } }; + // 존재하지 않는 categortId 일 경우의 예외 처리, 하드 코딩. + useEffect(() => { + if (categoryId > 3 || categoryId == 2) { + navi('/error'); + } + setArticles([]); + // 리렌더링 되면서 let으로 선언한 page, hasNextPage가 자동으로 초기화 되므로 state만 초기화 주면 된다. + // page = 1; + // hasNextPage = true; + }, [categoryId]); + useEffect(() => { let observer; if (target) { @@ -87,15 +84,26 @@ const CategoryBody = () => { observer.observe(target); // observer가 해당 객체를 감시하여 변경된다면 onIntersect 콜백 함수를 실행할 것이다. } return () => observer && observer.disconnect(); // 주석 씌워도 잘 돌아가네? - }, [target]); + }, [target, categoryId]); return ( <> -
{curCate}
+ + { + handleChangeCate(e.target.value); + }} + > + {cateList.map((cate, idx) => { + if (idx === 1) return; + return ; + })} + +
- {articles && articles.map(article => { if (categoryId === '3') diff --git a/src/Pages/CreateArticlePage/Components/CreateArticleBody.jsx b/src/Pages/CreateArticlePage/Components/CreateArticleBody.jsx index 5a5269c..8b9c04d 100644 --- a/src/Pages/CreateArticlePage/Components/CreateArticleBody.jsx +++ b/src/Pages/CreateArticlePage/Components/CreateArticleBody.jsx @@ -1,30 +1,42 @@ -import { useState, useEffect } from 'react'; -import { getCategoryByUrl } from '../../../Utils'; -import { useLocation, useNavigate } from 'react-router-dom'; -import TextareaAutosize from 'react-textarea-autosize'; +import { useState, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; -import ArticleService from '../../../Network/ArticleService'; +import '@toast-ui/editor/dist/toastui-editor.css'; +import { Editor } from '@toast-ui/react-editor'; +import FormControl from '@mui/material/FormControl'; +import NativeSelect from '@mui/material/NativeSelect'; import LoadingButton from '@mui/lab/LoadingButton'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import GlobalStyled from '../../../Styled/Global.styled'; + +import { getCategoryId } from 'Utils'; +import { ArticleService, ImageService } from 'Network'; + +import GlobalStyled from 'Styled/Global.styled'; const CreateArticleBody = () => { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); - const [curCate, setCurCate] = useState(''); + const [curCate, setCurCate] = useState(0); + const cateList = ['자유 게시판']; const [isSending, setIsSending] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); - const loca = useLocation(); const navi = useNavigate(); - const pathArray = loca.pathname.split('/'); + const open = Boolean(anchorEl); + + const editorRef = useRef(null); + + const handleClickPopper = event => { + setAnchorEl(event.currentTarget); + }; const handleChangeTitle = e => { setTitle(e.target.value); }; const handleChangeContent = e => { - setContent(e.target.value); + setContent(editorRef.current.getInstance().getMarkdown()); }; const handleClickCancel = () => { @@ -32,6 +44,8 @@ const CreateArticleBody = () => { }; const handleClickSubmit = async () => { + setContent(editorRef.current.getInstance().getMarkdown()); + if (title === '') { alert('제목을 입력하세요!'); return; @@ -40,28 +54,47 @@ const CreateArticleBody = () => { alert('내용을 입력하세요!'); return; } + // 이동한 뒤에 API 실행됨 setIsSending(true); + const categoryId = getCategoryId(curCate); const result = await ArticleService.createArticles({ title: title, content: content, - categoryId: +pathArray[2], // + 붙이면 number 타입 + categoryId: categoryId, // + 붙이면 number 타입 }); setIsSending(false); navi(-1); }; - const handleFormSubmit = e => { - e.preventDefault(); - handleClickSubmit(); + const markdownEditorSetting = () => { + const editor = editorRef.current; + editor.getRootElement().classList.add('editor'); + editor.getInstance().removeHook('addImageBlobHook'); + editor.getInstance().addHook('addImageBlobHook', (blob, callback) => { + (async () => { + ImageService.uploadImage(blob).then(res => { + callback(res); + }); + })(); + }); }; useEffect(() => { - setCurCate(getCategoryByUrl(loca)); - }, [loca]); + setCurCate(cateList[0]); + + if (editorRef.current) { + markdownEditorSetting(); + } + }, [editorRef]); + + const handleClick = () => { + console.log(editorRef.current.getInstance()); + }; + return ( <> -
+
@@ -81,20 +114,33 @@ const CreateArticleBody = () => {
-
{curCate}
+ + { + setCurCate(cateList[e.target.value]); + }} + > + {cateList.map((cate, idx) => { + return ; + })} + +
-
+ - -

{content.length}/4200

diff --git a/src/Pages/CreateArticlePage/CreateArticlePage.jsx b/src/Pages/CreateArticlePage/CreateArticlePage.jsx index 76a27af..ac86fea 100644 --- a/src/Pages/CreateArticlePage/CreateArticlePage.jsx +++ b/src/Pages/CreateArticlePage/CreateArticlePage.jsx @@ -1,5 +1,7 @@ import { CreateArticleBody } from './Components'; + import Styled from './CreateArticlePage.styled'; + const CreateArticlePage = () => { return ( <> diff --git a/src/Pages/CreateArticlePage/CreateArticlePage.styled.js b/src/Pages/CreateArticlePage/CreateArticlePage.styled.js index 99616b9..7c7e3f9 100644 --- a/src/Pages/CreateArticlePage/CreateArticlePage.styled.js +++ b/src/Pages/CreateArticlePage/CreateArticlePage.styled.js @@ -1,7 +1,9 @@ import styled from 'styled-components'; + import GlobalStyled from 'Styled/Global.styled'; + const CreateArticlePage = styled.div` - .header { + .page_header { position: fixed; box-sizing: border-box; @@ -74,7 +76,6 @@ const CreateArticlePage = styled.div` flex-direction: column; flex-grow: 1; input { - position: sticky; top: 4.5rem; flex-grow: 0; box-sizing: border-box; @@ -82,34 +83,66 @@ const CreateArticlePage = styled.div` display: flex; background-color: #fff; padding: 0.9rem 1rem; - border: 1px solid #ccc; + border: 1px solid + ${GlobalStyled.theme.createArticlePageInputBorderColor}; font-size: 1rem; &:focus { outline: none; } } - textarea { + .editor { flex-grow: 1; box-sizing: border-box; width: 100%; - overflow: hidden; - - display: flex; - background-color: #fff; - padding: 0.9rem 1rem; - border: 1px solid #ccc; - resize: none; - &:focus { - outline: none; + .toastui-editor-dropdown-toolbar { + flex-wrap: wrap; + height: auto; + width: min-content; + } + .toastui-editor-popup { + width: auto; + max-width: 400px; + .toastui-editor-file-select-button { + width: auto; + padding: 0 4px; + font-size: 0.7rem; + } + @media (max-width: 480px) { + margin-left: 0px; + } } } } - p { - position: fixed; - color: rgba(0, 0, 0, 30%); - bottom: 10px; - right: 10px; + + .category_form { + * { + border: none !important; + border-bottom: none !important; + } + *:hover { + border: none !important; + border-bottom: none !important; + } + *::before { + border: none !important; + border-bottom: none !important; + } + *::after { + border: none !important; + border-bottom: none !important; + } + + select { + color: ${GlobalStyled.theme.categoryNameTextColor}; + font-size: 1.1rem; + font-weight: 700; + padding-top: 0; + padding-bottom: 0; + } + svg { + color: ${GlobalStyled.theme.categoryNameTextColor}; + } } } `; diff --git a/src/Pages/EditArticlePage/Components/EditArticlePageBody.jsx b/src/Pages/EditArticlePage/Components/EditArticlePageBody.jsx new file mode 100644 index 0000000..2ad231a --- /dev/null +++ b/src/Pages/EditArticlePage/Components/EditArticlePageBody.jsx @@ -0,0 +1,132 @@ +import { useState, useEffect, useContext, useRef } from 'react'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; + +import '@toast-ui/editor/dist/toastui-editor.css'; +import { Editor } from '@toast-ui/react-editor'; + +import Button from '@mui/material/Button'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; + +import { getCategoryById } from 'Utils'; +import { AuthContext } from 'App'; +import { ArticleService } from 'Network'; + +import GlobalStyled from 'Styled/Global.styled'; + +const EditArticlePageBody = () => { + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [categoryId, setCategoryId] = useState(0); + + const loca = useLocation(); + const navi = useNavigate(); + const { id } = useParams(); + + const editorRef = useRef(null); + + const handleChangeTitle = e => { + setTitle(e.target.value); + }; + + const handleChangeContent = e => { + setContent(editorRef.current.getInstance().getMarkdown()); + }; + + const handleClickCancel = () => { + navi(-1); + }; + + const handleClickSubmit = async () => { + setContent(editorRef.current.getInstance().getMarkdown()); + + console.log(content); + if (title === '') { + alert('제목을 입력하세요!'); + return; + } + if (content === '') { + alert('내용을 입력하세요!'); + return; + } + // 수정하려고 카테고리 아이디 API 받는 게 조회로 인식. + // 이동한 뒤에 API 실행됨 + const result = await ArticleService.editArticles(id, { + title: title, + content: content, + categoryId: categoryId, // + 붙이면 number 타입 + }); + navi(-1); + }; + + const handleFormSubmit = e => { + e.preventDefault(); + handleClickSubmit(); + }; + + const markdownEditorSetting = () => { + const editor = editorRef.current; + //console.log(editor); + editor.getRootElement().classList.add('editor'); + //console.log(editor.getInstance()); + }; + + useEffect(() => { + if (loca.state) { + const { article } = loca.state; + setTitle(article.title); + setContent(article.content); + setCategoryId(article.categoryId); + editorRef.current.getInstance().setMarkdown(article.content); + } else { + alert('없는 페이지입니다'); + navi('/'); + } + markdownEditorSetting(); + }, []); + return ( + <> +
+
+ +
+
+ 글 수정하기 +
+
+ +
+
+ +
+ +
{getCategoryById(categoryId)}
+
+ +
+ + + +
+ + ); +}; + +export default EditArticlePageBody; diff --git a/src/Pages/EditArticlePage/Components/index.jsx b/src/Pages/EditArticlePage/Components/index.jsx new file mode 100644 index 0000000..51a8a07 --- /dev/null +++ b/src/Pages/EditArticlePage/Components/index.jsx @@ -0,0 +1,3 @@ +import EditArticlePageBody from './EditArticlePageBody'; + +export { EditArticlePageBody }; diff --git a/src/Pages/EditArticlePage/Conponents/EditArticlePageBody.jsx b/src/Pages/EditArticlePage/Conponents/EditArticlePageBody.jsx deleted file mode 100644 index 88709a9..0000000 --- a/src/Pages/EditArticlePage/Conponents/EditArticlePageBody.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import TextareaAutosize from 'react-textarea-autosize'; -import GlobalStyled from '../../../Styled/Global.styled'; -import { getCategoryById } from '../../../Utils'; - -const EditArticlePageBody = ({ - categoryId, - curLength, - onChangeTitle, - title, - onChangeContent, - content, - onSubmit, -}) => { - const handleFormSubmit = e => { - e.preventDefault(); - onSubmit(); - }; - return ( -
- -
{getCategoryById(categoryId)}
-
- -
- - - -

{curLength}/4200

-
- ); -}; - -export default EditArticlePageBody; diff --git a/src/Pages/EditArticlePage/Conponents/EditArticlePageHeader.jsx b/src/Pages/EditArticlePage/Conponents/EditArticlePageHeader.jsx deleted file mode 100644 index 08f703c..0000000 --- a/src/Pages/EditArticlePage/Conponents/EditArticlePageHeader.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import Button from '@mui/material/Button'; -import ArrowBackIcon from '@mui/icons-material/ArrowBack'; - -const EditArticlePageHeader = ({ onClickCancel, onClickSubmit }) => { - return ( -
-
- -
-
- 글 수정하기 -
-
- -
-
- ); -}; - -export default EditArticlePageHeader; diff --git a/src/Pages/EditArticlePage/Conponents/index.jsx b/src/Pages/EditArticlePage/Conponents/index.jsx deleted file mode 100644 index 82e8dc1..0000000 --- a/src/Pages/EditArticlePage/Conponents/index.jsx +++ /dev/null @@ -1,4 +0,0 @@ -import EditArticlePageHeader from './EditArticlePageHeader'; -import EditArticlePageBody from './EditArticlePageBody'; - -export { EditArticlePageHeader, EditArticlePageBody }; diff --git a/src/Pages/EditArticlePage/EditArticlePage.jsx b/src/Pages/EditArticlePage/EditArticlePage.jsx index 7184c3f..5828d2b 100644 --- a/src/Pages/EditArticlePage/EditArticlePage.jsx +++ b/src/Pages/EditArticlePage/EditArticlePage.jsx @@ -1,83 +1,14 @@ -import { useState, useEffect, useContext, useRef } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { EditArticlePageBody } from './Components'; -import ArticleService from '../../Network/ArticleService'; -import { AuthContext } from '../../App'; -import { getCategoryByUrl } from '../../Utils'; - -import { EditArticlePageHeader, EditArticlePageBody } from './Conponents'; import Styled from './EditArticlePage.styled'; const EditArticlePage = () => { - const [title, setTitle] = useState(''); - const [content, setContent] = useState(''); - const [categoryId, setCategoryId] = useState(0); - - const auth = useContext(AuthContext); - - const loca = useLocation(); - const navi = useNavigate(); - const pathArray = loca.pathname.split('/'); - - const handleChangeTitle = e => { - setTitle(e.target.value); - }; - - const handleChangeContent = e => { - setContent(e.target.value); - }; - - const handleClickCancel = () => { - navi(-1); - }; - - const handleClickSubmit = async () => { - if (title === '') { - alert('제목을 입력하세요!'); - return; - } - if (content === '') { - alert('내용을 입력하세요!'); - return; - } - // 수정하려고 카테고리 아이디 API 받는 게 조회로 인식. - // 이동한 뒤에 API 실행됨 - const result = await ArticleService.editArticles(+pathArray[2], { - title: title, - content: content, - categoryId: categoryId, // + 붙이면 number 타입 - }); - navi(-1); - }; - - useEffect(() => { - const getArticle = async () => { - const response = await ArticleService.getArticlesById(pathArray[2]); - const article = response; - setTitle(article.title); - setContent(article.content); - setCategoryId(article.categoryId); - }; - getArticle(); - }, [setTitle, setContent, categoryId]); - return ( - - - - + <> + + + + ); }; - export default EditArticlePage; diff --git a/src/Pages/EditArticlePage/EditArticlePage.styled.js b/src/Pages/EditArticlePage/EditArticlePage.styled.js index ad706f4..ef3d762 100644 --- a/src/Pages/EditArticlePage/EditArticlePage.styled.js +++ b/src/Pages/EditArticlePage/EditArticlePage.styled.js @@ -99,11 +99,28 @@ const EditArticlePage = styled.div` } } } - p { - position: fixed; - color: rgba(0, 0, 0, 30%); - bottom: 10px; - right: 10px; + + .editor { + flex-grow: 1; + box-sizing: border-box; + width: 100%; + .toastui-editor-dropdown-toolbar { + flex-wrap: wrap; + height: auto; + width: min-content; + } + .toastui-editor-popup { + width: auto; + max-width: 400px; + .toastui-editor-file-select-button { + width: auto; + padding: 0 4px; + font-size: 0.7rem; + } + @media (max-width: 480px) { + margin-left: 0px; + } + } } } `; diff --git a/src/Pages/ErrorPage/ErrorPage.jsx b/src/Pages/ErrorPage/ErrorPage.jsx index a025cac..d57f99d 100644 --- a/src/Pages/ErrorPage/ErrorPage.jsx +++ b/src/Pages/ErrorPage/ErrorPage.jsx @@ -1,4 +1,5 @@ import { useNavigate } from 'react-router-dom'; + import Styled from './ErrorPage.styled'; const ErrorPage = () => { diff --git a/src/Pages/LikedArticlePage/Components/LikedArticle.jsx b/src/Pages/LikedArticlePage/Components/LikedArticle.jsx index 532e690..58e8aaf 100644 --- a/src/Pages/LikedArticlePage/Components/LikedArticle.jsx +++ b/src/Pages/LikedArticlePage/Components/LikedArticle.jsx @@ -1,8 +1,10 @@ -import { FavoriteBorder, SmsOutlined } from '@mui/icons-material'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import UserService from 'Network/UserService'; + +import { UserService } from 'Network'; + import { PreviewArticle } from 'Components'; +import { FavoriteBorder, SmsOutlined } from '@mui/icons-material'; import Styled from './LikedArticle.styled'; diff --git a/src/Pages/LikedArticlePage/LikedArticlePage.jsx b/src/Pages/LikedArticlePage/LikedArticlePage.jsx index fa3bf27..5a2db2e 100644 --- a/src/Pages/LikedArticlePage/LikedArticlePage.jsx +++ b/src/Pages/LikedArticlePage/LikedArticlePage.jsx @@ -1,8 +1,8 @@ -import { Footer } from 'Components'; -import { ProfileHeader } from 'Components'; -import GlobalStyled from 'Styled/Global.styled'; +import { Footer, ProfileHeader } from 'Components'; import { LikedArticle } from './Components'; +import GlobalStyled from 'Styled/Global.styled'; + const LikedArticlePage = () => { return ( diff --git a/src/Pages/LoginPage/LoginPage.jsx b/src/Pages/LoginPage/LoginPage.jsx index abc3524..c639ccc 100644 --- a/src/Pages/LoginPage/LoginPage.jsx +++ b/src/Pages/LoginPage/LoginPage.jsx @@ -1,14 +1,15 @@ import { useEffect, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; -import Styled from './LoginPage.styled'; -import qs from 'qs'; -import AuthService from '../../Network/AuthService'; +import { AuthContext } from 'App'; +import { AuthService } from 'Network'; +import qs from 'qs'; import Button from '@mui/material/Button'; -import GitHubIcon from '@mui/icons-material/GitHub'; import CircularProgress from '@mui/material/CircularProgress'; -import { AuthContext } from '../../App'; +import GitHubIcon from '@mui/icons-material/GitHub'; + +import Styled from './LoginPage.styled'; const LoginPage = ({ isCallback }) => { const auth = useContext(AuthContext); @@ -55,7 +56,9 @@ const LoginPage = ({ isCallback }) => { ) : ( <> - GitHub 계정으로 로그인 하기 + + GitHub 계정으로 로그인 하기 + )} diff --git a/src/Pages/MainPage/Components/Body.styled.js b/src/Pages/MainPage/Components/Body.styled.js index ec6235e..0fafd9f 100644 --- a/src/Pages/MainPage/Components/Body.styled.js +++ b/src/Pages/MainPage/Components/Body.styled.js @@ -1,6 +1,8 @@ import styled from 'styled-components'; + import List from '@mui/material/List'; -import GlobalStyled from '../../../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const ListDivider = styled.div` box-sizing: border-box; @@ -11,16 +13,23 @@ const ListDivider = styled.div` const StyledList = styled(List)` padding: 0; -`; -const BestStyledList = styled(List)` - padding: 0; - img { - position: absolute; - margin-left: 1em; - margin-top: 0.7em; - } - .top { - padding-left: 2.2em; + + .fab_button { + position: fixed; + bottom: 1.8rem; + right: 1.5rem; + z-index: 100; + background-color: ${GlobalStyled.theme.secondary}; + color: ${GlobalStyled.theme.headerIconColor}; + border-radius: 40%; + cursor: 'pointer'; + @media (min-width: ${GlobalStyled.theme.mobileMinWidth}) { + bottom: 1.8rem; + left: calc(50% + 7rem); + } + &:hover { + background-color: ${GlobalStyled.theme.primary}; + } } `; const BoardTitleDiv = styled.div` @@ -140,7 +149,6 @@ const CheckInBody = styled.div` const Styled = { BoardTitleDiv, - BestStyledList, StyledList, ListDivider, CheckInHeader, diff --git a/src/Pages/MainPage/Components/BodyPreView.jsx b/src/Pages/MainPage/Components/BodyPreView.jsx index 20c8d5b..75900af 100644 --- a/src/Pages/MainPage/Components/BodyPreView.jsx +++ b/src/Pages/MainPage/Components/BodyPreView.jsx @@ -1,5 +1,6 @@ import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; + import Styled from './BodyPreView.styled'; const BodyPreView = ({ onChangeTab, highlight }) => { diff --git a/src/Pages/MainPage/Components/BodyPreView.styled.js b/src/Pages/MainPage/Components/BodyPreView.styled.js index 4d5f804..d7097e3 100644 --- a/src/Pages/MainPage/Components/BodyPreView.styled.js +++ b/src/Pages/MainPage/Components/BodyPreView.styled.js @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import { Tab, Tabs } from '@mui/material'; const StyledTabs = styled(Tabs)` diff --git a/src/Pages/MainPage/Components/Community.jsx b/src/Pages/MainPage/Components/Community.jsx index 59bd2e1..560ace0 100644 --- a/src/Pages/MainPage/Components/Community.jsx +++ b/src/Pages/MainPage/Components/Community.jsx @@ -1,4 +1,7 @@ -import { PreviewArticle } from '../../../Components'; +import { Fragment } from 'react'; +import { List } from '@mui/material'; + +import { PreviewArticle } from 'Components'; import Styled from './Body.styled'; @@ -11,26 +14,22 @@ const Community = ({ }) => { return ( <> - + {bestArticles && bestArticles.map(article => { return ( - <> - + moveArticles(article.id)} /> - + ); })} - + {/* margin="0.4rem" */} - {/* margin="0.4rem" */} + {/* { const [checkInStatus, setCheckInStatus] = useState({ max: { diff --git a/src/Pages/MainPage/Components/MainBody.jsx b/src/Pages/MainPage/Components/MainBody.jsx index b6df42f..296cdc2 100644 --- a/src/Pages/MainPage/Components/MainBody.jsx +++ b/src/Pages/MainPage/Components/MainBody.jsx @@ -1,17 +1,18 @@ import { useState, useEffect } from 'react'; import { useNavigate, useSearchParams, useLocation } from 'react-router-dom'; + +import { BestService, ArticleService } from 'Network'; import qs from 'qs'; import BodyPreView from './BodyPreView'; import Community from './Community'; import Home from './Home'; +import Divider from '@mui/material/Divider'; +import Fab from '@mui/material/Fab'; +import CreateIcon from '@mui/icons-material/Create'; -import BestService from '../../../Network/BestService'; -import ArticleService from '../../../Network/ArticleService'; import Styled from './Body.styled'; -import Divider from '@mui/material/Divider'; - const MainBody = () => { const navi = useNavigate(); const location = useLocation(); @@ -34,19 +35,23 @@ const MainBody = () => { navi(`/article/${articleId}`); }; + const handleClickWrite = () => { + navi('/create'); + }; + useEffect(() => { const getFreeArticles = async () => { - const response = await ArticleService.getArticles(1); + const response = await ArticleService.getAllArticles(1); setFreeArticles(response.data); }; const getAnonyArticles = async () => { - const response = await ArticleService.getArticles(2); + const response = await ArticleService.getAllArticles(2); setAnonyArticles(response.data); }; const getNotiArticles = async () => { - const response = await ArticleService.getArticles(3); + const response = await ArticleService.getAllArticles(3); setNotiArticles(response.data); }; @@ -56,7 +61,7 @@ const MainBody = () => { }; getBestArticles(); getFreeArticles(); - getAnonyArticles(); + // getAnonyArticles(); getNotiArticles(); }, []); @@ -68,22 +73,28 @@ const MainBody = () => { aria-label="mailbox folders" > + +
+ {highlight === 'home' ? ( + + ) : ( + + )} +
+ + +
- - {/**/} -
- {highlight === 'home' ? ( - - ) : ( - - )} -
); }; diff --git a/src/Pages/MainPage/MainPage.jsx b/src/Pages/MainPage/MainPage.jsx index 37f2f3f..252a111 100644 --- a/src/Pages/MainPage/MainPage.jsx +++ b/src/Pages/MainPage/MainPage.jsx @@ -1,7 +1,6 @@ -import { useEffect } from 'react'; +import { Header, Footer } from 'Components'; import { MainBody } from './Components'; -import { Header } from '../../Components'; -import Footer from '../../Components/Footer'; + import Styled from './MainPage.styled'; // 프론트 -> github API -> 깃허브 인증 -> 쿼리 스트링으로 code가 날아옴 -> githubCallback API -> 서버에서 쿠키 보내줌. diff --git a/src/Pages/MainPage/MainPage.styled.js b/src/Pages/MainPage/MainPage.styled.js index a724a41..146772e 100644 --- a/src/Pages/MainPage/MainPage.styled.js +++ b/src/Pages/MainPage/MainPage.styled.js @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import ListItem from '@mui/material/ListItem'; const MainHeader = styled.div``; diff --git a/src/Pages/MyArticlePage/Components/MyArticle.jsx b/src/Pages/MyArticlePage/Components/MyArticle.jsx index 1e73a0c..a0c975e 100644 --- a/src/Pages/MyArticlePage/Components/MyArticle.jsx +++ b/src/Pages/MyArticlePage/Components/MyArticle.jsx @@ -1,8 +1,9 @@ -import { FavoriteBorder, SmsOutlined } from '@mui/icons-material'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; + +import { UserService } from 'Network'; + import { PreviewArticle } from 'Components'; -import UserService from 'Network/UserService'; import Styled from './MyArticle.styled'; diff --git a/src/Pages/MyArticlePage/MyArticlePage.jsx b/src/Pages/MyArticlePage/MyArticlePage.jsx index b67788a..8a094cb 100644 --- a/src/Pages/MyArticlePage/MyArticlePage.jsx +++ b/src/Pages/MyArticlePage/MyArticlePage.jsx @@ -1,8 +1,8 @@ -import { Footer } from 'Components'; -import { ProfileHeader } from 'Components'; -import GlobalStyled from 'Styled/Global.styled'; +import { Footer, ProfileHeader } from 'Components'; import { MyArticle } from './Components'; +import GlobalStyled from 'Styled/Global.styled'; + const MyArticlePage = () => { return ( diff --git a/src/Pages/MyCommentPage/Components/MyComment.jsx b/src/Pages/MyCommentPage/Components/MyComment.jsx index 8fb1429..27fbb3f 100644 --- a/src/Pages/MyCommentPage/Components/MyComment.jsx +++ b/src/Pages/MyCommentPage/Components/MyComment.jsx @@ -1,11 +1,13 @@ -import { ArrowForwardIos } from '@mui/icons-material'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import UserService from 'Network/UserService'; -import Styled from './MyComment.styled'; +import { UserService } from 'Network'; import dayjs from 'dayjs'; +import { ArrowForwardIos } from '@mui/icons-material'; + +import Styled from './MyComment.styled'; + const MyComment = () => { const navi = useNavigate(); const [comments, setComments] = useState([]); diff --git a/src/Pages/MyCommentPage/Components/MyComment.styled.js b/src/Pages/MyCommentPage/Components/MyComment.styled.js index ea2f2a8..b492bbb 100644 --- a/src/Pages/MyCommentPage/Components/MyComment.styled.js +++ b/src/Pages/MyCommentPage/Components/MyComment.styled.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; -import GlobalStyled from '../../../Styled/Global.styled'; + +import GlobalStyled from 'Styled/Global.styled'; const MyCommentsDiv = styled.div` padding: 0.5rem 0; diff --git a/src/Pages/MyCommentPage/MyCommentPage.jsx b/src/Pages/MyCommentPage/MyCommentPage.jsx index c860527..01f701b 100644 --- a/src/Pages/MyCommentPage/MyCommentPage.jsx +++ b/src/Pages/MyCommentPage/MyCommentPage.jsx @@ -1,8 +1,8 @@ -import { Footer } from 'Components'; -import { ProfileHeader } from 'Components'; -import GlobalStyled from 'Styled/Global.styled'; +import { Footer, ProfileHeader } from 'Components'; import { MyComment } from './Components'; +import GlobalStyled from 'Styled/Global.styled'; + const MyCommentPage = () => { return ( diff --git a/src/Pages/ProfilePage/Components/Articles.jsx b/src/Pages/ProfilePage/Components/Articles.jsx index 7c22091..c66b451 100644 --- a/src/Pages/ProfilePage/Components/Articles.jsx +++ b/src/Pages/ProfilePage/Components/Articles.jsx @@ -1,15 +1,15 @@ -import { Box } from "@mui/material"; -import LikedArticle from "./LikedArticle"; -import MyArticle from "./MyArticle"; -import MyComment from "./MyComment"; +import LikedArticle from './LikedArticle'; +import MyArticle from './MyArticle'; +import MyComment from './MyComment'; +import { Box } from '@mui/material'; const Articles = () => { return ( - + -
+
-
+
); diff --git a/src/Pages/ProfilePage/Components/Authenticate.jsx b/src/Pages/ProfilePage/Components/Authenticate.jsx index 0162570..a1a72ba 100644 --- a/src/Pages/ProfilePage/Components/Authenticate.jsx +++ b/src/Pages/ProfilePage/Components/Authenticate.jsx @@ -1,8 +1,11 @@ +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { AuthContext } from 'App'; + import { ArrowForwardIos } from '@mui/icons-material'; + import CommonStyled from '../Common.styled'; -import { useNavigate } from 'react-router-dom'; -import { useContext } from 'react'; -import { AuthContext } from '../../../App'; const Authenticate = () => { const navigate = useNavigate(); diff --git a/src/Pages/ProfilePage/Components/GithubLink.jsx b/src/Pages/ProfilePage/Components/GithubLink.jsx index de05dc4..30e4ebe 100644 --- a/src/Pages/ProfilePage/Components/GithubLink.jsx +++ b/src/Pages/ProfilePage/Components/GithubLink.jsx @@ -1,8 +1,11 @@ -import React, { useEffect, useState, useContext } from 'react'; +import { useEffect, useState, useContext } from 'react'; + +import { AuthContext } from 'App'; +import { UserService } from 'Network'; + import { ArrowForwardIos } from '@mui/icons-material'; + import CommonStyled from '../Common.styled'; -import UserService from '../../../Network/UserService'; -import { AuthContext } from '../../../App'; const githubLink = 'https://github.com'; diff --git a/src/Pages/ProfilePage/Components/Info.jsx b/src/Pages/ProfilePage/Components/Info.jsx index 7cd28b8..da4e191 100644 --- a/src/Pages/ProfilePage/Components/Info.jsx +++ b/src/Pages/ProfilePage/Components/Info.jsx @@ -1,10 +1,13 @@ -import { ArrowForwardIos } from '@mui/icons-material'; -import UserService from 'Network/UserService'; -import Styled from './Info.styled'; -import { useNavigate } from 'react-router-dom'; import { useContext, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + import { AuthContext } from 'App'; -import { getProfileImg } from 'Utils/profileList'; +import { getProfile } from 'Utils'; +import { UserService } from 'Network'; + +import { ArrowForwardIos } from '@mui/icons-material'; + +import Styled from './Info.styled'; const Info = () => { const navigate = useNavigate(); @@ -32,7 +35,7 @@ const Info = () => {
profile_img diff --git a/src/Pages/ProfilePage/Components/Info.styled.js b/src/Pages/ProfilePage/Components/Info.styled.js index 207df0a..10480ba 100644 --- a/src/Pages/ProfilePage/Components/Info.styled.js +++ b/src/Pages/ProfilePage/Components/Info.styled.js @@ -1,4 +1,5 @@ import styled from 'styled-components'; + import GlobalStyled from 'Styled/Global.styled'; const InfoDiv = styled.div` diff --git a/src/Pages/ProfilePage/Components/LikedArticle.jsx b/src/Pages/ProfilePage/Components/LikedArticle.jsx index 6591587..720fdef 100644 --- a/src/Pages/ProfilePage/Components/LikedArticle.jsx +++ b/src/Pages/ProfilePage/Components/LikedArticle.jsx @@ -1,6 +1,7 @@ +import { useNavigate } from 'react-router-dom'; + import { Button, Box } from '@mui/material'; import { FavoriteBorder } from '@mui/icons-material'; -import { useNavigate } from 'react-router-dom'; const LikedArticle = () => { const navigate = useNavigate(); diff --git a/src/Pages/ProfilePage/Components/MyArticle.jsx b/src/Pages/ProfilePage/Components/MyArticle.jsx index 811c0a4..f60e298 100644 --- a/src/Pages/ProfilePage/Components/MyArticle.jsx +++ b/src/Pages/ProfilePage/Components/MyArticle.jsx @@ -1,6 +1,7 @@ +import { useNavigate } from 'react-router-dom'; + import { Button, Box } from '@mui/material'; import { ArticleOutlined } from '@mui/icons-material'; -import { useNavigate } from 'react-router-dom'; const MyArticle = () => { const navigate = useNavigate(); diff --git a/src/Pages/ProfilePage/Components/MyComment.jsx b/src/Pages/ProfilePage/Components/MyComment.jsx index 2726872..d96699e 100644 --- a/src/Pages/ProfilePage/Components/MyComment.jsx +++ b/src/Pages/ProfilePage/Components/MyComment.jsx @@ -1,6 +1,7 @@ +import { useNavigate } from 'react-router-dom'; + import { Button, Box } from '@mui/material'; import { SmsOutlined } from '@mui/icons-material'; -import { useNavigate } from 'react-router-dom'; const MyComment = () => { const navigate = useNavigate(); diff --git a/src/Pages/ProfilePage/Components/SignOut.jsx b/src/Pages/ProfilePage/Components/SignOut.jsx index a64f922..dcaa73b 100644 --- a/src/Pages/ProfilePage/Components/SignOut.jsx +++ b/src/Pages/ProfilePage/Components/SignOut.jsx @@ -1,9 +1,11 @@ -import CommonStyled from '../Common.styled'; -import AuthService from '../../../Network/AuthService'; -import { AuthContext } from '../../../App'; import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; +import { AuthContext } from 'App'; +import { AuthService } from 'Network'; + +import CommonStyled from '../Common.styled'; + const SignOut = () => { const auth = useContext(AuthContext); const navi = useNavigate(); diff --git a/src/Pages/ProfilePage/ProfilePage.jsx b/src/Pages/ProfilePage/ProfilePage.jsx index c5e1aed..cac7185 100644 --- a/src/Pages/ProfilePage/ProfilePage.jsx +++ b/src/Pages/ProfilePage/ProfilePage.jsx @@ -1,3 +1,4 @@ +import { Footer, ProfileHeader } from 'Components'; import { Info, SignOut, @@ -7,10 +8,9 @@ import { } from './Components'; import { createTheme, ThemeProvider } from '@mui/material/styles'; + +import GlobalStyled from 'Styled/Global.styled'; import Styled from './ProfilePage.styled'; -import GlobalStyled from '../../Styled/Global.styled'; -import { ProfileHeader } from '../../Components'; -import { Footer } from 'Components'; const theme = createTheme({ // mui Button 컬러 적용 diff --git a/src/Pages/SettingPage/Components/Setting.jsx b/src/Pages/SettingPage/Components/Setting.jsx index d6811b3..ad387d7 100644 --- a/src/Pages/SettingPage/Components/Setting.jsx +++ b/src/Pages/SettingPage/Components/Setting.jsx @@ -1,7 +1,9 @@ import { useContext, useState } from 'react'; -import { AuthContext } from '../../../App'; -import UserService from '../../../Network/UserService'; -import { PROFILE_LIST } from '../../../Utils/profileList'; + +import { AuthContext } from 'App'; +import { getProfile } from 'Utils'; +import { UserService } from 'Network'; + import Styled from './Setting.styled'; const Setting = () => { @@ -34,7 +36,7 @@ const Setting = () => {
캐릭터 선택
- {PROFILE_LIST.map(profile => ( + {getProfile.PROFILE_LIST.map(profile => (